Click here to Skip to main content
Click here to Skip to main content

Performing a hex dump of another process's memory

By , 6 Sep 2003
 

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

About the Author

weariless
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRunning into an error.memberignoranceisbliss19 Jan '06 - 14:41 
When I run this app on one of my processes (I did make some modifications I admit, but the same issue occurs with the original program), I get a consistent error in VirtualAlloc (it the line in MemWalk.cpp that is commented "Reserved pages got used elsewhere"). This always happens at the same address 0x55F060, and appears to only happen for this one process (other process report just fine).
 
Does anyone know why this might happen? Where is "elsewhere"? Does this mean that the app itself committed the memory elsewhere? How can I avoid this/recover?
 
Thanks,
Sven
 
-- modified at 13:06 Friday 20th January, 2006
GeneralRe: Running into an error.memberweariless20 Jan '06 - 7:47 
Hi Sven,
 
Not really sure what you're asking so I'll start this thread with this:
 
Each memory block can have 3 states: Free, Commit, or Reserve.
In my program, you can only look at commited memory, that is, memory that the application is using. A block that is marked as reserved by an application really doesn't exist. It's just a flag that an application uses to mark a chunk of memory( a set of numbers), that it can later commit and use. You cant use reserved memory until its commited. In a virtual environment like an Intel chip-based one, our set of addresses may not even correspond to actual ram. So uncommited memory is just some numbers in a table, not real RAM or paged virtual memory, until you call the kernel procedure and set the commit flag. Then those numbers are RAM and/or disk sectors.
GeneralRe: Running into an error.memberignoranceisbliss20 Jan '06 - 8:21 
Yep... so if I understand the code correctly the
 
if (VirtualAlloc(
lpUncommited,
4096,
MEM_COMMIT,
PAGE_READWRITE) == NULL )
{
 
is what makes the memory address available (commited) in the local app, so the VirtualQueryEx can access it on the other side of the fence (sorry if this is not correct... I admit that this is not really my area of expertise Smile | :) . However, for my app that call fails after working for some time. What I see is that I can get addresses up to 0x0055F060 committed, and then it returns an "invalid address" error. My question is... why would that happen? As far as I can tell, it would only happen if the local app already grabbed that page, but to confirm that I changed the code to ensure that there are absolutely no allocations happening, and it still fails.
 
I hope this makes sense...
 
Thanks,
Sven
GeneralRe: Running into an error.memberweariless21 Jan '06 - 10:20 
The way I think of this is like malloc()
 
VirtualAlloc(lpUncommited,4096,MEM_COMMIT,PAGE_READWRITE)
 
VirtualQueyEx() is a function that takes foreign process information and
simply fills in local memory with whatever it finds.
 
If we do this
VirtualAlloc(lpUncommited,4096,MEM_RESERVE,PAGE_READWRITE)
nothing has been malloced, but we have flagged a memory area that we want set aside.
 
In the original program, VirtualAlloc sets up an array of structures that holds information about foreign processes memory. We then walk through the process and fill it in unless we can't commit the page we reserved earlier.
 
So, I guess what you are saying is that, in doing this, VirtualAlloc is failing for you when you set the commit flag. Since we reserved it earlier, the only thing I can imagine is that you commited an area of memory elsewhere in your program or you are trying to commit pages oustide of your reserved area. If you allocate with your reserved flag, I dont think you have to worry about virtual memory settings, processor limits, or real ram since the reserve fuction will fail first.
 


GeneralRe: Running into an error.memberignoranceisbliss22 Jan '06 - 12:51 
I agree with your analysis. However, this fails with my code as well as your code in the same way (I can commit memory up to that address, and then it fails). Looking at your code there is no extra commit, and my code is also not committing memory explicitly.
 
I went ahead and changed my code to just loop through a minimal VirtualAlloc(reserve), VirtualAlloc(commit) and VirtualQueryEx() without any actual data storeage/display. Even that code failed, which leads me to believe that either some external process is interfering (like a virus scanner), or there is some weird OS thing. I am not sure what to make of this... I have tried to use Heap32First (and related APIs), and they fail as well. All this is just failing for one specific app, every other app is working fine (of course, I need to diagnose that problematic app...).
 
As you point out... I should have the memory reserved at that point. So how can the commit fail... very strange. Hm... I will see if we could walk out of the previously reserved area... that's an interesting idea (the code seems to check for that...).
 
Thanks,
Sven
 


GeneralRe: Running into an error.memberignoranceisbliss23 Jan '06 - 7:52 
Ok... found it. I looked at the source more closely over the weekend, and now that I understand it better (at least I believe I do Smile | :) , the issue is that the memory dump utility is running out of memory when storing it's local per page structures (somehow the detection code for memory overflow doesn't work right).
 
My initial misunderstanding was that I thought that the lpList allocation was required for scanning the target apps memory (to map the memory in from the scanned app). As far as I can tell, that is purely local data structure building for the found pages (for later traversal/display).
 
I changed the code to not store anything (in my previous attempt to do so, I still did leave the lpList allocation in there because I wasn't clear on it's exact use). Without that storin g of data elements, I can print my entire virtual address space now. So it might be useful to make the memory allocation scheme for the MemWalker code more flexible when planning to look at larger apps.
 
Thanks for the help,
Sven
GeneralRe: Running into an error.memberweariless24 Jan '06 - 6:47 
Glad you figured it out. I just want to post for others who might be confused as well. First, nothing fancy is going on with VirutalAlloc, its just a fancy malloc(). In the program, I reserve a chunk of memory then take little pieces out of it to store the memory info from the application we are debugging.
 
It is reserved in this peice of code with
#define TOTALVMRESERVE 100000
m_lpWalk = VirtualAlloc(NULL, //Any Address
TOTALVMRESERVE,
MEM_RESERVE, //No alloc, hold for LocalAlloc
PAGE_NOACCESS); //DO a GP fault for r,w or x
and checked with this
/* Get maximum memory address range from system info */
GetSystemInfo(&si);
...
while (lpMem < si.lpMaximumApplicationAddress){
//Check for out of memory
if(((int)lpList + 4096) >= ((int)m_lpWalk + TOTALVMRESERVE) )
 


GeneralRe: Running into an error.memberignoranceisbliss24 Jan '06 - 7:17 
Yep... would it make sense to make that more dynamic? It seems that for larger apps (or apps with a lot of memory fragmentation), you may run out of memory. Also, what I found interesting was that there seems to be code in there to check for the out of memory condition, but that did not trigger for me. Instead the Virtual Alloc commit code triggered.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 7 Sep 2003
Article Copyright 2003 by weariless
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid