我打印出来的地址是什么?

时间:2014-10-28 20:18:46

标签: c security stack buffer-overflow

在此示例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;
}

4 个答案:

答案 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):

  1. 您的代码将指向格式字符串的指针推送到堆栈并调用 函数printf

  2. printf读取格式字符串,该字符串位于堆栈顶部。

  3. printf在格式字符串中看到%p。你告诉它有 堆栈上的另一个参数,这个参数是一个指针,printf 应该打印这个指针的值。

  4. 但是堆栈上没有其他参数。 printf解释任何事情 任意垃圾作为指针放在堆栈上并打印出来。

  5. 所以你提供的%p越多,它打印的堆栈数据就越多。

    它打印的垃圾是什么(你猜对了)未定义。你必须要学习你的 平台和编译器,以了解和理解那里有什么。

答案 2 :(得分:1)

您有一些undefined behavior(作为answered by Vlad from Moscow)。另见this。解释实际打印值是试图理解未定义的行为,这需要了解具体的实现细节(ABI,生成了什么确切的机器代码 - 取决于编译器,系统,处理器......- ,main等开始时的机器状态是什么......)。

每次运行时打印地址不同的可能原因是ASLR。编译器可能正在堆栈中传递一些地址,请参阅this&amp; that。我想如果你将几个不同的参数传递给main,或者在运行程序之前更改环境(在Linux上,bashexport SOME_LONG_VARIABLE_NAME=something-useless-but-long),运行时行为也会有所不同

答案 3 :(得分:0)

printf依赖于格式字符串来告诉它需要多少以及哪些类型的附加参数。当您在格式字符串中传递%p时,printf期望还有一个类型为void *的附加参数,并且它将尝试从其预期附加参数的任何位置读取值(无论是在堆栈上还是在寄存器中)。从恶意软件的角度来看这是否可以利用取决于该平台的调用约定。

如果为给定的转换说明符传递的参数或参数类型太少,则可以从垃圾输出到段错误。语言标准使行为未定义,这意味着编译器不需要发出警告或以任何特定方式处理问题。