Implementation principle
Modifying the data displayed in the game is to modify the memory of the process where the game is located, because these data are retained in memory. Because the address spaces of processes are isolated from each other, API functions must be used to access the memory of other processes. The following two functions are usually used to read and write the memory space of other processes.
BOOL ReadProcessMemory( HANDLE hProcess, //Handle to the process to be read LPCVOID lpBaseAddress, //The starting address of the memory to be read in the target process LPVOID lpBuffer, //A buffer used to accept read data DWORD nSize, //Number of bytes to read LPDWORD lpNumberOfBytesRead //Used by the function to return the number of bytes actually read ); WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead); //The meaning of parameters is the same as above
One of their functions is to read the memory of the specified process, and the other is to write the memory of the specified process. The specific usage will be introduced later.
How to program and modify the vitality, money value and other data displayed in the game? First of all, in the game process, which memory address should be searched to store these data, search the unique address, and then call the WriteProcessMemory function to write the data you expect to this address.
The trouble is to search the memory of the target process.
You should search the entire user address space of the target process. In the whole 4GB address of the process, the operating system of Windows 98 series reserves 4MB to 2GB for the application, and the operating system of Windows 2000 series reserves 64KB to 2GB. Therefore, before searching, it is necessary to judge the type of operating system to determine the search scope. Windows provides the GetVersionEx function to return the version information of the current operating system. The function usage is as follows.
BOOL GetVersionEx(LPOSVERSIONINFO lpVersionInfo);
The system will return the version information of the operating system to the OSVERSIONINFO structure pointed to by the parameter lpVersionInfo.
typedef struct _OSVERSIONINFO { DWORD dwOSVersionInfoSize; //The size of this structure must be set before calling DWORD dwMajorVersion; //Major version number of the operating system DWORD dwMinorVersion; //Minor version number of the operating system DWORD dwBuildNumber; //The compiled version number of the operating system DWORD dwPlatformId; //Operating system platform. Can be VER_PLATFORM_WIN32_NT (2000 Series), etc TCHAR szCSDVersion[128]; //Specify the latest service pack installed on the system, such as "Service Pack 3" } OSVERSIONINFO;
Here, you only need to judge whether it is a Windows 98 series system or a Windows 2000 series system, so the following code is sufficient.
OSVERSIONINFO vi = { sizeof(vi) }; ::GetVersionEx(&vi); if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) ... //Windows 98 series operating system else ... //Windows NT Series operating system
There are probably multiple values you want to search in the memory of the target process, so when searching for the first time, record the searched address, then let the user change the value to be searched, and then search in the recorded address until the searched address is unique. To do this, write two auxiliary functions and three global variables.
BOOL FindFirst(DWORD dwValue); //Make the first lookup in the target process space BOOL FindNext(DWORD dwValue); //Perform the 2nd, 3rd, 4th... Lookup in the address space of the target process DWORD g_arList[1024]; //Address list int g_nListCnt; //Number of valid addresses HANDLE g_hProcess; //Target process handle
The above five lines of code constitute a more practical search system. For example, the money value displayed in the game is 12345. First, pass 12345 to the FindFirst function for the first search. The FindFirst function will save all the addresses with 12345 in the memory of the game process in G_ In the arlist global array, record the number of such addresses in g_nListCnt variable.
After the FindFirst function returns, check G_ If the value of nlistcnt is greater than 1, it means that more than 1 addresses have been searched. At this time, you should do something to change the money value displayed in the game. For example, after changing the money value to 13345, you need to call the FindNext function with 13345 as the parameter. This function will be in g_arList array to find the address of the record and update G_ For the record of arlist array, write all the addresses with 13345 contents to it, and write the number of such addresses to g_nListCnt variable.
After the FindNext function returns, check G_ If the value of nlistcnt is not equal to 1, continue to change the money value and call the function FindNext until the final g_nListCnt has a value of 1. At this time, G_ The value of arlist [0] is the address where the money value is saved in the target process.
Write test program
In order to carry out the experiment, write a test program as the target process (game process). Try to change a value in the program memory first. The simple implementation code of the program is as follows.
StdAfx.h
// stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently // #if !defined(AFX_STDAFX_H__9C88BEEE_5C7F_4140_B411_3B326A3986F1__INCLUDED_) #define AFX_STDAFX_H__9C88BEEE_5C7F_4140_B411_3B326A3986F1__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include <stdio.h> // TODO: reference additional headers your program requires here //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_STDAFX_H__9C88BEEE_5C7F_4140_B411_3B326A3986F1__INCLUDED_)
StdAfx.cpp
// stdafx.cpp : source file that includes just the standard includes // 02ProcessList.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" // TODO: reference any additional headers you need in STDAFX.H // and not in this file
02Testor.cpp
#include "stdafx.h" #include <stdio.h> // Global variable test int g_nNum; int main(int argc, char* argv[]) { int i = 198; // Local variable test g_nNum = 1003; while(1) { // Output the value and address of a variable printf(" i = %d, addr = %08lX; g_nNum = %d, addr = %08lX \n", ++i, &i, --g_nNum, &g_nNum); getchar(); } return 0; }
Now let's study how to change the variable g in this program in another program_ The values of nnum and i, which is the process of changing the memory of the game process.
Search memory
Next, create a project 02MemRepair to write the memory modifier program. In order to realize the function of searching memory, you can first write the definitions of the three global variables and the declarations of the two functions mentioned above before the main function.
Windows uses paging mechanism to manage memory. The size of each page is 4KB (on x86 processor). In other words, windows allocates memory for applications in 4KB units. Therefore, you can search the target memory by page to improve the search efficiency. The function of the CompareAPage function below is to compare the memory of 1 page in the memory of the target process.
BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue) { // Read 1 page of memory BYTE arBytes[4096]; if(!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL)) return FALSE; // This page is unreadable // Find in this 1 page of memory DWORD* pdw; for(int i=0; i<(int)4*1024-3; i++) { pdw = (DWORD*)&arBytes[i]; if(pdw[0] == dwValue) // Equal to the value to find? { if(g_nListCnt >= 1024) return FALSE; // Add to global variable g_arList[g_nListCnt++] = dwBaseAddr + i; } } return TRUE; }
The FindFirst function takes the longest time because it needs to search in an address space of nearly 2GB. Here is its implementation code.
BOOL FindFirst(DWORD dwValue) { const DWORD dwOneGB = 1024*1024*1024; // 1GB const DWORD dwOnePage = 4*1024; // 4KB if(g_hProcess == NULL) return FALSE; // View the operating system type to determine the start address DWORD dwBase; OSVERSIONINFO vi = { sizeof(vi) }; ::GetVersionEx(&vi); if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) dwBase = 4*1024*1024; // Windows 98 series, 4MB else dwBase = 640*1024; // Windows NT Series, 64KB // Search in the address space from the start address to 2GB for(; dwBase < 2*dwOneGB; dwBase += dwOnePage) { // Compare 1 page of memory CompareAPage(dwBase, dwValue); } return TRUE; }
The FindFirst function records all qualified memory addresses into the global array G_ In arlist. Next, write an auxiliary function ShowList to print out the searched address.
void ShowList() { for(int i=0; i< g_nListCnt; i++) { printf("%08lX \n", g_arList[i]); } }
The code in the main function is.
int main(int argc, char* argv[]) { // Start the 02tester process char szFileName[] = "C:\\Users\\Freddy\\source\\repos\\02testor\\Debug\\02testor.exe"; STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; ::CreateProcess(NULL, szFileName, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); // Close the thread handle, since we don't use it ::CloseHandle(pi.hThread); g_hProcess = pi.hProcess; // Enter the value to modify int iVal; printf(" Input val = "); scanf_s("%d", &iVal); // Make the first find FindFirst(iVal); // Print out search results ShowList(); ::CloseHandle(g_hProcess); return 0; }
Since the address found above is not unique, click enter several times in the 02 tester window. After changing the value of the variable, you need to click g_nListCnt searches the addresses listed in the array variable, which is the function of FindNext.
BOOL FindNext(DWORD dwValue) { // Save M_ The number of valid addresses in the arlist array, initializing a new m_nListCnt value int nOrgCnt = g_nListCnt; g_nListCnt = 0; // In M_ The arlist array looks at the address of the record BOOL bRet = FALSE; // Hypothetical failure DWORD dwReadValue; for(int i=0; i<nOrgCnt; i++) { if(::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL)) { if(dwReadValue == dwValue) { g_arList[g_nListCnt++] = g_arList[i]; bRet = TRUE; } } } return bRet; }
Add the following code to the main function.
while(g_nListCnt > 1) { printf(" Input val = "); scanf_s("%d", &iVal); // Next search FindNext(iVal); // Display search results ShowList(); }
Run the program. When the output address is not unique, change the value of the variable in the target process until the unique address is output, and the search is completed.
Write process space
After finding the address of the variable, you can change its value. The WriteMemory function is used to achieve this function.
BOOL WriteMemory(DWORD dwAddr, DWORD dwValue) { return ::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL); }
Add the following code to the main function.
// Get new value printf(" New value = "); scanf_s("%d", &iVal); // Write new value if(WriteMemory(g_arList[0], iVal)) printf(" Write data success \n");
Now that you have all the basic functions, start the program.
(1) Enter 1002 and the found address is not unique.
(2) Click enter twice in the 02tester window, and then search again after the change. This cycle will continue until the address found is unique
One.
(3) Enter the expected value and modify it successfully!
StdAfx.h
// stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently // #if !defined(AFX_STDAFX_H__9C88BEEE_5C7F_4140_B411_3B326A3986F1__INCLUDED_) #define AFX_STDAFX_H__9C88BEEE_5C7F_4140_B411_3B326A3986F1__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include <stdio.h> // TODO: reference additional headers your program requires here //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_STDAFX_H__9C88BEEE_5C7F_4140_B411_3B326A3986F1__INCLUDED_)
StdAfx.cpp
// stdafx.cpp : source file that includes just the standard includes // 02ProcessList.pch will be the pre-compiled header // stdafx.obj will contain the pre-compiled type information #include "stdafx.h" // TODO: reference any additional headers you need in STDAFX.H // and not in this file
MemRepair.h
#ifndef __MEMFINDER_H__ #define __MEMFINDER_H__ #include <windows.h> class CMemFinder { public: CMemFinder(DWORD dwProcessId); virtual ~CMemFinder(); // attribute public: BOOL IsFirst() const { return m_bFirst; } BOOL IsValid() const { return m_hProcess != NULL; } int GetListCount() const { return m_nListCnt; } DWORD operator [](int nIndex) { return m_arList[nIndex]; } // operation virtual BOOL FindFirst(DWORD dwValue); virtual BOOL FindNext(DWORD dwValue); virtual BOOL WriteMemory(DWORD dwAddr, DWORD dwValue); // realization protected: virtual BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue); DWORD m_arList[1024]; // Address list int m_nListCnt; // Number of valid addresses HANDLE m_hProcess; // Target process handle BOOL m_bFirst; // Is this your first search }; CMemFinder::CMemFinder(DWORD dwProcessId) { m_nListCnt = 0; m_bFirst = TRUE; m_hProcess = ::OpenProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, FALSE, dwProcessId); } CMemFinder::~CMemFinder() { if(m_hProcess != NULL) ::CloseHandle(m_hProcess); } BOOL CMemFinder::FindFirst(DWORD dwValue) { const DWORD dwOneGB = 1024*1024*1024; // 1GB const DWORD dwOnePage = 4*1024; // 4KB if(m_hProcess == NULL) return FALSE; // View the operating system type to determine the start address DWORD dwBase; OSVERSIONINFO vi = { sizeof(vi) }; ::GetVersionEx(&vi); if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) dwBase = 4*1024*1024; // Windows 98 series, 4MB else dwBase = 640*1024; // Windows NT Series, 64KB // Search in the address space from the start address to 2GB for(; dwBase < 2*dwOneGB; dwBase += dwOnePage) { // Compare 1 page of memory CompareAPage(dwBase, dwValue); } m_bFirst = FALSE; return TRUE; } BOOL CMemFinder::CompareAPage(DWORD dwBaseAddr, DWORD dwValue) { // Read 1 page of memory BYTE arBytes[4096]; if(!::ReadProcessMemory(m_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL)) return FALSE; // This page is unreadable // Find in this 1 page of memory DWORD* pdw; for(int i=0; i<(int)4*1024-3; i++) { pdw = (DWORD*)&arBytes[i]; if(pdw[0] == dwValue) // Equal to the value to find? { if(m_nListCnt >= 1024) return FALSE; // Add to global variable m_arList[m_nListCnt++] = dwBaseAddr + i; } } return TRUE; } BOOL CMemFinder::FindNext(DWORD dwValue) { // Save M_ The number of valid addresses in the arlist array, initializing a new m_nListCnt value int nOrgCnt = m_nListCnt; m_nListCnt = 0; // In M_ The arlist array looks at the address of the record BOOL bRet = FALSE; // Hypothetical failure DWORD dwReadValue; for(int i=0; i<nOrgCnt; i++) { if(::ReadProcessMemory(m_hProcess, (LPVOID)m_arList[i], &dwReadValue, sizeof(DWORD), NULL)) { if(dwReadValue == dwValue) { m_arList[m_nListCnt++] = m_arList[i]; bRet = TRUE; } } } return bRet; } BOOL CMemFinder::WriteMemory(DWORD dwAddr, DWORD dwValue) { return ::WriteProcessMemory(m_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL); } #endif // __MEMFINDER_H__
02MemRepair.cpp
/// // 02MemRepair.cpp file #include "stdafx.h" #include "windows.h" #include "stdio.h" #include <iostream> BOOL FindFirst(DWORD dwValue); // Make the first lookup in the target process space BOOL FindNext(DWORD dwValue); // Perform the 2nd, 3rd, 4th... Lookup in the address space of the target process DWORD g_arList[1024]; // Address list int g_nListCnt; // Number of valid addresses HANDLE g_hProcess; // Target process handle // BOOL WriteMemory(DWORD dwAddr, DWORD dwValue); void ShowList(); int main(int argc, char* argv[]) { // Start the 02tester process char szFileName[] = "C:\\Users\\Freddy\\source\\repos\\02testor\\Debug\\02testor.exe"; STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; ::CreateProcess(NULL, szFileName, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); // Close the thread handle, since we don't use it ::CloseHandle(pi.hThread); g_hProcess = pi.hProcess; // Enter the value to modify int iVal; printf(" Input val = "); scanf_s("%d", &iVal); // Make the first find FindFirst(iVal); // Print out search results ShowList(); while(g_nListCnt > 1) { printf(" Input val = "); scanf_s("%d", &iVal); // Next search FindNext(iVal); // Display search results ShowList(); } // Get new value printf(" New value = "); scanf_s("%d", &iVal); // Write new value if(WriteMemory(g_arList[0], iVal)) printf(" Write data success \n"); ::CloseHandle(g_hProcess); return 0; } BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue) { // Read 1 page of memory BYTE arBytes[4096]; if(!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL)) return FALSE; // This page is unreadable // Find in this 1 page of memory DWORD* pdw; for(int i=0; i<(int)4*1024-3; i++) { pdw = (DWORD*)&arBytes[i]; if(pdw[0] == dwValue) // Equal to the value to find? { if(g_nListCnt >= 1024) return FALSE; // Add to global variable g_arList[g_nListCnt++] = dwBaseAddr + i; } } return TRUE; } BOOL FindFirst(DWORD dwValue) { const DWORD dwOneGB = 1024*1024*1024; // 1GB const DWORD dwOnePage = 4*1024; // 4KB if(g_hProcess == NULL) return FALSE; // View the operating system type to determine the start address DWORD dwBase; OSVERSIONINFO vi = { sizeof(vi) }; ::GetVersionEx(&vi); if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) dwBase = 4*1024*1024; // Windows 98 series, 4MB else dwBase = 640*1024; // Windows NT Series, 64KB // Search in the address space from the start address to 2GB for(; dwBase < 2*dwOneGB; dwBase += dwOnePage) { // Compare 1 page of memory CompareAPage(dwBase, dwValue); } return TRUE; } BOOL FindNext(DWORD dwValue) { // Save M_ The number of valid addresses in the arlist array, initializing a new m_nListCnt value int nOrgCnt = g_nListCnt; g_nListCnt = 0; // In M_ The arlist array looks at the address of the record BOOL bRet = FALSE; // Hypothetical failure DWORD dwReadValue; for(int i=0; i<nOrgCnt; i++) { if(::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL)) { if(dwReadValue == dwValue) { g_arList[g_nListCnt++] = g_arList[i]; bRet = TRUE; } } } return bRet; } // Print out the searched address void ShowList() { for(int i=0; i< g_nListCnt; i++) { printf("%08lX \n", g_arList[i]); } } BOOL WriteMemory(DWORD dwAddr, DWORD dwValue) { return ::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL); }
extract interface
The above implements the core function of modifying the game memory. In order to enable readers to directly use the code in this section in the actual development process, this book encapsulates the function of searching memory in a CMemFinder class. The following is the definition of this class.
class CMemFinder { public: CMemFinder(DWORD dwProcessId); virtual ~CMemFinder(); // attribute public: BOOL IsFirst() const { return m_bFirst; } BOOL IsValid() const { return m_hProcess != NULL; } int GetListCount() const { return m_nListCnt; } DWORD operator [](int nIndex) { return m_arList[nIndex]; } // operation virtual BOOL FindFirst(DWORD dwValue); virtual BOOL FindNext(DWORD dwValue); virtual BOOL WriteMemory(DWORD dwAddr, DWORD dwValue); // realization protected: virtual BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue); DWORD m_arList[1024]; // Address list int m_nListCnt; // Number of valid addresses HANDLE m_hProcess; // Target process handle BOOL m_bFirst; // Is this your first search };
Because the implementation code of the class is similar to the code described above, it is not listed here. If there is anything unclear, please check the supporting CD of this book (MemRepair project).
This class provides friendly interface members, which can also be overloaded to achieve special functions. The MemRepair instance of the CD-ROM in this book realizes most of the functions of modifying the game memory, and eliminates the thread blocking problem encountered in the process of searching the memory for a long time by overloading the CMemFinder class. If the current development project uses the function of searching memory, you can refer to the source code of this example.