PE文件(五)代码节空白区添加代码

学习目的

本节的目的就是教会我们在一个可执行文件的代码节的空白区添加一段代码。

大致思路:正常的文件中OEP记录着程序入口的地址,现在我们将此可执行文件的程序入口OEP地址指向call0 x123456指令的地址,使其先执行我们添加的代码,执行完指令后再jmp 0x456789跳转回原来正常的文件OEP指向的程序入口地址,之后程序正常运行

注意在代码节空白区添加代码相当于给在硬盘上的文件中添加数据,添加完后再运行文件。该过程可以理解为文件注入。但是我们平时说的注入,则是文件在运行时把代码添加进去,这个过程可以理解为内存注入

在本节课中,我们将以添加一个MessageBox函数到一个可执行文件的代码节(.text)空白区为例进行讲解。

预备知识

MessageBox函数原型:

int MessageBox(

HWND hWnd,

LPCTSTR lpText,

LPCTSTR lpCaption = NULL,

UINT nType = MB_OK

);

四个参数说明:

hWnd:表示窗口句柄,用来该对话框属于哪个主窗口。如果该参数为空(0/NULL),则该对话框不属于任何窗口

lpText:字符串,指显示在对话框中的内容

lpCaption:字符串,指对话框的标题;如果此参数为空,则默认使用“错误”作为标题

nType:指定显示按钮的数目及形式,表名使用的图标样式、缺省按钮是什么、以及消息框的强制回应等

MessageBox()函数功能是弹出一个标准的Windows对话框,相当于程序的一个断点。该函数不是以C语言函数库的标准函数的方式去执行,而是通过使用MessageBox函数去调用Windows的API,即MessageBoxA。使用该API时需要包含头文件windows.h。如果一个程序关联了user32.dll动态链接库,则此程序就有Windows API函数MessageBoxA

我们知道一个文件是由一堆二进制数据组成的,所以我们要想将MessageBox函数加到一个可执行文件中,不是直接把函数加进去,而是需要把这个函数对应的二进制数据添加到文件中

由于一个函数的二进制数据过于复杂,所以我们将MessageBox函数的二进制数据添加到文件中是一件很困难的事情。所以我们只需要使用push指令将该函数需要的4个参数传入,找到此文件中的MessageBox函数的地址,使用call指令调用它,此时该函数会调用MessageBoxA API,于是我们便完成了添加MessageBox函数的操作

指令硬编码

由于计算机只认识二进制数据,所以我们需要知道call指令 jmp指令 push指令的硬编码以及使用它们需要传入的各种参数的硬编码

硬编码查看:当一个程序编译以后,我们可以进入反汇编中,右键打开code bytes选项即可显示硬编码。此时我们可以看到每条指令的左边都是它对应的硬编码。硬编码都是二进制数,在本节学习中用十六进制显示

call指令有不同类型的硬编码,一般都是E8类型的。其中当call指令的硬编码为E8时,使用该指令时后面加上4个字节call要跳转的的相对地址。而call指令的硬编码为FF15,使用时后面加上4个字节call要跳转的地方的绝对地址,即ImageBase + RVA后的地址值

jmp指令的硬编码是E9,使用时后面加上4个字节大小的jmp要跳转的相对地址

push指令的硬编码是6A,使用时后面加上需要传入函数参数的值

相对地址X的计算公式:X = 文件在虚拟内存状态下真正要跳转的地址 – 文件在虚拟内存状态下E8这条指令的下一行地址。由于call指令的硬编码固定一共占5字节,即一字节call硬编码和四字节地址,所以E8指令的下一行地址为E8当前地址+5。所以公式最终为:X = 要跳转的地址 - (E8的地址 + 该指令长度),

实例说明

构造一个需要调用函数的C代码:

#include <stdio.h>

void Function(int a,int b,int c,int d){}

int main(int argc,char* argv[])

{

    Function(1,2,3,4);

    return 0;

}

查看反汇编:

其中E8为call指令硬编码,E8 7F FE FF FF 为调用函数相对地址,00101014为调用函数的这真实地址。

根据公式:X = 要跳转的真实地址 - (E8的地址 + 该指令长度)可知:0x00401014 - (0x00401190 + 5)= 0xFFFFFE7F,由于内存小端序存储,所以为内存中存储的地址为7F FE FF FF,因此最后call指令的硬编码为E8 7F FE FF FF

预备工作

在我们使用call指令和jmp指令前,需要获取MessageBoxAPI函数的地址和程序本来的入口地址

1.获取MessageBox函数的地址:使用OD打开可执行文件,OD会模拟文件运行时装入内存中的状态。在左下角的命令栏输入bp MessageBoxA后回车。由于弹窗代表着断点,所以该指令表示在此函数起始位置设置了一个断点。接着我们点击上方栏中的B按钮查看断点,接着双击我们刚设置的断点就会跳转到断点所在地址,这个地址就是MessageBoxA函数的起始地址

2.获取程序本来的入口地址:通过可选PE头中的AddressOfEntryPoint字段值可以知道程序入口地址的相对于PE头偏移量,再通过可选PE头中的ImageBase字段知道文件装载到虚拟内存中的起始地址。根据ImageBase + AddressOfEntryPoint可以得出来程序在4GB虚拟内存中的真正的入口地址

添加流程

1.判断要添加代码的空白区内存大小是否能存放得下添加的硬编码(使用节对应的节表中的Misc.VirtualSize – SizeOfRawData即可计算空白区的大小)。因为我们使用一个push指令长度为2字节(一个一字节push硬编码大小,一个一字节地址大小),MessageBox函数一共需要push四个参数,所以需要四个push指令一共八字节大小。call和jmp指令长度为5字节,加起来一共是18字节。所以要留空的空间至少需要18字节

2.将要添加的代码转换成对应的硬编码:由于需要计算call指令E8 和 push指令E9后面的地址,所以要用公式X = 要跳转的地址 - (E8的地址 + 5)进行计算。

当我们使用UE编辑器或者winhex打开可执行文件进行编辑修改时,由于UE编辑器打开的文件的状态是在硬盘上的状态,所以我们修改的数据是FileBuffer中的数据,而不是ImageBuffer中的。此时计算公式的地址需要从文件地址转换成内存地址后再计算的

如果可执行文件是ipmsg.exe时,其文件对齐和内存对齐是一样的,所以该文件在硬盘上的数据格式和加载到内存中的格式是一样的。此时那使用UE打开文件时,上面显示的地址可以直接使用

但是如果可执行文件是notepad.exe等时,由于文件对齐和内存对齐不一样,从硬盘加载到虚拟内存是需要拉伸的,所以如果此时用UE添加硬编码计算E8 E9后面的地址值时需要使用内存地址,所以多了一个FOA转化成RVA的过程

3.定位到任意一个节后面的空白区,作为添加的硬编码的起始地址,数据最好添加在代码节中(一般默认为.text),这是因为代码区的属性一般是可执行的。所以数据加到代码节中不用修改节的characteristic属性。

由于我们添加的硬编码中有call和jmp指令后面的地址都是在虚拟内存中的地址,所以我们如果写程序来模拟这个过程,需要在拉伸后的文件中添加硬编码,这样更方便计算E8 和 E9后面跟的地址值。如果我们在文件在硬盘上的状态中添加也可以,但是计算E8 和 E9 后面跟的地址值在使用公式前,需要先把文件地址转换成内存地址

4.最后找到程序的AddressOfEntryPoint字段所在地址,将此字段的值改为添加的硬编码的地址

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值