两种可能性(实际上具有相同的主要原因):
你正在做静态链接。这意味着链接器不需要从 libc 中提取 printf,因此 printf 根本不会链接到您的程序中。
您正在执行动态链接,但在您的体系结构中,函数指针实际上并不指向函数本身,而是指向它的动态蹦床。这意味着您找到的魔术指针指向一个蹦床,该蹦床只是因为您实际上从程序中调用 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 条目的顺序受链接器中内部哈希表的顺序影响)。换句话说,只有当您的程序看起来与您用来打印地址的程序完全一样时,这才有可能发挥作用。