* We all know that Cheat Engine is a type of program known as a memory scanner. * Memory scanners allow you to search for and edit memory inside a process. ## Understand We will scan all memory from 0x00000000 to 0x7FFFFFFF. This range of addresses represents all the virtual address space that a 32-bit Windows executable has access to. When scanning, we will save any address that is set to a certain value. To filter these addresses, we will perform the same scan operation decribed above with one major different: instead of scanning from 0x00000000 to 0x7FFFFFFF, we will only scan saved addresses identified from the previous scan step. Any addresses that still match a provided value will again be saved. In this way, we can countinue filter down the list of valid addresses. Finally, to write to an address, we can use **WriteProcessMemory** technique. ## Program Structure Before we write our program, we need to determine how we will handle the multiple operations and passing data from one operation to another. Since we have three distinct operations for our memory scanner to perform, we need to determine how to handle these cases. One a approach is to create a separate program for each operation and then transfer data between the three programs. However, this approach would require us to duplicate logic between multiple programs, such as the logic to open a process handle. We will use the command-line arguments to designate which operation we want to perform. Since we need to call our scanner multiple times, we need a way to pass results from one operation to the next. The easiest way to accomplish this is to use a file. When we filter, addresses will be read from this file, and then new addresses are placed in the file if they still match. ## Process Handle To read and write memory, we need a process handle ``` #include <windows.h> #include <tlhelp32.h> #include <stdio.h> int main(int argc, char** argv) { HANDLE process_snapshot = 0; PROCESSENTRY32 pe32 = { 0 }; pe32.dwSize = sizeof(PROCESSENTRY32); process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); Process32First(process_snapshot, &pe32); do { if (wcscmp(pe32.szExeFile, L"test.exe") == 0) { HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, true, pe32.th32ProcessID); // handle operations CloseHandle(process); break; } } while (Process32Next(process_snapshot, &pe32)); return 0; } ``` We will pass this process handle to all of our operations. ## Operations Next, we can add in our operations. To access command-line arguments passed to our program, we can use the **agrv** argument. **argv[0]** will always hold our program's name on the command-line **(MemoryScanner.exe)**, with **argv[1]** representing the first argument. All arguments are passed in as a strings. We want our program to search for DWORD values. To convert from a string to a value that we can use to search for **DWORD**'s, we will use **strtol** (string to long): ``` char* p; long value = strtol(argv[2], &p, 10); if(strcmp(argv[1], "search") == 0){ search(process, value); } else if(strcmp(argv[1], "filter") == 0){ filter(process, value); } else if(strcmp(argv[1], "write") == 0){ write(process, value); } ``` ### Search We will start with our search function: ``` void search(const HANDLE process, const int passed_val){ } ``` Like we discussed before, we will store the results of the search in a text file. Using **fopen_s** to create a text file we can write to: ``` FILE* temp_file = NULL; fopen_s(&temp_file, "res.txt", "w"); ``` As we know, memory does not have a particular structure. For example, the memory from 0x12345678 to 0x1234567C could hold the values 0x44 0x45 0x41 0x44. If read as a DWORD, this memory would hold the value 1145389381. However, if each byte is read as a char, this memory would hold the value DEAD. In this post, we will scan all memory as if it was a DWORD. This will allow us to search for values that are numbers. Our search operation will scan all memory from 0x00000000 to 0x7FFFFFFF and compare each 4 bytes to the value passed in the second argument. Previously, we used ReadProcessMemory to read a single 4-byte DWORD. However, ReadProcessMemory allows us to *read any size of memory* into *any type of allocated buffer*. While Wesnoth can use all memory from 0x00000000 to 0x7FFFFFFF, it first needs to request access via several API's, like VirtualAlloc. If Wesnoth has not requested access to a certain piece of memory, it will not be able to read or write data to it. We are using Wesnoth's handle to read memory, so I will need to account for this behavior. If we try to read all memory from 0x00000000 to 0x7FFFFFFF with one ReadProcessMemory call, the call will fail. This is because ReadProcessMemory's behavior is to immediately fail and place a NULL value in our buffer if we encounter a section of memory we do not have access to. As a result, we will need to split out read requests up into blocks. That way, if we attempt to scan a block that Wesnoth has not allcated, only that block's read will fail. We can choose any value for our block size, but there is a trade-off between speed and accuracy. The larger each block is, the faster the scan process will take, but more areas of memory may not be read successfully due to part of the block being inaccessible. I will choose a block size of 2056, or 0x808: ``` #define size 0x00000808 ``` We will then allocate a buffer that can hold a block-size worth of data: ``` unsigned char* buff = (unsigned char*)calloc(1,size); ``` Next, we will loop through each block of memory from 0x00000000 to 0x7FFFFFFF and reead that block into the buffer: ``` DWORD bytes_read = 0; for(DWORD i = 0x00000000; i < 0x7FFFFFFF; i += size){ ReadProcessMemory(process, (void*)i, buffer, size, &bytes_read); } ``` Finally, we will cast each 4 bytes of our buffer as a DWORD and determine if its value equals ther argument passed. If so, we will write its location to our results file: ``` for(int j = 0; j < size - 4; j += 4){ DWORD val = 0; memcpy(&val, &buffer[j], 4); if(val == passed_val){ fprintf(temp_file, "%x\n", i+j); } } ``` If a read fails, our buffer will contain nothing but 0's and this final step will find nothing. After we finish with our ReadProcessMemory loop, we will close the file and free the buffer's memory: ``` fclose(temp_file); free(buffer); ``` ### Filtering The next operation I will focus on is filtering. The filtering operation will take a list of addresses produced by the search operation and check to see if those addresses equal a new value. If the address does equal the value, it will be saved. If it does not, it will be deleted. ``` void filter(const HANDLE process, const int passed_val){ } ``` We will conduct the filtering operation in two parts: * Read each memory address from *res.txt* and if it matches the new value, save it to *res_fil.txt*. * Copy *res_fil.txt* to *res.txt* and then delete *res_fil.txt*. The end result will be a new *res.txt* file that contains only the filtered addresses. This model will allow us to filter multiple times. First, we will open *res.txt* for reading (r) and *res_fil.txt* for writing (w): ``` FILE* temp_file = NULL; FILE* temp_file_filter = NULL; fopen_s(&temp_file, "res.txt", r) fopen_s(&temp_file_filter, "res_fil.txt", w); ``` We will then read each address from *res.txt* line by line and read Wesnoth's memory at that address. If the value matches our argument, we will write the address to res_fil.txt: ``` DWORD address = 0; while(fscanf_f(temp_file, "%x\n", &address) != EOF){ DWORD val = 0 DWORD bytes_read = 0; ReadProcessMemory(process, (void*)address, &val, 4,&bytes_read); if(val == passed_val){ fprintf(temp_file_filter, "%x\n", address); } } ``` With all the filtered addresses in *res_fil.txt*, we will then close both res.txt and res_fil.txt. Then, we will open up these files in the opposite order from above, with res.txt for writing and res_fil.txt for reading: ``` fclose(temp_file); fclose(temp_file_filter); fopen_s(&temp_file, "res.txt", "w"); fopen_s(&temp_file_filter, "res_fil.txt", "r"); ``` Next, we will loop through each address in res_fil.txt and copy it tores.txt: ``` while(fscanf_s(temp_file_filter, "%x\n", &address) != EOF){ fprintf_s(temp_file, "%x\n", address); } ``` With res.txt now containing our addresses, we will close each file and delete res_fil.txt: ``` fclose(temp_file); fclose(temp_file_filter); remove("res_fil.txt"); ``` ### Writing ``` void write(const HANDLE process, const int passed_val) { FILE* temp_file = NULL; fopen_s(&temp_file, "res.txt", "r"); DWORD address = 0; while (fscanf_s(temp_file, "%x\n", &address) != EOF) { DWORD bytes_written = 0; WriteProcessMemory(process, (void*)address, &passed_val, 4, &bytes_written); } fclose(temp_file); } ```