一、函数原型

int printf(const char format,…)*
常规用法

1
2
3
char str[10];
scanf("%s",str);
printf("%s",str);

另外一种用法

1
2
3
char str[10];
scanf("%s",str);
printf(str)

第一种用法没有错误,但是在第二种方法中没有规定format参数,用户便可以通过构造str来实现内存的任意读写。
printf函数的格式化字符串常见的有

%d,%f,%c,%s,%x(16进制数,没有0x),%p(16进制数,有0x)等。
%n ,它的功能是将%n之前打印出来的字符个数,赋值给一个变量。
%hn,%hhn,%lln,分别为写入目标空间2字节,1字节,8字节。

注意是对应参数(这个参数是指针)的对应的地址开始起几个字节。不要觉得%lln,取的是8个字节的指针,%n取的就是4个字节的指针,取的是多少字节的指针只跟程序的位数有关,如果是32位的程序,%n取的就是4字节指针,64位取的就是8字节指针,这是因为不同位数的程序,每个参数对应的字节数是不同的。

二、原理分析

以一段代码为例

1
2
3
4
5
6
7
8
#include<stdio.h>
int main()
{
char a[100];
scanf("%s",a);
printf(a);
return 0;
}

第七个参数就是AAAA在栈中的位置

PS:
linux下直接读取第七个参数的方法:
%< number >$x 是直接读取第number个位置的参数,同样可以用在%n,%d等等。
但是需要注意64位程序,前6个参数是存在寄存器中的,从第7个参数开始才会出现在栈中,所以栈中从格式化串开始的第一个,应该是
%7$n**。

三、读取内存

输入的字符串的前4个字节如果是一个有效的字符串的首地址,就可以用%s将其打印出来,做到任意内存读取。如果不是有效的字符串,会出现段错误。

四、修改内存

局部变量保存在栈中

前提需要关闭PIE保护,不然局部变量的地址是随机的。
例如前四个字节地址在0xABCDEFGH
构造字符串如下

1
2
`printf "\xGH\xEF\xCD\xAB"`%08x%08x%08x%08x%08x%08x%08n
//`printf "\xGH\xEF\xCD\xAB"`%7$n

五、实战

CGfsb
在IDA中源码如下

确定偏移量

payload