Linux 二进制分析(3):ELF 代码注入技术
文章目录
Task 1:使用十六进制编辑器修改 Bare-Metal 二进制文件
修复off-by-one漏洞
- 按照提示将大于判断改为大于等于,意为能够遍历到组后一个字节
- 这里改为了自减一后与n比较,大于等于1后跳转
最后一字节已加密
Task 2:使用 LD_PRELOAD 修改共享库行为
堆溢出漏洞
- 主要原理使对标准库函数进行重写,比如对一些关键的函数,只要重写这个函数,他在执行的时候就会按照我们重写的代码取执行,以达到修改二进制行为的目的
这里存在输入字符串的长度大于第一个参数长度,有可能将多余的字符串复制到了堆的外面,导致了堆溢出
测试:
-
良性输入
-
字符串超长
Q :根据执行结果,程序出现崩溃的具体原因是什么?
A :实际为缓冲区只分配了13 字节的长度,此处输入100 字节的A 导致了堆的缓冲区溢出,将字节写到了一些存有其他数据的内存区域。可能导致程序无法正常执行。如图,出现了崩溃。
检测堆溢出漏洞
使用heapcheck.so 库来防止堆溢出
Task 3:注入代码节(Code Section)
****elfinject 的原理:
-
将新的节附加到二进制文件的末尾。
-
为注入的节创建程序头和节头。(此处是覆盖现有的程序头而不是添加一个新的程序头)
分析:
- 覆盖 PT_NOTE 段
考虑覆盖的是PT_NOTE 的程序头信息。
该头部信息只是包括了一些辅助信息,没有这部分,加载器会默认其为本机文件。因此可以安全地覆盖该头部。
- 重定向ELF 入口点.
将ELF 头部中的e_entry 字段修改为指向新的.injected 节的地址,而不是原始入口点。
使用elfinject 注入ELF 节
将/bin/ls程序复制到当前文件夹中
使用readelf查看该文件,在节头中有.note.ABI.tag,也就是需要覆盖的头部
程序头部看到NOTE段,也是需要覆盖的地方
接着,使用elfinject 工具,为ls 程序中注入hello.bin 程序。其中,设置注入的节名为.injected 。使用0x800000 作为加载地址,0 表示程序的入口点,即hello.bin 的入口点。
之前的NOTE和tag两个已经消失,并且能看到注入的节
(NOTE变LOAD)
结果
打印出了信息
Task 4:调用注入的代码
入口点修改
使用elfinject 命令将hello.bin 文件注入到ls.entry 文件中,注入后的节名是.injected ,地址是0x8000000 。
需要注意的是这里的注入代码入口点的偏移设置为-1 ,表示没有入口点。使得elfinject 不修改入口点。
如下图,可以看到二进制文件的原始入口地址是0x4049a0 。该地址也就是执行完注入的代码之后,需要跳转去的地址。也就是打印完hello,world !之后需要去执行ls 的代码。
- 使用readelf 命令查看修改之后注入代码的地址,是0x800e78 开始的实际地址。
- 正常执行
- 用hexedit修改程序入口,程序运行时也会先跳转到该执行位置执行代码
- 再次执行发现成功执行
劫持构造函数和析构函数
- 通过readelf 可以查看到存在的.init_array 节,这也就是我们要劫持的构造函数,需要找到这个构造函数保存的指针,把这个指针改为注入代码的地址即可。
也可看到我们注入的代码在.injected 节。
- 使用objdump 查看.init_array 节的构造函数指针,可以看到是小端形式保存的704a40 ,也就是0x404a70
- 接着使用HexEdit 工 具搜索这个小端地址704a40 ,并将其改为780e80 ,就完成了对于构造函数的劫持过程。构造函数的指针已经被替换为了目标代码的地址。
- 此时表示,先执行注入的代码,接着注入的代码会将控制权转移回原始的构造函数。
- 先执行了注入的代码,后执行了ls 的命令