格式化字符串函数:
就是将计算机内存中的表示的数据转化为人眼可以识别的字符串类型。
以打印函数printf()
为例
printf("color %s,numbre %d,float %4.2f","blue",1234,1.13)
%d - 十进制 - 输出十进制整数 %s - 字符串 - 从内存中读取字符串 %x - 十六进制 - 输出十六进制数 %c - 字符 - 输出字符 %p - 指针 - 指针地址 %n - 到目前为止所写的字符数
这样应该就好理解一点,printf()
函数在被调用时会在根据传参顺序来进行调用,这一点在上图就已经很明显的可以看出来了,但是会一个字符一个字符的去读取,就会遇到无法读取的情况:
当前字符不是%直接输出 当前字符时%:
这样一来,如果只有一个占位符后面却没有跟参数时,%s
就会从读取一个数字并作为地址,如果这个地址是无效的时候,就会导致程序崩溃,也就是我们说的格式化字符串漏洞。
这种利用方式就和我们上面说的一样,通过输入很多的%s
来让程序崩溃。对于每一个 %s
,printf()
都会从栈上取一个数字,把该数字视为地址,然后打印出该地址指向的内存内容,由于不可能获取的每一个数字都是地址,所以数字对应的内容可能不存在,或者这个地址是被保护的,那么便会使程序崩溃。
在 Linux 中,存取无效的指针会引起进程收到
SIGSEGV
信号,从而使程序非正常终止并产生核心转储
一段C代码来具体看一下
#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;
}
p1师傅提醒编译的时候记得-w关闭异常检查
gcc -m32 -w -fno-stack-protector -no-pie -o fs1 1.c
然后就开始使用Pwndbg
在printf
下个断点,然后运行,输入 %08x.%08x.%08x
然后接着看下栈空间。
我们可以来看下程序的执行,第一个框里的红色是printf
函数的返回地址,紧接着是printf
的第一个参数,后面printf
会根据这个字符串来解析后面的参数。
第一个%08x
解析的是 0x1
也就是源程序里的a
,第二个%08x
解析的是0x22222222
第三个%08x
解析的是0xffffffff
也就是参数-1
最后的%s
会把我们输入的参数打印出来。
我们再让程序执行一次
发现程序把我们第一次传入的参数%08x.%08x.%08x
打印了出来,但是再次执行程序时,发现程序断在了第二个printf
,把我们之前输入的参数作为 格式化字符串,但是这一次没有其他的参数,但是他同样会在栈上找临近的三个参数,然后按照顺序给他们打印出来,这样就把他后面三个栈上的值给输出出来了。
上边的方法是依次获得栈中的值,我们可以用%n$x
来获得n+1的值
%n:令printf把自己到该点已打出的字符总数放到相应变元指向的整形变量中
这里我输入了%3$x
也就是会打印出第四个参数对应的值。
gdb
调程序运行两次后发现第四个参数的值。
我们也可以通过%$s
来获取栈变量对应的字符串
参考链接: