Wrote this for someone that was asking for help on Cheat Engine’s forums. This is a very very basic and light-weight memory scanner that will scan for 4 byte values.
/** * Simple Memory Scanner Example * (c) 2014 atom0s [atom0s@live.com] */ #include <Windows.h> #include <string> #include <TlHelp32.h> /** * @brief The target process to scan within. */ #define TARGET_NAME "winmine.exe" /** * @brief Obtains the process id of the given target. * * @return The process id if found, 0 otherwise. */ unsigned int getTargetProcessId() { PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) }; // Obtain a snapshot of the current process list.. auto handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (handle == INVALID_HANDLE_VALUE) return 0; // Obtain the first process.. if (!::Process32First(handle, &pe32)) { ::CloseHandle(handle); return 0; } // Loop each process looking for the target.. do { if (!_stricmp(pe32.szExeFile, TARGET_NAME)) { ::CloseHandle(handle); return pe32.th32ProcessID; } } while (::Process32Next(handle, &pe32)); // Cleanup.. ::CloseHandle(handle); return 0; } /** * @brief Entry point of this application. * * @param argc The count of arguments passed to this application. * @param argv The array of arguments passed to this application. * * @return Non-important return. */ int __cdecl main(int argc, char* argv[]) { // Obtain the target process id.. auto processId = getTargetProcessId(); if (processId == 0) return 0; // Open a handle to the target.. auto handle = ::OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId); if (handle == INVALID_HANDLE_VALUE) return 0; // Obtain the current system information.. SYSTEM_INFO sysInfo = { 0 }; ::GetSystemInfo(&sysInfo); auto addr_min = (long)sysInfo.lpMinimumApplicationAddress; auto addr_max = (long)sysInfo.lpMaximumApplicationAddress; auto found = 0; // Loop the pages of memory of the application.. while (addr_min < addr_max) { MEMORY_BASIC_INFORMATION mbi = { 0 }; if (!::VirtualQueryEx(handle, (LPCVOID)addr_min, &mbi, sizeof(mbi))) { printf_s("Failed to query memory.\n"); break; } // Determine if we have access to the page.. if (mbi.State == MEM_COMMIT && ((mbi.Protect & PAGE_GUARD) == 0) && ((mbi.Protect & PAGE_NOACCESS) == 0)) { // // Below are flags about the current region of memory. If you want to specifically scan for only // certain things like if the area is writable, executable, etc. you can use these flags to prevent // reading non-desired protection types. // auto isCopyOnWrite = ((mbi.Protect & PAGE_WRITECOPY) != 0 || (mbi.Protect & PAGE_EXECUTE_WRITECOPY) != 0); auto isExecutable = ((mbi.Protect & PAGE_EXECUTE) != 0 || (mbi.Protect & PAGE_EXECUTE_READ) != 0 || (mbi.Protect & PAGE_EXECUTE_READWRITE) != 0 || (mbi.Protect & PAGE_EXECUTE_WRITECOPY) != 0); auto isWritable = ((mbi.Protect & PAGE_READWRITE) != 0 || (mbi.Protect & PAGE_WRITECOPY) != 0 || (mbi.Protect & PAGE_EXECUTE_READWRITE) != 0 || (mbi.Protect & PAGE_EXECUTE_WRITECOPY) != 0); // Dump the region into a memory block.. auto dump = new unsigned char[mbi.RegionSize + 1]; memset(dump, 0x00, mbi.RegionSize + 1); if (!::ReadProcessMemory(handle, mbi.BaseAddress, dump, mbi.RegionSize, NULL)) { printf_s("Failed to read memory of location: %08X\n", mbi.BaseAddress); break; } // Scan for 4 byte value of 1337.. for (auto x = 0; x < mbi.RegionSize - 4; x += 4) { if (*(DWORD*)(dump + x) == 1337) found++; } // Cleanup the memory dump.. delete[] dump; } // Step the current address by this regions size.. addr_min += mbi.RegionSize; } printf_s("Found %d results!\n", found); // Cleanup.. ::CloseHandle(handle); return ERROR_SUCCESS; }