以下适合有PE基础的人看,最起码要知道PE的基本结构和rva以及foa之间如何相互转换,不然会看的迷迷糊糊
先决条件
首先我们需要准备一个程序,待会将代码注入这个程序中
随便编写一个简单的程序,将随机基址给关闭
硬编码
程序编译完成之后就是一堆二进制的数据,如果我们想将代码注入到这个程序中,我们也只能以二进制的形式进行注入,不能像编码高级语言一样,比如我们想将 MessageBoxA(0, 0, 0, 0)
这段代码注入进去,要求是在程序运行时首先运行这段代码,然后再跳转到之前的代码逻辑
我们想将这段代码注入进去的话就只能将这段代码翻译成二进制的形式,然后再到这个exe的某一处地方将这段代码写进去。
那么我们就有必要来了解一下硬编码,首先我们来看看一个程序是如何调用一个函数的,主要关注两个地方,调用函数用到的指令是什么?指令的参数是什么?
我们来看看下面这段代码是如何调用的
将代码转到反汇编的模式下
可以看到 调用函数使用的是 E8 指令后面紧跟着 EC F4 FF FF
这是一个地址,可是这个地址并不是真正的函数地址,真正的函数地址是 2415D2
(ps 其实这也不是真正的函数地址,这个地址一般情况下指向一个jmp的地址再由jmp地址跳转到真正的函数)
既然根据E8后面的这个地址可以找到真正的函数入口,那么就证明这个地址一定跟函数的地址有着某种联系,他们之前肯定有一个换算的关系,公式如下
E8后面跟着的地址 = 函数的地址 - E8下一行执行的地址
由于 E8指令的长度是5(E8加上4个字节地址)公式又能变形成如下
E8后面跟着的地址 = 函数的地址 - (E8的地址 + 5)
照着上面的地址换算一下
test函数的地址是 2415D2
E8的地址是 002420E1
那么E8后面跟着的地址就是 2415D2 - (002420E1 + 5)= FFFF F4EC
算的时候一定要选择 DWORD类型,因为我们是32位程序,每个地址只占4个字节
调用函数搞定了,但是调用完我们注入进去的代码后我们要回到之前的代码逻辑,不然的话就会影响程序的正常运行
想要回到程序之前的代码逻辑我们使用 jmp
指令硬编码为 E9
E9
指令和E8
一样后面也是跟着一个地址,且这个地址也要进行换算,换算的规则和E8
的换算规则一摸一样
代码注入
搞定了硬编码之后我们就要进行代码注入了
那么代码注入到哪里呢?这里为了方便就直接注入到 .text.
段,这里大家都应该明白为什么要注入到 .text
段,因为一个正常编译出来的程序默认的代码段就是 .text
当然我们学过PE之后,我们也可以把代码注入到其他段,只需要将这个段的属性改为可执行即可,或者干脆自己新增一个段,然后将代码写入到自己新增的这个段里,我们要注入哪个段就将哪个段的属性该为可执行即可 .text
默认就有可执行权限了,所以我们注入到这个段不需要进行任何更改
下面我将使用 010Editor来进行手动的代码注入
首先我们打开要注入的程序
然后找到 .rdata
这个段,之后向上滑动
![在这里插入图片描述](https://img-blog.csdnimg.cn/f959aed07bf049589cc976f641e35be5.png
向上滑动后我们就能快速的找到 .text
段的结尾
我们在都是0的位置添加我们需要注入的代码
我们首先需要找到 MessageBoxA函数的地址,以及调用这个函数需要传递什么参数
先看参数 为了简单我们传递 4个0,且这个传递参数的硬编码是 6A 要传递4个0的话就是 6A 00 6A 00 6A 00 6A 00
再看 MessageBoxA的地址
他的地址在 038111C这个位置存着,我们查看内存就能找到 MessageBoxA的地址
好了,到这里为止已经把 MessageBoxA的函数地址以及参数都搞定了,接下来就是注入代码了
我们只在程序运行前调用一个MessagaeBoxA 然后再跳回原来的程序入口
那么可以开始写代码了
6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00
首先将这段代码写进去,之后我们就只要修改 E8 以及E9后面的地址了
首先来计算 E8后面的地址
E8后面跟着的地址 = 函数的地址 - (E8的地址 + 5)
我们要调用MessageBoxA且这个函数的地址我们也知道了 762682A0
重点是这个E8地址是什么,所以首先我们需要查看 文件对齐值和内存对齐值是否一致
接着查看程序的基址也就是IamgegBase是多少才能来计算E8真正的地址
ImageBase为 400000h
内存对齐值为 1000h
文件对齐值为 200h
首先我们将E8现在处在的文件偏移地址加上 400000h
E8现在在文件中的偏移为 71D248h 加上之后等于 B1D248h
然后由于文件对齐和内存对齐不一致,所以导致程序加载进内存之后这个E8的地址会发生变化,那我们如何知道E8加载进内存之后的地址呢?
因为我们的代码是写在 .text
这个段里面的,所以我们需要计算这个段在内存中的开始位置和在文件中的开始位置的差
那就是 36B000 - 400 = 36AC00
也就是说我们在文件中的.text
段的地址偏移加上这个 36AC00 就是这个地址在内存中的真正的地址了
那就是我们上面算出来的这个值B1D248h
+ 36AC00
= E8 7E48(E8在内存中的真正地址)
那我们E8后面跟着的地址就是 762682A0 - (E8 7E48 + 5) = 753E 0453
这个 753E 0453
就是E8后面真正要跟着的地址
之后就要计算 E9后面跟着地址,我们要找到OEP
OEP为 36B4BFh
这里值得注意的是这个OEP是一个rva所以我们直接加上 400000(程序的基址) 就得出了 OEP在内存中的地址了
OEP在内存中真正的地址是:76B4BFh
OEP就是我们真正要跳转的地址,接下来就找E9的下一行地址了,可以根据上面算E8下一行地址的步骤来算E9的下一行地址,不过这里有个更快的方法,上面我们算出来了E8下一行的地址,刚好E9就是E8下一行的地址,所以我们之前拿刚刚算出来的地址再加上5就是E9的下一行地址了也就是 E8 7E48 + 5 + 5 = E8 7E52
然后就是 76B4BFh - E8 7E52h = FF8E 366D
那E9后面跟着的地址就是 FF8E 366D
代码注入到这一步就完成了,但是还差一步,就是程序加载到内存之后首先要运行我们的代码
所以我们要将之前的OEP更改掉
这段代码开始的位置是 71D240
那么在内存中的偏移就是再加上 36AC00
71D240 + 36AC00 = A8 7E40
注意这里不需要再加上 400000(ImageBase)了,因为OEP是一个RVA,所以我们算出在内存中的偏移就可以了
所以我们直接将OEP更改为 A87E40h
然后就将文件保存
到这里为止整个代码注入就完成了
接下来就是测试
我们运行刚刚我们更改过的程序
就会看到弹窗,这是我们刚刚注入进去的代码就是 MessageBoxA(0, 0, 0, 0)执行的效果
点击确定后,程序就会和之前一样跑起来
把程序放到OD里跑起来可以发现确实首先运行的就是我们刚刚注入的代码