是否可以保证编译器会连续存储“Val: %d”和“5”
这几乎可以保证他们won't是。 5 足够小,可以直接嵌入指令流中,而不是通过内存地址(指针)加载 - 类似于movl #5, %eax
和/或随后压入堆栈 - 而字符串对象将放置在可执行映像的只读数据区域中,并将通过指针引用。我们正在谈论编译时间的布局可执行映像.
除非你的意思是runtime的布局stack其中是的,字大小pointer该字符串和字长常量 5 将彼此相邻。但顺序可能与您期望的相反 - 研究“C 函数调用约定”。
[稍后编辑:现在使用 -S (输出程序集)运行一些代码示例;我记得,由于调用者中寄存器的使用较少(即 CPU 寄存器可以被覆盖而不会造成损害),并且被调用函数的参数很少,因此参数可以完全通过寄存器传递以保存指令和内存。因此,即使攻击者可以访问源代码,堆栈的布局实际上也很难预测。特别是使用 gcc -O2,它将我的 main -> my_function -> printf 函数序列折叠为 main -> printf]
大多数漏洞利用都是堆栈溢出,因为恶意代码试图修改上述只读数据区域中的内存,从而陷入困境——操作系统会中止该进程。
printf 的行为很特殊,因为格式字符串就像一个微型计算机程序,告诉 printf 在堆栈上查找它找到的每个“%”格式说明符的参数。如果这些参数实际上从未被推送,和/或大小不同,则 printf 将盲目地遍历堆栈中不应该的部分,并且可能会进一步显示堆栈上(调用链下)私有数据可能所在的数据。如果 printf 的第一个参数至少是constant,当后续参数与 '%' 说明符不匹配时,编译器至少可以警告您,但当它是变量时,所有的赌注都会被取消。
从安全角度来看,printf 很糟糕,并且计算量很大,但非常强大且富有表现力。欢迎来到 C。:-)
第二次稍后编辑现在你在评论中的第一个问题......正如你所看到的,你的术语和想法可能有点混乱。研究以下内容以了解正在发生的情况。暂时不用担心指向字符串的指针。这是在 Linux 3.13 64 位上使用 gcc 4.8.2 编译的,没有任何标志。请注意,过度使用格式说明符本质上是如何向后遍历堆栈的,从而显示在先前函数调用中传递的参数。
/* Do not compile this at home. */
#include <stdio.h>
int second() {
printf("%08X %08X %08X %08X %08X %08X %08X %08X\n");
}
int first(int a, int b, int c, int d, int e, int f, int g, int h) {
second();
}
int main(int argc, char **argv) {
first(0xDEEDC0DE, 0x1EADBEEF, 0x11BEDEAD, 0xCAFAF000, 0xDAFEBABE, 0xAACEBACE, 0xE1ED1EAA, 0x10F00FAA);
return 0;
}
两次连续运行,stdio 输出:
1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 75F83520 00400568 88B151C8
1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 8B4CBDC0 00400568 7BB841C8