Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Reading Memory from Another Process in C++ | Copilot's Solution

As the title suggests, I am trying to read memory from another process in C++ in order to check if the values from the other process reach a certain level. Since I don’t know anything about this, I decided to consult GitHub Copilot for help. On a normal basis, I would search the docs, but Github seems to disagree. Since I have access to GitHub Copilot, and since the front page advertisement clearly encourages users to trust Copilot’s programming ability, I chose to let Copilot make this function.

So I gave it a prompt in the form of a comment: //A function that can grab an address from the memory of another process and store it as a double value

What it gave me seemed pretty good, but I will never take a function that copilot makes and blindly use it unless I know for sure it will work (because I don’t trust that everything Copilot makes is never going to cause issues, especially when dealing with pointers and such). I wanted to see if someone who had experience with memory in C++ could tell me if this function will work and why it would or wouldn’t work as I know nothing about getting memory from another process.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel


There are three main reasons why I am not just searching the docs anyway despite GitHub’s statement:

  • Since this is a complicated and real-world use case, this will really test Copilot’s programming ability and it will give me insight into how much I can trust Copilot in the future for stuff I don’t know how to do (Obviously I wouldn’t let this get out of hand, but it would be good to know I can trust Copilot a little more than I do right now).
  • Searching the docs anyways despite the statement that GitHub made on their website is quite the opposite of what Copilot is supposed to help users with, and while I understand that it’s a public beta and it’s not complete yet, it should at least be good enough for real-world use cases rather than simple coding cases. An answer from someone experienced will really show if it is good enough for real-world coding cases.
  • The docs only tell me what a function does and what to put as its parameters, it doesn’t tell me how to use it. If I really wanted to know how to use it, I would have to search the web. Searching the web will most likely get me complicated examples that don’t pertain to my issue and defer me from what I am actually trying to accomplish. Not only that, but it is the opposite of what Copilot is supposed to help users with, as stated in reason #2.

Here is the code that Copilot generated for me:

DWORD GetAddress(DWORD dwProcessId, LPCWSTR szModuleName, const char* szProcName)
{
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess == NULL)
        return 0;

    MODULEINFO modinfo;
    GetModuleInformation(hProcess, GetModuleHandle(szModuleName), &modinfo, sizeof(MODULEINFO));

    DWORD dwAddress = (DWORD)modinfo.lpBaseOfDll;
    DWORD dwSize = (DWORD)modinfo.SizeOfImage;

    MEMORY_BASIC_INFORMATION mbi;
    while (dwSize > 0)
    {
        VirtualQueryEx(hProcess, (LPVOID)dwAddress, &mbi, sizeof(mbi));
        if (mbi.State == MEM_COMMIT && !(mbi.Protect & PAGE_GUARD) && mbi.Protect & PAGE_EXECUTE_READWRITE)
        {
            DWORD dwOldProtect;
            VirtualProtectEx(hProcess, (LPVOID)dwAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);

            char* szBuffer = new char[mbi.RegionSize];
            ReadProcessMemory(hProcess, (LPVOID)dwAddress, szBuffer, mbi.RegionSize, NULL);

            for (DWORD dwIndex = 0; dwIndex < mbi.RegionSize - 4; dwIndex++)
            {
                if (szBuffer[dwIndex] == '\x55' && szBuffer[dwIndex + 1] == '\x8B' && szBuffer[dwIndex + 2] == 'E' && szBuffer[dwIndex + 3] == 'A')
                {
                    DWORD dwAddress2 = dwAddress + dwIndex + 7;
                    DWORD dwAddress3 = dwAddress2 + *(DWORD*)(dwAddress2);
                    if (strcmp((char*)dwAddress3, szProcName) == 0)
                    {
                        delete[] szBuffer;
                        CloseHandle(hProcess);
                        return dwAddress2 + 4;
                    }
                }
            }

            delete[] szBuffer;
            VirtualProtectEx(hProcess, (LPVOID)dwAddress, mbi.RegionSize, dwOldProtect, &dwOldProtect);
        }

        dwAddress += mbi.RegionSize;
        dwSize -= mbi.RegionSize;
    }

    CloseHandle(hProcess);
    return 0;
}

You may point out an immediately noticeable error: The function returns DWORD rather than double, which is what I asked Copilot to return. I saw that error but from examples that I have seen (Yes, I have done at least some searching), returning DWORD works as well. I could have seen those examples wrong, and if I am correct me.

>Solution :

The function returns double but it only returns the value that is stored in the memory, not actually double-typed data. If you do a casting to double, you get back your original data.

You can’t search in memory with anything other than a byte pointer on 64-bit systems: http://msdn.microsoft.com/en-us/library/aa746449%28v=vs.85%29.aspx

There are different ways to search for a string in memory, depending on what you are looking for: http://www.catatonicsoft.com/blog/need-to-read-and-write-strings-and-data-in-a-processs-memory/

(Read more here: https://github.com/MicrosoftArchiveOrgMember/copilot)

Memory Scraper

This program uses several techniques to obtain information from processes and memory as it runs so that it can be added to the evidence file when Cofactor terminates the target process (by default) or when you terminate the program manually (with CTRL+C).

This code was mostly cobbled together from various examples at http://www.codeproject.com and https://forums.hak5.org, with some heavy modifications made to get the output in a useful format.

The process memory usage is checked continuously and added to the log file when it changes. This is done by getting a pointer to the process’ memory region, then checking all of its pages as they are referenced. When they are changed, the contents of that page will be read and added to the log file as evidence. If a process goes in and out of sleep mode or is stopped for some other reason, this program will detect that and add it to the log file accordingly.

The current DLLs loaded by processes are recorded every 5 seconds so that if a DLL gets loaded after Cofactor terminates its target, it will still be included in the log file as evidence. A process is also checked every 5 seconds for new threads being spawned so that child processes are also included in our evidence files.

We could extend this program by having it check for two modules:

1) A module containing functions that correspond to debug breakpoints (which would detect whether a debugger was attached).
2) A module containing crash signatures - integers that would trigger an alert if they were found written to memory in any of our processes (like stack smashing protections might provide).

In order to do this without complicating things too much, I’d probably use CreateRemoteThread with an address within each module to continue execution from that thread into your own code where you can check for the breakpoint or crash signature and act accordingly.

Conclusion

If you need to debug a process and can’t get it to stop for any reason, this program will still be able to grab the process memory at any time so that you can search for whatever you need.

You’ll have to do some extra work in order to use the log file that is created, like parsing it with a parser of your choice and searching it (using regexes or something) which I assume is outside of the scope of what Copilot is designed to do.

If you end up using this program, please let me know! I’m curious to see how many people find this program useful.

Interesting Techniques I Learned From Other Programs

Finding DLLs Loaded by a Process

The C++ code below uses the Windows API GetModuleFileNameW() to get the full path of loaded DLLs and parses it with split() to extract just the filename and not the whole path. The rest of that code just tries to avoid duplicates while being simple enough that it doesn’t get too confused between different processes and file system cases (hopefully).

// Code Example: Finding DLLs Loaded by a Process
#include <stdio.h>
#include <string.h>
#include <tchar.h>

#define BUFF_SIZE 200

// Find the full path to a loaded DLL by process ID (PID) and its filename (first 8 characters)
void GetModuleFileNameEx(int pid, const char* szName, char* buff, int buffSize)
{
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);

    if (hProcess == NULL) { return; }

    HMODULE hMods[1024];
    DWORD cbNeeded;

    if (!EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { return; }

    for (int i = 0; i < (int)(cbNeeded / sizeof(HMODULE)); i++) {
        TCHAR szModName[MAX_PATH];

        if (!GetModuleFileNameEx(hProcess, hMods[i], szModName, sizeof(szModName))) { continue; }

        strcat_s((char*)buff, buffSize - 1 , (char*)szModName);

        // Check if the first 8 characters of the filename in the process matches
        // with what we are looking for and avoid adding duplicates
        char* chPtr = strchr((char*)buff, '\\');

        if (chPtr != NULL) {
            *chPtr = 0;
            strcat_s((char*)buff, buffSize - 1 , "\\");
            strcat_s((char*)buff, buffSize - 1 , szName);

            HANDLE hFile = CreateFileA((LPCSTR)buff, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

            if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); return; }
        } else { break; }

    }
}

int main() {
    char szDllName[8];       // Maximum length of a module name is MAXPATH - 1 bytes including the NULL terminator. However we only need 8 characters to store the DLL name so use this limit to save memory.

    int pid; scanf("%d", &pid);

    if (pid == 0) { return 0; }

    char buff[BUFF_SIZE];

    GetModuleFileNameEx(pid, szDllName, buff, BUFF_SIZE);

    char* chPtr = strchr(buff, '\\');

    // Change the path separator character to a null terminator so we can split it
    if (chPtr != NULL) { *chPtr = 0; }

    chPtr = strtok(buff, "\\");
    while (chPtr != NULL) {
        printf("%s\n", chPtr);
        chPtr = strtok(NULL, "\\");
    }

    return 0;
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading