当我使用 exec-wrapper 脚本来执行我的目标二进制文件时,gdb 不会中断

逆向工程 数据库
2021-06-26 21:04:00

为什么当我使用 exec-wrapper 脚本来执行我的目标二进制文件时 gdb 不会中断。

这是我的小测试。

我使用没有饼图选项的二进制文件来使用可预测的地址。

$ gcc -no-pie main.c

我有这个小小的 gdb exec 包装脚本。

$ cat wrapper.sh 
#!/bin/bash

env -i ./a.out

然后我像这样调试我的程序,通过在 *main 处设置 exec-wrapper 变量和调试断点,断点不起作用:

$ gdb -q ./a.out 
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) b *main
Breakpoint 1 at 0x401126
(gdb) set exec-wrapper ./wrapper.sh
(gdb) r
Starting program: /home/snake/gdb-test/a.out 
boum !
During startup program exited normally.
(gdb)

这里是没有设置 exec-wrapper 变量和在 *main 处的调试断点的相同示例,断点有效:

$ gdb -q ./a.out 
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) b *main
Breakpoint 1 at 0x401126
(gdb) r
Starting program: /home/snake/gdb-test/a.out 

Breakpoint 1, 0x0000000000401126 in main ()
(gdb) 

为什么在第一种情况下断点不起作用?

也许 gdb 没有附加到我的 exec 包装脚本运行的进程中。

谢谢你的帮助。

更新:

谢谢@ws,你的提醒和观点很有趣。

我使用env -i为了在空环境中启动程序,以便在非随机地址空间、gdb 输入/输出和任何路径中具有可预测的堆栈。但是 GDB 缺少使用相对路径来配置二进制名称以传递给 argv[0] 的选项,而其他压入堆栈的变量将根据执行上下文的目录位置导致堆栈偏移。

所以我构建了这个小包装来克服这个问题。也许脏,但工作得很好。

#include <unistd.h>

int main()
{
  int e;
  char *envp[] = { NULL };
  char *argv[] = { "./a.out", NULL };

  e = execve("./a.out", argv, envp);
}

然后

$ gcc exec_wrapper.c -o exec_wrapper

最后

$ gdb -q ./a.out 
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) set exec-wrapper ./exec_wrapper
(gdb) b *main
Breakpoint 1 at 0x1149
(gdb) r
Starting program: /home/snake/gdb-test/a.out 

Breakpoint 1, 0x0000555555555149 in main ()
(gdb)

所以我不太明白你的答案@ws,我的可执行文件名称直接编码在二进制文件中,并且运行良好。如果我了解 gdb 的行为,通常 GDB 会监视第一个进程陷阱 execve() 由 shell 启动以跟随它。在 exec-wrapper 被配置的情况下,它跟在第二个 execve() 之后。(感谢一位朋友向我展示这种行为)。

gdb/nat/fork-inferior.h

/* Number of traps that happen between exec'ing the shell to run an
   inferior and when we finally get to the inferior code, not counting
   the exec for the shell.  This is 1 on all supported
   implementations.  */
#define START_INFERIOR_TRAPS_EXPECTED 1

gdb/nat/fork-inferior.c

  /* The process was started by the fork that created it, but it will
     have stopped one instruction after execing the shell.  Here we
     must get it up to actual execution of the real program.  */
  if (get_exec_wrapper () != NULL)
       pending_execs++;

也许我不明白你的回答@ws。对不起。您的意思是说 gdb 将二进制名称与完整路径与 execve() 调用的 argv[0] 进行字符串比较?

1个回答

简而言之,它是糟糕的 exec-wrapper。Exec-wrapper 定义为(例如,此处)为

当 'exec-wrapper' 被设置时,指定的包装器用于启动调试程序。GDB 使用 exec 包装程序形式的 shell 命令启动您的程序。引号被添加到程序及其参数中,但不会添加到包装器中,因此如果适合您的 shell,您应该添加引号。包装器运行直到它执行您的程序,然后 GDB 获得控制权。

您可以使用任何最终调用 execve 并将其参数作为包装器的程序。一些标准的 Unix 实用程序可以做到这一点,例如 env 和 nohup。任何以 exec "$@" 结尾的 Unix shell 脚本也可以使用。

例如,您可以使用 env 将环境变量传递给被调试的程序,而无需在 shell 环境中设置该变量:

(gdb) set exec-wrapper env 'LD_PRELOAD=libtest.so' (gdb) run 此命令在大多数目标上本地调试时可用,不包括 DJGPP、Cygwin、MS Windows 和 QNX Neutrino。

据我所知,您的 shell 脚本不适合

任何以 exec "$@" 结尾的 Unix shell 脚本也可以使用。

因此将无法正常工作。

一些解释:

首先,一些定义。

  • gdb是一个调试器,我们都知道它的含义。它是基于ptrace系统调用编写的 ,它允许(除其他功能外)调试和拦截“子进程”的系统调用
  • 子进程是父进程使用fork或clone和execve系统调用创建的进程,可以被ptrace拦截。
  • exec-wrapper是 gdb 中的一个执行包装器(参见之前的引用)。自定义执行环境需要它,请参阅此处的文档
  • env - 是用于修改执行环境的程序,例如从环境变量的角度来看(顺便说一句, env -i 意味着根本没有环境变量)

如果 gdb 用 ptrace 拦截了被调试进程的 execve 系统调用(据我所知它应该这样做),它在系统调用参数中找不到你的可执行文件名(因为它直接编码到你的脚本中)和因此不执行设置断点。