printf()var-arg引用如何与堆栈内存布局交互?

use*_*955 13 c memory stack exploit string-formatting

给出代码片段:

int main()
{
    printf("Val: %d", 5);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

有没有保证编译器会存储"Val: %d"'5'连续?例如:

+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| ... |  %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
      ^                                   ^     ^
      |           Format String           | int |
Run Code Online (Sandbox Code Playgroud)

这些参数究竟是如何在内存中分配的?

此外,printf函数是否相对于格式字符串或绝对值访问int?例如,在数据中

+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| ... |  %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
      ^                                   ^     ^
      |           Format String           | int |
Run Code Online (Sandbox Code Playgroud)

当函数遇到时,%d是否已存在将被引用的函数的第一个参数的存储内存地址,或者是否相对于格式字符串的第一个元素计算该值?

很抱歉,如果我感到困惑,我的主要目标是了解字符串格式化漏洞,允许用户提供格式字符串,如本文档中所述

http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf

我对第3页和第4页描述的攻击产生了担忧.我认为%x要跳过字符串占用的16位,这表示函数连续分配并引用相对但其他来源表明没有保证编译器必须连续分配,我担心该文件是一个简化.

Bas*_*Zen 16

有没有保证编译器会连续存储"Val:%d"和"5"

它几乎保证不会.5足够小,可以直接嵌入到指令流中,而不是通过内存地址(指针)加载 - 类似movl #5, %eax和/或后跟推入堆栈 - 而字符串对象将被布置在可执行映像的只读数据区,并将通过指针引用.我们谈论的是可执行映像的编译时布局.

除非你的意思是堆栈运行时布局,其中是指向该字符串的字大小的指针,以及字大小的常量5,它们将彼此相邻.但顺序可能与你期望的相反 - 学习'C函数调用约定'.

[稍后编辑:现在用-S(输出程序集)运行一些代码示例; 我提醒一下,在调用者中使用轻度寄存器(即CPU寄存器可以被无限制地覆盖),并且对被调用函数的参数很少,参数可以完全通过寄存器传递以保存指令和存储器.因此,即使攻击者可以访问源代码,堆栈的布局实际上也很难预测.特别是gcc -O2,它将我的main - > my_function - > printf函数序列折叠成main - > printf]

大多数漏洞使用堆栈溢出,因为恶意代码在试图修改上述只读数据区域中的内存时遇到了问题 - 操作系统中止了该过程.

printf的行为是特殊的,因为格式字符串就像一个微型计算机程序,它告诉printf查看它找到的每个'%'格式说明符的堆栈参数.如果这些参数从未被实际推送过,和/或具有不同的大小,则printf将盲目地遍历堆栈的不应该的部分,并且可能在私有数据所在的堆栈中(在调用链下方)进一步显示数据.如果printf的第一个参数至少是一个常量,编译器至少可以在后续参数与'%'说明符不匹配时发出警告,但是当它是变量时,所有的投注都会关闭.

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;
}
Run Code Online (Sandbox Code Playgroud)

两个背靠背运行,stdio输出:

1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 75F83520 00400568 88B151C8

1EADBEEF 11BEDEAD CAFAF000 DAFEBABE AACEBACE 8B4CBDC0 00400568 7BB841C8

  • 在评论中商定的后续问题很难解释.编辑回答以展示堆栈布局和利用概念,而不涉及OP似乎指的不切实际的强大和精确的漏洞利用. (2认同)
  • 这些参数并不都是在Linux AMD64上的堆栈上传递的. (2认同)