printf() var-arg 引用如何与堆栈内存布局交互?
Posted
技术标签:
【中文标题】printf() var-arg 引用如何与堆栈内存布局交互?【英文标题】:How does printf() var-arg referencing interact with stack memory layout? 【发布时间】:2015-10-24 10:08:50 【问题描述】:给定代码sn-p:
int main()
printf("Val: %d", 5);
return 0;
是否可以保证编译器会连续存储"Val: %d"
和'5'
?例如:
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| ... | %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
^ ^ ^
| Format String | int |
这些参数究竟是如何在内存中分配的?
此外,printf 函数是相对于格式字符串还是通过绝对值访问 int?所以例如在数据中
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| ... | %d | ' ' | ':' | 'l' | 'a' | 'V' | '5' | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+
^ ^ ^
| Format String | int |
当函数遇到%d
时,是否已经存储了函数第一个参数的内存地址,该参数将被引用,或者该值是否相对于格式字符串的第一个元素计算?
对不起,如果我感到困惑,我的主要目标是了解允许用户提供本文档中描述的格式字符串的字符串格式化漏洞
http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf
我担心第 3 页和第 4 页上描述的攻击。我认为%x
将跳过字符串占用的 16 位,这表明该函数是连续分配的,并且相对引用但其他来源表明不能保证编译器必须连续分配,我担心这篇论文是一种简化。
【问题讨论】:
好吧,一方面,格式字符串没有存储在堆栈中。 谢谢。修正了问题。 那些讲义太糟糕了。全世界都不是i386。就 C 而言,甚至可能没有堆栈。 看起来所引用的讲义几乎直接取自 2001 年的论文。那些仍然假设 386 架构。 AMD64 上可能有类似的攻击,但论文没有解决这个问题。 除了答案之外的注意事项:C 规范很大 长度避免 指定关于布局的任何东西可变参数函数调用中的参数。当您阅读它时,他们非常想避免让您做出这样的假设,这几乎是病态的。 【参考方案1】:是否可以保证编译器会连续存储 "Val: %d" 和 '5'
几乎可以保证他们不会。 5 足够小,可以直接嵌入到指令流中,而不是通过内存地址(指针)加载——类似于movl #5, %eax
和/或随后压入堆栈——而字符串对象将是布局在可执行映像的只读数据区域中,并将通过指针引用。我们正在讨论可执行图像的编译时间布局。
除非您指的是 stack 的 runtime 布局,其中是的,字大小的 pointer 指向该字符串,而字-大小常数 5,将彼此相邻。但顺序可能与您期望的相反——学习“C 函数调用约定”。
[稍后编辑:现在使用 -S(输出程序集)运行一些代码示例;提醒我,在调用者中使用少量寄存器(即 CPU 寄存器可以被覆盖而不会造成损害),并且被调用函数的参数很少,参数可以完全通过寄存器传递以节省指令和内存。所以堆栈的布局实际上很难预测,即使攻击者可以访问源代码。尤其是 gcc -O2,它将我的 main -> my_function -> printf 函数序列折叠为 main -> printf]
大多数漏洞利用都使用堆栈溢出,因为恶意代码会在试图修改上述只读数据区域中的内存时遇到障碍——操作系统会中止进程。
printf 的行为是奇特的,因为格式字符串就像一个微型计算机程序,它告诉 printf 为它找到的每个 '%' 格式说明符查看堆栈上的参数。如果这些参数实际上从未被推送,和/或大小不同,printf 将盲目地遍历它不应该遍历的堆栈部分,并且可能会在堆栈的更上方(调用链的下方)显示私有数据可能所在的数据。如果 printf 的第一个参数至少是一个常量,编译器至少可以在后续参数与 '%' 说明符不匹配时发出警告,但是当它是一个变量时,所有的赌注都没有了。
从安全角度来看,printf 很糟糕,而且计算量很大,但非常强大且富有表现力。欢迎来到 C. :-)
第二次稍后编辑 现在你在 cmets 中的第一个问题......你可以看到你的术语,也许想法有点乱码。研究以下内容以了解正在发生的事情。不要担心指向字符串的指针。这是在没有标志的 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
【讨论】:
假设有一个 printf 语句允许用户提供没有其他参数的格式化字符串。鉴于格式字符串是一个连续的指针,我们如何获得一个 %s 格式字符来读取任何给定内存地址的值?如果此字符串仅连续表示为指针,那么我们将只能读取格式字符串本身。一旦我们补偿了格式字符串中的任何缓冲区和变量,是否有任何方法可以让 %s 读取特定系列的位并将其解释为指针,以便读取这些位? 您认为“连续表示为指针”是什么意思? 我的意思是直接在其他参数旁边的指针。 同意,cmets 中的后续问题很难解释。编辑答案以演示堆栈布局和利用概念,而不涉及 OP 似乎所指的不切实际的强大和精确的利用。 在 Linux AMD64 上,这些参数并非全部在堆栈上传递。【参考方案2】:有趣的问题。这是两个测试程序的汇编输出:一个是 32 位/MSVC,另一个是 64 位 GCC:
测试程序:
/*
* Sample output:
* A
* B: 49, 2, 5.000000
*/
#include <stdio.h>
int main(int argc, char *argv[])
printf ("A\n");
printf ("B: %d, %c, %f\n", 0x31, 0x32, 5.0);
return 0;
MSVS/32 位汇编 (cl /Fa
):
_DATA SEGMENT
$SG2938 DB 'A', 0aH, 00H
ORG $+1
$SG2939 DB 'B: %d, %c, %f', 0aH, 00H
...
CONST SEGMENT
__real@4014000000000000 DQ 04014000000000000r ; 5
...
push OFFSET $SG2938
call _printf
...
movsd xmm0, QWORD PTR __real@4014000000000000
movsd QWORD PTR [esp], xmm0
push 50 ; 00000032H
push 49 ; 00000031H
push OFFSET $SG2939
call _printf
GCC/64 位汇编 (gcc -S
):
.LC0:
.string "A"
.LC1:
.string "B: %d, %c, %f\n"
...
movl %edi, -4(%rbp) // You'll notice that GCC substitutes "puts()" for "printf()" here
movq %rsi, -16(%rbp)
movl $.LC0, %edi
call puts
...
movl $.LC1, %eax // Also notice the absence of "push": we're passing arguments in registers, instead of on the stack
movsd .LC2(%rip), %xmm0
movl $50, %edx
movl $49, %esi
movq %rax, %rdi
movl $1, %eax
call printf
【讨论】:
以上是关于printf() var-arg 引用如何与堆栈内存布局交互?的主要内容,如果未能解决你的问题,请参考以下文章