我知道格式化字符串攻击发生在格式化的I / O函数需要的参数多于提供的参数时。
在C中,
读取内存位置的一个示例:
printf("%x"); // this prints a memory address location in the stack
覆盖内存位置的另一个例子:
printf("Overwritten%n"); //this prints the number of chars in "Overwritten"
我的问题是:为什么在这两种情况下都会发生这种情况?为什么在没有提供相应值的情况下只在格式化字符串中使用%x会在内存中打印一个地址?那究竟是什么解决了?我知道它会发生,但真正发生了什么?
覆盖相同。
答案 0 :(得分:4)
发生的是未定义的行为。 Printf不知道没有相应的值。它尝试访问您的呼叫的第二个参数(您没有提供),并访问一些随机内存值。
对于"overwritten%n"
,%n存储在内存中,在调用%n之前写入的字符数。如果你打电话,如果没有传递正确的地址,它会在随机的地方写一些东西,冒着破坏你的记忆的风险。
答案 1 :(得分:2)
鉴于C被命名为“高级汇编程序”,答案在于其编译器结构。 printf
是一个函数,它接受可变数量的参数,而不检查是否所有参数都是实际提供的。因此,根据该函数如何在编译器级别接受参数,可能出现以下情况:
首先,解析传递给printf的字符串,导致后续调用内部函数,在第二种情况下输出字符串'Overwritten'(在第一种情况下,格式化字符串的第一个符号是a '%'表示参数)。然后,当请求打印参数时,调用相应的原始数据打印例程,其中下一个应该放在堆栈中的参数(在编译时计算偏移量)。在%x
的情况下,没有参数,并且要打印的未更改字符串是空的,因此未分配,因此堆栈中的下一个32位值是当前返回地址,即由操作系统在EXE加载时,实际上通过call printf_hex_address
汇编指令放入堆栈。这种攻击显然基于以下事实:如果处理程序实际上在内存中持久存在,并且不会被交换,则该地址是程序地址空间内的可写内存位置。为什么出现“覆盖”的长度,可以通过设计的内部字符串操作例程来解释,以便将字符串的实际长度传递给它。
答案 2 :(得分:1)
可能对您有所帮助:
我想你在windows工作。没有进行严格的检查
我在Linux上尝试过相同的警告。
int main(){
的printf( “%X”);
的printf( “被覆盖的%N”);
}
发出以下警告:
$ gcc test.c
test.c:在函数'main'中:
test.c:4:警告:格式的参数太少了
test.c:5:警告:格式
答案 3 :(得分:1)
这是因为printf
不知道程序员的意图。当它在格式字符串中看到格式标识符时,它会在那里格式化相应的参数。
printf("%x");
需要一个额外的参数(通常存储在寄存器或堆栈中)。由于程序员没有告诉编译器在这个调用中提供一个额外的参数,而printf
期望在那里存储的任何内容将被打印出来。
出于优化原因,编译器通常在调用下一个函数之前不清除寄存器。