【问题标题】:Usage of %p in printf ?在 printf 中使用 %p 吗?
【发布时间】:2014-04-26 14:17:14
【问题描述】:

我正在通过此链接在线阅读以下代码:http://www.cse.scu.edu/~tschwarz/coen152_05/Lectures/BufferOverflow.html

我对这一行中 %p 的用法感到困惑:

 printf("Now the stack looks like:\n%p\n%p\n%p\n%p\n%p\n%p\n\n");

取自这段代码的sn-p:

 /*
  StackOverrun.c
  This program shows an example of how a stack-based 
  buffer overrun can be used to execute arbitrary code.  Its 
  objective is to find an input string that executes the function bar.
*/

#pragma check_stack(off)

#include <string.h>
#include <stdio.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[])
{
    //Blatant cheating to make life easier on myself
    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;
}

我知道 %p 是指针的格式化程序,但是为什么格式化程序后面没有值?这里实际打印了哪些值?如果它正在打印提供给 foo 函数的参数的地址,那么为什么有 5 个 '%p' 以及为什么不是所有的 '%p' 都格式化相同的值?

非常感谢。

【问题讨论】:

  • 不是你的主要观点,但“foo 的地址”行也会导致未定义的行为 - %p 仅用于打印 void *,您甚至可能无法将函数指针转换为如果你尝试过。
  • 我认为常规 C 函数指针可以转换为 void*。我知道指向成员函数的 C++ 指针更复杂,但如果无法转换常规函数指针,它会破坏 C 兼容性。

标签: c memory assembly


【解决方案1】:

这段代码很糟糕,而且是用调试器查看堆栈的极差替代品。不要浪费你的时间,直到/除非你了解它是如何滥用调用约定以及它到底在打印什么。 (还要查看编译器生成的代码,以查看调用时堆栈指针指向的位置)。

这取决于堆栈参数调用约定,就像 32 位 x86 上的典型调用约定。 (因此可变参数printf 函数会将堆栈上的数据视为额外的参数)。函数“拥有”它们的参数(并且可以修改它们),但实际上大多数函数不会修改它们的堆栈参数,可变参数函数更是如此。

这还取决于编译器没有插入一堆额外的填充来将call printf 的 ESP 对齐 16,这是 i386 System V ABI 的现代版本所需要的。如果发生这种情况,在您想用strcpy 缓冲区溢出覆盖的实际内容之前会有一些额外的填充指针。

在 x86-64 代码中,x86-64 System V 在整数寄存器中传递前 6 个整数/指针参数,因此在格式字符串之后,前 5 个%p 转换将抓取 RSI、RDX、 RCX、R8 和 R9。 (RDI 中的格式字符串)。然后,您将从堆栈中获得一些 qwords,用于其余的 %p 转换。

在 Windows x64 中,调用约定包括“影子空间”:被调用者拥有的 RSP 上方 32 个字节,它们可以在其中转储寄存器 args 以使 args 数组与堆栈 args 连续。调用者必须保留这个空间。 strcpy 也会发生这种情况,所以这可能有点平衡,但您仍将打印 RDX、R8 和 R9 中的任何垃圾。 (RCX 中的格式字符串)。


一般而言,其他非 x86 ISA 的大多数调用约定都有一些寄存器参数(通常为 4 个,例如 ARM 或 MIPS),并且大多数不使用影子空间。

【讨论】:

    【解决方案2】:

    这是利用未定义的行为。

    通过故意不向 printf 提供值,va_arg 将从要打印的堆栈中拉出任意值。这不是正确的代码。事实上,这段代码 sn-p 似乎试图解释黑客技术,这些技术通常利用发生未定义行为时出现的故障。

    【讨论】:

    • 就此而言,在某些架构(包括 64 位模式下的 x86)上,它会将值从寄存器中提取出来,而不是——或者有时除了——“出栈”之外。参见,例如,en.wikipedia.org/wiki/X86_calling_conventions
    • 由于安全攻击,如果字符串来自用户,printf(user_provided_string) 不好,改用 printf("%s", user_string) 或 puts(user_string)。
    • @user26347 注意puts() 追加\nfputs(user_string, stdout) 就像 printf("%s", user_string)
    猜你喜欢
    • 2017-06-26
    • 1970-01-01
    • 1970-01-01
    • 2011-01-23
    • 2021-01-10
    • 2019-07-03
    • 2014-09-12
    • 2021-07-09
    相关资源
    最近更新 更多