今天我们来个比较简单的任务,在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运行看看



打赏