开头
- 看完《加密与解密第四版》,有些心得体会,收获主要集中在补丁程序开发、Dll注入、函数hook、程序二次开发、加壳5个方面,可以统称为PEDIY
- 按照自己的理解和思考,上述5个方面各进行了一次实践,分4篇内容写出来,其中Dll注入一篇贯穿在二次开发和函数hook中,不单独写了
描述
- 本篇补丁程序分为文件补丁和内存补丁
- 写了个messagebox的例程,目标是通过补丁程序修改弹窗显示的内容
原程序
#include <windows.h>
int main(int argc, char* argv[])
{
MessageBox(NULL, "恭喜你成功进入游戏!", "成功", MB_OK);
return 0;
}
文件补丁
原理
步骤
- 首先在IDA打开程序,查找到title和text两个字符串的地址
- 由于IDA中看到的是默认加载的映像地址,还需要转化为文件地址才能使用,方法是减掉
.rdata
段的起始地址,得到段内偏移地址,再用PE Tools打开文件,查找到.rdata的文件地址,再加上段内偏移地址,即可定位到目标字符串在文件中的偏移地址0x89b0
- 接下来,编写程序打开目标文件,在指定偏移处填入字符串,需要注意一个中文字符占两个字节,多余的字节用
\x00
填充
代码
#include <windows.h>
#include <cstdio>
int main(int argc, char* argv[])
{
HANDLE hFile = CreateFile("Encoding.exe", 0xC0000000, 3, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Error opening file. Error code: %d\n", GetLastError());
return 1;
}
DWORD dwBytesWritten;
char title[] = "失败";
DWORD title_offset = 0x89b0;
SetFilePointer(hFile, title_offset, NULL, FILE_BEGIN);
if (!WriteFile(hFile, title, sizeof(title)-1, &dwBytesWritten, NULL)) {
printf("Error writing to file. Error code: %d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
char text[] = "进入游戏失败!\x00\x00\x00\x00\x00\x00";
DWORD text_offset = 0x89b8;
SetFilePointer(hFile, text_offset, NULL, FILE_BEGIN);
if (!WriteFile(hFile, text, sizeof(text)-1, &dwBytesWritten, NULL)) {
printf("Error writing to file. Error code: %d\n", GetLastError());
CloseHandle(hFile);
return 1;
}
CloseHandle(hFile);
}
效果展示
- 打补丁前运行程序提示成功进入游戏,打补丁后,运行程序提示失败
内存补丁
原理
- 设想场景是文件被加密处理,无法直接打上文件补丁,需要程序运行到特定处后,在内存中修改数据,改变程序运行结果
- 按照判断程序运行到特定处的方法,可以划分为多种方法,包括
- 创建进程后挂起线程,隔一段时间查询程序目标位置是否被解密还原
- 创建调试进程,在目标位置处设置硬件断点或者写入int 3断点,程序运行到目标位置处触发异常,被父进程捕获
- 本文尝试了很多遍第二种方法,在64位程序上不管如何用
waitfordebugevent
都捕捉不到程序异常。不光捕捉不到异常,目标程序还卡在了奇怪的位置不能继续运行,故放弃,采用了第一种比较笨的方法可行
步骤
- 创建进程后立即挂起线程,读取目标地址处的值(这里选用的是call messagebox处的值),判断是否已被还原为指定的值,如果没有还原,则让线程继续执行10秒,重复进行判断
- 如果已被还原,则修改目标地址的值,恢复线程运行,很奇怪的是,我这里必须要调用两次
ResumeThread
才能目标程序才能正常运行,难以解释...
代码
#include <windows.h>
#include <cstdio>
#include "psapi.h"
#define BREAK_POINT1 0x00007FF7FA171796 //需要中断的地址
#define BREAK_SIZE 6
#define SZFILENAME ".\\Encoding.exe" //目标文件名
#define TITLE_ADDR 0x00007FF7FA179BB0
#define TEXT_ADDR 0x00007FF7FA179BB8
BYTE TarGetData[] = { 0xFF,0x15, 0xB4, 0xE9, 0x00, 0x00 }; //补丁前的代码
char title[] = "失败";
char text[] = "进入游戏失败!\x00\x00\x00\x00\x00\x00";
int main(int argc, char* argv[])
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
BYTE ReadBuffer[MAX_PATH] = { 0 };
BOOL bContinueRun = TRUE;
DWORD Oldpp;
ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
if (!CreateProcess(SZFILENAME,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi)
)
{
MessageBox(NULL, "CreateProcess Failed.", "ERROR", MB_OK);
return FALSE;
}
while (bContinueRun) {
SuspendThread(pi.hThread);
ReadProcessMemory(pi.hProcess, (LPVOID)BREAK_POINT1, &ReadBuffer, BREAK_SIZE, NULL);
//判断是不是完全解码出来了
if (!memcmp(TarGetData, ReadBuffer, BREAK_SIZE)) {
MessageBox(NULL, "已运行至目标代码处,准备打补丁", "提示", MB_OK);
VirtualProtectEx(pi.hProcess, (LPVOID)TITLE_ADDR, sizeof(title) - 1, PAGE_EXECUTE_READWRITE, &Oldpp);
VirtualProtectEx(pi.hProcess, (LPVOID)TEXT_ADDR, sizeof(text) - 1, PAGE_EXECUTE_READWRITE, &Oldpp);
WriteProcessMemory(pi.hProcess, (LPVOID)TITLE_ADDR, title, sizeof(title) - 1, NULL);
WriteProcessMemory(pi.hProcess, (LPVOID)TEXT_ADDR, text, sizeof(text) - 1, NULL);
ResumeThread(pi.hThread);
ResumeThread(pi.hThread);
break;
}
ResumeThread(pi.hThread);
Sleep(10);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
效果
- 直接运行目标程序
- 通过补丁程序运行目标程序