将 jmp detour 插入到编译好的程序中

逆向工程 拆卸 C 重新组装
2021-06-29 02:41:28

我在 Stack Exchange 和其他地方看到过在编译代码中插入弯路的参考资料。我的理解是,本质上插入了一条 jmp 指令,然后以某种方式将修补程序与包含 jmp 目标的附加代码链接起来。

作为一个具体但(希望)简单的例子,考虑一个程序。

#include <stdio.h>

void hello(void) {
    printf("Hello ");
    printf("world!\n");
}

int main(void) {
    hello();
}

假设我只有一个从这个程序编译的二进制文件。没有采取任何措施来去除符号或以任何方式混淆程序。我想插入一个绕道来调用从这个函数编译的代码。

#include <stdio.h>

void detour(void) {
    printf("detoured ");
}

修补程序的输出应该是:

你好迂回的世界!

我该怎么做?我如何避免破坏编译代码中的地址偏移?

我可用的编译器是 gcc、clang 和 icc。我可用的操作系统是 OS X 和 Ubuntu。在答案中选择您喜欢的任何内容。

如果这里不能合理地回答这个问题,那么简要概述和一些阅读材料的指针也将是一个很好的答案。

我知道 LD_PRELOAD 和 ld --wrap。选择这个例子是为了使这些方法不容易满足。(当然,您可以将整个 hello() 函数绕到一个打印“Hello detoured world!”的函数中,但让我们假设您没有源代码并且该函数很重要。)

相关问题

我首先要问的是如何简单地反汇编和重新组装已编译的程序,但已被问到。得到的答复是这非常困难。我从答案中的感觉是,这不是一件常见的事情。我怀疑我认为拆卸和重新组装是绕道而行的必要步骤的假设可能不正确。

为什么没有任何可以生成可重新组装的汇编代码的反汇编程序?

StackOverflow 上的一个类似问题得到了不冷不热的回应。这个问题的重点只是插入绕道而不是一般的“修改”,我认为这里的观众会更容易接受。

https://stackoverflow.com/questions/4309771/disassemble-modifying-and-then-reassemble-a-linux-executable

这个逆向工程问题询问一般修改二进制文件。提到了很多不同的工具和 LD_PRELOAD。一些答案说可以使用十六进制编辑器来做到这一点。我想这是我最感兴趣的方法。

如何向现有的二进制可执行文件添加功能?

最近我看到的例子是指做这样的事情

一篇博文。

http://charlessolar.com/post/tag/disassemble

这个堆栈溢出问题。

https://stackoverflow.com/questions/9449845/how-to-link-object-file-to-executable-compiled-binary

1个回答

我在 Ubuntu 14.04 上编译了您的程序并将其放在https://mega.co.nz/#!gdRRxRzZ!dw08GEHvXeTxXqurcpMLOxpXVjZa807TJN0PH60h4Rg 上如果您想回溯以下步骤,您可能想使用该二进制文件,因为如果您没有 C 编译器和库的确切版本,您的二进制文件可能会有所不同。

该文件是一个 zip,包括原始 detour.c、已编译程序 (detour.orig) 和修补程序 (detour.patched)。

首先,让我们使用 objdump 反汇编二进制文件:

$ objdump -d detour.orig|less
.. stuff omitted ..
000000000040057d <hello>:
  40057d:       55                      push   %rbp
  40057e:       48 89 e5                mov    %rsp,%rbp
  400581:       bf 34 06 40 00          mov    $0x400634,%edi
  400586:       b8 00 00 00 00          mov    $0x0,%eax
  40058b:       e8 d0 fe ff ff          callq  400460 <printf@plt>
  400590:       bf 3b 06 40 00          mov    $0x40063b,%edi
  400595:       e8 b6 fe ff ff          callq  400450 <puts@plt>
  40059a:       5d                      pop    %rbp
  40059b:       c3                      retq

000000000040059c <main>:
  40059c:       55                      push   %rbp
  40059d:       48 89 e5                mov    %rsp,%rbp
  4005a0:       e8 d8 ff ff ff          callq  40057d <hello>
  4005a5:       5d                      pop    %rbp
  4005a6:       c3                      retq
  4005a7:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  4005ae:       00 00
00000000004005b0 <__libc_csu_init>:
.. more stuff omitted ..

并检查数据部分:

$ objdump  -s detour.orig | less
.. stuff omitted ..
Contents of section .rodata:
 400630 01000200 48656c6c 6f200077 6f726c64  ....Hello .world
 400640 2100                                 !.
Contents of section .eh_frame_hdr:
.. more stuff omitted ..

如您所见,字符串Hello,world位于只读数据部分,位于 400634 和 40063b。这些偏移被传递到printfputshello为什么puts好吧,优化器足够聪明,可以将以 '\n' 结尾的常量字符串的 printf 重写为 puts;您可以看到 .rodata 部分的字符串中省略了 '\n'。

现在,我们要插入一个puts("detoured"). 但是 hello 函数中没有空间,如果我们试图在那里插入一些字节,程序中的其他所有内容都会被移动,这是我们想要避免的。此外,.rodata 部分中没有空间用于另一个字符串,.eh_frame_hdr 直接在它后面开始。

但是,请检查地址 4005a7。main 函数在 4005a6 处返回,C 编译器使用一些填充来获取下一个函数 __libc_csu_init,在 16 字节边界处。这意味着我们可以使用一些未使用的字节。查看反汇编的其余部分,我们发现更多这样的字节:

4004ba          66 0f 1f 44 00 00
4004e9          0f 1f 80 00 00 00 00
400529          0f 1f 80 00 00 00 00
4005a7          66 0f 1f 84 00 00 00 00 00
400615          66 66 2e 0f 1f 84 00 00 00 00 00

其中最后一个 400615 的大小适合“绕道”的字符串。

现在我们要对程序集做的是将其修补为:

400590          jmp 4004e9                              e9 54 ff ff ff
4004e9          mov $0x400615, %edi; jmp 400529         bf 15 06 40 00 eb 39
400529          callq 400460; jmp 4005a7                e8 32 ff ff ff eb 77
4005a7          mov 0x40063b, %edi; jmp 400595          bf eb 06 40 00 eb e7

它覆盖400590处的指令,将需要的指令放在备用字节中,在部分之间添加跳转,并在跳转到下一条指令之前恢复被覆盖的mov。

现在,是时候使用十六进制编辑器将这些补丁应用到二进制文件中了(不要忘记将 'detoured' 字符串也移动到 400615)。生成的二进制文件应该与 zip 中的 detour.patched 相同。

最后,我们运行打过补丁的程序:

$ detour.patched
Hello detoured world!

如您所见,诀窍是让我们在程序中使用未使用的区域,以避免四处移动东西,这会破坏偏移量。当我将“绕道”字符串放入代码部分时,我有点作弊——如果有任何东西想要对字符串进行写访问,这将失败。如果我想要可写数据,我需要使用数据部分,甚至可能扩展它 - 但这种情况要复杂得多,因为我必须摆弄 ELF 标头和大小,而且我可能需要特定的 ELF正确使用的工具。当前示例只需要一个十六进制编辑器。我什至根据操作码手工制作了十六进制编码;要更复杂一些,请使用radare2 工具。