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   
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.
GeneralVMOBJECTmemberesskar7 Dec '05 - 0:52 
you do not need the memmbers
 
char szObjType[12];
char szModule[MAX_PATH];
char szSection[IMAGE_SIZEOF_SHORT_NAME];
BOOL bNew;
 
what are they for ?
GeneralRe: VMOBJECTmemberweariless7 Dec '05 - 2:50 
They are probably left over from the original program whose complexity I found annoying. The program, The PWalker source in samples\sdktools\winnt\walker Microsoft Win32 SDK - August 1996, loaded some debug DLLs to further probe each memory segement as it walked process memory. I imagine these variables got filled in with PWalker.exe. See that source code for further information. I don't think I have that CD anymore. Either that, or at design time I thought I would fill these in with relevant information and never got around to it. To have found this, you must really understand the program - kudos to you.
QuestionHow can i get a hex dump ?memberHyri14 Aug '04 - 20:44 
Hi weariless.
 
I downloaded your program - dump of another process's memory.
 
but, i dont know how to show the value of address?
 
how can i show the hex value of another process's memory?
 
please, teach me. Smile | :) have a good day
 
thanx for your good program ^^
 
ps.
 
i already edit source by using ReadProcessMemory
 
i make a function in TCWindow class.
-------------------------------------------------------------------
LPVMOBJECT pPage;
int iPages;
pPage = walkObj.mGetPageList(&iPages);
 
BYTE* destBuffer = NULL;
destBuffer = new Byte[32*1024*1024];
 
DWORD ReadByte;
ReadProcessMemory(m_hProcess, pPage->mbi.AllocationBase, destBuffer,
32*1024*1024, &ReadByte);
 
--------------------------------------------------------------------
but always ReadByte is zero.
is it wrong ?

AnswerRe: How can i get a hex dump ?memberweariless16 Aug '04 - 9:01 
Hi Hyri,
 
OK this seems to be two questions so I will answer what I believe you're asking. First - using the program to get a hex dump of a memory area...
 
First run the program, and select file open, then place the bullseye icon on the application's window you wish to explore.
You will then see memory addresses the application has reserved for use. Notice the listing marked "State": "Free" or "Reserved" areas are basically unused memory chunks so the only ones you can display are the ones marked with Commit. Double click on the line with a "Commit" state and you can browse that chunk of memory.
 
The second question: This whole article is basically an example of how to get the ReadProcessMemory() function to work. Follow each step in the example, including getting debug rights on an application, to get ReadProcessMemory to work. If you skip a step or the program hasn't commited the memory for use, this function will return 0-bytes read.
 
Keep in mind, if you're on a machine where you're not the administrator, you probably wont' be able to get debug rights for an application. This is a security feature of Microsoft Windows. The idea is that you want to allow people to examine and debug their own programs, but not the memory of someone else's program (unless you're an administrator who wants to verify the security of a program you've installed.)
GeneralRe: How can i get a hex dump ?memberHyri16 Aug '04 - 22:00 
Thank you for your kind reply.
I didn't know about double click Smile | :)
 
thank you.
 
have a good day. Smile | :) WTF | :WTF:

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

Permalink | Advertise | Privacy | Mobile
Web02 | 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