初识格式化字符串(上)
深入理解格式化字符串
要想理解格式化字符串的原理,我们必须要知道的一个问题是,printf调用时,它的第一个参数是什么,比如printf("%d %f\n", x, y);,它的一参是x吗?
让我们来看看printf的声明,int printf(const char *format, ...),没错,printf的一参是”%d %f\n”,即格式化字符串,这一点是很多同学都没有意识到的
我们必须要深刻理解的第二个问题是,什么是格式化字符串
格式化字符串是一些程序设计语言的输入/输出库中能将字符串参数转换为另一种形式输出的函数,它通过用于把随后对应的0个或多个函数参数转换为相应的格式输出;格式化字符串中转换说明以外的其它字符原样输出。我补充一点,这里的转换说明即格式化占位符。
(ps:不要因为长就不读了,反而要读得更仔细,通过学习pwn我认识到,越涉及到一个概念的本质,描述的文字就会越详细)
通俗的说,它就是包含格式化占位符的字符串
格式化字符串的工作原理
我们平时使用printf时,格式化占位符是必须和后面的参数一一对应的,有多少个格式化占位符,就必须有多少个参数
这里必须再说一下printf是如何调用参数的。在64位系统中,我们的参数先会按照一定的顺序被放到6个寄存器里,从第7个参数往后,参数会被放在栈里;32位系统中,参数是直接被放到栈里保存的。
1 | #include<stdio.h> |
在这段程序中,我们的参数先会按顺序被放到栈空间里保存,然后,printf会从栈空间里去找对应的数据
1 | #include<stdio.h> |
而我们去掉了参数c会发生什么呢
结果会是:1 2 一个很奇怪的数
因为有第三个%d的存在,printf会试图去栈或寄存器(原本c所在的位置)再取一个整数,但是显然是无法找到c的,于是就会随机地从错误的位置读取内存,就把这个奇怪的数翻了出来。这也侧面证明了printf是从栈里去寻址的
1 | #include<stdio.h> |
我们把占位符改成%p,输出的结果就是c在栈上的地址
1 | #include<stdio.h> |
更极端的情况如上
因此,我们可以利用格式化字符串实现可读可写
eg1:newstar2025 week2 刻在栈上的秘密
题目内容:
欢迎来到 x64 位餐厅!服务员 printf 先生有点健忘,他只能记住您菜单上的前 6 道菜 (RDI, RSI, RDX…),再多就只能堆在摇摇晃晃的餐盘 (栈) 上了。更糟糕的是,他会把你写的菜单原封不动地大声念出来。你能设计一份别有用心的菜单,让他念着念着,就把秘密房间的密码念给你听吗?
这个题没有给我们可执行文件,直接nc链接做即可

我们先输入aaaa加几个%p试一下,发现没有看到41414141,这说明aaaa被当成了格式化字符串的一部分被传入了。由于题目简单,我合理推测该prntf函数只有一个参数,即偏移量为0。不过这不重要,正常题目不会这样的

下面开始正式做题

我们可以看到,第11个%p打印出了密码所在寄存器的地址


总结
格式化字符串漏洞的产生,就是参数和格式化占位符没有数量上的一一对应,即源代码里的格式化占位符少于参数的数量,于是我们就可以通过输入不同的格式化占位符,去打印栈空间里对应的内容






