C语言枚举进程,实现一个简单的内存补丁
2017-10-17 20:03
579 查看
直接进入正题,本文使用c语言对一款小软件实现内存补丁,今天下午才自学过,便想在博客中记录下来,分析过程仅供初学者学习,大神可以绕过。
大概思路为: 1、枚举进程获得系统进程列表
2、遍历进程列表,根据进程名得到进程的PID
3、根据PID获取进程句柄
4、根据进程句柄读写内存操作
先介绍要用到的几个API函数:
一、CreateToolhelp32Snapshot
该函数用于获取进程信息或模块信息快照,函数的原型如下:
函数成功返回快照句柄,失败返回INVALID_HANDLE_VALUE。
二、Process32First与Process32Next
这两个函数都是从指定的进程快照句柄中获取一个进程,从名字就可以看出Process32First用于获取第一个进程,后面的函数用于获取下一个进程,第一个函数的原型如下:
三、OpenProcess
该函数用户打开一个进程,可以指定打开后的权限,函数原型如下:
四、ReadProcessMemory
该函数用于向指定的进程的指定地址处读取数据,数据长度自定义,函数原型如下:
五、WriteProcessMemory
该函数用于向指定的进程的指定地址处写数据,数据长度自定义,函数原型如下:
这里还需要介绍下进程结构体PROCESSENTRY32,该结构体的定义如下:
现在介绍我们需要打补丁的小软件,软件截图如下:
![](https://img-blog.csdn.net/20171017204721556?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTU0NTk1ODQ5OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
该软件是一个注册码验证,需要输入用户名和正确的序列号,输入正确时弹出正确的提示信息,否则弹出错误的提示信息,如下图:
![](https://img-blog.csdn.net/20171017204958254?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTU0NTk1ODQ5OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
我们现在要做的就是,不管输入是否正确,总是弹出正确的提示,用OD打开软件,输入提示字符串,定位到如下地方:
地址0040258B处的跳转跳过了正确的提示,直接到达了错误提示,如果
a69e
此处不跳转,就会执行正确的提示,而且在下方的jmp可以跳过错误的提示,因此,只需要将此地址处的指令进行NOP,即写入两字节的0x90,即可以破解,源码如下:
打开程序后,再执行该补丁程序,提示注册成功:
![](https://img-blog.csdn.net/20171017210654011?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTU0NTk1ODQ5OA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
本文只是记录了自己的学习过程,新手可以用来参考学习,大神发现问题欢迎指出。
大概思路为: 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; }
打开程序后,再执行该补丁程序,提示注册成功:
本文只是记录了自己的学习过程,新手可以用来参考学习,大神发现问题欢迎指出。
相关文章推荐
- 转载的标准文档:C语言实现一个简单的单向链表list
- 一个简单的加密算法Kaiser(C语言实现)
- 一个简单模拟内存分配和释放的实现
- 用C语言实现一个简单的HTTP客户端
- 一个简单的四则运算程序C语言实现--无法处理括号
- 一个简单的四则运算程序C语言实现--实现处理括号
- C语言实现一个简单的单向链表list
- 深入浅出编译原理-5-一个简单语法分析器的C语言实现
- C语言实现的一个简单的万年历
- 【转】用C语言实现将一个文件读入内存中(分享转载)
- 数据结构 --静态队列的一个简单的C语言代码实现
- 一个简单用C语言实现的日志函数
- 用C语言实现一个简单的HTTP客户端(HTTP Client)
- BloomFilter的一个简单实现(C语言)
- 一个简单的内存补丁程序
- 模拟器与程序分析-4-一个简单的周期精确模拟器(CAS)的C语言实现
- C语言实现一个简单的单向链表list
- C语言实现一个简单的计算器
- 一个大学C语言试题的简单实现--员工信息管理程序
- 用C语言实现一个简单的HTTP客户端(HTTP Client)