Explanation of “How to Hack Any Game Tutorial C++ Trainer #1”
Explanation of “How to Hack Any Game Tutorial C++ Trainer #1”
Recently, I have started to get interested in game hacking. Thanks to knowledge in malware analysis, it was easier to understand the basics. Indeed in both fields you have to investigate and reverse engineer piece of code.
In this post, I’m going to explain, as clear as possible, functions used in the video made by Guided Hacking.
As described in the video. An header has to be created for various useful functions explained further. Also <window.h> and <TlHelp32.h> dll headers are included to manipulate memory, process, windows…
#pragma once #include <vector> #include <Windows.h> #include <TlHelp32.h>DWORD GetProcId(const wchar_t* procName);
uintptrt GetModuleBaseAddress(DWORD procId, const wchart* modname);
uintptrt FindDMAAddy(HANDLE hProc, uintptrt ptr, std::vector<unsigned int>);
Firstly in order to make a trainer for a game we must find the process Id of the targeted game.
GetProcId() function
DWORD GetProcId(const wchar_t* procName) { DWORD procId = 0; HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnap != INVALID_HANDLE_VALUE) { PROCESSENTRY32 procEntry; procEntry.dwSize = sizeof(procEntry);if (Process32First(hSnap, &amp;procEntry)) { do { if (!_wcsicmp(procEntry.szExeFile, procName)) { procId = procEntry.th32ProcessID; } } while (Process32Next(hSnap, &amp;procEntry) &amp;&amp; procId == 0); } } CloseHandle(hSnap); return procId;
}
DWORD is a 32-bit unsigned integer and wchar_t represents a 16-bit wide character string. They aren’t C++ type but a type implemented in <windows.h> because Windows uses UTF16-LE format.
So GetProcId return a DWORD (holding the process id) and take a wide string literal as parameter such as :
GetProcId(L"ac_client.exe");
Here, L is an encoding prefix meaning L is coded in 2 bytes rather than 1 byte. For example the size of a classic char on GNU Linux or Mac OS is 1 byte. Microsoft has just rewriten the char struc available in C for these needs.
'C' = 0x43 // type: char L'C' = 0x0043 // type: wchar_t
According to the MSDN : CreateToolhelp32Snapshot() “takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes”.
We don’t care about taking snapshot of modules, threads or the heaplist. Only the processes loaded are interesting. Therefore the TH32CS_SNAPPROCESS value is set for the dwFlags parameter and whatever you set for the second one is fine. The second parameter th32ProcessId is useful if you have to specify a process identifier (e.g typically when you work on module of a specified process).
The TH32CS_SNAPPROCESS value is specified : all processes are going to be fetched so the second parameter is ignored. A HANDLE is returned and “stored” into the hSnap variable.
The first “if” is pretty clear, its goal is to avoid crashes in case the previous function fails. Let’s focus more on declaration before the loop that compares process names taken in the snapshot.
PROCESSENTRY32 which “Describes an entry from a list of the processes residing in the system address space when a snapshot was taken” and one of his argument dwSize must be defined before calling the Process32First function.
Well, dwSize is the size of the PROCESSENTRY32 structure and If you do not initialize dwSize, Process32First fails. At this point we need the Process32First to fetch the first process of the list then Process32Next for the next.
As i said earlier, the objective is to compare string of each process with another one. For that the function _wcsicmp will performs a comparison of wchar_t strings. If strings are identical the result is 0 and the do-while loop breaked. So we have to prefix it with a ! for the “if” to be passed through.
Afterwards procId has a value and can be returned. By the way as handle occupy both kernel and userspace memory you should close them as we did.
GetModuleBaseAddress function is really similar to the previous one. It is worthless to explain further. Information are available on this link if you don’t understand what is a module base address.
FindDMAAddy() function
If you have done first tutorials of CheatEngine, you may know what is multilevel pointer. FINDMAAddy() is a function for calculating multilevel pointers.
A pointer points to only one address and sometimes you have chain of pointers. As memory is dynamicaly allocated you have to find the end address of the pointer for knowing “the path”.
The baseAddress alias first pointer in the pointer chain is generally always located at the same address or can be accessed using a relative offset from the base address of a module.
For a better explanation check this link from GuidedHacking.
Main class
Now we can call those function in our main and create a HANDLE to open the process :
//get ProcId of the target proc DWORD procId = GetProcId(L"ac_client.exe"); //getModuleBaseAddress uintptr_t moduleBase = GetModuleBaseAddress(procId,L"ac_client.exe"); printf("ModuleBaseAddr= 0x%p \n", moduleBase); //GetHandle to the process HANDLE hProcess = 0; hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, procId);
For the OpenProcess function, the first argument is an accessibility value. You could use PROCESS_VM_READ and PROCESS_VM_WRITE instead of PROCESS_ALL_ACCESS. The process do not inherit a handle thus this parameter is set to NULL.
Then we have to resolve the module base address that can be dynamic (but not for assault cube).
Once it is done we have to reproduce mechanism used in CheatEngine by specifying the pointer paths.
//Resolve base address of the pointer chain uintptr_t dynamicPtrBaseAddr = moduleBase + 0x10f4f4;std::cout << "DynamicPtrBaseAddr = " << "0x" << std::hex << dynamicPtrBaseAddr << std::endl; //Resolve our ammo pointer chain std::vector<unsigned int> ammoOffsets = { 0x374, 0x14, 0x0 }; uintptr_t ammoAddr = FindDMAAddy(hProcess, dynamicPtrBaseAddr, ammoOffsets); std::cout << "ammoAddr = " << "0x" << std::hex << ammoAddr << std::endl; //Read Ammo value int ammoValue = 0; ReadProcessMemory(hProcess, (BYTE*)ammoAddr, &amp;ammoValue, sizeof(ammoValue), nullptr); std::cout << "Curent ammo = " << std::dec << ammoValue << std::endl; //Write to it int newAmmo = 1337; WriteProcessMemory(hProcess, (BYTE*)ammoAddr, &amp;newAmmo, sizeof(newAmmo), nullptr); //Read out again ReadProcessMemory(hProcess, (BYTE*)ammoAddr, &amp;ammoValue, sizeof(ammoValue), nullptr); std::cout << "New ammo = " << std::dec << ammoValue << std::endl; getchar(); return 0;
The ammoValue of the current weapon is read with ReadProcessMemory and will be modified later by 1337 in the process memory.
ReadProcessMemory(hProcess, (BYTE*)ammoAddr, &ammoValue, sizeof(ammoValue), nullptr);
hProcess is the handle of the process, ammoAddr is a pointer to the base address from which to read, it has to be casted in BYTE because it is a uintptr_t. The third one is the address where the read value is located. The fourth one is the number of bytes to read. The last is only worth in case you want to know the number of bytes transferred into the ammoValue pointer.
WriteProcessMemory is similar except that it writes.
Results
The following window appears when you execute the code.
And the current ammo value is modified ;).