Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

C# How to Scan a Process' Memory

Rate me:
Please Sign up or sign in to vote.
5.00/5 (10 votes)
27 Jan 2014CPOL2 min read 87.1K   23   11
This article is about how to get the memory dump of a process, by checking almost all memory addresses that can store data.

Introduction

This article is about how to get the memory dump of a process, by checking almost all memory addresses that can store data. Since C# is quite a high level programming language, I think this is the only method available to do this.

And since someone asked how to search a string in a process' memory - well, the easiest way would be to search in this generated memory dump. There are also other methods that imply pointers, offsets and Assembly or injecting some DLL in the target application, but...this is C#.

In this tutorial, I'll try to output all memory allocated by Notepad, I recommend you target processes that don't take too much RAM memory. Notepad allocates about 1-2MB of memory and the generated dump file has about 38MB (however, I also include the memory address for each byte and newlines).

Here's a small image that shows the outcome:

* spaces between chars (empty bytes) are caused by Notepad's usage of Unicode Encoding.

Image 1

How To

Whenever a process starts, the system allocates enough memory for its heap, stack and regions - however Windows won't allocate an 'entire block' of memory. It tries to allocate any free memory available for the User-Mode - so the allocated memory won't be contiguous. Basically, Windows won't tell us a range of addresses where we can find the program's data.

Image 2

So, the remaining solution is to scan almost every possible address (we get this using GetSystemInfo()) and check if it belongs to the target process (with VirtualQueryEx()): if it does, we read the values from there (ReadProcessMemory()).

Methods that will be required (including the ones above):

  1. GetSystemInfo()
    C#
    [DllImport("kernel32.dll")]
    static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);

    Retrieves random information about the system in a structure called SYSTEM_INFO. This structure also contains 2 variables: minimumApplicationAddress & maximumApplicationAddress which store the minimum and the maximum address where the system can allocate memory for User-Mode applications.

    SYSTEM_INFO looks like this:

    C#
    public struct SYSTEM_INFO
    {
        public ushort processorArchitecture;
        ushort reserved;
        public uint pageSize;
        public IntPtr minimumApplicationAddress;  // minimum address
        public IntPtr maximumApplicationAddress;  // maximum address
        public IntPtr activeProcessorMask;
        public uint numberOfProcessors;
        public uint processorType;
        public uint allocationGranularity;
        public ushort processorLevel;
        public ushort processorRevision;
    }
  2. VirtualQueryEx()
    C#
    [DllImport("kernel32.dll", SetLastError=true)]
    static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, 
    out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);

    This method gets information about a range of memory addresses and returns it into a structure named MEMORY_BASIC_INFORMATION. Given a minimum address, we use this to find out if there's a region of memory that's allocated by that program (this way, we reduce the search range by directly jumping over memory chunks). Basically, this method tells us the range of a memory chunk that starts from the specified address: in order to get to the next memory chunk, we add the length of this region to the current memory address (sum).

    Requires PROCESS_QUERY_INFORMATION.

    Image 3

    MEMORY_BASIC_INFORMATION must be defined this way:

    C#
    public struct MEMORY_BASIC_INFORMATION
    {
        public int BaseAddress;
        public int AllocationBase;
        public int AllocationProtect;
        public int RegionSize;   // size of the region allocated by the program
        public int State;   // check if allocated (MEM_COMMIT)
        public int Protect; // page protection (must be PAGE_READWRITE)
        public int lType;
    }
  3. ReadProcessMemory()
    C#
    [DllImport("kernel32.dll")]
    public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, 
    byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead); 

    Used to read a number of bytes starting from a specific memory address.

    Requires PROCESS_WM_READ.

  4. OpenProcess()
    C#
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); 

    Returns a handle to a specific process - the process must be opened with PROCESS_QUERY_INFORMATION and PROCESS_WM_READ.

Source Code

Once you understand what happens above, we can move to some code - but since there isn't much more to explain, I'll provide the whole source and cover what's left using comments.

C#
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace MemoryScanner
{
    class Program
    {
        // REQUIRED CONSTS

        const int PROCESS_QUERY_INFORMATION = 0x0400;
        const int MEM_COMMIT = 0x00001000;
        const int PAGE_READWRITE = 0x04;
        const int PROCESS_WM_READ = 0x0010;

        // REQUIRED METHODS

        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess
             (int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

        [DllImport("kernel32.dll")]
        public static extern bool ReadProcessMemory
        (int hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);

        [DllImport("kernel32.dll")]
        static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);

        [DllImport("kernel32.dll", SetLastError=true)]
        static extern int VirtualQueryEx(IntPtr hProcess, 
        IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);


        // REQUIRED STRUCTS

        public struct MEMORY_BASIC_INFORMATION
        {
            public int BaseAddress;
            public int AllocationBase;
            public int AllocationProtect;
            public int RegionSize;
            public int State;
            public int Protect;
            public int lType;
        }

        public struct SYSTEM_INFO
        {
            public ushort processorArchitecture;
            ushort reserved;
            public uint pageSize;
            public IntPtr minimumApplicationAddress;
            public IntPtr maximumApplicationAddress;
            public IntPtr activeProcessorMask;
            public uint numberOfProcessors;
            public uint processorType;
            public uint allocationGranularity;
            public ushort processorLevel;
            public ushort processorRevision;
        }        

        // finally...
        public static void Main()
        {
            // getting minimum & maximum address

            SYSTEM_INFO sys_info = new SYSTEM_INFO();
            GetSystemInfo(out sys_info);  

            IntPtr proc_min_address = sys_info.minimumApplicationAddress;
            IntPtr proc_max_address = sys_info.maximumApplicationAddress;

            // saving the values as long ints so I won't have to do a lot of casts later
            long proc_min_address_l = (long)proc_min_address;
            long proc_max_address_l = (long)proc_max_address;


            // notepad better be runnin'
            Process process = Process.GetProcessesByName("notepad")[0];

            // opening the process with desired access level
            IntPtr processHandle = 
            OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_WM_READ, false, process.Id);

            StreamWriter sw = new StreamWriter("dump.txt");

            // this will store any information we get from VirtualQueryEx()
            MEMORY_BASIC_INFORMATION mem_basic_info = new MEMORY_BASIC_INFORMATION();

            int bytesRead = 0;  // number of bytes read with ReadProcessMemory

            while (proc_min_address_l < proc_max_address_l)
            {
                // 28 = sizeof(MEMORY_BASIC_INFORMATION)
                VirtualQueryEx(processHandle, proc_min_address, out mem_basic_info, 28);
                
                // if this memory chunk is accessible
                if (mem_basic_info.Protect == 
                PAGE_READWRITE && mem_basic_info.State == MEM_COMMIT)
                {
                    byte[] buffer = new byte[mem_basic_info.RegionSize];

                    // read everything in the buffer above
                    ReadProcessMemory((int)processHandle, 
                    mem_basic_info.BaseAddress, buffer, mem_basic_info.RegionSize, ref bytesRead);

                    // then output this in the file
                    for (int i = 0; i < mem_basic_info.RegionSize; i++)
                        sw.WriteLine("0x{0} : {1}", 
                        (mem_basic_info.BaseAddress+i).ToString("X"), (char)buffer[i]);
                }

                // move to the next memory chunk
                proc_min_address_l += mem_basic_info.RegionSize;
                proc_min_address = new IntPtr(proc_min_address_l);
            }

            sw.Close();

            Console.ReadLine();
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
Romania Romania
Master's student @ ACS / UPB (Advanced Cybersecurity), Graduate Teaching Assistant, Junior Security Researcher. Also, webmaster of coding.vision

Comments and Discussions

 
SuggestionNote Pin
Mandai24-Apr-21 8:50
Mandai24-Apr-21 8:50 
QuestionDont work in 64bits Pin
Member 1323139830-May-17 6:57
Member 1323139830-May-17 6:57 
AnswerRe: Dont work in 64bits Pin
Member 1145979210-Oct-17 8:34
Member 1145979210-Oct-17 8:34 
QuestionDoubt???? Pin
Member 1313525018-Apr-17 0:22
Member 1313525018-Apr-17 0:22 
AnswerRe: Doubt???? Pin
Member 1094654815-Oct-19 3:03
Member 1094654815-Oct-19 3:03 
QuestionNot working Pin
Member 121270194-Mar-16 2:50
Member 121270194-Mar-16 2:50 
AnswerRe: Not working Pin
The Chaotic Void14-Oct-16 14:23
The Chaotic Void14-Oct-16 14:23 
GeneralRe: Not working Pin
Carlton Gannett11-Nov-16 8:53
Carlton Gannett11-Nov-16 8:53 
GeneralRe: Not working Pin
TheOnlyRealTodd26-Dec-16 1:14
professionalTheOnlyRealTodd26-Dec-16 1:14 
GeneralRe: Not working Pin
Mandai24-Apr-21 8:39
Mandai24-Apr-21 8:39 
GeneralMy vote of 3 Pin
A.Girish27-Jan-14 19:53
A.Girish27-Jan-14 19:53 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.