Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Performing a hex dump of another process's memory

0.00/5 (No votes)
6 Sep 2003 1  
An article on examining process memory

Introduction

I was browsing The Code Project for information on using ReadProcessMemory to do a Hex dump of a process once it's loaded. While there are a number of utilities available for dumping a file image as it exists on disk, I didn't find much for dumping the memory image of an application once its loaded in memory.

Although the Win32 SDK Walker example does a nice job of this, I found it a bit too cumbersome to parse as an introduction program. I also had a few problems with it. One, it uses the debug procedures which load hook DLLs into the process thus altering the memory image we're looking at. Two, it crashes when you unload the process before terminating the Walker program. Three, the thumb tab on the vertical scroller doesn't work for browsing the memory details, and lastly it is somewhat hard to figure out where all of its structures get initialized since they happen all over the place.

Background

To start with, the MSDN documentation of ReadProcessMemory is a good thing to look at. A number of questions come up if you are just trying this sucker out. Where do I get a process handle? Once I have a process handle, it seems to blow up on me. How do I know what address to load in? The answer to the address question is the VirtualQueryEx function. This article covers the use of both functions and answers a number a questions pertaining to obtaining the proper variables to submit to the functions.

Using the code

The bulk of the MDump project simply displays the information it finds in a scrollable fashion. Aside from that, there are only three real questions which come up in writing an application like this:. how you get a process handle from outside your own process, how do you collect all the memory pages managed by this process, and how do you display this memory once found?

How do you get a process handle?

The simplest way I can think of to choose a process of interested is to click on its window. Given that, the question becomes how do I get a process handle by clicking on a window? Ideally, one would select File|Open, have the cursor change to a bulls eye, and return when you have selected your window.

As I'm sure most of you know, SetCapture got most of its utility removed a while back and no longer lets a window receive WM_MOUSEMOVE messages unless a mouse button is down. I find this awkward. The way around this is to throw up a transparent window which covers the entire screen, change the mouse cursor to let you know you're in this mode, and then change it back when you have your coordinates from a click.

Since this is a bit awkward, it bears a bit of discussion. Here is the function that get called when we select File|Open from the menu.

LRESULT TCWindow::OnFileOpen(HWND hWnd)
{
    HWND hTransparent;
    gWindow.hcurOld = GetCursor(); //Save our cursor

    ShowWindow(gWindow.m_hWnd, SW_SHOWMINIMIZED); //Minimize

    //Callback function to handle a mouse click and return to main window

    gwchild.fpCallback = cmdSelectWindow;
    //Cover the screen with transparent same-process window

    hTransparent = gwchild.Create(gWindow.m_hInst, SW_SHOW);
    return 0l;
}

Ok, all we're doing is changing the cursor and throwing up the transparent child window. I pass it a static function to use as a callback - which it will call as soon as it gets a mouse click. In order to get back inside our window class, SendMessage(gWindow.m_hWnd, WM_ONGETHWND, 0, (LPARAM)hParent); passes the top-level window handle back to our window object.

//Callback function for selecting the window under the cursor

void cmdSelectWindow(HWND hWnd, LPARAM lParam)
{
    ...
    //Ok restore the visual state of things

    DestroyWindow(gwchild.hWindow);
    SetCursor(gWindow.hcurOld);

    //Ok now get the window we clicked on before we possibly cover it.

    pt.x = LOWORD(lParam);    pt.y = HIWORD(lParam);
    hWndClicked = WindowFromPoint(pt);
    //Get outermost window

    hParent = hWndClicked;    
    hNewParent = GetParent(hWndClicked);
    while(hNewParent) {
        hParent = hNewParent;
        hNewParent = GetParent(hParent);
    }
    //Maximize our window

    ShowWindow(gWindow.m_hWnd, SW_RESTORE);
    SendMessage(gWindow.m_hWnd, WM_ONGETHWND, 0, 
        (LPARAM)hParent);    //Process handle

    return ;
}

So now we have a window handle? Now what? Well, the following code will give us the process handle which is what we need to walk the process's memory pages.

    GetWindowThreadProcessId(hWndToOpen, &dwProcessID);
    // Get Process Handle

    m_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
    //Map the address space

    walkObj.memWalk(m_hProcess);
    walkObj.mGetPageList(&iPages);

There is one catch here though. In order for this to work, I call the AdjustPrivileges function in the same module as WinMain to give this application debug rights. Otherwise OpenProcess with PROCESS_ALL_ACCESS fails. This code is straight out of the MSDN Knowledge base - Article ID: Q131065

In short, here is what AdjustPrivileges does:

OpenProcessToken(... TOKEN_QUERY, &hToken))    //Get hToken

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)    //Get Debug rights    

tp.Privileges[0].Luid = luid;                //Enable debug rights

tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges (hToken, FALSE, &tp, ...

How do you examine the memory?

Ok, so we have m_hProcess now, the handle to the other process. Why did we need it again? You need it for this function:

    VirtualQueryEx(
        hOtherProcess,
        lpMem,
        &mbi,
        sizeof(MEMORY_BASIC_INFORMATION)
    );

VirtualQueryEx takes an address (lpMem) and returns information about it in the mbi structure. This is why we needed the process handle.

In the demo program, CMemWalker is the class that reads the other process's memory. All it does is set lpMem to 0 and increments it to scan all the memory address up to the top of the process's memory space. The simplified loop is as follows:

    MEMORY_BASIC_INFORMATION    mbi;
    /* Get maximum address range from system info */
    GetSystemInfo(&si);
    /* walk process addresses */
    lpMem = 0;
    while (lpMem < si.lpMaximumApplicationAddress) {
            VirtualQueryEx(...)
            /* increment lpMem to next region of memory */
            lpMem = (LPVOID)((DWORD)lpList->mbi.BaseAddress +
            (DWORD)lpList->mbi.RegionSize);
    }

This information is then displayed in the main Window which responds to scroll events and such. Among other things, the information in the mbi structure holds a beginning address and a length for each section of managed memory. In the demo program, you double click on a line which then browses that chunk of memory in the standard hex dump fashion.

How do you read the memory?

To actually dump the individual bytes of memory that are available to be read, you use the following function:

    ReadProcessMemory(m_hProcess, lpAddress, 
        destBuffer, dwBytesToRead, &dwBytesRead);

This function takes a memory address in a foreign process, and copies it into memory in the current process (destBuffer). There is not much more to the demo program than that. Most of the other code in the TCHexWindow object merely allows the user to browse the bytes using the scroll widgets. For the sake of simplicity, I intentionally left out the sorting, heap probing, and searching functions which I miss dearly. These might be a few things to add if you're planning on enhancing this.

If you're still interested in probing memory segments, I suggest looking at the Walker source code in the Win32 SDK kit. I would love a clearer explanation as to how the thing figures out which chunks are DLLs etc. As a point to start from, if you open a process using the various Win32 Debug functions, you can trap debug events and have the loop notify your program whenever a DLL is loaded or unloaded by a process. Other information is also available in this fashion. This does, of course, load the Hook98.DLL which changes the memory so it may or may not work for your purposes.

History

  • Sep 1, 2003: Article published
  • Sep 7, 2003: Downloads updated

References

  • How to Obtain a Handle to Any Process with SeDebugPrivilege  - Microsoft Knowledge Base Article ID: Q131065
  • The PWalker source in samples\sdktools\winnt\walker - Microsoft Win32 SDK - August 1996

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here