我正在尝试查看是否可以在 ELF 的 .text 部分附加函数代码,同时仍保持原始 ELF 的执行流程。理想情况下,我想调用新函数,但那是自己要爬的山。我更关心只是添加代码。这是现实的吗?或者我已经超出了我的头脑?我已经能够通过简单地覆盖字节来添加代码。但是,我想扩展 .text 部分并插入它。如果有更好的方法将新功能插入到 ELF 中,我会全力以赴。但任何指导都是有帮助的。
精灵 x86_64 添加功能
有可能做你想做的。不过,您可能会遇到一些困难。我将向您展示如何在几种不同情况下完成的简短说明。
- 查看ELF 规范以了解该格式的每个文件的外观,它包含哪些部分以及它们在运行时如何位于过程映像中。即使存在可以自动为您执行工作的工具,知道在“下面”必须做什么也是很好的。
- 编写并编译要添加的代码。你当然可以稍后再做,但这样你就会知道需要添加多少空间。
- 有了在 中获得的知识
1.
,您现在可以解释readelf -S executableToPatch
命令的输出。它将列出可执行文件中存在的部分以及它们在文件中的偏移量等信息。 - 运气好的话,你要更新的section的偏移量+它的基本大小+你要添加的代码的大小小于下一个section的偏移量。在这种情况下,您只想创建一个包含此部分新内容的文件(即修改前的全部内容以及您附加的额外代码)。然后你可以简单地运行:
objcopy --update-section .text=fileWithTheNewContent nameOfExe nameOfExePatched
你就完成了。 - 如果你不是那么幸运,你仍然可以从上一点运行命令,但是你会得到类似这样的消息:
objcopy: nameOfExe: section .text offset adjusted to newOffset
,这只是意味着objcopy
已经向前移动了一些后续部分,这样它们就不会与每个部分重叠其他(来自1.
,是 ELF 规范的要求之一)。但是,与上一点相反,您不能简单地在这里结束,因为可能存在某些包含数据的部分也更改了其偏移量的情况。 - 检查哪些部分已被重新定位。如果它们都不包含数据,您现在可以测试程序的行为是否与修改前相同。如果是,则您的工作已完成 - 您的代码已成功更新。
- 如果它的行为不同或者你的一些数据段改变了它的偏移量,则有必要修复对此段的数据引用,即修补代码,以便它引用新数据段位置中的偏移量。例如,当
.rodata
已向前移动4096
字节时,您必须.rodata
通过添加4096
到每个地址来更改所有引用,即,如果mov rax, [rip+0x20]
您想将其更改为mov rax, [rip+0x1020]
,但这样做并不简单,尽管它仍然是可能的。
我想扩展 .text 部分并插入 [code]。如果有更好的方法将新功能插入到 ELF 中,我会全力以赴。
将任意代码添加到 ELF 文件的技术是由 Linux 病毒编写者从 1990 年代开始开创的。与他们开发的方法以及更现代的技术相比,这objcopy
条路线似乎相当粗糙。没有必要重新发明轮子。
从那时起,出现了更现代的操纵 ELF 二进制运行时行为的方法,包括各种注入技术和检测框架。
出于这个问题的目的,我们可以将修改二进制功能的方法分为两种方法:
- 向文件本身添加代码(静态修改,发生在程序运行之前)
- 通过注入(运行时修改)诱导新行为。
静态改性技术
文本段填充感染
经典的 ELFtext
段(不是段)填充感染方法似乎与您当前尝试的方法最密切相关。
然而,考虑到寄生代码的大小,段边界的页面填充为寄生代码提供了一个实用的位置。这个空间不会干扰原来的段,不需要重新定位。遵循刚刚给出的优先选择文本段的指南,我们可以看到文本段末尾的填充是一个可行的解决方案。1
实现这一点的算法如下:
- 增加
p_shoff
通过PAGE_SIZE
在ELF头- 补丁插入代码(寄生虫)跳转到入口点(原创)
- 找到文本段程序头
- 修改 ELF 头的入口点以指向新代码 (
p_vaddr
+p_filesz
)p_filesz
按账号增加新码(寄生虫)- 增加
p_memsz
占新码(寄生虫)- 对于每个
phdr
who 的片段在插入之后(文本片段)
- 增加
p_offset
的PAGE_SIZE
- 对于
shdr
文本段中 的最后一个
sh_len
寄生虫长度增加- 对于每个
shdr
who 的部分在插入后驻留
- 增加
sh_offset
PAGE_SIZE- 物理插入新代码(寄生虫)并填充到
PAGE_SIZE
, 到文件中 - 文本段p_offset
+p_filesz
(原始)1
除了该技术的解释以及 1998 年文章 [1] 中包含的实现该技术的病毒的源代码之外,还有一个 2016 年(经过这么多年它仍然有效)关于 ELF 段填充感染的教程在 0x00sec 处可用,称为ELFun File Injector。
除了文本段感染方法外,还有数据段感染,非常相似。如果他们愿意,还可以添加额外的细分市场。
PLT/GOT 感染
在 2000 年2的 Phrack 文章中首先详细介绍了这种方法,该方法涉及修补 PLT/GOT 以指向二进制文件中的代码,该代码已通过感染方法插入,而不是指向动态链接到二进制文件的共享库中的代码:
可以注意到,我们可以将【GOT中的函数名】改为指向我们自己的代码,从而替代库调用。如果我们在替换之前保存 GOT 的状态,我们可以调用旧的库例程,从而重定向任何库调用。2
实现此方法的最简单方法似乎是使用 LIEF 检测框架。LIEF 文档中标题为 Infecting the plt/got 的教程提供了执行此操作的教程。请注意,在本教程中,修补 GOT 而不是 PLT,并且通过创建新段(上述感染技术之一)将代码添加到文件中。
除此之外还有其他的技术可以直接修改文件,但是segment padding感染和PLT/GOT感染似乎是最直接的。
运行时修改技术
由于 DLL 注入和运行时进程操作与您当前的任务没有直接关系,因此我不会详细介绍。
- DLL 注入通过
LD_PRELOAD
非常强大和有用。我发现有用的 2 个教程是- LD_PRELOAD 技巧- 用于从 C 源代码编译的二进制文件
- 从 C++ 源代码编译的二进制文件在 C++ 中的函数插入注意事项
- DLL 挂钩也可以使用 LIEF 完成:请参阅ELF 挂钩
- 处理代码注入使用
ptrace()
有些方法比较晦涩,比如The Cerberus ELF Interface中DT_NEEDED
详述的感染和Cheating the ELF中描述的“颠覆性动态链接”
参考
Unix 病毒。西尔维奥·切萨雷
通过 ELF PLT 感染共享库调用重定向, Silvio Cesare
我在高中时写了一个小实用程序来做类似的事情:https : //github.com/jdefrancesco/elfy
您将负载注入 .note 部分,它将修改指向 .note 部分的 ELF 条目。之后它会跳回到原来的入口点。