在简单程序上使用ftrace,内联汇编__asm __(“ leave”)导致段错误

时间:2019-06-27 01:35:21

标签: c gcc x86 inline-assembly ftrace

我正在阅读有关学习Linux Binary Analysis的本书。在这本书中,作者介绍了ftrace,他将其放在github上并演示了如何使用它。他提供了一些代码来测试ftrace。

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

这是我的代码:

int foo(void) {  
}

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

预期结果表明ftrace在执行过程中跟踪了函数调用。

这是我要摘录的文字的图像:

simple ftrace

这是我正在使用的ftrace:

https://github.com/elfmaster/ftrace

我想的问题是,我是否完全遗漏了一些东西,做错了什么,文本是否过时,或者正确的处理方法是什么?如果这是一个愚蠢的问题,我深表歉意,我只是在讲完文字。我还在VM上使用32位发行版进行了尝试,但没有做任何更改,但是仅尝试了一下,因为作者的一些示例在32位上进行。谢谢。

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

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

1 个答案:

答案 0 :(得分:3)

_exit(0);末尾致电exit_group(0)_start。 (使用gcc -static -nostartfiles而不是-nostdlib进行链接,因此您可以调用libc系统调用包装函数;即使尚未运行so malloc or printf would crash的glibc初始化函数,它们也应该可以工作。)

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

另请参阅How Get arguments value using inline assembly in C without Glibc?,以获取有关编写骇客的x86-64 _start来运行自己的C函数的更多信息。 (但是大多数答案是关于修改调用约定以访问argc / argv,这很讨厌,我不建议这样做。)Matteo在该问题上的答案是用asm编写的整个_start最小,它调用a正常的C main函数。


这本书的代码由于两个原因而被普通破解。 (我不知道它在i386或x86-64上是如何工作的。对我来说似乎很奇怪。您确定它不只是应该崩溃的,而是您看看它在此之前发生了什么?)< / p>

  1. _start在Linux中不是功能;您(或编译器生成的代码)无法ret使用它。您需要进行_exit系统调用。堆栈 1 上没有返回地址。

    在函数具有返回地址的位置,ELF入口点_start具有argc,如ABI文档中所指定。 (x86-64系统V或i386系统V取决于您构建的是64位还是gcc -m32 32位可执行文件。)

  2. (将leave / mov %ebp, %esp或等效的RBP / RSP插入pop %ebp 到编译器生成的代码中< / strong>在这里毫无意义。有点像额外的pop,但是破坏了编译器的EBP / RBP,因此如果碰巧选择leave而不是pop %rbp作为自己的序言,那么编译器生成的代码将出错。 (在静态链接的可执行文件中,进入_start的RBP为0。或者在PIE可执行文件中,保留跳转到_start之前在RBP中保留的任何动态链接器。)

    但是最终,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来自动找到该问题。 ({layout reg并使用stepisi)。

通常,您弄乱了编译器背后的堆栈指针和其他寄存器通常只会使事情崩溃。而且,如果在堆栈上有一个更高的返回地址,则pop %rcxleave更有意义。


脚注1:

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

您与-nostdlib链接,因此没有libc链接。如果您确实动态链接了libc,但仍然编写了自己的_start(例如,用gcc -nostartfiles而不是完整的-nostdlib),则ASLR表示libc _exit函数处于某个运行时-可变地址。

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