直接进入正题,本文使用c语言对一款小软件实现内存补丁,今天下午才自学过,便想在博客中记录下来,分析过程仅供初学者学习,大神可以绕过。
大概思路为: 1、枚举进程获得系统进程列表
2、遍历进程列表,根据进程名得到进程的PID
3、根据PID获取进程句柄
4、根据进程句柄读写内存操作
先介绍要用到的几个API函数:
一、CreateToolhelp32Snapshot
该函数用于获取进程信息或模块信息快照,函数的原型如下:
HANDLE WINAPI CreateToolhelp32Snapshot(
_In_ DWORD dwFlags, //指定快照中返回的对象,为TH32CS_SNAPPROCESS表示系统中的所有进程
_In_ DWORD th32ProcessID //进程PID,用于指定进程,当为0时表示获取系统所有进程
);
函数成功返回快照句柄,失败返回INVALID_HANDLE_VALUE。
二、Process32First与Process32Next
这两个函数都是从指定的进程快照句柄中获取一个进程,从名字就可以看出Process32First用于获取第一个进程,后面的函数用于获取下一个进程,第一个函数的原型如下:
BOOL WINAPI Process32First(
_In_ HANDLE hSnapshot, //快照句柄
_Inout_ LPPROCESSENTRY32 lppe //LPPROCESSENTRY32进程结构体
);
函数成功返回true,并将一个进程信息存储到第二个参数中,失败返回false,后面的函数参数一样。
三、OpenProcess
该函数用户打开一个进程,可以指定打开后的权限,函数原型如下:
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess, //访问权限,PROCESS_ALL_ACCESS指定所有权限
_In_ BOOL bInheritHandle, //是否继承句柄
_In_ DWORD dwProcessId //要打开进程的PID
);
函数成功则返回进程的句柄,失败则返回NULL。
四、ReadProcessMemory
该函数用于向指定的进程的指定地址处读取数据,数据长度自定义,函数原型如下:
BOOL WINAPI ReadProcessMemory(
_In_ HANDLE hProcess, //进程句柄
_In_ LPCVOID lpBaseAddress, //要读取的地址
_Out_ LPVOID lpBuffer, //要读取的数据缓存区
_In_ SIZE_T nSize, //指定读取的字节大小
_Out_ SIZE_T *lpNumberOfBytesRead //实际读取的字节大小
);
函数失败返回0,否则读取成功。
五、WriteProcessMemory
该函数用于向指定的进程的指定地址处写数据,数据长度自定义,函数原型如下:
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, //进程句柄
_In_ LPVOID lpBaseAddress, //要写入的地址
_In_ LPCVOID lpBuffer, //要写入的数据缓存区
_In_ SIZE_T nSize, //指定写入的大小
_Out_ SIZE_T *lpNumberOfBytesWritten //实际写入的大小
);
函数失败返回0,否则写入成功。
这里还需要介绍下进程结构体 PROCESSENTRY32,该结构体的定义如下:
typedef struct tagPROCESSENTRY32 {
DWORD dwSize; // 结构大小;
DWORD cntUsage; // 此进程的引用计数;
DWORD th32ProcessID; // 进程ID;
DWORD th32DefaultHeapID; // 进程默认堆ID;
DWORD th32ModuleID; // 进程模块ID;
DWORD cntThreads; // 此进程开启的线程计数;
DWORD th32ParentProcessID;// 父进程ID;
LONG pcPriClassBase; // 线程优先权;
DWORD dwFlags; // 保留;
WCHAR szExeFile[MAX_PATH]; // 进程全名;
} PROCESSENTRY32;
这里我们只需要用到th32ProcessID和szExeFile这两个属性。
现在介绍我们需要打补丁的小软件,软件截图如下:
该软件是一个注册码验证,需要输入用户名和正确的序列号,输入正确时弹出正确的提示信息,否则弹出错误的提示信息,如下图:
我们现在要做的就是,不管输入是否正确,总是弹出正确的提示,用OD打开软件,输入提示字符串,定位到如下地方:
00402588 . 8945 B4 mov dword ptr ss:[ebp-0x4C],eax
0040258B 74 58 je short Afkayas.004025E5 ; 关键跳
0040258D . 68 801B4000 push Afkayas.00401B80 ; You Get It
00402592 . 68 9C1B4000 push Afkayas.00401B9C ; \r\n
00402597 . FFD7 call edi ; msvbvm50.__vbaStrCat
00402599 . 8BD0 mov edx,eax
0040259B . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18]
0040259E . FFD3 call ebx ; msvbvm50.__vbaStrMove
004025A0 . 50 push eax
004025A1 . 68 A81B4000 push Afkayas.00401BA8 ; KeyGen It Now
004025A6 . FFD7 call edi ; msvbvm50.__vbaStrCat
004025A8 . 8D4D 94 lea ecx,dword ptr ss:[ebp-0x6C]
004025AB . 8945 CC mov dword ptr ss:[ebp-0x34],eax
004025AE . 8D55 A4 lea edx,dword ptr ss:[ebp-0x5C]
004025B1 . 51 push ecx
004025B2 . 8D45 B4 lea eax,dword ptr ss:[ebp-0x4C]
004025B5 . 52 push edx
004025B6 . 50 push eax
004025B7 . 8D4D C4 lea ecx,dword ptr ss:[ebp-0x3C]
004025BA . 6A 00 push 0x0
004025BC . 51 push ecx
004025BD . C745 C4 08000>mov dword ptr ss:[ebp-0x3C],0x8
004025C4 . FF15 10414000 call dword ptr ds:[<&MSVBVM50.#rtcMsgBox>; 正确的提示
004025CA . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18]
004025CD . FF15 80414000 call dword ptr ds:[<&MSVBVM50.__vbaFreeS>; msvbvm50.__vbaFreeStr
004025D3 . 8D55 94 lea edx,dword ptr ss:[ebp-0x6C]
004025D6 . 8D45 A4 lea eax,dword ptr ss:[ebp-0x5C]
004025D9 . 52 push edx
004025DA . 8D4D B4 lea ecx,dword ptr ss:[ebp-0x4C]
004025DD . 50 push eax
004025DE . 8D55 C4 lea edx,dword ptr ss:[ebp-0x3C]
004025E1 . 51 push ecx
004025E2 . 52 push edx
004025E3 . EB 56 jmp short Afkayas.0040263B ; 跳过错误提示
004025E5 > 68 C81B4000 push Afkayas.00401BC8 ; You Get Wrong
004025EA . 68 9C1B4000 push Afkayas.00401B9C ; \r\n
004025EF . FFD7 call edi ; msvbvm50.__vbaStrCat
004025F1 . 8BD0 mov edx,eax
004025F3 . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18]
004025F6 . FFD3 call ebx ; msvbvm50.__vbaStrMove
004025F8 . 50 push eax
004025F9 . 68 E81B4000 push Afkayas.00401BE8 ; Try Again
004025FE . FFD7 call edi ; msvbvm50.__vbaStrCat
00402600 . 8945 CC mov dword ptr ss:[ebp-0x34],eax
00402603 . 8D45 94 lea eax,dword ptr ss:[ebp-0x6C]
00402606 . 8D4D A4 lea ecx,dword ptr ss:[ebp-0x5C]
00402609 . 50 push eax
0040260A . 8D55 B4 lea edx,dword ptr ss:[ebp-0x4C]
0040260D . 51 push ecx
0040260E . 52 push edx
0040260F . 8D45 C4 lea eax,dword ptr ss:[ebp-0x3C]
00402612 . 6A 00 push 0x0
00402614 . 50 push eax
00402615 . C745 C4 08000>mov dword ptr ss:[ebp-0x3C],0x8
0040261C . FF15 10414000 call dword ptr ds:[<&MSVBVM50.#rtcMsgBox>; 错误的提示
00402622 . 8D4D E8 lea ecx,dword ptr ss:[ebp-0x18]
地址0040258B处的跳转跳过了正确的提示,直接到达了错误提示,如果此处不跳转,就会执行正确的提示,而且在下方的jmp可以跳过错误的提示,因此,只需要将此地址处的指令进行NOP,即写入两字节的0x90,即可以破解,源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <tlhelp32.h>
int main()
{
//首先获取进程PID
int pid=0;
PROCESSENTRY32 processentry={0}; //创建一个进程结构体
processentry.dwSize=sizeof(PROCESSENTRY32);
HANDLE hprocessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); //获取进程快照
if(hprocessSnap==INVALID_HANDLE_VALUE){
return -1;
}
int flag=Process32First(hprocessSnap,&processentry); //获取第一个进程
while(flag){
if(lstrcmpi(processentry.szExeFile,"Afkayas.exe")==0){ //lstrcmpi函数用于比较两个字符串,相同时返回0
pid=processentry.th32ProcessID;
}
// printf("%d----%s\n",processentry.th32ProcessID,processentry.szExeFile);
flag=Process32Next(hprocessSnap,&processentry);
}
CloseHandle(hprocessSnap);
if(pid==0){
printf("请先打开进程");
return 0;
}
//获取进程句柄
HANDLE procHandle=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
if(procHandle==NULL){
printf("打开进程失败!");
return 0;
}
//测试读进程数据
int tmp;
DWORD buffer;
if(ReadProcessMemory(procHandle,0x400000,&tmp,4,&buffer)){
printf("读取成功,读取内容为:%#X\n",tmp);
}
else{
printf("读取进程内容失败!");
}
tmp=0x9090;
if(WriteProcessMemory(procHandle,0x40258B,&tmp,2,&buffer)){
printf("写入进程内容成功\n");
}
else{
printf("写入进程内容失败!\n");
return 0;
}
system("pause");
return 0;
}
打开程序后,再执行该补丁程序,提示注册成功:
本文只是记录了自己的学习过程,新手可以用来参考学习,大神发现问题欢迎指出。