补丁技术
逆向工程中,经常需改更改程序的原有执行流程,使其增加或减少一些功能代码。这就需要给原有的文件添加补丁。
补丁分为两种文件补丁和内存补丁,文件补丁修改文件本身的某个数据,内存补丁是在内存中修改数据。
1.文件补丁
文件补丁直接对二进制可执行文件进行修改使其满足需求。文件补丁实现很加简单,对修改前的文件与修改后的文件进行比较操作,把不同之处记录下来,然后写一个程序实现补丁功能
有以下优点:
- 理论简单、实施容易:只需要通过基本的十六进制操作就可以完成任务。
- 有很多的现成的补丁工具
2.内存补丁
文件补丁虽然简单但是有局限性,如果文件被加壳、或压缩壳或文件完整性校验则无法顺利使用补丁。由此产生了内存补丁技术
内存补丁的思想是在,目标程序的地址空间中修改数据
2.1 跨进程内存存取机制
就程序设计原则而言, 运行同一个系统的进程A和进程B之间应该完全“绝缘”的,也就是说,一个进程的地址、指令、内存使用等信息另一个进程完全是透明的,说的更高级一点,操作系统应该对运行其中的所有进程进行“封装”。但是封装和灵活从来都是一对矛盾。再C++中为了达到两者的平衡,引入了Friend关键字,以及public、private、protected存储概念。同样对操作系统而言,对进程进行封装的同时,也需要提供一种再一定条件下可以实现进程之间的互访机制。作为最简单的“进程互访”动作, 内存的存期是最重要的功能。 Windows提供了两个进程之间互访的函数ReadProcessMemory和WriteProcessMemory, 只需要打开进程权限,就可以读写进程的地址空间。
在整个运行过程中,Loader载入待打补丁的状态城西可以由CreateProcess函数实现,资源释放,清理工作可以由ExitProcess函数完成。整个核心的流程就是中间的Loader对创建出来的待打补丁进程的控制与修改,通过WriteProcessMemory函数写道目标进程的适当位置
#include <Windows.h>
#include <iostream>
#define PATCH_ADDRESS 0x4011F5
using namespace std;
int main()
{
LPCWSTR sizeFile = L"F:\\C++\\内存补丁\\Debug\\TraceMe.exe"; //文件位置
byte TarGetData[] = {0x74, 0x37}; //修改前的代码
byte newTarGetData[] = {0x90, 0x90}; //修改后的代码
byte ReadBuffer[128] = { 0 };
bool TASTARTRUNFalg = true;
DWORD Oldpp;
STARTUPINFOW si = {0};
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcess(sizeFile, 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi))
{
cout << "程序挂起异常" << endl;
return FALSE;
}
while (TASTARTRUNFalg)
{
ResumeThread(pi.hThread); //运行线程
Sleep(1000); //给个时间让加载
SuspendThread(pi.hThread); //挂起线程
/*BOOL ReadProcessMemory( //读取内存
[in] HANDLE hProcess, 进程句柄
[in] LPCVOID lpBaseAddress, 指向从中读取的指定进程中基址的指针
[out] LPVOID lpBuffer, 指向从指定进程的地址空间接收内容的缓冲区的指针。
[in] SIZE_T nSize, 修改大小
[out] SIZE_T * lpNumberOfBytesRead
);*/
ReadProcessMemory(pi.hProcess, (LPVOID)PATCH_ADDRESS, &ReadBuffer, 2, NULL);
if (!memcmp(TarGetData, ReadBuffer, 2))
{
/* BOOL VirtualProtectEx( 修改提交内存的保护区
[in] HANDLE hProcess,
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flNewProtect,
[out] PDWORD lpflOldProtect
);*/
VirtualProtectEx(pi.hProcess, (LPVOID)PATCH_ADDRESS, 2, 0x40, &Oldpp);
WriteProcessMemory(pi.hProcess, (LPVOID)PATCH_ADDRESS, &newTarGetData, 2,NULL);
ResumeThread(pi.hThread);
TASTARTRUNFalg = FALSE;
}
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
以上代码实现了再指定的内存区域修改特征码从而跳过验证,使用ReadProcessMemory函数读取特定的内容进行校验工作。因为补丁代码是与程序代码精准对应的,所以在运行WriteProcessMemory函数之前去做校验是有必要的。
读取内容后进行校验后, 调用WriteProcessMemory函数将数据写入到指定位置,这个方法缺陷是很明显的在CreateProcess进程之后需要马上进入Suspend挂起目标,等打完补丁后在运行程序。
理想状况是采用一种"通知机制"也就是在程序eip=[目标位置]时发送一个消息,收到消息后在进行补丁操作,这还需要系统更底层的支持DebugApi则可以有这种作用
...