【Game reverse engineering】Common methods of locking the target process to share

After we have analyzed the game logic and collected enough game data, we can start to develop our own auxiliary tool. The first step in developing assistance is to find the game process first. After locking the game process, the next step is to consider whether the implementation of auxiliary functions is to choose to change data, change code, or call the function. Locking the target process can be divided into three categories in terms of implementation: 1. Lock the target process according to the process name or process path; 2. Lock the target process according to the window name; target process.

The so-called locking the target process according to the process name is straightforward. It is to first enumerate the system process to get all the process information running in the current system, and then compare the process name or process path to determine whether it is the target process. Therefore, the whole process is divided into two steps: 1. Enumerate system processes; 2. Determine whether it is a target process according to the process name or process path information. There are several ways to realize these two processes, and one of them can be selected in any combination to achieve the goal.

There are several ways to enumerate system processes: 1. Use API EnumProcesses provided by Psapi; 2. Use API Process32First&Process32Next provided by TlHelp32; 3. Use native API NtQuerySystemInformation. 4. Use the windows terminal service function WTSEnumerateProcessesA. Among them, the first two methods are recommended by Microsoft, and these API interfaces will generally not change as the operating system is continuously updated. The third type is native API. For native API, Microsoft does not directly provide external interfaces and does not guarantee that all operating system versions will provide similar interfaces. Before introducing it in detail, we will introduce the use of key functions.

1. Implement process enumeration based on PSAPI to find the target process

The process search based on PSAPI is mainly to use EnumProcess to obtain the process ID array of the current system, and then use the process ID or process path or process name information to lock the target by comparing the process path or name information with the relevant information of the specified target process process.

Introduction to key functions:

BOOL WINAPI EnumProcesses(

__out DWORD* pProcessIds,

__in DWORD cb,

__out DWORD* pBytesReturned

);

EnumProcesses is used to obtain an array of currently running process IDs at one time. The parameter pProcessIds is a DWORD array pointer, pointing to a data used to store the process ID. cb is used to inform the operating system of the memory size of the pProcessIds process ID array buffer passed in earlier. pBytesReturned is used to return the actual buffer size required to store the process array. If the value of cb is not enough to store all process ID information, the function scope is FALSE, and then the actual memory required is notified to the caller through pBytesReturned.

HANDLE WINAPI OpenProcess(

__in DWORD dwDesiredAccess,

__in BOOL bInheritHandle,

__in DWORD dwProcessId

);

The OpenProcess function obtains a process handle based on the process ID. The process handle is an identity assigned to the process object by the operating system, and is the only way to operate the process, such as reading and writing the process memory, obtaining process information, and so on. dwDesiredAccess is used to set which permissions of the target process you want to obtain, so which operation permissions the returned handle information has. If you don’t know what permissions to fill in, you can apply for all permissions PROCESS_ALL_ACCESS. bInheritHandle is used to identify whether the handle wants to be inherited by the child process, but it is directly assigned a value of FALSE regardless of the inheritance permissions of the child process. dwProcessId is used to inform the process ID of the open process.

DWORD WINAPI GetProcessImageFileName(

__in HANDLE hProcess,

__out LPTSTR lpImageFileName,

__in DWORD nSize

);

The GetProcessImageFileName function is used to obtain the complete path information of the hProcess process, which is stored in the buffer pointed to by lpImageFileName, and nSize indicates the buffer size of lpImageFileName. This function has been provided in vista and later operating systems, but the XP system and previous systems do not support the call of this function. If it needs to be used on xp and previous operating systems, it can be replaced by the GetModuleFileNameEx function.

DWORD WINAPI GetModuleFileNameEx(

__in HANDLE hProcess,

__in HMODULE hModule,

__out LPTSTR lpFilename,

__in DWORD nSize

);

The GetModuleFileNameEx function is used to obtain a complete path of the hModule module that refers to the hProcess program. The obtained path is placed in lpFilename, and nSize indicates the buffer size of lpFilename. If hModule is NULL, the function returns the complete path of the main module of the process. At this time, the functions of GetModuleFileNameEx and GetProcessImageFileName are exactly the same.

DWORD WINAPI GetModuleBaseName(

__in HANDLE hProcess,

__in HMODULE hModule,

__out LPTSTR lpBaseName,

__in DWORD nSize

);

The GetModuleBaseName function is used to obtain the base name of the process, which is the short name. The number and usage of its parameters are basically the same as those of GetModuleFileNameEx and will not be repeated here.

BOOL WINAPI CloseHandle(

__in HANDLE hObject

);

The CloseHandle function is used to release various handle resources previously allocated. Generally, we check the description of the api in MSDN before using the api. MSDN generally contains the following items: Parameters\Return Value\Remarks\Example Code. Taking OpenProcess as an example, there is such a sentence in the Remarks item: When you are finished with the handle, be sure to close it using the CloseHandle function. This sentence means that you need to use CloseHandle when the process handle is no longer used to close the handle.

Related source code sharing:

//The role of the function is to obtain the process path information in 3 ways

VOID PrintProcessNameAndID(DWORD processID)

{

char szProcessName[1024] = { 0 };

char szOutputText[1024] = { 0 };

// Get a handle to the process.

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,

FALSE, processID);

if (hProcess == NULL)

{

wsprintfA(szOutputText, “open process Id %d error, error id:%d”, processID, GetLastError());

OutputDebugStringA(szOutputText);

return;// open process error

}

// Get the process name.

GetProcessImageFileName(hProcess, szProcessName, 1024);

wsprintfA(szOutputText, “get path by GetProcessImageFileName %s”, szProcessName);

OutputDebugStringA(szOutputText);

GetModuleFileNameEx(hProcess, NULL, szProcessName, 1024);

wsprintfA(szOutputText, “get path by GetModuleFileNameEx %s”, szProcessName);

OutputDebugStringA(szOutputText);

GetModuleBaseName(hProcess, NULL, szProcessName, 1024);

wsprintfA(szOutputText, “get path by GetModuleBaseName %s”, szProcessName);

OutputDebugStringA(szOutputText);

CloseHandle(hProcess);

}

//The function uses EnumProcess to find the process whose process is szTargetProcess

DWORD FindProcessByEnumProcess(char *szTargetProcess)

{

DWORD dwTargetProcessId = 0;

DWORD pdwProcesses[1024] = { 0 };

DWORD dwNeeded = 0;

if (!EnumProcesses(pdwProcesses, sizeof(pdwProcesses), &dwNeeded))

return dwTargetProcessId;//call EnumProcesses error

DWORD dwProcessNumber = dwNeeded / sizeof(DWORD);

for (unsigned int i = 0; i < dwProcessNumber; i++)

{

if (pdwProcesses[i] != 0)

{

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,

FALSE, pdwProcesses[i]);

if (hProcess != NULL)

{

char szProcessName[1024] = { 0 };

GetModuleFileNameEx(hProcess, NULL, szProcessName, 1024);

_strlwr_s(szProcessName, sizeof(szProcessName));

if (strstr(szProcessName, szTargetProcess) != NULL)

{

dwTargetProcessId = pdwProcesses[i];

}

CloseHandle(hProcess);

hProcess = NULL;

}

}

}

return dwTargetProcessId;

}

2. Use Process32First&Process32Next to enumerate the process and lock the target process

Process snapshots are a series of APIs declared in the tlhelp32.h file, mainly through CreateToolhelp32Snapshot to create a process snapshot, and then traverse the current system process through Process32First and Process32Next. Process information is stored in a PROCESSENTRY32 structure. The PROCESSENTRY32 structure is defined as follows:

typedef struct tagPROCESSENTRY32 {

DWORD dwSize;

DWORD cntUsage;

DWORD th32ProcessID;

DWORD th32DefaultHeapID;

DWORD th32ModuleID;

DWORD cntThreads;

DWORD th32ParentProcessID;

LONG pcPriClassBase;

DWORD dwFlags;

TCHAR szExeFile[MAX_PATH];

DWORD th32MemoryBase;

DWORD th32AccessKey;

} PROCESSENTRY32;

th32ProcessID is the process ID information, and szExeFile is the process path information. Compared with the PSAPI series of APIs, the processes enumerated in the snapshot mode can directly obtain the path information, so there is no need to call other APIs again to obtain the process path information. It should be noted that after using the snapshot handle, you need to call CloseToolhelp32Snapshot to close the snapshot handle instead of CloseHandle to close the snapshot handle.

Introduction to key functions:

HANDLE WINAPI CreateToolhelp32Snapshot(

DWORD dwFlags,

DWORD th32ProcessID

);

The CreateToolhelp32Snapshot function is used to create a snapshot, and dwFlags is used to indicate the snapshot type. When the value of dwFlags is equal to TH32CS_SNAPPROCESS, it is used to create a process snapshot, and the value of th32ProcessID will be ignored in the process snapshot.

BOOL WINAPI Process32First(

HANDLE hSnapshot,

LPPROCESSENTRY32 lppe

);

BOOL WINAPI Process32Next(

HANDLE hSnapshot,

LPPROCESSENTRY32 lppe

);

Process32First and Process32Next are used together. hSnapshot passes in the process snapshot handle, and lppe returns the relevant information of the process. The first process information in the snapshot is obtained with Process32First, and the subsequent process information is obtained with Process32Next. The Process32Next function returns FALSE when the snapshot traversal is complete.

Related source code sharing:

//The role of the function is to find the process whose process is szTargetProcess through the snapshot method

DWORD FindProcessBySnapshot(char *szTargetProcess)

{

char szOutputText[1024] = { 0 };

DWORD dwTargetProcessId = 0;

HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

if (INVALID_HANDLE_VALUE == hProcessSnapshot)

{

wsprintfA(szOutputText, “call CreateToolhelp32Snapshot error, error id:%d”, GetLastError());

OutputDebugStringA(szOutputText);

return dwTargetProcessId;

}

PROCESSENTRY32 stProcessEntry = { 0 };

stProcessEntry.dwSize = sizeof(stProcessEntry);

if (Process32First(hProcessSnapshot, &stProcessEntry) == FALSE)

{

wsprintfA(szOutputText, “call Process32First error, error id:%d”, GetLastError());

OutputDebugStringA(szOutputText);

CloseHandle(hProcessSnapshot);

return dwTargetProcessId;

}

do

{

wsprintfA(szOutputText, “process id:%d %s”, stProcessEntry.th32ProcessID, stProcessEntry.szExeFile);

OutputDebugStringA(szOutputText);

if (_stricmp(stProcessEntry.szExeFile, szTargetProcess) == 0)

{

dwTargetProcessId = stProcessEntry.th32ProcessID;

}

memset(&stProcessEntry, 0, sizeof(stProcessEntry));

stProcessEntry.dwSize = sizeof(stProcessEntry);

} while (Process32Next(hProcessSnapshot, &stProcessEntry));

CloseHandle(hProcessSnapshot);

return dwTargetProcessId;

}

3. Use NtQuerySystemInformation to enumerate the process and lock the target process

The main function of the NtQuerySystemInformation(ZwQuerySystemInformation) function is to obtain or set system information, which is declared as follows:

typedef NTSTATUS (__stdcall *NTQUERYSYSTEMINFORMATION)
(IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL);

It can be seen from the statement that the parameter SystemInformationClass is a type of information, which can view or set more than 50 kinds of system information. The parameter SystemInformation is the buffer for receiving information. You can obtain process information by setting the SystemInformationClass value to NT_PROCESSTHREAD_INFO. The obtained process structure is as follows:

typedef struct _SYSTEM_PROCESSES

{

ULONG NextEntryDelta;

HEAD ThreadCount;

HEAD Reserved1[6];

LARGE_INTEGER CreateTime;

LARGE_INTEGER UserTime;

LARGE_INTEGER KernelTime;

UNICODE_STRING ProcessName;

KPRIORITY BasePriority;

HEAD ProcessId;

HEAD InheritedFromProcessId;

HEAD HandleCount;

HEAD Reserved2[2];

VM_COUNTERS VmCounters;

IO_COUNTERS IoCounters;

SYSTEM_THREADS Threads[1];

}SYSTEM_PROCESSES,*PSYSTEM_PROCESSES;

It should be noted here that the obtained process name is UNICODE_STRING, and its structure is defined as follows:

typedef struct _LSA_UNICODE_STRING

{

USHORT Length;

USHORT MaximumLength;

PWSTR Buffer;

}LSA_UNICODE_STRING,*PLSA_UNICODE_STRING;

typedef LSA_UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING;

It needs to be converted to an ASCII string, using the WideCharToMultiByte function.

Related source code sharing:

//The function uses the NtQuerySytemInfo method to find the process whose process name is szTargetProcess

int FindProcessByNtQuery(char *szTargetProcess)

{

HINSTANCE hModule = NULL;

DWORD dwTotalProcess = 0;

DWORD dwReturnLength = 0;

NTSTATUS Status = 0;

PSYSTEM_PROCESSES pstSystemProc = NULL;

pNtQuerySystemInformation pfNtQuerySystemInformation = NULL;

DWORD dwNumberBytes = MAX_INFO_BUF_LEN;

hModule = GetModuleHandleA(“ntdll.dll”);

if (NULL == hModule)

{

return -1;

}

pfNtQuerySystemInformation = (pNtQuerySystemInformation)GetProcAddress(hModule, (LPCSTR)“ZwQuerySystemInformation”);

if (NULL == pfNtQuerySystemInformation)

{

return -1;

}

void *lpSystemInfo = (LPVOID)malloc(dwNumberBytes);

if (!lpSystemInfo)

{

return -1;

}

Status = pfNtQuerySystemInformation(NT_PROCESSTHREAD_INFO, lpSystemInfo, dwNumberBytes, &dwReturnLength);

if (Status == STATUS_INFO_LENGTH_MISMATCH)

{

printf(“STATUS_INFO_LENGTH_MISMATCH\n”);

return -1;

}

else if (Status != STATUS_SUCCESS)

{

printf(“NtQuerySystemInformation Error: %d\n”, GetLastError());

return -1;

}

pstSystemProc = (PSYSTEM_PROCESSES)lpSystemInfo;

while (pstSystemProc->NextEntryDelta != 0)

{

char pszProcessName = (char)malloc(pstSystemProc->ProcessName.Length + 1);

if (!pszProcessName)

{

return -1;

}

WideCharToMultiByte(CP_ACP,

0,

pstSystemProc->ProcessName.Buffer,

pstSystemProc->ProcessName.Length + 1,

pszProcessName,

pstSystemProc->ProcessName.Length + 1,

NULL,

NULL);

if (!strcmp(pszProcessName, szTargetProcess))

{

return pstSystemProc->ProcessId;

}

pstSystemProc = (PSYSTEM_PROCESSES)((char *)pstSystemProc + pstSystemProc->NextEntryDelta);

}

return -1;

}

4. Use the windows terminal service function WTSEnumerateProcessesA to enumerate the process and lock the target process

The functions related to Windows Terminal Services all start with WTS. The WTSOpenServer function can open the access link of a remote machine. The WTSEnumerateProcesses function can enumerate all the processes on the specified Server. The function declaration is as follows:

typedef BOOL (_stdcall WTSENUMERATEPROCESSES)(
HANDLE hServer, //The handle returned by WTSOpenServer
DWORD Reserved, //Reserved value, 0
DWORD Version, //Specify the version required by the enumeration, must be 1
PWTS_PROCESS_INFO
ppProcessInfo, //This parameter is the key, Store the process name and process id we want
DWORD* pCount // used to store the number pointer of the WTS_PROCESS_INFO structure in ppProcessInfo);

When the hServer value of the first parameter of the function is specified as WTS_CURRENT_SERVER_HANDLE, it means to enumerate the processes on the local machine.

Related source code sharing:

int FindProcessByWtsEnumerate(char *szTargetProcess)

{

HINSTANCE hWtsApi32 = LoadLibraryA(“wtsapi32.dll”);

if (NULL == hWtsApi32)

{

return -1;

}

WTSENUMERATEPROCESSES pWtsEnumerateProcesses = (WTSENUMERATEPROCESSES)GetProcAddress(hWtsApi32, “WTSEnumerateProcessesA”);

if (NULL == pWtsEnumerateProcesses)

{

return -1;

}

PWTS_PROCESS_INFOA pWtspi = {0};

DWORD dwCount = 0;

if (!pWtsEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pWtspi, &dwCount))

{

return -1;

}

for (int i = 0; i < dwCount; i++)

{

if (!strcmp(pWtspi[i].pProcessName, szTargetProcess))

{

return pWtspi[i].ProcessId;

}

}

return -1;

}

Five, use FindWindow\EnumWindow to find the target process

The operating system provides an api GetWindowThreadProcessId, which can be used to obtain the thread and process information of the window, so if the target process has a window, the window can be used to find the target process. The way to get the window handle is generally FindWindow and EnumWindows.

Introduction to key functions:

HWND FindWindow(

LPCTSTR lpClassName,

LPCTSTR lpWindowName

);

The FindWindow function is used to find a specific window whose class name is equal to lpClassName and whose window name is equal to lpWindowName. If the class name and window name are equal to NULL, the filter condition of this item is ignored, and the class name and window name cannot be equal to NULL at the same time, that is to say, at least one parameter is required to be valid. Returns the window handle if a window with the compound condition is found, or NULL otherwise.

BOOL EnumWindows(

WNDENUMPROC lpEnumFunc,

LPARAM lParam

);

The EnumWindows function is used to enumerate all windows in the current system, lParam is a developer-defined value, and lpEnumFunc is a callback function for finding a window. When the system finds a window, it calls the callback function lpEnumFunc once, and the parameters are the found window handle and the lParam value passed in when calling EnumWindows. The prototype of the callback function is as follows:

BOOL CALLBACK EnumWindowsProc(

HWND hwnd,

LPARAM lParam

);

The EnumWindows function can only be used to find the top-level window. If you want to find the finger window, you need to call the function EnumChildWindows.

BOOL EnumChildWindows(

HWND hWndParent,

WNDENUMPROC lpEnumFunc,

LPARAM lParam

);

The EnumChildWindows function is similar to the EnumWindows parameter and has similar functions. There is an additional hWndParent on the parameter, which is used to indicate that the window to be found is a child window of the hWndParent window. If you use the EnumWindows and EnumChildWindows functions to find the window, you need to call GetWindowText in the lpEnumFunc callback function to get the window name, and call GetClassName to get the class name of the window. Then confirm the target window by comparing the window name and class name.

Generally, the window name can be seen directly on the window, but the class name is hidden and needs to be seen with some specific tools, such as Spy ++ and so on.

DWORD GetWindowThreadProcessId(

HWND hWnd,

LPDWORD lpdwProcessId

);

The GetWindowThreadProcessId function is used to obtain the process and thread information of the specified window hWnd, the thread ID is passed by the return value, and the process ID information is passed by lpdwProcessId.

Key code sharing:

//The role of the function is to find the process where the window szWindowTitle is located by means of FindWindows

DWORD FindProcessByFindWindow(char *szWindowTitle)

{

DWORD dwTargetProcessId = 0;

HWND hTargetWindow = FindWindow(NULL, szWindowTitle);

if (hTargetWindow == NULL)

{

return dwTargetProcessId;

}

GetWindowThreadProcessId(hTargetWindow, &dwTargetProcessId);

return dwTargetProcessId;

}

/**************************************************************/

typedef struct tagWINDOWPROCESSINFO32 {

DWORD dwWindowProcessId;

char szWindowTitle[256];

} WINDOWPROCESSINFO32;

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)

{

WINDOWPROCESSINFO32 *pstWindowProcessInfo = (WINDOWPROCESSINFO32 *)lParam;

char szWindowText[1024] = { 0 };

GetWindowText(hwnd, szWindowText, 1024);

if (_stricmp(szWindowText, pstWindowProcessInfo->szWindowTitle) == 0)

{

DWORD dwProcessId = 0;

GetWindowThreadProcessId(hwnd, &dwProcessId);

pstWindowProcessInfo->dwWindowProcessId = dwProcessId;

return FALSE;

}

else

{

return TRUE;

}

}

//The role of the function is to find the process where the window szWindowTitle is located by means of EnumWindows

DWORD FindProcessByEnumWindow(char *szWindowTitle)

{

WINDOWPROCESSINFO32 stWindowProcessInfo = { 0 };

strncpy_s(stWindowProcessInfo.szWindowTitle, szWindowTitle, sizeof(stWindowProcessInfo.szWindowTitle) - 1);

EnumWindows(EnumWindowsProc, (LPARAM)&stWindowProcessInfo);

return stWindowProcessInfo.dwWindowProcessId;

}

6. Judging the target process after injecting the module into the whole system

Among the many injection methods, some injection methods do not know the target process to be injected during the injection process, as long as the process with compound conditions will be injected. For example, if hook injection is used, the process of the thread with its own message queue will be injected. In this case, we can use the module information to determine whether it is the target process, and if it is the target process, perform corresponding operations (such as changing the game code, calling the game function, etc.). We will introduce the injection methods in detail in the following chapters. Now we will mainly introduce three methods for concentrating and simply determining the target process. GetModuleFileName\GetModuleFileNameEx\GetModuleHandle. Among them, the usage of GetModuleFileName is similar to that of GetModuleFileNameEx. The usage instructions have been introduced before and will not be repeated here. First, go to the EXE path of the current process and then determine whether it is the EXE of the specified process.

HMODULE GetModuleHandle(

LPCTSTR lpModuleName

);

The GetModuleHandle function is used to obtain the handle of the module whose name is lpModuleName. If the module with the compound condition is found, the module handle is returned, and if the module is not found, NULL is returned.

Key code sharing:

//The functions of the following functions are used to determine whether the current process is a process named szTargetProcess, if so, return true, otherwise return false.

BOOL IsTargetProcessByGetModuleHandle(char *szTargetProcess)

{

BOOL bFindProcess = FALSE;

if (GetModuleHandle(szTargetProcess) != NULL)

bFindProcess = TRUE;

return bFindProcess;

}

BOOL IsTargetProcessByGetModuleFileName(char *szTargetProcess)

{

BOOL bFindProcess = FALSE;

char szProcessPath[1024] = { 0 };

GetModuleFileName(NULL, szProcessPath, 1024);

if (strstr(szProcessPath, szTargetProcess) != NULL)

bFindProcess = TRUE;

return bFindProcess;

}

BOOL IsTargetProcessByGetModuleFileNameEx(char *szTargetProcess)

{

BOOL bFindProcess = FALSE;

char szProcessPath[1024] = { 0 };

GetModuleFileNameEx(GetCurrentProcess(), NULL, szProcessPath, 1024);

if (strstr(szProcessPath, szTargetProcess) != NULL)

bFindProcess = TRUE;

return bFindProcess;

}

BOOL IsTargetProcessByGetModuleBaseName(char *szTargetProcess)

{

BOOL bFindProcess = FALSE;

char szProcessPath[1024] = { 0 };

GetModuleBaseName(GetCurrentProcess(), NULL, szProcessPath, 1024);

if (strstr(szProcessPath, szTargetProcess) != NULL)

bFindProcess = TRUE;

return bFindProcess;

}

Guess you like

Origin blog.csdn.net/douluo998/article/details/130037872