TheBrokenPipe/ExeOnlyDump

GitHub: TheBrokenPipe/ExeOnlyDump

一款利用 ptrace 系统调用注入技术绕过 Linux 仅可执行文件权限限制的内存转储与进程调试工具。

Stars: 1 | Forks: 0

# ExeOnlyDump (xodump) - Linux 仅可执行二进制文件转储工具 适用于 x86-64 (AMD64, x64) Linux 的仅可执行二进制文件转储工具。支持处理 静态和动态链接的、普通的以及 SUID/SGID 的 Linux 可执行文件。 仅可执行二进制文件是指你只能执行但不能读取、复制或调试的二进制文件。 它们通常被用来阻止用户复制或对其进行逆向工程。 [](./images/unattended.png)   [](./images/dbgdemo.png) ## 历史 我在上一门 C 语言编程大学课程时写了这个工具。当时我们有 编程作业,讲师们提供了编译好的作业 解决方案作为仅可执行文件,供我们测试自己的实现。 我向其中一位讲师请求“借用”这些演示程序并 对其进行逆向工程,并且得到了许可。 没有任何东西可以读取仅可执行程序的内存,但程序本身 可以。那些演示可执行文件全部是动态链接的,因此我利用 `LD_PRELOAD` 来预加载一个带有构造函数的库,该构造函数会转储 主可执行文件的已映射内存页。 我把这件事告诉了课程工作人员,他们说会开始 更多地使用静态链接。与此同时,一个调试作业发布了,它 包含一个仅可执行且带有 SGID 权限的服务器组件,这是不打算 让学生们知道的。出于显而易见的原因,`LD_PRELOAD` 对静态或 SUID/SGID 二进制文件不起作用,所以我需要一种更好的方法。 很快我发现 `strace(1)` 可以作用于仅可执行和/或 SUID/SGID 二进制文件。我快速拼凑了一个 `strace(1)` 的仿制品,通过操纵 对 `stdout` 的 `write(2)` 调用,在每次调用 `printf(3)` 时 将整个程序(而不仅仅是一个字符串)转储到终端上。它奏效了,让我 获得了调试作业的服务器后端访问权限,这使我能够 寻找一个可以免费拿分的后门/漏洞。 之后,我扩展了我的 `strace(1)` 仿制品,使其能够向 仅可执行程序注入任意的系统调用,这基本上允许我让程序 自己将其转储到磁盘文件中。我还添加了一些非常基础的调试 命令,比如内存转储,以增加一层灵活性。后来我 私下把这个程序分享给了课程工作人员,并承诺在他们 找到保护其二进制文件安全的解决方案之前,不会将其发布给 任何人。 我在这里发布这个工具的原因是,他们终于保护了那些 二进制文件的安全。**请不要将 `xodump` 用于任何形式的不当行为。** 如果你 因为使用 `xodump` 窃取你无权访问的代码而被抓到,请不要来找我哭诉。 已警告过你。 ## 如何使用 ExeOnlyDump ### 全自动二进制转储 要在无需任何用户交互的情况下自动转储二进制文件,请运行 以下命令: `xodump -o ` 它将执行 ``,对其进行转储并终止它,所有操作 一气呵成。在这种模式下,你将没有机会让可执行文件真正运行起来。 [](./images/unattended.png) ### 手动二进制转储 如果你想要一点交互,可以直接运行: `xodump [args]` 你首先会看到一个询问你是否真的想继续的问题: [](./images/disclaimer.png) 然后你会被提示选择一种模式。你可以选择自动模式、 交互模式,或者退出程序: [](./images/mode.png) #### 自动模式 如果你选择自动模式,你将被提示输入输出 文件的名称: [](./images/autodump.png) 然后可执行文件将被转储,随后你会被问及是否想 继续执行它: [](./images/run.png) #### 交互模式 如果你选择交互模式,你将被问及要在哪个系统调用处中断, 以及是否要指定任何附加条件: [](./images/breakpoint.png) 只需为系统调用输入 `12`,为条件输入 `no`,你就会看到 内存映射和命令提示符: [](./images/debugger.png) `DUMP` 允许你将一段内存范围转储到磁盘文件中,`VIEW` 允许你 查看一个内存区域,`MAP` 显示内存映射,`AUTO` 执行 可执行文件的自动转储(与自动模式相同),`SYSCALL` 执行 用户自定义的系统调用注入,`QUIT` 退出程序,`GO` 继续 仅可执行文件的执行,而 `SYS` 允许你运行系统 命令(例如,使用 `ls` 列出目录)。以下是一些 命令的快速演示: [](./images/dbgdemo.png) ## 从零开始的实现原理 ### 任意系统调用注入 假设被调试程序停在一个系统调用入口处,保存所有寄存器。 为你想要注入的系统调用加载一组自定义的寄存器并执行 该系统调用。系统调用完成后,保存结果并从你 之前保存的寄存器备份中恢复寄存器。回退一条指令 并返回你保存的系统调用结果。 ### 被调试程序内存分配 不可能使用被调试程序的堆来分配内存,因此你必须 通过注入 `mmap(2)` 调用来直接映射内存。用完该 内存后,使用 `munmap(2)` 将其释放。 ### 调试器/被调试程序通信 通信可以通过管道完成。在 fork 之前创建一个管道,并使用 该管道与被调试程序对话。要将缓冲区从调试器传输 到被调试程序,首先通过注入 `mmap(2)` 分配内存。 在被调试程序有了空闲内存来接收缓冲区之后,从调试器端 将缓冲区写入管道的写入端。最后,通过注入 `read(2)` 调用,使 被调试程序将缓冲区从管道的读取端读取到 已分配的内存中。 被调试程序到调试器的内存传输可以按相同的 方式进行。需要特别注意的是,调试器和被调试程序不能同时 运行,因为它们会相互等待,因此你不希望向管道写入过 多的数据从而阻塞当前运行的程序,这会导致 死锁。 共享内存应该也行得通,但我还没有测试过。在进行从 被调试程序到调试器的内存传输时,这将会是一件 麻烦事,因为你必须实际注入代码才能将内存复制到共享页面。 ### 二进制转储 注入 `open(2)` 和 `read(2)` 来读取被调试程序的 `/proc/self/maps` 文件, 然后将其传输给调试器。对其进行解析,并注入 `write(2)` 调用,将 所需的内存区域写入磁盘文件中。 ## 局限性 * `ptrace(2)` 必须可用。大多数系统上都应该满足这个条件。如果 不行,只要可执行文件不是静态链接的或 SUID/SGID 的,你就可以使用 `LD_PRELOAD` 技巧。 * 符号无法被转储。我认为它们通常不会被映射到 内存中。 * 我的代码只能在 x86-64 Linux 上运行。然而,将其移植到 其他架构和类 Unix 操作系统应该不会太难。
标签:AMD64, ELF, Execute-Only Binary, LD_PRELOAD, strace, SUID/SGID, x86-64, 二进制转储, 云资产清单, 内存读取, 动态链接, 后门检测, 客户端加密, 教育安全, 数据展示, 系统调用劫持, 红队, 逆向工程, 静态链接