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 操作系统应该不会太难。
](./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
](./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, 二进制转储, 云资产清单, 内存读取, 动态链接, 后门检测, 客户端加密, 教育安全, 数据展示, 系统调用劫持, 红队, 逆向工程, 静态链接