在此示例C程序中,
int main (int argc, char* argv[]) {
printf("%p\n");
return 0;
}
我对我正在打印的内容感到困惑。每次运行程序时,它打印的地址都会改变,所以我假设地址与堆栈有关,比如它可能在哪里开始或者其他什么,但我不确定。
编辑:上面的程序来自Michael Howard和David LeBlanc(2003)的“编写安全代码”(第2版)中的简单缓冲区溢出攻击的更详细的例子。在foo方法中,第一个printf说“我的堆栈看起来像:\ n%p ...等所以我想知道这是怎么可能的,因为没有参数传递给printf函数,但我问这里因为可能有我不知道的事情。我很抱歉不把它包括在原帖中。#include <stdio.h>
#include <string.h>
void foo (const char* input)
{
char buf[10];
printf("My stack looks like:\n%p\n%p\n%p\n%p\n%p\n%p\n\n");
strcpy(buf, input);
printf("%s\n", buf);
printf("Now the stack looks like:\n%p\n%p\n%p\n%p\n%p\n%p\n\n");
}
void bar (void)
{
printf("Augh! I've been hacked!\n");
}
int main(int argc, char* argv[])
{
printf("Address of foo = %p\n", foo);
printf("Address of bar = %p\n", bar);
if (argc != 2) {
printf("Please supply a string as an argument!\n");
return -1;
}
foo (argv[1]);
return 0;
}
答案 0 :(得分:4)
程序有未定义的行为,因为函数调用中没有格式说明符%p
的参数
printf("%p\n");
来自C标准
描述
2 fprintf函数将输出写入指向的流 流,在格式指向的字符串的控制下 指定后续参数如何转换为输出。 如果 格式没有足够的参数,行为是 未定义。强>
同样适用于函数printf
答案 1 :(得分:3)
所以我们已经涵盖了这是未定义的行为。你做不了 保证调用未定义行为的代码。所以你拥有的代码 发布的可能不起作用除了非常具体的实现(例如, g86上的gcc 4.3,没有额外的编译器标志或优化,让我们说)。
但是,让我们玩得开心,猜猜代码是如何针对特定的 具有特定编译器的平台,在特定优化时具有特定标志 水平。
这对你有帮助的主要思想是编译器必须生成一些 代码为函数赋予其参数,函数必须有一些代码 能够访问这些参数。然后有人,无论是来电还是来电 该函数必须有代码来清理参数以便内存(如果使用的话) (根本没有)没有泄露。
但是,这就是问题:生成调用函数的代码的编译器可能不会 与编译函数的编译器相同。
所以平台架构师和编译器编写者以及其他各种利益相关者 聚在一起,想出了一些calling conventions。呼唤 约定是平台的ABI的一部分,并且只要是每个编译器 实现相同的ABI,然后它们的编译库将是兼容的。
由于ABI,我可以使用我的编译器实现一个函数并给你 生成的目标文件。您可以编写代码来调用此函数,并链接 用我的目标文件(库)。如果我们的编译器都生成了坚持的代码 对于正确的调用约定和ABI,它将全部解决。
正如您所猜测的,每个平台的调用约定都是不同的。该
calling convention on x86 processors用于printf
等可变函数
被称为 cdecl 调用约定。在这个召集会议中,
调用者将所有参数推送到堆栈(以相反的顺序)和一次
函数完成后,调用者将参数弹出堆栈。
所以你看到的是你调用了printf
并提供了1个参数,
这是你的格式字符串。这是发生的事情(对于cdecl):
您的代码将指向格式字符串的指针推送到堆栈并调用
函数printf
。
printf
读取格式字符串,该字符串位于堆栈顶部。
printf
在格式字符串中看到%p
。你告诉它有
堆栈上的另一个参数,这个参数是一个指针,printf
应该打印这个指针的值。
但是堆栈上没有其他参数。 printf
解释任何事情
任意垃圾作为指针放在堆栈上并打印出来。
所以你提供的%p
越多,它打印的堆栈数据就越多。
它打印的垃圾是什么(你猜对了)未定义。你必须要学习你的 平台和编译器,以了解和理解那里有什么。
答案 2 :(得分:1)
您有一些undefined behavior(作为answered by Vlad from Moscow)。另见this。解释实际打印值是试图理解未定义的行为,这需要了解具体的实现细节(ABI,生成了什么确切的机器代码 - 取决于编译器,系统,处理器......- ,main
等开始时的机器状态是什么......)。
每次运行时打印地址不同的可能原因是ASLR。编译器可能正在堆栈中传递一些地址,请参阅this&amp; that。我想如果你将几个不同的参数传递给main
,或者在运行程序之前更改环境(在Linux上,bash
,export SOME_LONG_VARIABLE_NAME=something-useless-but-long
),运行时行为也会有所不同
答案 3 :(得分:0)
printf
依赖于格式字符串来告诉它需要多少以及哪些类型的附加参数。当您在格式字符串中传递%p
时,printf
期望还有一个类型为void *
的附加参数,并且它将尝试从其预期附加参数的任何位置读取值(无论是在堆栈上还是在寄存器中)。从恶意软件的角度来看这是否可以利用取决于该平台的调用约定。
如果为给定的转换说明符传递的参数或参数类型太少,则可以从垃圾输出到段错误。语言标准使行为未定义,这意味着编译器不需要发出警告或以任何特定方式处理问题。