Click here to Skip to main content
15,880,405 members
Articles / Programming Languages / C#
Article

How to write a Memory Scanner using C#

Rate me:
Please Sign up or sign in to vote.
4.86/5 (67 votes)
23 Sep 2006CPOL8 min read 346.7K   16K   164   81
Search a process' memory to find specified 16, 32 or 64 bit data values.

Introduction

Have you ever had a problem completing a game because your health meter shows just 5% of your health available?
Have you ever used a game trainer to solve this problem?
Have you ever wondered how this trainer freezes your health meter to 100%?
These game trainers, do a simple job!
They write a little part of the Game's memory with the 100 value!
But how to find the exact part of memory?

The Question and the Answer

Q: How to find the location in which a program stores a value in its memory?
A: Some programs named 'Memory Scanners', are written to read and search a program's memory for exact location and help freezing it! (And I have written this article to show you, how to write a 'Memory Scanner')

Step 1: Where to begin?

Let's have a look at a program's memory.
Think that I have written a program and we can have a look at its memory.
To have a picture in mind, we can say, it looks something like this:

A look at the Memory - FirstLook.jpg

As you can see, the memory is made up of a huge number of small sectors, that hold a value in it. Our picture, just shows a small part of the memory, from sector 0 to sector 99 and a detail of sector 0 to sector 15. But as you know, a computer just knows the meaning of 0 and 1, so what do these Hexadecimal values mean?

Let's take a deep look at the memory again.

A look at the Memory Bits - Bits.jpg

As you can see, every byte is made up of 8 bits with each of them being just that 1 or 0, and in Binary mode, they can return the value stored in the byte((00010111)2 = (23)10 = (17)16).

As we saw, a computer's memory stores information by holding the 0 and 1s in the memory bits, and 8 bits of memory make a section of memory named byte, so a byte can hold values up to (11111111)2 = (255)10 = (FF)16, but how about the bigger values?
We usually work with values greater than 255!
Ok, the answer is that, we have bigger units of memory to hold the bigger values.

Let's look at another picture of memory that shows the bigger units and then, I will explain everything:

A look at the Memory Data Types - DataTypes.jpg

So we have 3 bigger memory units: 2 Bytes that make a 16 bit memory unit that we call 'short' in C#, 4 Bytes that make a 32 bit memory unit that we call it 'int' in C#, and 8 Bytes that make a 64 bit memory unit, and we call it 'long' in C#.

Step 2: What to look for?

Now that we have a simple picture of the memory in our mind, let's go back to the first picture:

Another look at the Memory - LookAgain.jpg

I know that, you are going to say: "Hey, it's just a row of bytes! How to find the memory units?" And then I'll tell you that, you asked the biggest question in writing a memory scanner!

Ok, let's think that it's a part of the memory of my program, and I know, where in the memory, I have stored the values and I will show you that:

Memory Units - MemoryUnits.jpg

As you see, a memory unit, can be stored in any part of the memory and start from any memory sector. In this program for example, I have stored a 32 bit value in the 0 sector, and because a 32 bit value takes 4 bytes of memory, from the sector 0 to sector 3 is assigned for a 32 bit variable in the program, and after that, from the sector 4, there is a 16 bit variable that takes 2 bytes of memory, next is a 64 bit variable and at the end, there is again a 16 bit variable.

Now let's think that the values of the sectors are the same, but the memory units start from different sectors:

The other Memory Units - TheOtherMemoryUnits.jpg

Why everything changed?
Because, a variable could be stored in any memory sector number, and most of the time, even the programmer doesn't know, where the variable is stored in the memory, and just the program knows it!

Step 3: Where to find it?

Now, let's think that we are playing a game, and the health meter shows 83%, and we don't know the location of the variable in the memory and we want to find the variable and we start from sector 0, so the memory looks like this:

Game mamory - GameMemory.jpg

So what? Is there any 83 in the memory?
First, we know that we have the hexadecimal values of the memory bytes. Second, we should guess the variable type to look for.

Ok, let's say that the programmers of the game have used a 32 bit (int) variable, that is the most usual data type being used for storing the value of the health meter. So the value is stored in a 4 bytes long part of the memory. But, how to find it?
The only way to search the memory completely, is to start from the beginning, take 4 bytes, test them to see if the value equals our digit (here 83), and find the location. Like this:

Memory Search - Animation_1.gif

Memory Search - Animation_2.gif

Memory Search - Animation_3.gif

Memory Search - Animation_4.gif

Memory Search - Animation_5.gif

Memory Search - Animation_6.gif

Ok, now you know the main concept of memory scanning, but there are some other things that you should know to be able to write the Memory scanner:

  1. Q: How long is a program's memory? (Where to begin and where to stop?)
    A: As you know, Microsoft's first OS was DOS that was a 8 bit OS, after that, the Windows 3.1 became a 16 bit OS, and after that, the Windows OS became a 32 bit OS. (I'm a real fan of Apple Co. that developed the Apple Macintosh OS, a 64 bit OS, exactly when Microsoft was working on DOS (a 8 bit OS) and today, Microsoft is going to write a 64 bit Windows (and like the first Windows versions, it still looks like the Apple OSs) but, I still recommend Apple MacOS X (Ver. 10)).
    So, in the DOS OS that was a 8 bit OS, programmers named 8 bits of memory, a "Byte". After that, When the Windows 3.1 OS was a 16 bit OS, they named 16 bits of memory(2 Bytes) a "WORD", and 32 bits of memory(4 Bytes) a "DWORD"(Double Word) and 64 bits of memory(8 Bytes) a "QWORD"(Quad Word).
    As I experienced, the length of every program's memory in Windows XP, is from "0x00000000" to the maximum value of a "Int"("DWORD"), and equals to "0x7FFFFFFF".
    I'm not sure, but I guess, it's because of that, the Windows is a 32 bit OS and the main memory unit for it, is a 32 bit memory unit, and so, the length of a program's memory, is the maximum value of a 32 bit memory unit!
    Ok. So, we should start our search from "0x0000000" to "0x7FFFFFFF".
  2. Q: Is the first found memory address, the exact answer of our search?
    A: No! As you can see, there are "0x7FFFFFFF" sectors, and when you search it for a value like 83, you could find so many of them. So you need to hold the memory addresses and wait for the values to be changed. Then search the addresses you have, for the new value, and do this, until you find, just one memory address that matches your value.
  3. Q: How to read a program's memory and search it?
    A: There are some functions in Windows API that make it possible for us to read and write the memory, from another program.
    Thanks goes to "Arik Poznanski" for P/Invokes and methods needed to read and write the Memory, I just used his classes to do this and didn't do the P/Invokes myself.
    You can search Codeproject.com, for "Minesweeper, Behind the scenes", to find his comments about these classes.
  4. Q: How to convert these bytes to a 16, 32 or 64 bit value?
    A: For this, we used .NET goods! There is a class with static methods that does this for us:

    C#
    byte[] bytes = new byte[] { 0x53, 0x00, 0x00, 0x00 };
    int value = System.BitConverter.ToInt32(bytes, 0);
    //The value is 83

Step 4: Let's do the final job

Ok, now you have all the information you need to write the memory scanner.
You just need to do these in your code:

  1. Select a process to scan its memory.
  2. Scan the whole memory for the specified value and hold the addresses.
  3. Wait for the value to be changed and search the memory address list that you got from the first scan and again wait for the value to be changed and scan again, and do this until you find just the address that matches the value.
  4. At the end, you can freeze the address with a new value, by using a timer to write the memory in every timer's tick.

Comments on my classes

If you download my code, you will find them with comments for every command and every line of code. But there it a little thing I should explain:

ReadProcessMemory(IntPtr MemoryAddress, uint bytesToRead, out int bytesRead) that is the most important function, returns an empty bytes array if reading your request's size is too big! So I had to read the memory in parts as big as 20480 bytes (20KB), and because, when you are searching these memory parts, byte by byte, at the end of the bytes array, there will be some bytes left! (for example, 3 bytes will be left when you are searching for 32 bit values)!

So the solution I used was this:

C#
if (/*scan requirement is less than 20480 bytes*/)
{
    //Read the memory at once
}
else
{
    /*Loop through blocks(of length 20480 bytes),
    until the whole memory is read;

    After the first loop, move the current address to
    [Data type bytes count - 1] steps back in the memory,
    to fix the previously told problem;

    After the loops, check to see if any other memory addresses
    are left outside of the loops and if so, read them;*/
}

You can see it:

Memory Search solution - Fina_Animation_0.gif

Memory Search solution - Fina_Animation_1.gif

Memory Search solution - Fina_Animation_2.gif

Memory Search solution - Fina_Animation_3.gif

Memory Search solution - Fina_Animation_4.gif

Memory Search solution - Fina_Animation_5.gif

Memory Search solution - Fina_Animation_6.gif

Memory Search solution - Fina_Animation_7.gif

Memory Search solution - Fina_Animation_8.gif

Memory Search solution - Fina_Animation_9.gif

Memory Search solution - Fina_Animation_10.gif

That's all

Ok folks, that's all! Hope you like and enjoy it!
And now I'm working on a Enhanced Memory Scanner that could scan all types of data, including the Bytes, Signed Data Types and even Strings, it just takes some time!
I'll be back soon.

License

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


Written By
Founder Sojaner AB
Sweden Sweden
UX designer and full stack developer mainly focused on .NET technologies.
Currently loving .NET Core 2.0.

Comments and Discussions

 
QuestionCharacters and Strings? Pin
Kasperlitheater30-Jul-09 14:35
Kasperlitheater30-Jul-09 14:35 
AnswerRe: Characters and Strings? Pin
Jordanwb27-Oct-09 16:27
Jordanwb27-Oct-09 16:27 
AnswerRe: Characters and Strings? Pin
dzCepheus8-Oct-10 8:22
dzCepheus8-Oct-10 8:22 
GeneralSOJANER I NEED YOUR HELP :) Pin
Goor23-Jul-09 10:29
Goor23-Jul-09 10:29 
AnswerRe: SOJANER I NEED YOUR HELP :) Pin
Rojan Gh.29-Jul-09 21:45
professionalRojan Gh.29-Jul-09 21:45 
GeneralReadProcessMemory fails when pointing at protected memory Pin
The_Mega_ZZTer16-Nov-07 17:03
The_Mega_ZZTer16-Nov-07 17:03 
GeneralSolution! Pin
The_Mega_ZZTer17-Nov-07 15:08
The_Mega_ZZTer17-Nov-07 15:08 
I finally found VirtualQueryEx, which can be used for this exact purpose.

Here is my full solution... I will use VB.NET code, but you can get C#.NET equivilants of all my Windows API function declarations at pinvoke.net.

STEP 1: We determine which areas of a virtual memory block an application can reside in. Going outside that block can result in VirtualQueryEx throwing errors if we query a kernel area. This is easy enough to fix.

Public Declare Sub GetSystemInfo Lib "kernel32.dll" (ByRef lpSystemInfo As SYSTEM_INFO)

We use this function to get this information, and the following one wraps it up nicely.

<br />
    Public Shared Function GetAppMemoryRange() As MZZT.MemoryRange<br />
        If _appmem Is Nothing Then<br />
            Dim si As SYSTEM_INFO<br />
            GetSystemInfo(si)<br />
            _appmem = New MZZT.MemoryRange(si.lpMinimumApplicationAddress, _<br />
                 si.lpMaximumApplicationAddress)<br />
        End If<br />
<br />
        Return _appmem<br />
    End Function<br />
    Private Shared _appmem As MZZT.MemoryRange = Nothing


You will notice an MZZT.MemoryRange class. It is simple enough that I don't think I need to explain it here, but I designed it to hold a 32-bit memory range (a start, and an end) which you can see I use to store the maximum range it is safe to use VirtualQueryEx on.

You can get the MemoryRange class at the end of this post if you really really want it.

Next we need some declarations for VirtualQueryEx and helper structures and enumerations:

    Public Declare Function VirtualQueryEx Lib "kernel32.dll" (ByVal hProcess As IntPtr, _<br />
        ByVal lpAddress As UIntPtr, ByRef lpBuffer As MEMORY_BASIC_INFORMATION, ByVal _<br />
        dwLength As UInteger) As UInteger<br />
 <br />
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential)> _<br />
    Public Structure MEMORY_BASIC_INFORMATION<br />
        Public BaseAddress As UIntPtr<br />
        Public AllocationBase As UIntPtr<br />
        Public AllocationProtect As PAGE<br />
        Public RegionSize As UInteger<br />
        Public State As MEM<br />
        Public Protect As PAGE<br />
        Public Type As Integer<br />
    End Structure<br />
<br />
    Public Enum PAGE As Integer<br />
        EXECUTE = &H10<br />
        EXECUTE_READ = &H20<br />
        EXECUTE_READWRITE = &H40<br />
        EXECUTE_WRITECOPY = &H80<br />
        NOACCESS = &H1<br />
        [READONLY] = &H2<br />
        READWRITE = &H4<br />
        WRITECOPY = &H8<br />
<br />
        GUARD = &H100<br />
        NOCACHE = &H200<br />
        WRITECOMBINE = &H400<br />
<br />
        ANYREAD = &H66<br />
        ANYWRITE = &HCC<br />
    End Enum<br />
<br />
    Public Enum MEM As Integer<br />
        COMMIT = &H1000<br />
        FREE = &H10000<br />
        RESERVE = &H2000<br />
    End Enum


Now we will make a few small helper functions:

<br />
        Private Function CanRead(ByVal mbi As WinAPI.MEMORY_BASIC_INFORMATION) As Boolean<br />
            If mbi.Protect And WinAPI.PAGE.GUARD Then<br />
                Return False<br />
            End If<br />
<br />
            If mbi.State <> WinAPI.MEM.COMMIT Then<br />
                Return False<br />
            End If<br />
<br />
            Return True<br />
        End Function<br />
<br />
        Private Function CanWrite(ByVal mbi As WinAPI.MEMORY_BASIC_INFORMATION) As Boolean<br />
            If mbi.Protect And WinAPI.PAGE.GUARD Then<br />
                Return False<br />
            End If<br />
<br />
            If mbi.State <> WinAPI.MEM.COMMIT Then<br />
                Return False<br />
            End If<br />
<br />
            Return True<br />
        End Function<br />
<br />
        Private Function GetMBIRange(ByVal mbi As WinAPI.MEMORY_BASIC_INFORMATION) As _<br />
            MemoryRange<br />
<br />
            Return New MemoryRange(mbi.BaseAddress, CDec(mbi.BaseAddress) + _<br />
                CDec(mbi.RegionSize) - 1)<br />
        End Function


More use of my secret class. Wink | ;)

CanRead determines from the information obtained through VirtualQueryEx (which is an mbi struct) whether or not we will be able to read it with ReadProcessMemory. CanWrite will do the same for WriteProcesMemory... right now I haven't made CanWrite work since I'm not concerned with writing memory at the moment. It will likely need a flag check for a WRITE attribute. GetMBIRange turns the mbi into a MemoryRange object.

Now the main function, which takes a MemoryRange and returns an array of MemoryRanges which are not protected. So we would pass the Range we want to read, and then iterate through the resulting array and read each MemoryRange one at a time.

        Public Function FindUnprotectedMemory(ByVal range As MemoryRange, ByVal writable As _<br />
            Boolean) As MemoryRange()<br />
<br />
            range = range.Intersect(WinAPI.GetAppMemoryRange)


My MemoryRange class has an intersect function which works like the one on rectangle.. it returns the area the two MemoryRanges share in common. In this case, it clips of areas in range which do not belong to the application, since we already know we can't read those.

If range Is Nothing Then<br />
    Return Nothing<br />
End If


Intersect returns null if the MemoryRanges have nothing in common.

            Dim a As New ArrayList<br />
            Dim mbi As WinAPI.MEMORY_BASIC_INFORMATION<br />
            Dim pos As UInteger = range.RangeStart<br />
            Dim applies As MemoryRange<br />
            Dim access As Boolean<br />
<br />
            While pos <= range.RangeEnd


pos records the current memory address we are inspecting with VirtualQueryEx. Once we pass range we are done and can quit.

                If Not WinAPI.VirtualQueryEx(_handle, pos, mbi, _<br />
                    Runtime.InteropServices.Marshal.SizeOf(mbi)) Then<br />
<br />
                    Dim ex As New System.ComponentModel.Win32Exception<br />
                    If ex.NativeErrorCode <> 0 Then<br />
                        Throw ex<br />
                    End If<br />
                End If


Oddly, VirtualQueryEx returns 0 all the time, even though the MSDN docs say it should only do so on error. I put in an extra error check.

applies = GetMBIRange(mbi).Intersect(range)

It's possible for VirtualQueryEx to return an area which extends before or after our block. This clips it to the block we are interested in.

If writable Then<br />
    access = CanWrite(mbi)<br />
Else<br />
    access = CanRead(mbi)<br />
End If


The actual test to see if we can read/write.

If access Then<br />
    a.Add(applies)<br />
End If


Add it to our master list of addresses that are not protected.

    pos = applies.RangeEnd + 1<br />
End While


Move on to the next memory page/block.

            If a.Count = 0 Then<br />
                Return Nothing<br />
            End If<br />
<br />
            Return a.ToArray(GetType(MemoryRange))<br />
        End Function


Return our array, or null if the entire thing is protected.



PS: Now that ReadProcessMemory should no longer be returning protected memory errors, I altered my function accordingly:

<br />
        Public Function ReadMemory(ByVal range As MemoryRange) As Byte()<br />
            Dim read As UInteger = 0<br />
            Dim buffer(range.RangeSize - 1) As Byte<br />
            If Not WinAPI.ReadProcessMemory(_handle, range.RangeStart, buffer, _<br />
                range.RangeSize, read) Then<br />
<br />
                Throw New System.ComponentModel.Win32Exception<br />
            End If<br />
<br />
            If read = 0 Then<br />
                Return Nothing<br />
            End If<br />
<br />
            If read <> range.RangeSize Then<br />
                Array.Resize(Of Byte)(buffer, read)<br />
            End If<br />
            Return buffer<br />
        End Function


I did not make much changes apart from the exception. I was going to integrate VirtualQueryEx right into ReadMemory, but then I realized my calling function would still need the array of unprotected addresses. So I let the calling function handle the masking out of protected addresses and kept ReadMemory simple.

PPS: Here is my MemoryRange class:

Namespace MZZT<br />
    Public Class MemoryRange<br />
        Implements ICloneable<br />
<br />
        Public Sub New(ByVal address As UInteger)<br />
            _start = address<br />
            _end = address<br />
        End Sub<br />
<br />
        Public Sub New(ByVal rangestart As UInteger, ByVal rangeend As UInteger)<br />
            _start = rangestart<br />
            _end = rangeend<br />
        End Sub<br />
<br />
        Public Property RangeStart() As UInteger<br />
            Get<br />
                Return _start<br />
            End Get<br />
            Set(ByVal value As UInteger)<br />
                _start = value<br />
            End Set<br />
        End Property<br />
        Private _start As UInteger<br />
<br />
        Public Property RangeEnd() As UInteger<br />
            Get<br />
                Return _end<br />
            End Get<br />
            Set(ByVal value As UInteger)<br />
                _end = value<br />
            End Set<br />
        End Property<br />
        Private _end As UInteger<br />
<br />
        Public ReadOnly Property RangeSize() As UInteger<br />
            Get<br />
                Return _end - _start + 1<br />
            End Get<br />
        End Property<br />
<br />
        Public Function Intersect(ByVal r As MemoryRange) As MemoryRange<br />
            If _start > r.RangeEnd OrElse r.RangeStart > _end Then<br />
                Return Nothing<br />
            End If<br />
<br />
            If _start >= r.RangeStart Then<br />
                If _end <= r.RangeEnd Then<br />
                    Return Clone()<br />
                Else<br />
                    Return New MemoryRange(_start, r.RangeEnd)<br />
                End If<br />
            Else<br />
                If r.RangeEnd <= _end Then<br />
                    Return r.Clone<br />
                Else<br />
                    Return New MemoryRange(r.RangeStart, _end)<br />
                End If<br />
            End If<br />
        End Function<br />
<br />
        Public Function Clone() As Object Implements System.ICloneable.Clone<br />
            Return New MemoryRange(_start, _end)<br />
        End Function<br />
    End Class<br />
End Namespace



-- modified at 21:13 Saturday 17th November, 2007
QuestionProblems with finding a value Pin
mathg15-Oct-07 8:58
mathg15-Oct-07 8:58 
GeneralRe: Brilliant thinking Pin
Rojan Gh.6-Aug-07 10:14
professionalRojan Gh.6-Aug-07 10:14 
Generalanother memory scanner Pin
skumria30-Jul-07 1:22
skumria30-Jul-07 1:22 
GeneralReally useful Pin
MrKalinka11-Jul-07 7:53
MrKalinka11-Jul-07 7:53 
Generalcool~ Pin
nancun30-May-07 17:33
nancun30-May-07 17:33 
GeneralReally Nice! Pin
Coksnuss9-Feb-07 5:03
Coksnuss9-Feb-07 5:03 
GeneralRe: Really Nice! Pin
Rojan Gh.18-Feb-07 0:15
professionalRojan Gh.18-Feb-07 0:15 
Generalscan game memory fail Pin
flash.lin1-Feb-07 14:18
flash.lin1-Feb-07 14:18 
GeneralRe: scan game memory fail Pin
Rojan Gh.18-Feb-07 0:13
professionalRojan Gh.18-Feb-07 0:13 
GeneralCloseHandle Error Pin
ic3dwabit21-Dec-06 15:25
ic3dwabit21-Dec-06 15:25 
GeneralRe: CloseHandle Error Pin
Rojan Gh.29-Dec-06 13:05
professionalRojan Gh.29-Dec-06 13:05 
GeneralRe: CloseHandle Error Pin
ic3dwabit8-Jan-07 22:09
ic3dwabit8-Jan-07 22:09 
QuestionError 299 Pin
Hall Maru28-Oct-06 3:15
Hall Maru28-Oct-06 3:15 
AnswerRe: Error 299 Pin
Rojan Gh.29-Dec-06 13:08
professionalRojan Gh.29-Dec-06 13:08 
AnswerRe: Error 299 [modified] Pin
The_Mega_ZZTer16-Nov-07 15:55
The_Mega_ZZTer16-Nov-07 15:55 
GeneralGreat! Pin
AdidasSG228-Sep-06 11:23
AdidasSG228-Sep-06 11:23 
GeneralRe: Great! Pin
Rojan Gh.28-Sep-06 13:29
professionalRojan Gh.28-Sep-06 13:29 
Generalanother way.... Pin
boommann27-Sep-06 3:53
boommann27-Sep-06 3: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.