-
-
[分享]格式化字符串漏洞
-
发表于: 2021-10-5 08:50 28455
-
格式化字符串是一种很常见的漏洞,其产生根源是printf函数设计的缺陷,即printf()函数并不能确定数据参数arg1,arg2…究竟在什么地方结束,也就是说,它不知道参数的个数。它只会根据format中的打印格式的数目依次打印堆栈中参数format后面地址的内容
格式字符串漏洞发生的条件就是格式字符串要求的参数和实际提供的参数不匹配
。
如图所示,执行的命令为printf("%s %d %d %d %x\n",buf, 1, 2, 3)
,紧随格式化字串后压入栈上的参数为4个,但格式化字串有五个参数,printf
在解析第五个参数%x
时,会继续往栈上读取,造成了信息泄露:
格式化字符串符号说明:
拿到一个程序之后可以通过输入若干个%s
来进行判断是否存在漏洞
因为如果存在格式化字符串漏洞。在输入一串%s后,就会把对应地址的内容当做指针 来打印该指针指向的地址空间的字符串。然而并不是所有空间都具有可读权限,这样的话,就会导致程序崩溃。
由于格式化字符串变长参数的特性,在实际运行中,如果Format String的符号说明个数超过待匹配的参数个数,即有更多的符号说明需要被匹配时,printf会根据解析结果和调用约定去取栈上(reg)相应的值并输出。
我们输入:%08x.%08x.%08x
得到的结果如下图:
我们来调试一下:
首先第一个printf的参数如下
%08x.%08x.%08x
是作为%s相对应的参数的,所以他的打印结果如下:
第二个printf的参数对应如下:
很奇怪的是 当程序结束后才会打印出来
我们不只是可以%x%x%x来泄露数据,还可以用%p
如果还想输出特定位置的内容还可以用%n$x
,这里的n为一个自然数,代表第n位。
输入%3$x
,得到:
任意地址读需要用到printf的另外一个特性,$操作符.这个操作符可以输出指定位置的参数.利用%n$x这样的字符串就可以获得对应的第n+1个参数的数值(因为格式化参数里边的n指的是格式化字符串对应的第n个输出参数,那么相对于输出函数来说就成了第n+1个).
代码:
首先测一下字符串开头的偏移量:
point : 实际偏移量
注意64位算偏移时要先将调用约定中寄存器的数量加进去,并且payload里地址之前的其他格式化字符串也会在栈上占去位置,会导致实际偏移量增加,需要进行新的计算,假如无法确定%K$n中的K到底是多少,可以多输出几个%p来确定。
如果将AAAA替换成某个函数的got地址,那么程序就会打印这个函数的真实地址
内存覆盖的思路和任意地址泄露的思路相似,一种可行的方法是同上面一样,构造payload,在格式化字符串中包含想要写入的地址,此时该地址会随格式化字符串放在栈上,然后用格式化字符串的'%K$n'来实现写功能。
利用%n改写
函数:
在图中 明显的参数0x1e为第5个
所以我们输入的是:%.2000x%5$n
结果如下:%.2000x
是以2000对其的方式输出某地址%n
是写入,本身也是记录输出个数;%5$n
是写入第5个常数。
所以%2000x%5$n
是将栈中第5个刚好修改为2000
格式化字符串漏洞简介
Pwn学习笔记:printf格式化字符串漏洞原理与利用
格式化字符串漏洞解析
字符 | 类型 | 使用 |
---|---|---|
d | 4-byte | Integer |
u | 4-byte | Unsigned Integer |
x | 4-byte | Hex |
s | 4-byte | ptr String |
c | 1-byte | Character |
字符 | 类型 | 使用 |
---|---|---|
hh | 1-byte | char |
h | 2-byte | short int |
l | 4-byte | long int |
ll | 8-byte | long long int |
#include <stdio.h>
int
main(void)
{
int
a;
printf(
"aaaaaaa%n\n"
,&a);
printf(
"%d\n"
,a);
return
0
;
}
#include <stdio.h>
int
main(void)
{
int
a;
printf(
"aaaaaaa%n\n"
,&a);
printf(
"%d\n"
,a);
return
0
;
}
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
/
/
leakmem
#include <stdio.h>
int
main() {
char s[
100
];
int
a
=
1
, b
=
0x22222222
, c
=
-
1
;
scanf(
"%s"
, s);
printf(
"%08x.%08x.%08x.%s\n"
, a, b, c, s);
printf(s);
return
0
;
}
/
/
leakmem
#include <stdio.h>
int
main() {
char s[
100
];
int
a
=
1
, b
=
0x22222222
, c
=
-
1
;
scanf(
"%s"
, s);
printf(
"%08x.%08x.%08x.%s\n"
, a, b, c, s);
printf(s);
return
0
;
}
/
/
gcc test.c
-
o test
-
m32
#include <stdio.h>
int
main(void)
{
char
str
[
100
];
scanf(
"%s"
,
str
);
printf(
str
);
return
0
;
}
/
/
gcc test.c
-
o test
-
m32
#include <stdio.h>
int
main(void)
{
char
str
[
100
];
scanf(
"%s"
,
str
);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- Android主流的污点分析工具 1541
- [翻译]“过滤-静音”操作:调查EDR内部通信 12059
- [翻译]Windows授权指南 5899
- [翻译]利用Android WebView漏洞 7931
- [翻译]伪造调用堆栈以混淆EDR 11449