c++windowswinapimemoryaccess-violation

"Access Violation" error when trying to print the MODULEENTRY32::modBaseAddr field from the TlHelp32.h library


I'm trying to get this code to work:

#include <iostream> // For general input and output
#include <Windows.h> // Windows API for interacting with windows processes
#include <TlHelp32.h> // For getting snapshots

int main()
{
    /*
        Get the process ID to attach later
    */

    DWORD gameProcID = -1; // Variable to hold the process ID, has to be a DWORD because the windows API wants it that way. Starts as -1 (which is invald) so it is known if the process wasn't found
    HANDLE procSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS , 0); // Creates a "snapshot" of the current processes (Essentially a list of the processes)
    
    // Creates a placeholder process entry, that will hold each process for checking
    PROCESSENTRY32 currentProc;
    currentProc.dwSize = sizeof(PROCESSENTRY32); // Has to have it's "sizeof" member set, or else the loop fails. 

    // Loops through each process in the snapshot until it finds Winquake.exe
    if (Process32First(procSnap, &currentProc)) { // Starts by getting the first process
        do {
            if (!_wcsicmp(currentProc.szExeFile, L"Winquake.exe")) { // Checks if the process is Winquake, using a Wide Character String Compare function
                // If found, update the variable and break out the loop
                gameProcID = currentProc.th32ProcessID;
                break;
            }
        } while (Process32Next(procSnap, &currentProc)); // While there are still more processes, keep checking
    }
    CloseHandle(procSnap); // Close the snapshot. Good memory management :)

    // If it can't find the process, print and then close
    if (gameProcID == -1) {
        std::cout << "Couldn't find process" << std::endl;
        exit(1);
    }

    /*
        Get the module inside the found process (Quite similar to finding the process)
    */

    uintptr_t gameBaseAddr = -1; // Variable to hold the base memory address of the module we want to find, Starts as -1 (which is invalid) so it is known if the module wasn't found
    HANDLE modSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, gameProcID); // Creates a snapshot of all the modules inside the process

    // Creates a placeholder module entry, that will hold each module for checking
    MODULEENTRY32 currentMod;
    currentMod.dwSize = sizeof(currentMod); // Has to have it's "sizeof" member set, or else the loop fails.

    // Loops through each module in the snapshot until it finds Winquake.exe
    if (Module32First(modSnap, &currentMod)) { // Starts by getting the first module
        do {
            if (!_wcsicmp(currentMod.szModule, L"Winquake.exe")) { // Checks if the module is Winquake, using a Wide Character String Compare fucntion
                // If found, update the variable and break out the loop
                gameBaseAddr = (uintptr_t)currentMod.modBaseAddr;
            }
        } while (Module32Next(modSnap, &currentMod)); // While there are still more modules, keep checking
    }
    CloseHandle(modSnap); // Close the snapshot. Good memory management

    // If it can't find the module, print and then close
    if (gameBaseAddr == -1) {
        std::cout << "Couldn't find module inside game. Perhaps you have the wrong 'Winquake.exe' executable" << std::endl;
        exit(1);
    }

    // The final handle for the game process
    HANDLE gameProc = OpenProcess(PROCESS_ALL_ACCESS, NULL, gameProcID); // Opens a handle to attach to the Winquake process and module. With full access rights

}

It does not throw an exception, but if you set a breakpoint on line 56 of the code, you can see in the debugger that the modBaseAddr field of the currentMod object contains the string modBaseAddr 0x00007ffac7c00000 <Error reading string characters.>.

If you try to output the value from this field to the console using std::cout, an exception will be thrown:

An exception was thrown at 0x00007FF9FAA77CC1 (ucrtbased.dll) in ProjectName.exe: 0xC0000005: Access violation reading from 0x00007FF7EF6C0000.

I tried to get data from different games.

My Visual Studio is running as administrator and runs the project as administrator. I also tried to just run the .exe file from the "debug" folder as administrator.

None of these affected the problem.

I also couldn't find anything about my problem on the Internet.

I expected the modBaseAddr field of the currentMod object to hold the module's base address.

if anything, all other fields of this object contain correct data.


Solution

  • The MODULEENTRY32::modBaseAddr field is declared as a BYTE* pointer, ie unsigned char*. The << operator of std::basic_ostream (which std::cout is an instance of) is overloaded to treat all types of char* pointers as C-style null-terminated strings, which modBaseAddr is not. That is why the debugger says it is trying to access string characters, and why the print crashes. They are both trying to access memory that they shouldn't be accessing.

    To inspect the pointer properly in the debugger, you can instruct the inspector to interpret it as a raw pointer not a character pointer.

    To print the address that a char* pointer is pointing at, you need to type-cast the pointer to void* or (u)intptr_t to invoke a more appropriate << operator, eg:

    std::cout << "modBaseAddr: " << static_cast<void*>(currentMod.modBaseAddr) << std::endl;
    
    std::cout << "modBaseAddr: " << std::hex << static_cast<uintptr_t>(currentMod.modBaseAddr) << std::endl;