您的位置:首页 > 编程语言 > C语言/C++

C语言枚举进程,实现一个简单的内存补丁

2017-10-17 20:03 579 查看
       直接进入正题,本文使用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处的跳转跳过了正确的提示,直接到达了错误提示,如果
a69e
此处不跳转,就会执行正确的提示,而且在下方的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;
}


打开程序后,再执行该补丁程序,提示注册成功:



本文只是记录了自己的学习过程,新手可以用来参考学习,大神发现问题欢迎指出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息