【问题标题】:How to find the "exit" of a C program如何找到 C 程序的“出口”
【发布时间】:2015-10-12 15:27:21
【问题描述】:

测试在32-bit x86 Linux 上进行。

所以基本上我试图通过在汇编代码中插入检测指令来记录执行的基本块的信息。

我的策略是这样的:将执行的基本块的索引写入一个全局数组,当数组满时(16M)将数组从内存刷新到磁盘。

这是我的问题。当检测二进制文件的执行结束时,我需要将数组刷新到磁盘,即使它没有达到 16M 边界。但是,我只是不知道在哪里可以找到assembly 程序的出口。

我试过了:

  1. grep exit 来自目标汇编程序,并在 call exit 指令之前刷新内存。但是根据一些调试经验,目标 C 程序,比如 md5sum 二进制文件,在执行完成时不会调用 exit

  2. main函数结束时刷新内存。但是,在汇编代码中,我只是不知道main 函数的确切结尾在哪里。我可以采取一种保守的方法,比如查找所有 ret 指令,但在我看来,并非所有 main 函数都以 ret 指令结尾。

所以这是我的问题,如何识别 assembly code 的确切执行结束,并在那里插入一些检测指令?挂钩一些库代码对我来说很好。我理解不同的输入,二进制可能会在不同的位置退出,所以我想我需要一些保守的估计。我清楚了吗?谢谢!

【问题讨论】:

  • 您的仪器是免费软件吗?我很乐意看看你的代码....
  • 我不认为那总是很好的定义。一旦优化器完成了它的工作,甚至可能在函数之间甚至在main() 和在它之前调用的 libc 中的东西(程序的入口点)之间都没有定义边界。一个可能的线索是跟踪堆栈是如何被使用的,但我认为,当程序退出时堆栈将完全反转,这并不一定是真的。

标签: c linux assembly binary


【解决方案1】:

我相信在一般情况下你不能这样做。首先,如果main 正在返回一些代码,那么它就是一个退出代码(如果main 没有显式的return,最近的C 标准要求编译器添加一个隐式 return 0;)。然后一个函数可以将exit的地址存储在一些数据中(例如一个全局函数,一个struct中的一个字段,...),而一些其他函数可以间接 通过函数指针调用它。实际上,程序可以使用dlopen 加载一些插件,并使用dlsym 作为"exit" 名称,或者只是在插件内部调用exit,等等...... AFAIU 解决了这个问题(找到实际的exit 调用,在动态意义上)完全一般性可以证明等价于halting problem。另见Rice's theorem

在不要求详尽无遗的情况下,我会提出其他建议(假设您有兴趣检测用 C 或 C++ 等编写的程序......其源代码可供您使用)。您可以使用MELT 自定义 GCC 编译器,以更改在 GCC 中处理的基本块以调用您的一些检测函数。这不是微不足道的,但它是可行的……当然,您需要使用这种定制的 GCC 重新编译一些 C 代码来检测它。

(免责声明,我是MELT的主要作者;请随时与我联系以获取更多信息...)

顺便说一句,你知道atexit(3)吗?它可能对您的冲洗问题有所帮助...您还可以使用LD_PRELOAD 技巧(阅读有关dynamic linkers 的信息,请参阅ld-linux(8))。

【讨论】:

    【解决方案2】:

    atexit() 将正确处理 95+% 的程序。您可以修改其注册处理程序链,也可以像其他块一样对其进行检测。但是,某些程序可能会通过使用不调用 atexit 处理程序的_exit() 来终止。可能检测 _exit 以调用数据刷新并安装 atexit(或在类似 BSD 的程序上的 on_exit())处理程序应该覆盖几乎 100% 的程序。


    附录:注意Linux Base Specification says C 库启动应:

    调用初始化函数 (*init)()。
    使用适当的参数调用 main()。
    使用 main() 的返回值调用 exit()。

    【讨论】:

    • 这取决于被调试的程序是否使用常规开发工具链编写:大多数用汇编程序编写的程序(出于学习目的)直接调用“exit”系统调用,这意味着“on_exit”或“atexit”功能不处理。当程序被“kill”中断时也是如此。
    • @MartinRosenau:Linux 上可能有多少非学生汇编程序?
    【解决方案3】:

    应该每次都有效的方法是创建一个共享内存部分,用于在那里存储您的数据。

    您还创建了一个等待被调试进程完成的子进程。

    一旦被调试的进程完成,子进程将使用共享内存中的数据完成写入操作。

    这应该适用于所有形式的退出、进程中断(例如 Ctrl+C、关闭终端窗口……),或者即使进程已被使用“kill”杀死。

    【讨论】:

      【解决方案4】:

      但是根据一些调试经验,目标 C 程序,比如 md5sum 二进制文件,在完成执行时不会调用 exit。

      让我们看看i686 GNU/Linux 系统上的md5sum 二进制文件:

      在反汇编中 (objdump -d /usr/bin/md5sum) 我们有这个:

      Disassembly of section .text:
      
      08048f50 <.text>:
       8048f50:       55                      push   %ebp
       8048f51:       89 e5                   mov    %esp,%ebp
       8048f53:       57                      push   %edi
       8048f54:       56                      push   %esi
       8048f55:       53                      push   %ebx
       8048f56:       83 e4 f0                and    $0xfffffff0,%esp
       8048f59:       81 ec c0 00 00 00       sub    $0xc0,%esp
       8048f5f:       8b 7d 0c                mov    0xc(%ebp),%edi
      
      [ ... ]
      
       8049e8f:       68 b0 d6 04 08          push   $0x804d6b0
       8049e94:       68 40 d6 04 08          push   $0x804d640
       8049e99:       51                      push   %ecx
       8049e9a:       56                      push   %esi
       8049e9b:       68 50 8f 04 08          push   $0x8048f50
       8049ea0:       e8 4b ef ff ff          call   8048df0 <__libc_start_main@plt>
       8049ea5:       f4                      hlt    
      

      这是所有启动样板代码。实际程序的main 调用在调用__libc_start_main 内部调用。如果程序从那里返回,那么,你看,有一个hlt 指令。那是你的目标。查找 hlt 指令并在程序结束时进行检测。

      【讨论】:

      • 技术上__libc_start_main() 应该永远不会返回。永远不应到达hlt__libc_start_main 将在某一时刻调用其中一个退出函数,该函数将执行 sys_exit 系统调用以终止用户程序。
      • 有趣的是,hlt 是一个特权指令,实际上会引发异常。请参阅 cmets herehere 中的讨论。 __libc_start_main() 的 LSB 规范为 here
      • @Edward 这有点道理;在虚拟化下,VM 监视器知道来宾操作系统何时空闲非常有用。
      【解决方案5】:

      你可以试试这个:

      int main() 
      bool keepGoing = true;
      {
          while(keepGoing) {
              string x;
              cin >> x;
              if(x == "stop") {
                  keepGoing = false;
              }
          }
      }
      

      即使它是原始的......我可能会扼杀编码,但这只是一个概念。

      【讨论】:

      • 这是错误的(您在main() 和大括号之间声明一个变量keepGoing!)并且它需要用户交互(cin)才能工作。我认为你没有理解这个问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 1970-01-01
      • 1970-01-01
      • 2019-09-11
      • 2014-05-10
      相关资源
      最近更新 更多