当前位置:   article > 正文

Linux二进制分析:ELF 代码注入技术_elfinject

elfinject

Linux二进制分析:ELF 代码注入技术

欢迎大家访问我的GitHub博客

https://lunan0320.cn

一、实验目标

熟悉编写二进制代码编写技术
了解汇编代码过程
学习反汇编平台的二进制分析工具
加深对汇编代码的理解和使用技巧

学习集中ELF文件的代码注入技术

二、实验步骤与结果

Lab Tasks

Task 1:使用十六进制编辑器修改Bare-Metal 二进制文件

步骤:

1) 通过反汇编程序识别要修改的代码或数据字节

2) 使用十六进制编辑器进行修改

优点:操作简单

缺点:只允许原地修改,不能添加内容(破坏对移位字节的引用)

2.1 观察off-by-one 漏洞

首先对于要分析的文件xor_encrypt.c 文件,编译链接后得到xor_encrypt 的可执行文件。

查看源代码文件xor_encrypt.c ,可以看到该文件在执行的时候需要接收3 个参数,第一个参数是输入文件,也就是xor_encrypt.c ,第二个文件是输出文件,这里设置为encrypted ,第三个参数是密钥key ,此处使用foobar 作为密钥。

根据实验指导书的提示,off-by-one 漏洞出现在对于循环的边界条件的判断的位置。如图中所示,此处应该是对于文件的n个字节都加密,最后一个字节应该是n-1,而按照示例代码的输入,此处的最后一个字节为第n-2个字节,因此不符合。需要更改循环的终止条件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9thfISLM-1661958303444)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261937143.png)]

接着使用xxd 命令,查看源代码文件的十六进制的表示。如下图所示,最后一个字节是0x0a

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ASqy10CE-1661958303445)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261938278.png)]

然后对于执行加密后的文件,查看加密后的十六进制表示。如下,可以看到,此时的最后一个字节依旧是0x0a ,未被加密。而其他的字节都已经是乱码的情况,也就是成功被加密。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1WZXmFP-1661958303446)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261938108.png)]

2.2 修复off-by-one 漏洞

1 、查找导致漏洞的字节

为了找到for 循环判断的位置出错的字节,采用objdump 工具查看目标文件xor_encrypt 的反汇编代码。

关键代码如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rfvDNyx-1661958303447)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261939952.png)]

此处在观察反汇编代码的时候,我发现这与实验手册中给出的示例反汇编代码是不太相同的,这应该是编译器做了不同的处理或者优化。

首先观察strlen 函数的调用过程。找到每次累加1 的计数器位于rbp-0x50 的PTR ,以及比较关键的比较部分。

sub rax, 0x1
cmp rax,QWORD PTR[rbp-0x50]
ja  4009f2<main+0x13a>
  • 1
  • 2
  • 3

其中,rax 中保存的是n ,rax 执行自减操作,得到rax-1 也就是n-1

PTR 中保存的是计数器的值。也就是i 的值。

此处判断的操作符与实验指导手册给出的不同,此处是使用ja 符号 ,也就是如果n-1 > i ,那就跳转到对应的位置。即循环开始的位置。

ja 表示的是如果大于,就跳转 。此处希望改为,如果大于等于就跳转。

2 、替换违规字节

使用十六进制编辑器来替换此处的ja 操作码。因此,应该改为jae ,也就是大于等于的时候跳转的指令。使得循环在n-1 >= i 的时候跳转。

使用HexEdit 来修复off-by-one 漏洞。

运行代码文件中的Makefile 文件,在十六进制的情况下打开xor_encrypt

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-moddKWQU-1661958303448)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261942304.png)]

根据实验指导手册的提示,此时为了修改这里的ja 操作码,我需要在十六进制编辑器中找到它对应的十六进制表示,也就是779c 。其中77 是ja 的表示,9c 是偏移量的表示 ,不需要更改。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AcbY3IEb-1661958303448)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261953842.png)]

需要改为jae 也就是大于等于,对应的操作码是73 。只需要对这个77 修改即可。得到如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8rHYzvHQ-1661958303449)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261953550.png)]

更改之后保存此二进制文件。

再次使用objdump 来确认是否修复成功,即查看对应位置的ja 是否改为了jae 。如图所示,可以看到成功修改了循环的终止条件。

注意:这个地方不能直接执行指导手册示例中的代码,此处修改后的文件还是xor_encrypt ,而不是xor_encrypt.fixed 。因此需要查看该文件的反汇编代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ALNGeGrn-1661958303450)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261955839.png)]

并重新运行,输入原始的三个参数,输入文件,输出文件,密钥即可。

此处附上修改之前和修改之后的对比图。

可以看到修改之后,最后一个字节成功被修改,而且前面的字节的十六进制表示是跟原文件完全一样的。

可以看到包括最后一字节在内的所有字节现在都已经正确加密

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3pFKEAB-1661958303451)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261955060.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJQ5XDZg-1661958303453)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261955542.png)]

Task 2:使用LD_PRELOAD 修改共享库行为

原理:

使用十六进制编辑工具只能在很小范围内修改,而不能修改共享库的行为,使用LD_PRELOAD****环境变量来指定一个库文件在链接其他库之前加载。

如果预加载库中的函数与稍后加载的库中的函数同名,则加载的第一个函数将是在运行时使用的函数。

因此,我们可以对标准的库函数重写,比如对于一些关键的函数,只要重写这个函数,它在执行的时候就会按照我们重写的代码去执行,以达到修改二进制行为的目的。

1、堆溢出漏洞

如图所示,源代码共有两个输入参数,第一个表示的是字符串的长度,得到这个长度后对应开辟这个大小的缓冲区。

然后第二个参数中是字符串,将字符串复制到缓冲区中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yfEryOJA-1661958303453)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261956730.png)]

漏洞原理:

存在的一个Heapoverflow 就是说输入的字符串的长度实际上比输入的第一个参数表示的长度要长,这就导致在复制字符串的时候,将多出去的字符串复制到了堆的外面,导致了堆溢出。

使用的是良性输入的时候,可以保证这个是正确的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ptIOEDD6-1661958303454)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261957279.png)]

当使用长度超过字符串应该的长度时候,结果就会导致堆溢出。如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5dK5WPJM-1661958303454)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261957590.png)]

Q :根据执行结果,程序出现崩溃的具体原因是什么?

A :实际为缓冲区只分配了13 字节的长度,此处输入100 字节的A 导致了堆的缓冲区溢出,将字节写到了一些存有其他数据的内存区域。可能导致程序无法正常执行。如图,出现了崩溃。

2、检测堆溢出漏洞

堆溢出检测的思想就是需要重写几个重要的函数。比如说malloc 和free 的函数库,以及strcpy 函数也需要重写,使得在复制的时候可以自动判断区域是否足够大,以存储字符串。

观察heapcheck.c 文件,查看重写后的函数。

可以看到重写了strcpy() 函数。此处先是解析原始的strcpy ,在调用之前先检查了缓冲区的大小是否能存放这个字符串。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYugxQAZ-1661958303455)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261958667.png)]

在释放和分配之前都会进行比较。如果该地址确实在allocs 的全局搜索中可以找到就才允许释放。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VhR0MN6-1661958303455)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261958590.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqnq5Iq1-1661958303456)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261959286.png)]

根据实验指导手册的提示,使用如下命令在预加载的时候将重写后的库文件。修改了LD_PRELOAD 环境变量,使得链接器在预加载阶段加载该库文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rMvX3Hy-1661958303457)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208261959168.png)]

重写的库文件使得在调用malloc,free,strcpy 函数的时候会先执行我们引入的代码,因此在分配空间、释放空间以及复制字符串的时候会先判断空间是否足够,否则就会报出堆溢出。

修改后的strcpy 成功检测到不安全的复制行为,输出错误。并安全地终止程序,使攻击者无法利用此漏洞。

Task 3:注入代码节(Code Section)

1、概述

elfinject 的原理:

1) 将新的节附加到二进制文件的末尾。

2) 为注入的节创建程序头和节头。(此处是覆盖现有的程序头而不是添加一个新的程序头)

分析:

1 、覆盖 PT_NOTE

考虑覆盖的是PT_NOTE 的程序头信息。

该头部信息只是包括了一些辅助信息,没有这部分,加载器会默认其为本机文件。因此可以安全地覆盖该头部。

2 、重定向ELF 入口点.

将ELF 头部中的e_entry 字段修改为指向新的.injected 节的地址,而不是原始入口点

2、使用elfinject 注入ELF 节

首先是将/bin/ls 程序复制到当前文件夹中,执行ls 命令可以得到当前目录下的文件情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X7hAec4u-1661958303457)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262001255.png)]

使用readelf 命令查看该文件,可以看到在节头中,有.note.ABI.tag ,这也就是我们需要覆盖的头部。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-26ufcIdt-1661958303458)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262001956.png)]

同时也可以在程序头部看到NOTE****段。这也是需要覆盖的地方。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uWaVbhgO-1661958303458)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262001997.png)]

接着,使用elfinject 工具,为ls 程序中注入hello.bin 程序。其中,设置注入的节名为.injected 。使用0x800000 作为加载地址,0 表示程序的入口点,即hello.bin 的入口点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99veSeCc-1661958303459)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262003839.png)]

在注入之后,使用readelf 命令查看ls 的二进制文件,可以看到.note.ABI-tag 节和 PT_NOTE 段已经消失。这两个节都被覆盖,表示注入成功。

在表中找到了注入的节,.injected ,同时.note.ABI.tag 节消失。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jCE3ws1T-1661958303460)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262003024.png)]

查看程序头的节,可以看到NOTE 节也已经消失。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gw4tJrLr-1661958303460)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262003719.png)]

最后重新执行ls 程序,首先会输出hello.bin 的执行结果,也就是hello,world! ,然后注入的代码执行结束,程序将代码的执行权交给原始的入口点。重新执行了ls 的命令,打印出了当前目录的信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXv29pNw-1661958303460)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262004263.png)]

Task 4:调用注入的代码

目的:使得在二进制程序启动的时候不希望立即运行注入的代码。

1、入口点修改

将hello.s 汇编到原有的二进制文件中。此处使用的是nasm 命令。创建一个hello.bin 文件,包含了适合注入的原始二进制指令和数据。.,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z06cxwAo-1661958303461)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262005079.png)]

使用elfinject 命令将hello.bin 文件注入到ls.entry 文件中,注入后的节名是.injected ,地址是0x8000000

需要注意的是这里的注入代码入口点的偏移设置为-1 ,表示没有入口点。使得elfinject 不修改入口点。

如下图,可以看到二进制文件的原始入口地址是0x4049a0 。该地址也就是执行完注入的代码之后,需要跳转去的地址。也就是打印完hello,world !之后需要去执行ls 的代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c557CeVv-1661958303461)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262006935.png)]

使用readelf 命令查看修改之后注入代码的地址,是0x800e78 开始的实际地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1NmYm53-1661958303461)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262006232.png)]

而我们设置的是0x800000 ,根据实验指导手册中的提示,这是因为elfinject 为了满足ELF 格式的对齐要求所采取的优化 。此处的0x800e78 也就是我们要重写入口点的新地址。

如果此时直接去运行这个注入代码的程序,执行的结果和注入之前是没有什么区别的。这是因为设置的代码入口点是-1 ,表示不修改入口点。在即使注入了代码的情况下,程序还是执行了ls 命令而没有执行注入的代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBhon4J5-1661958303462)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262006853.png)]

因此,为了修改入口点,HexEdit 工具修改ls.entry 。因为在binary 中地址是以小端的格式存储,因此实际在可执行程序中的地址字节是a04940 ,从而将这个入口点改为780e80

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaMgAKHf-1661958303462)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262007671.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXwaAzi0-1661958303463)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262007382.png)]

修改之后,重新查看该文件的入口地址,是0x800e78 ,表示修改成功。程序运行的时候也会先跳转到该位置执行我们注入的代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VAHbjdG-1661958303463)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262007182.png)]

此时,再次执行该程序,会发现成功执行了注入的代码。也就是我们对于入口点完成了更新。成功覆盖了原来的入口点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4noAY0IT-1661958303463)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262008502.png)]

1、劫持构造函数和析构函数

与上一个任务类似,首先对于hello-ctor.s 的文件转换为bin 格式的hello-ctor.bin 的汇编文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gkZarFFF-1661958303464)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262008740.png)]

然后将/bin/ls 的可执行文件复制到当前文件夹中,并命名为ls-ctor

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ratvvdaX-1661958303464)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262009838.png)]

使用elfinject 工具注入代码,这个过程与之前的实验类似,但是不修改入口点,入口点是默认的-1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsVUfzkP-1661958303464)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262009463.png)]

通过readelf 可以查看到存在的.init_array 节,这也就是我们要劫持的构造函数,需要找到这个构造函数保存的指针,把这个指针改为注入代码的地址即可。

也可看到我们注入的代码在.injected 节。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CYeBNVnv-1661958303465)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262010534.png)]

使用objdump 查看.init_array 节的构造函数指针,可以看到是小端形式保存的704a40 ,也就是0x404a70

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGJFjmmG-1661958303465)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262011503.png)]

接着使用HexEdit 工 具搜索这个小端地址704a40 ,并将其改为780e80 ,就完成了对于构造函数的劫持过程。构造函数的指针已经被替换为了目标代码的地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQzMK2z0-1661958303466)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262012673.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NPw7pqOu-1661958303466)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262012953.png)]

此时表示,先执行注入的代码,接着注入的代码会将控制权转移回原始的构造函数。

可以看到,成功完成了对于构造函数指针的修改。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MwrHkgIG-1661958303467)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262012426.png)]

最后执行该程序,可以看到成功执行。

先执行了注入的代码,后执行了ls 的命令。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MOMx8fHI-1661958303467)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262012725.png)]

心得:

此处由于我一开始是使用hello.s去用nasm命令汇编得到hello-ctor.bin的,而不是hello-ctor.s,因此在其他步骤完全一致的情况下出现了如下的情况,不断地输出Hello,world!,直到提示fault。

猜想:

此处应该注入的代码没有正确将控制权转移回原始的构造函数的原因,因为hello-ctor.bin这个注入代码中应该是要包括控制权的转回的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jYUaRzOt-1661958303468)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262013020.png)]

解决:

接着,我查看了hello.s和hello-ctor.s的代码,如下图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQU1uzD6-1661958303468)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262013839.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJyI4wZj-1661958303469)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262013289.png)]

可以看到,两个汇编代码显示的返回地址是不一样的,hello.s是0x4049a0,hello-ctor.s是0x404a70。当我用hello.s去作为注入代码的时候,它的返回地址并没有返回到原来的构造函数的地方,因此导致一致在运行注入的代码,没有返回,最终导致fault。

而这个0x404a70正是我们修改的构造函数的保存的指针的位置。也就是说,在我之前错误的使用下,导致构造函数并没有跳转到正确的位置,导致一直运行注入的代码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SmG5Db7w-1661958303469)(https://cdn.jsdelivr.net/gh/lunan0320/pics/img/reverse-engineering/Linux二进制分析202208262013239.png)]

五、参考文献

[1]linux 系统调用号表

https://blog.csdn.net/qq_29343201/article/details/52209588

[2] Linux X86 系统调用列表 system call table 32 bits and 64 bits

https://blog.csdn.net/benben2301/article/details/39205287

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/849231
推荐阅读
相关标签
  

闽ICP备14008679号