【问题标题】:Calling printf() by its pointer通过指针调用 printf()
【发布时间】:2013-10-09 12:06:44
【问题描述】:

一切正常:

#include <stdio.h>

int (* func)(const char * fmt, ...);

int main()
{
  func = (void *)0x400420; /* printf pointer */
  printf("printf address: %p\n", printf);
  func("func() calling\n");
  return 0;
}

但这不起作用:

#include <stdio.h>

int (* func)(const char * fmt, ...);

int main()
{
  func = (void *)0x400420; /* printf pointer */
  /* printf("printf address: %p\n", printf); */
  func("func() calling\n");
  return 0;
}

我做错了什么?请告诉我。我可以通过指针调用 printf(或其他函数)吗?

【问题讨论】:

  • 像这样初始化指针func=printf;
  • 您写道,“这不起作用:”,但没有描述如何它不起作用.编译时错误?链接时间错误?运行时崩溃? Nasal Demons?
  • @abelenky 它给了 SIGILL(至少对我来说)。
  • @abelenky,它与 SIGSEGV 一起崩溃。
  • @user2862654 很奇怪,我让它与 SIGILL 一起崩溃。但我也注意到我的printf 地址与你的不同。

标签: c pointers printf


【解决方案1】:

在第二个示例中,printf 未链接到您的程序。

【讨论】:

    【解决方案2】:

    更改您的代码以评估 printf 地址,而不是将其固定:

    #include <stdio.h>
    
    int (* func)(const char * fmt, ...);
    
    int main()
    {
        func = printf; /* printf pointer */
        func("func() calling\n");
        return 0;
    }
    

    【讨论】:

    • 不要将其转换为 void *,不过,void * 用于指向对象类型的指针。
    • @PaulGriffiths 已修复。
    【解决方案3】:

    两种可能(实际上有相同的主要原因):

    1. 您正在执行静态链接。这意味着链接器不需要从 libc 中拉入 printf,因此 printf 根本不会链接到您的程序中。

    2. 您正在执行动态链接,但在您的体系结构中,函数指针实际上并不指向函数本身,而是指向它的动态蹦床。这意味着您找到的魔术指针指向一个蹦床,该蹦床只是因为您实际上从程序中调用 printf 而创建的。

    我很确定您正在动态链接。让我们看看这个简单的程序在实践中是如何工作的:

    #include <stdio.h>
    
    int
    main(int argc, char **argv)
    {
        void *foo = (void *)printf;
        return 0;
    }
    

    我们编译并链接它,然后反汇编。这是将 printf 的地址加载到局部变量中的相关指令。

      4004cf:       48 c7 45 f8 c0 03 40    movq   $0x4003c0,-0x8(%rbp)
    

    请注意,指针是 0x4003c0,与您的指针非常相似。但请稍等。该指令的地址为 0x4004cf。这与 printf 指针指向的位置非常接近,实际上它在同一页中,因此这不可能是指向 libc 的指针。让我们看看那个地址有什么:

    00000000004003c0 <printf@plt>:
      4003c0:       ff 25 92 04 20 00       jmpq   *0x200492(%rip)
      4003c6:       68 00 00 00 00          pushq  $0x0
      4003cb:       e9 e0 ff ff ff          jmpq   4003b0 <_init+0x18>
    

    这是程序的一部分,而不是 libc。如果把程序改成调用printf,实际上会跳转到这个地址。这就是我说的动态蹦床。这里的“plt”代表过程链接表,它是动态链接器用来在不修改程序文本段的情况下解析函数调用的魔力。

    您的问题是您在程序中删除了任何指向 printf 的链接。由于它不存在,链接器不会生成此蹦床,您只需将指针设置为指向恰好在该地址生成的随机数。

    顺便说一句。实际上,在您的程序中引用 printf 不足以使这样的技巧发挥作用。很可能只调用另一个函数会将该函数的 PLT 条目放在生成 printf 的蹦床的相同位置。您绝对不能保证这些蹦床最终会在哪里结束,并且根据我对链接器如何生成代码的经验,它似乎是随机的(因为 PLT 条目的顺序受链接器中内部哈希表的顺序影响)。换句话说,只有当您的程序看起来与您用来打印地址的程序完全一样时,这才有可能发挥作用。

    【讨论】:

    • 好的,谢谢。这是否意味着我无法通过其固定地址调用 libc 标准函数?
    • @user2862654 libc 函数没有固定地址。曾经。当您只静态链接您实际使用的 libc 的部分时,函数的地址将取决于链接的 libc 的其他部分。在进行动态链接时,您的操作系统很可能正在执行地址空间随机化,并且每次执行时 libc 都会映射到不同的位置。一些操作系统甚至随机化程序的地址,甚至每次运行时都会改变地址。
    【解决方案4】:

    在 Windows 7 上,编译器:mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0

    int main()
    {
        int c = 5;
        int (*pr)(const char *const _Format, ...);
        pr = &printf;
        (*pr)("%d\n", c);
        
        return 0;
    }
    

    编译并运行。您可以按照此视频Pointers in C / C++ [Full Course] 中的说明进行操作,以获得更好的理解。在 3 小时左右,讲师开始谈论函数指针。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-21
      • 1970-01-01
      相关资源
      最近更新 更多