- 环境: windowsXP
- 工具: 吾爱版OD,winhex , vc6++
当然,知道一些PE文件头的知识更好
在最后有介绍一点PE文件结构的知识,有助于理解此文章
目标:在一个程序中的任意空白区域添加代码 ( 转换为机器码 )
如在文件头与节数据之间添加代码:
在此位置添加一个message弹框的机器码,使程序在执行前先弹出一个消息框。
先查看message消息框的汇编代码
在00401032处先把message函数方法需要四个参数压入栈中,然后call直接调用message函数
但是message函数的地址未知,需要去调试程序查看,使用OD调试
将程序拖入OD中后,点击显示模块窗口 即最上面一排的 e
双击选择user32.dll
进入,然后ctrl+n
快捷键查看模块的名称,找到MessageBoxA
的地址可见在此程序中MessageBoxA
的地址为0x77D507EA
要调用这个函数的话,先构造一个call调用函数
可知
调用函数的地址为:0x00401193
call 的值为:0x7794F652
( 因为是程序是小端存储,所以值会反过来 )
MessageBoxA
的地址为:0x77D507EA
这里有一个call调用函数的规律:
发现MessageBoxA
的地址与当前调用函数的地址的差值减去call后的值等于5
即 MessageBoxA的地址 - 当前调用函数的地址 - call后的值 = 5
那么现在就构造出调用MessageBoxA
的机器码,首先应该把MessageBoxA
的四个参数压入栈,在前面已经看过了其汇编代码对应的机器码,所以就可以先把调用的参数写入
然后调用函数,此时应该在0x608
的位置写入数据,但是调用函数的地址应该是在内存中调用函数的地址,所以要找到这个位置在内存中对应的位置,就要先找到程序载入内存的基地址ImageBase
,在扩展PE头的第 28 个字节后面的 4 个字节表示的 ImageBase
( 程序基地址 ),见下图
其中
1)表示扩展PE头
2)表示标准PE头
Imagebase
是00 00 04 00,因为是小段存储所以表示为16进制应该是 0x00400000
,也就说明程序的基地址为0x00400000
接着上面的构造调用函数,现在也就能算出调用函数内存中的地址为0x400000 + 0x608 = 0x400608
(其实还有考虑一个内存和硬盘中要计算页对齐的问题,但是目前大多数程序的内存页大小和硬盘中的页大小相同,所以至少在这里是不用考虑的)
然后再计算call后面的值:根据之前的规律公式:
MessageBoxA的地址 - 当前调用函数的地址 - call后的值 = 5
所以
call后的值 = MessageBoxA的地址 - 当前调用函数的地址 - 5 = 0x77D507EA - 0x400608-5 = 0x779501dd
因为程序是小段存储所以应该填入dd019577
E8
表示 call
还有最后一个问题就是怎么让程序到这个地址来执行,那么就可以修改程序入口来改变程序开始执行的地址,接下来介绍一下程序入口
程序入口是程序开始执行的位置,是在扩展PE头的第 16 个字节后面的 4 个字节表示程序入口(相对 与ImageBase
),在此程序中
1)程序入口:0x401170
(相对于程序基地址)
2)扩展PE头的开始
把程序拖入OD,程序第一次停止的的地址就是程序入口
所以只要修改程序入口地址,把程序入口改为调用函数的地址,然后在调用后再返回到真正的程序入口,就可以在程序执行前执行我们想调用的函数
修改程序入口为0x600
现在就9可以达到在程序执行前调用MessgaBoxA
函数的效果,但是要执行完这个函数后,再返回到真正的程序入口,不然程序不能够正常运行,于是又要在我们要调用的函数后面再制造一个返回到真正程序入口处的机器码
这里就在用到jmp
这个指令,表示直接跳转到某个指定的地址
先看一下jmp
要如何使用
这里jmp的使用方法其实和call的规律一样只是开头的 E8 变成了 E9
所以E9后面的值应该是
jmp后的值 = 程序入口的地址 - 当前的地址 - 5 = 0x401170 - 0x40060D - 5 = 0x0B5E
当前的地址应该是0x40060D
所以修改后就是这样的
此时把程序另存为message_test.exe
, 运行出现了我们预期的效果
源程序的代码是很简单,但是要注意要有MessageBoxA函数
不然在最开始没有user32dll,我也不知道为什么,但如果是正常的程序都会有user32dll(因为一般的程序都有窗口)
我是用下面的代码做的实验
// messge.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
int main(int argc, char* argv[])
{
printf("nothing...");
MessageBox(0,"这是已有的弹框","提示",0);
Sleep(60000);
//printf("Hello World!\n");
return 0;
}
简单介绍一点点PE文件头结构
DOS头部分:
- DOS MZ 文件头 (64字节):
前 64 个字节,最后 8 个字节表示 PE 文件头的开始位置, DOS 块结束前的位置,绿色箭头指向PE文件头的开始
- DOS 块 (不确定):
链接器可插入数据
PE文件头:
-
PE文件标识 (4字节):
50 45 00 00 -
标准 PE 头 (20字节):
扩展 PE 头
(32位 224 [0xE0] , 64 位 240 [0xF0] ):
第 36 个字节开始的后 4 个字节表示 硬盘 中文件对齐的字节数大小,
前 4 个字节表示 内存 中文件对齐的字节数大小,
第 60 个字节开始的四个字节表示整个文件头的大小( DOS 部分+ PE 文件头),一定是对齐字节数的整数倍
第 16 个字节后面的 4 个字节表示程序入口(相对 ImageBase )
第 28 个字节后面面的 4 个字节表示的 ImageBase ( 程序基地址 )