0

So I have been trying to get into memory reading in C++ and I thought a cool project would be to read all addresses a process is using (similar to how Cheat Engine works).

I started by reading

Link1: Read Memory of Process C++

Link2: Read memory of 64bit process address

Link3: http://www.cplusplus.com/forum/general/42132/

And I also watched a tutorial on youtube where he explained how a process (game) worked with addresses. Link to youtube video: https://www.youtube.com/watch?v=wiX5LmdD5yk

This resulted in me making three different methods:

DWORD GetProcId(const wchar_t* procName) {
    DWORD pid = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if(hSnap != INVALID_HANDLE_VALUE) {
        PROCESSENTRY32 procEntry;
        procEntry.dwSize = sizeof(procEntry);
        if(Process32First(hSnap, &procEntry)) {
            do {
                if(!_wcsicmp(procEntry.szExeFile, procName)) {
                    pid = procEntry.th32ProcessID;
                    break;
                }
            } while (Process32Next(hSnap, &procEntry));
        }
    }
    CloseHandle(hSnap);
    return pid;
}

This method is to get the process-id which I could also just type manually by finding the same PID in task-manager (which gave me the same baseaddress later on).

uintptr_t GetModuleBaseAddress(DWORD procId, const wchar_t* modName) {
    uintptr_t modBaseAddr = 0;
//I use 0x10 instead of TH32CS_SNAPMODULE32 since it didnt work and according to documentation 
// this is the value it should have.
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | 0x10, procId); 
    if(hSnap != INVALID_HANDLE_VALUE) {
        MODULEENTRY32 modEntry;
        modEntry.dwSize = sizeof(modEntry);
        if(Module32First(hSnap, &modEntry)) {
            do {
                if(!_wcsicmp(modEntry.szModule, modName)) {
                    modBaseAddr = (uintptr_t)modEntry.modBaseAddr;
                    break;
                }
            } while(Module32Next(hSnap, &modEntry));
        }
    }
    CloseHandle(hSnap);
    return modBaseAddr;
}

This method (I assume) will return the base address of the process. Which for example in my code I tried to find the base address of the discord.exe process. When discord.exe wasn't running I got 0 and when it was running I got an address (which I believe is the correct base address, correct me if I am wrong).

And my main method:

int main() {
    DWORD procId = GetProcId(L"Discord.exe");
    uintptr_t moduleBase = GetModuleBaseAddress(procId, L"Discord.exe");

    HANDLE hProcess = 0;
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, procId);

    uintptr_t dynamicPtrBaseAddr = moduleBase;

    std::cout << "Dynamic: " << dynamicPtrBaseAddr << std::endl;

    int value = 0;
    int arr [10000] = {};
    for (int i = 0; i < 100000; i++) {
        ReadProcessMemory(hProcess, (BYTE*)dynamicPtrBaseAddr, &value, sizeof(value),0);
        dynamicPtrBaseAddr += 1;
        arr[i] = value;
    }
}

Where I try to put all the values of all 100000 addresses in an array.

So my questions are:

  • Have I retrieved the base address of the process correctly?
  • For reading the other addresses I just increase dynamicPtrBaseAddr by 1, is there a better way to implement an offset? Or is this the correct way?
  • Now I increase the base address by 100000. Can I find the last address of the process instead?

I compile with g++ main.cpp -o test -lpsapi -DUNICODE (MinGW).

darclander
  • 1,526
  • 1
  • 13
  • 35
  • 1
    Pretty good question, especially considering most of the read process memory questions have code and assumptions that make me want to claw my brain out. Even the three questions that are actually asked are related enough to make sense in one answer. – user4581301 Apr 24 '20 at 23:47
  • 1
    @user4581301 I tried to include as much information as I could since I understand what you mean how it can be really hard for the community to help if I assume too much. Do you think there is more information I can include? – darclander Apr 24 '20 at 23:51
  • You aren't doing any error handling in `main()`, always check for errors. Also, asking `OpenProcess()` for `PROCESS_ALL_ACCESS` rights is just asking for too much, `ReadProcessMemory()` needs only `PROCESS_VM_READ` rights. Don't ask for more rights than you actually need. Also, the organization of a process's memory is complex. Reading 10000 `int`s starting from `moduleBase` is fairly useless. Besides, you are incrementing `dynamicPtrBaseAddr` in 1-byte increments, but are reading in 4-byte increments. Lastly, `MODULEENTRY32` gives you the size of each module, add that size to the base address – Remy Lebeau Apr 25 '20 at 00:50
  • @RemyLebeau don't I need `PROCESS_ALL_ACCESS` if I would want to write to the process addresses in the future? How do I know when to stop then if I dont want to read 100000 `int`s? Like my goal is to find values for example in a game (like cheat engine does). One `int` is 2-4 bytes right? Or is it fixed 4 bytes? Should I add the size of a module in each increment you mean? – darclander Apr 25 '20 at 01:09
  • "*don't I need PROCESS_ALL_ACCESS if I would want to write to the process addresses in the future?*" - no. [Read the documentation](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory), all you need for that is `PROCESS_VM_WRITE` and `PROCESS_VM_OPERATION` access. In practice, you almost NEVER need `PROCESS_ALL_ACCESS`. "*One int is 2-4 bytes right? Or is it fixed 4 bytes?*" - it really depends on the compiler, but generally on Windows `short` is 2 bytes and `int`/`long` is 4 bytes. Best to use fixed-sized types, like `int16_t`, `int32_t`, etc – Remy Lebeau Apr 25 '20 at 01:21
  • 1
    "*Should I add the size of a module in each increment you mean?*" - no. I mean add a module's size to its base address to know what the module's last address is, ie the address you can't read past. In your loop, if you are going to read whole `int`s, then you should be incrementing `dynamicPtrBaseAddr` by whole `int`s, not by single bytes. – Remy Lebeau Apr 25 '20 at 01:23
  • @RemyLebeau now it seems to be working as you said by limiting my address range to `BaseAddr + modulesize`. But that only works for discord.exe, do you know why I cant read other applications such as games? Like when I try to change `Discord.exe` to `FTK.exe` I receive 0 addresses. Firefox.exe also returns 0 addresses. Does it have something to do with if they are 32bit or 64bit? – darclander Apr 25 '20 at 01:53
  • 1
    The bitness of your app will need to match the bitness of the target I think. – Jonathan Potter Apr 25 '20 at 02:44
  • @darclander that is what debugging and error checking is meant to discover. – Remy Lebeau Apr 25 '20 at 02:58
  • @RemyLebeau I think I managed to fix everything but when I tried to write to the memory I got an error message - 998 for `WriteProcessMemory()` even if I try to run it as admin. I looked into the docs and it seems to be something about priviliges. You told me that `PROCESS_ALL_ACCESS`, wasnt needed. Is there something else I am doing wrong then? – darclander Apr 25 '20 at 22:19
  • @darclander are you sure it is really 998 (ERROR_NOACCESS) and not 299 (ERROR_PARTIAL_COPY)? In any case, the [doc](https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory) says: "*The function fails if the requested write operation crosses into an area of the process that is inaccessible ... The entire area to be written to must be accessible, and if it is not accessible, the function fails.*" So, you are probably just trying to write to memory that is not writable. Not all memory in a process can be written to, there are areas that are read-only. – Remy Lebeau Apr 25 '20 at 23:09
  • @RemyLebeau yes unless the return value somehow bugs out it should be 998. I think I have come really far with what you have told me though and I appreciate the effort. I dont know if this is something I can ask in a comment but what is the reason to why people are downvoting my question? – darclander Apr 26 '20 at 14:38
  • @darclander It seems initial question about reading is solved, right? You can sharing your solution as an answer. About writing error "**998 (ERROR_NOACCESS)**", I can reproduce the same error when writing module address but it works on heap address. ([`Heap32First`](https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-heap32first)). Which part memory do you want to write? – Rita Han Apr 27 '20 at 07:01
  • @darclander And a range of committed pages of a process can be specified as [read/write, read-only, or no access](https://learn.microsoft.com/en-us/windows/win32/memory/virtual-memory-functions) which seems related to error "**998 (ERROR_NOACCESS)**". – Rita Han Apr 27 '20 at 07:17
  • @RitaHan-MSFT that depends, the video I linked goes through how you can find the `ModuleBaseAddress` of the process and then write to it with an offset which is how I thought you would modify memory. But if I need to find the `HeapModuleBaseAddress` so I can write to the process memory I dont think my answer is correct. Because the module addresses are not all the addresses within the process range? – darclander Apr 27 '20 at 11:29
  • @darclander I didn't redirect you to read/write heap instead but just confirm with you **Which part memory do you want to write?** About writing error **"998 (ERROR_NOACCESS)"**, you can check and change the access right using [`VirtualProtectEx`](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotectex#return-value). This line solve the error and make write success for me: `VirtualProtectEx(hProcess, (LPVOID)dynamicPtrBaseAddr, sizeof(value), PAGE_READWRITE, &oldProtect)`. – Rita Han Apr 28 '20 at 02:23

1 Answers1

0

You must run as administrator and you must compile for the same bitness as the target process.

You should not be reading 1 byte at a time, especially in an external hack.

You should be using VirtualQueryEx() to properly loop through only proper memory regions:

    DWORD procid = GetProcId("ac_client.exe");

    unsigned char* addr = 0;

    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procid);

    MEMORY_BASIC_INFORMATION mbi;

    while (VirtualQueryEx(hProc, addr, &mbi, sizeof(mbi)))
    {
        if (mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS && mbi.Protect != PAGE_GUARD)
        {
            std::cout << "base : 0x" << std::hex << mbi.BaseAddress << " end : 0x" << std::hex << (uintptr_t)mbi.BaseAddress + mbi.RegionSize << "\n";
        }
        addr += mbi.RegionSize;
    }

This ensures you're only reading proper memory. Next you would reach each region into a local buffer in one go, this will increase speed dramatically as you won't have the overhead of calling the API for each byte.

What you're looks like you're trying to do is pattern scanning. You can find our pattern scanning tutorials in the same place you found the original tutorial you were following.

GuidedHacking
  • 3,628
  • 1
  • 9
  • 59
  • What I dont understand is that in my application I assign a variable `number` the value 500. And then I print the address of `number` and it seems to be stored at `6421852` while your method gives me addresses between `140728537382912` and `140737488289792`. How will I be able to read the value of `number` then? – darclander May 15 '20 at 01:03
  • @darclander you seem to lack fundamental knowledge of what you're doing so I suggest you go back to the basics before trying to do something complicated like this. In the long run you will be better for it, build a foundation of knowledge which you can use to excel to the next steps. I am using a char* to represent addresses, while you're using an integer type, which is causing you this problem. – GuidedHacking May 15 '20 at 01:17
  • I tried to convert them from hex to dec in order for me to see more clearly the difference. Do you mean basics of memory addressing? Because I had a similar question where I was able to read some memory but not able to find a specific value. They suggested I looked into reading heap / stack which is why I am asking. @GuidedHacking – darclander May 15 '20 at 02:05