今天我们来个比较简单的任务,在exe启动的时候先弹出一个MessageBox,然后再启动它
我们修改一个PE之前 需要先知道PE文件的结构,具体可以参考PE权威指南
总的来说,一个PE文件 最开始的两个字节是mz 第一个结构是IMAGE_DOS_HEADER,里面有个成员叫做e_lfanew指向了PE文件的结构头四个字节是PE两个字,然后就是标准PE头,然后就是可选PE头,可选PE头后面就是节表,然后就是各个节数据了 如下图
用十六进制编辑器查看如下
好了,PE结构大概了解了,我们现在要知道在哪里添加,正常情况下,第一个节都是代码段(加壳和其他情况先不考虑),根据节表,我们能找到第一个节的位置(待会添加的时候再找)
如果我们要添加代码进去,只能添加硬编码,也就是机器指令,我们写段代码 看看是怎么调用MessageBox的
建立一个工程 加一个messagebox,参数先都给0,不管他,在MessageBox处下一个断点,然后编译,调试运行
断下来了,我们右键,转到反汇编
这里可以看到 4个参数的硬编码是0x6a,0x00,0x6a,0x000x6a,0x000x6a,0x00,调用call处的硬编码是0xff 0x15 0x98 x0b0 0xde 0x00,我们单步到call指令,然后F11跟入,如果你们是VC6的话 这里可能有个jmp 不管他 继续单步就是了,然后就来到了MessageBox,这里可以看到MessageBox的地址是 76EAFDCF(你们的机器肯定跟我的不同),我们记住这个地址,后面要用
E8(call)和E9(jmp)后面的4个字节并不是真正的地址
E8和E9后面的四字节是怎么来的呢
我们假设E8后面的4个字节是X 那么
X = 真正要跳转的地址-E8的下一条指令的地址
理论知识到此 现在开始实际部分
我们用16禁止编辑器打开一个exe,这里我打开xp下的notepad.exe(修改之前最好复制一份出来)
我们直接定位到节表数据
节表结构如下
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
VirtualAddress 表示运行时的内存地址
SizeOfRawData 在磁盘上的大小
PointerToRawData 在磁盘中的起始位置
图中表明 运行时的内存地址的开始处为0x00001000 RVA
在磁盘上的大小是0x00007800
在硬盘起始位置是0x0000400
我们来到400出
从0x400开始 一直到 0x400+0x7800都是.text节的内容,只要中间有空的,我们就能插入代码,我们往下找找
我们在0x7b50出发现一大片空白,可以往这里插代码
我们先插入4个0x6a,0x00,在插入E8 后面的地址留空,再插入E9 后面的地址也留空
我们算下E8 后面的地址填什么
根据公式 x = 真正要跳转的地址-e8的下一条指令的地址
E8的下一条指令的地址是什么呢,0x7b5d,但是0x7b5d是在硬盘上的地址,由于文件对齐和内存对齐可能不同,所以需要转成rva再加上基址
也就是0x7b50-0x400+0x1000
也就是 e8的下一条指令的RVA 0x875d加上基址后就是100875D(基址可以在可选头里面查)
那么e8后面的地址就是 0x76EAFDCF – 0x100875d = 0x75EA7672
e9后面的也是,e9要跳转到oep 所以 后面的地址根据oep-e9下一条指令的地址可以算出来 可选PE头有个AddressOfEntryPoint 加上基址就是OEP了
结果是FFFFEC3B
然后把OEP修改成875d运行看看
2018年1月17日 at 11:11
不知道 如果想给messagebox传参,怎么弄呢?
2018年1月17日 at 11:19
这里是插入了调用函数的代码 你只要把参数压栈的代码也加进去就行了