C 在printf中使用%p?

C 在printf中使用%p?,c,memory,assembly,C,Memory,Assembly,我从这个链接在线阅读了以下代码: 我对这行中%p的用法感到困惑: printf("Now the stack looks like:\n%p\n%p\n%p\n%p\n%p\n%p\n\n"); 这段代码取自以下代码片段: /* StackOverrun.c This program shows an example of how a stack-based buffer overrun can be used to execute arbitrary code. Its

我从这个链接在线阅读了以下代码:

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

 printf("Now the stack looks like:\n%p\n%p\n%p\n%p\n%p\n%p\n\n");
这段代码取自以下代码片段:

 /*
  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;
}
/*
堆栈溢出
这个程序展示了一个基于堆栈的
缓冲区溢出可用于执行任意代码。它的
目标是找到执行功能栏的输入字符串。
*/
#杂注检查堆栈(关闭)
#包括
#包括
void foo(常量字符*输入)
{
char-buf[10];
printf(“我的堆栈看起来像:\n%p\n%p\n%p\n%p\n%p\n%p\n\n”);
strcpy(buf,输入);
printf(“%s\n”,buf);
printf(“现在堆栈看起来像:\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n\n”);
}
空心条(空心)
{
printf(“啊!我被黑客攻击了!\n”);
}
int main(int argc,char*argv[])
{
//明目张胆地欺骗自己,让自己的生活更轻松
printf(“foo的地址=%p\n”,foo);
printf(“酒吧地址=%p\n”,酒吧);
如果(argc!=2)
{
printf(“请提供一个字符串作为参数!\n”);
返回-1;
} 
foo(argv[1]);
返回0;
}
我知道%p是指针的格式化程序,但为什么格式化程序后面没有值?这里实际打印的值是什么?如果它正在打印提供给foo函数的参数的地址,那么为什么有5个'%p',为什么所有'%p'的格式不是相同的值


非常感谢

这是利用未定义的行为


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

这段代码很糟糕,与使用调试器查看堆栈相比,它是一个非常糟糕的替代品。除非你了解它是如何滥用呼叫约定的,以及它到底在打印什么,否则不要浪费你的时间。(还要查看编译器生成的代码,以查看调用时堆栈指针指向的位置)

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

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

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

在Windows x64中,调用约定包括“阴影空间”:被调用方拥有的RSP之上32个字节,它们可以在其中转储寄存器参数,以使参数数组与堆栈参数相邻。呼叫方必须保留此空间。strcpy也会发生这种情况,所以可能会有一些平衡,但您仍然要打印RDX、R8和R9中的垃圾。(RCX中的格式字符串)



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

就此而言,在某些体系结构(包括64位模式下的x86)上,它将从寄存器中提取值,而不是从堆栈中提取值,有时还会从堆栈中提取值。请参阅,例如,由于安全攻击,如果字符串来自用户,printf(用户提供的字符串)不正确,请改用printf(“%s”,用户字符串)或put(用户字符串)。@user26347 Note
put()
appends
\n
fputs(user\u string,stdout)
类似于
printf(“%s”,user\u string)
。这不是您的重点,但“foo的地址”行也会导致未定义的行为-
%p
仅用于打印
void*
,即使您尝试了,也可能无法将函数指针转换为它。我认为常规C函数指针可以转换为
void*
。我知道C++函数指向成员函数更复杂,但是如果正则函数指针不能被转换,它会破坏C兼容性。