0

我正在阅读这本关于学习 Linux 二进制分析的书。在书中作者介绍了他在他的 github 上的 ftrace,并演示了如何使用它。他提供了一小段代码来测试 ftrace。

在其上运行 ftrace 时,没有任何反应。如果我自己运行可执行文件,我只会遇到段错误。我正在编译它: gcc -nostdlib test.c -o test

这是我的代码:

int foo(void) {  
}

_start()
{
    foo();
    __asm__("leave");
}

预期结果展示了 ftrace 通过执行跟踪函数调用。

这是我要表达的文字中的图像:

简单的 ftrace

这是我正在使用的 ftrace:

https://github.com/elfmaster/ftrace

我想问题是,我是否完全遗漏了某些东西,做错了什么,文本是否过时或者正确的方法是什么?如果这是一个愚蠢的问题,我很抱歉,我只是离开了文字。我还在虚拟机上使用 32 位发行版进行了尝试,但没有任何改变,但只是尝试了一下,因为作者的一些示例是在 32 位上的。谢谢你。

注意:当我使用不会导致段错误的程序运行他的 ftrace 时,我得到

pid_read() failed: Input/output error <0x1>
4

1 回答 1

3

调用_exit(0);exit_group(0)结束_start。(链接 withgcc -static -nostartfiles而不是,-nostdlib因此您可以调用 libc 系统调用包装函数;即使 glibc init 函数尚未运行,它们也应该可以工作,因此 malloc 或 printf 会崩溃)。

或者exit_group(0)使用内联汇编手动进行系统调用。在 x86-64 Linux 上:
asm("mov $231, %eax; xor %edi,%edi; syscall");

另请参阅如何在没有 Glibc 的情况下使用 C 中的内联汇编获取参数值?有关编写 hacky x86-64_start以运行您自己的 C 函数作为您进程中的第一件事的更多信息。(但大部分答案是关于破解调用约定以访问 argc / argv,这很讨厌,我不推荐它。)Matteo 对这个问题的回答是_start用 asm 编写的一个完整的最小值,它调用了一个普通的 Cmain函数。


这本书的代码完全被破坏了,原因有两个。(我不知道它是如何在 i386 或 x86-64 上工作的。对我来说似乎超级奇怪。你确定它不应该只是崩溃,但你看看它在发生之前做了什么?)

  1. _start不是 Linux 中的函数;您(或编译器生成的代码)不能ret从中获取。您需要进行_exit系统调用。堆栈1上没有返回地址。

    在函数有返回地址的地方,ELF 入口点_startargc,如 ABI 文档中所指定。(x86-64 System V 或 i386 System V 取决于您构建的是 64 位还是gcc -m3232 位可执行文件。)

  2. leave在编译器生成的代码中插入(它mov %ebp, %esp/pop %ebp或 RBP/RSP 等效项)在这里没有意义。它有点像一个额外的,但会破坏编译器的/所以如果它碰巧选择而不是它自己的序言,那么编译器生成的代码将会出错。(在静态链接的可执行文件中,入口的 RBP为 0。或者在跳转到PIE 可执行文件之前,保留 RBP 中留下的任何动态链接器。)popEBPRBPleavepop %rbp_start_start

    但最终,GCC 将_start作为正常函数编译,从而最终运行一条ret指令。任何地方都没有有效/有用的返回地址,所以根本没有办法ret工作。

    如果你在没有优化的情况下编译(默认),gcc 将默认为-fno-omit-frame-pointer,因此它的函数序言将设置 EBP 或 RBP 作为帧指针,使其leave自身不会出错。如果您使用优化(-O1和更高启用-fomit-frame-pointer)进行编译,则 gcc 不会与 RBP 混淆,并且在您运行时它将为零leave,从而直接导致段错误。(因为它做了 RSP=RBP 然后使用新的 RSP 作为堆栈指针pop %rbp。)

无论如何,如果它没有出错,那将argc在编译器生成pop %rbp作为正常函数结尾的一部分之前再次指向堆栈指针。 所以编译器生成的ret会尝试返回argv[0]. 由于默认情况下堆栈是不可执行的,因此会出现段错误。 (而且它指向 ASCII 字符,这些字符可能不会被解码为有用的 x86-64 机器代码。)

您可以通过使用 GDB 单步执行 asm 来自己发现这一点。(layout reg并使用stepiaka si)。

一般来说,你在编译器后面弄乱堆栈指针和其他寄存器通常只会让事情崩溃。如果在堆栈中存在更高的返回地址,那pop %rcx将比leave.


脚注1:

在进程的地址空间中甚至没有任何机器代码可以指向有用的返回地址来进行这样的系统调用,除非您将一些机器代码作为 arg 或环境变量注入。

你链接了-nostdlib所以没有 libc 链接。如果您确实动态链接 libc 但仍然编写自己的_start(例如 withgcc -nostartfiles而不是 full -nostdlib),则 ASLR 将意味着 libc_exit函数位于某个运行时变量地址。

如果您静态链接 libc ( gcc -nostartfiles -static),_exit()除非您实际引用它,否则代码不会被复制到您的可执行文件中,而这段代码不会。但是您仍然需要以某种方式调用它;没有指向它的返回地址。

于 2019-06-27T02:40:36.033 回答