vb.net ReadProcessMemory part1

Posts 113 of 13 · Page 1 of 1
vb.net ReadProcessMemory part1
Not sure if this should be here or in 'programming tutorials' but there still seem to be a lot of people who have trouble with it, so.

(I posted this at another forum, re-posting here)

The goal
To be able to read any memory address inside another process, like such.
Code:
  Dim ourValue as Int32 = 0
  ourValue = MemoryReader.ReadInt32(0x12345678)
  Dim ourValue2 as Int64 = 0
  ourValue2 = MemoryReader.ReadInt64(0x11223344)
  Dim ourBytes() as Byte
  ourBytes = MemoryReader.ReadBytes(0x1F00FF00,100) '100 = length to read
Basic Assumptions
I assume you can already program in .Net! That means variables,functions,classes. You feel comfortable with the OOP concepts of vb.net. If you don't know where to declare a variable so your program has access to it, I can't help you. I'm not going to specifically say when to create a class, what to name the files, etc. This guide is for those people who are really comfortable in .Net and have a basic conceptual understanding of what they are trying to do. I had quite hard time myself getting all of this to work (the ins-and-outs of OpenProcess, the mbi.Protect etc), and now that it does work, maybe the final results will help you avoid some frustration.
Before we get started
I AM NOT an expert. Some of my information may be incorrect or misleading. Trust content at own risk.

Some Background Information
Windows(R) Memory Management
Basically windows keeps track of a lot of complicated stuff for us, and as far as we care
each process (on Windows XP! 32bit) gets it's own memory range of 0-&FFFFFFF (32bit--4 gigs). This isn't possible. If the machine only has 4 gigs of ram, how can each process get 4 gigs? In reality it can't, and if each process actually used that much, your system would stop. But as far as each process is concerned, in theory, it can access 0-0xFFFFFFFF.

Windows API (I won't explain what they are, only declare them)
ReadProcessMemory() is the awesome API which actually lets us read into another processes ram. But, before we can just go reading away, there is a bit of security first. Basically, not just any program can/should/has any reason to, read the ram of another process. So before we can do that, we have to use another windows API, OpenProcess, which gives us special access rights (kind of). You probably have to be an administrator on the computer for this to work? (todo: experiment, if you care)
OpenProcess(). You pass in a reference to the desired process, and it returns a handle to that process which you can use for ReadProcessMemory (and WriteProcessMemory!) Be sure to call CloseHandle() when you're done with the handle, because it's an OS resource and might lead to mem leak.
Da Code
Code:
    Private Declare Function OpenProcess Lib "kernel32.dll" (ByVal dwDesiredAcess As UInt32, ByVal bInheritHandle As Boolean, ByVal dwProcessId As Int32) As IntPtr
    Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer() As Byte, ByVal iSize As Integer, ByRef lpNumberOfBytesRead As Integer) As Boolean
    Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As IntPtr) As Boolean
So now that we have our APIs declared. Let's start using them! But first thing first. Which process do we want to 'read from' and how do we define it in vb.net? Thank goodness there is already a 'Process' class in .Net! The way I find the process is by using .Net functions to find it's main Window based on Title. There are some more complicated scenerios, such as, what if the process doesn't have a window being displayed?! I'm not going into that here.
There are several options are far as how we select(find) our target process. Two convenient .Net functions.
Process.GetProcesses() Returns an array of every process currently running.
Process.GetProcessByName() Tries to find process by name. Funny thing is, since you can run a program multiple times, this returns an array!
I chose .GetProcesses because of convenience (discovering process name vs window title - which seems easier)
It returns an array of all processes currently running. We loop over the array,comparing the Process's .MainWindowTitle until we find our process.
A note about OpenProcess(). You can actually ask for different access rights. The two we care about: READ and WRITE. In my code
I use PROCESS_ALL_ACCESS (which is Read,Write + some) but this value changes depending on what version of Windows you're on. If all you
plan to do is read, VM_READ should work. This code is made specifically to run on Windows Xp 32 bit. Any other OS might fail.


I actually overload my function so there are 2 ways to call it. I only explicitly use the 'window caption' version atm.
Code:
Public Class MemoryManager
Private _targetProcess as Process = Nothing 'to keep track of it. not used yet.
Private _targetProcessHandle as IntPtr = Intptr.Zero 'Used for ReadProcessMemory
Private PROCESS_ALL_ACCESS as UInt32 = &H1F0FFF
Private PROCESS_VM_READ as Uint32 = &H10
    Public Function TryAttachToProcess(ByVal windowCaption As String) As Boolean
        Dim _allProcesses() As Process = Process.GetProcesses
        For Each pp As Process In _allProcesses
            If pp.MainWindowTitle.ToLower.Contains(windowCaption.ToLower) Then
                'found it! proceed.
                Return TryAttachToProcess(pp)
            End If
        Next
        MessageBox.Show("Unable to find process '" & windowCaption & ".' Is running?")
        Return False
    End Function

    Public Function TryAttachToProcess(ByVal proc As Process) As Boolean
        If _targetProcessHandle = IntPtr.Zero Then 'not already attached
            _targetProcess = proc
            _targetProcessHandle = OpenProcess(ReadProcessMemoryRights.PROCESS_ALL_ACCESS, False, _targetProcess.Id)
            If _targetProcessHandle = 0 Then
                TryAttachToProcess = False
                MessageBox.Show("OpenProcess() FAIL! Are you Administrator??")
            Else
                'if we get here, all connected and ready to use ReadProcessMemory()
                TryAttachToProcess = True
                MessageBox.Show("OpenProcess() OK")
            End If
        Else
            MessageBox.Show("Already attached! (Please Detach first?)")
            TryAttachToProcess = False
        End If
    End Function

    Public Sub DetachFromProcess()
        If Not (_targetProcessHandle = IntPtr.Zero) Then
            _targetProcess = Nothing
            Try
                CloseHandle(_targetProcessHandle)
                _targetProcessHandle = IntPtr.Zero
                MessageBox.Show("MemReader::Detach() OK")
            Catch ex As Exception
                MessageBox.Show("MemoryManager::DetachFromProcess::CloseHandle error " & Environment.NewLine & ex.Message)
            End Try
        End If
    End Sub

End Class
I guess I should explain something before I continue too much further. We have a choice. Call OpenProcess() every time we want to ReadProcessMemory (which, if we're planning on reading dozens of values every second, might not be efficient) or call it once and store the value returned, the handle, in our class. This seems ideal, but means we must verify we have attached to the process before trying to read, which can sometimes be a pain. The other option is to add an extra parameter to your Read() functions, and make each function attach before it reads. Since I'm planning on doing a lot of ReadProcessMemory calls, I took the route of 'attaching' once, and storing the handle. Maybe later we can add shared methods so MemoryManager.ReadInt32(aProcess,0x1234) is possible.

ReadProcessMemory()
ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer(), iSize,ByRef lpNumberOfBytesRead) As Boolean
[pre]
hProcess The process you want to read from. Duh. -- handle returned by OpenProcess()
lpBaseAddress The address you want to read (or start reading at) -- you can read as many bytes as you want. not just 1 address at a time!
lpBuffer The byte array where ReadProcessMemory stores the data it reads
iSize How many bytes to read
lpNumberOfBytesRead Actual number of bytes read (if this isn't == iSize, there was a partial error!)

As Boolean The function tells you if it succeeded or not.

demo. (no error checking)
Code:
Dim _myBytes(3) as Byte
_myBytes = ReadProcessMemory(_targetHandle,&H112233,_myBytes,4,vbNull) 'fills _myBytes with data stored at 0x112233, 0x112234, 0x112235, 0x112236
note. lpBuffer needs to be declared the correct size! ReadProcessMemory won't resize it for you.
If it's too small, it will return as normal and not throw any errors!
[/pre]

---------- Post added at 01:43 AM ---------- Previous post was at 01:40 AM ----------

The Goal
To verify that our MemoryManager code can, in fact, read another process's memory.
We will be using Cheat Engine (a memory scanner) to find addresses and to verify the data returned from our functions is correct.

Basically Cheat Engine scans all memory being used by a program. and can search it for a value. So using CE we can actually learn
the address where pinball stores our score! (note: this address can change each time you run pinball, we'll worry about that later if need be)
(crude demo on how to find memory address with Cheat Engine)
[pre]
-Open pinball. Play some. Make youre score go above 0
-Pause the game (F3) -- your score value needs to not change while CE scans
-Open Cheat Engine
-Attach CE to pinball
-Leave all options to default (4 bytes,range, etc)
-Do a first scan for your score
[/pre]
On my system, CE only finds the score stored at 1 location. If your score was, for example, 100, it probably would have found that value at a LOT more locations. Try changing your score and scanning again (new scan). If that doesn't work, read a tutorial on 'how to find addresses with Cheat Engine' ( 1) find all possible locs, 2)change your score, 3)see if those possible locs still == your score. CE will help you do this.) For pinball, there should only be 1 address. Either way, we're just trying to make sure our MemoryManager can actually read data. Any address could do: I chose reading pinball's score because it gives some "oh, I see it" satisfaction.



So, now we have an address to read! In my case 0x00AC4E4C (yours may/should be different, that's ok)

Back to the code. We haven't actually made a function in the MemoryManager class which can read data yet. We have a decision to make. There are actually two major design strategies we can use here: we can have lots of functions, one named for each data type we will read from ram. So our class will have ReadInt32(), ReadUint32(), ReadInt64(), ReadUint64 etc. Or, the other approach is create one function (or, prototype??) which lets you pass in what data type you want to read. For example, MemManager.ReadValue(0x123456,Int32) (This can only work with POD(?) types, which have a constant size in memory) You would want to do this, maybe, to read a custom structure from ram instead of just a plain old built-in type. It's part of
some new language features(function prototyping?) put into VB.Net. To be honest, I'm not really sure, and it's not something I've read about yet. I chose to instead, explicitly create a bunch of functions, each one made to read a specific data type. This means a LOT or duplicate code. Which isn't good. Keep that in mind.


MemoryManager class. The first Read() function. ReadInt32()
Code:
    Public Function ReadInt32(Byval addr as IntPtr) As Int32
	'Int32 data type-- is 32 bits long, 4 bytes.
        Dim _dataBytes(3) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
        Return BitConverter.ToInt32(_bytes, 0)
    End Function
[pre]
A couple things to note about the function
No error checking
lpBytesRead = vbNull ? This is used to know if RPM was actually able to read all data. If RPM only
read some of the memory locations, _bytesRead will be < iSize (in previous example, iSize=4)
If we pass in vbNull,because the parameter is ByRef, basically it's saying, I don't care, I won't use this value in my code.\
BitConverter A handy .Net class which converts an array of bytes to many core data types
[/pre]
error checking
Code:
Public Function ReadInt32(Byval addr as IntPtr) As Int32
	Dim _dataBytes(3) As Byte
	Dim _actualReadLength as Int32 = 0
        If ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, _actualReadLength) Then
	   'success
	If _actualReadLength = 4 Then
	   'all bytes read
	   Return BitConverter.ToInt32(_bytes, 0)
	Else
	'ReadProcessMemory only able to read *some* of the memory location. 
	Return 0 'or handle byte array at own risk? [should have been zero filled on creation?] + [some valid data?]
	Else
	'ReadProcessMemory returned FAIL. Possibly ram is inaccessable.
	Return 0
    End Function
The second Read() function. ReadInt64()
Code:
    Public Function ReadInt32(Byval addr as IntPtr) As Int32
        Dim _dataBytes(3) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
        Return BitConverter.ToInt32(_bytes, 0)
    End Function
    Public Function ReadInt64(Byval addr as IntPtr) As Int64
        Dim _dataBytes(7) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 8, vbNull)
        Return BitConverter.ToInt64(_bytes, 0)
    End Function
Side Note
Int32 and Uint32 are both 32 bits, 4 bytes, in length. You need to understand how signed and unsigned works! That's too much to explain inside another tutorial. I HIGHLY recomend physically writing on paper numbers in binary,decimal,hex..lining them up in different ways, and try to feel comfortable looking at numbers in different formats. This helps especially when debugging. For example, I kept reading a pointer and outputting it using .ToString(). Well I was getting a number like 110347892 instead of the hex address Cheat Engine was showing me. It was because .ToString() was displaying it in base 10, which is the default, we all use grew up using base 10. Fortunately, .ToString() can take a formatting parameter where it will convert it to hex for us! (and there are other .Net functions for converting bases). The point is that you need to understand numbers.
Tip. In vb.net hex numbers start with &H <-- get it..H, pretty obvious. In other languages the prefix is 0x. Sometimes you'll see me refer to a memory loc as 0x123456. In vb it would be &H123456 and in decimal it would be..you tell me.

Another Read() Function. This time UnSigned (if that should really matter..) Hooray BitCoverter class.
Code:
    Public Function ReadInt32(Byval addr as IntPtr) As Int32
        Dim _dataBytes(3) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
        Return BitConverter.ToInt32(_bytes, 0)
    End Function
    Public Function ReadInt64(Byval addr as IntPtr) As Int64
        Dim _dataBytes(7) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 8, vbNull)
        Return BitConverter.ToInt64(_bytes, 0)
    End Function
    Public Function ReadUInt32(Byval addr as IntPtr) As UInt32
        Dim _dataBytes(3) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _dataBytes, 4, vbNull)
        Return BitConverter.ToUInt32(_bytes, 0)
    End Function
Until you have one for each data type you might read
Code:
Public Function ReadInt16(ByVal addr As IntPtr) As Int16
        Dim _rtnBytes(1) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 2, vbNull)
        Return BitConverter.ToInt16(_rtnBytes, 0)
    End Function
    Public Function ReadInt32(ByVal addr As IntPtr) As Int32
        Dim _rtnBytes(3) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 4, vbNull)

        Return BitConverter.ToInt32(_rtnBytes, 0)
    End Function
    Public Function ReadInt64(ByVal addr As IntPtr) As Int64
        Dim _rtnBytes(7) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 8, vbNull)
        Return BitConverter.ToInt64(_rtnBytes, 0)
    End Function
    Public Function ReadUInt16(ByVal addr As IntPtr) As UInt16
        Dim _rtnBytes(1) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 2, vbNull)
        Return BitConverter.ToUInt16(_rtnBytes, 0)
    End Function
    Public Function ReadUInt32(ByVal addr As IntPtr) As UInt32
        Dim _rtnBytes(3) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 4, vbNull)
        Return BitConverter.ToUInt32(_rtnBytes, 0)
    End Function
    Public Function ReadUInt64(ByVal addr As IntPtr) As UInt64
        Dim _rtnBytes(7) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 8, vbNull)
        Return BitConverter.ToUInt64(_rtnBytes, 0)
    End Function
    Public Function ReadFloat(ByVal addr As IntPtr) As Single
        Dim _rtnBytes(3) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 4, vbNull)
        Return BitConverter.ToSingle(_rtnBytes, 0)
    End Function
    Public Function ReadDouble(ByVal addr As IntPtr) As Double
        Dim _rtnBytes(7) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, 8, vbNull)
        Return BitConverter.ToDouble(_rtnBytes, 0)
    End Function
    Public Function ReadIntPtr(ByVal addr As IntPtr) As IntPtr
        Dim _rtnBytes(IntPtr.Size - 1) As Byte
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, IntPtr.Size, Nothing)
            If IntPtr.Size = 4 Then
                Return New IntPtr(BitConverter.ToUInt32(_rtnBytes, 0))
            Else
                Return New IntPtr(BitConverter.ToInt64(_rtnBytes, 0))
            End If
    End Function
    Public Function ReadBytes(ByVal addr As IntPtr, ByVal size As Int32) As Byte()
        Dim _rtnBytes(size - 1) As Byte 
        ReadProcessMemory(_targetProcessHandle, addr, _rtnBytes, size, vbNull)
        Return _rtnBytes
    End Function
I also added one for IntPtr (which uses IntPtr.Size -- 32/64 problems ahead..) and one for ReadBytes. And forgot Boolean I hope they make sense. We'll be using ReadBytes() much later to scan the entire memory range because it's more efficient to read a large chunk of ram vs. 4 bytes at a time. It's the difference between calling ReadProcessMemory 10,000 times and 10,000,000 times...
I guess I forgot ReadByte(), singular. I don't see myself using it. If so desired, you should* be able to create it your self.

Back to actually testing if any of this works. I assume you've created a new project, and the MemoryManager class. And added all our new read functions.
Create a variable of the MemoryManager class. (Top of your form class? For demo purposes. I won't go into variable scoping) and use it!
Code:
Public Class Form1
Private _memManager as New MemoryManager 'New word is important! -I'm not explaining this.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If _memManager.TryAttachToProcess("3D Pinball") Then
  Dim _myScore as Int32 = _memManager.ReadInt32(&HAC4E4C)
   ' Done using MemoryManager for reading. Close it.
     _memManager.DetachFromProcess()
  MessageBox.Show("Your current 3D Pinball score is: " & _myScore.ToString())
Else
  MessageBox.Show("MemoryManager unable to attach to 3D Pinball for ram reading")
End If
End Sub

End Class

That's it. You can now go poking around, trying to read the memory of different programs. But please don't.

-see pt. 2
Nice tutorial. I'm not that experienced in this field, but this will help a lot
Thank you. again, not all the info is 100% accurate, but, as you said, for a person 'new to this subject', it may be helpful.
Credits to the original author? Don't tell me its yours because I've seen the quality of your English already.
http://wwwdot*********dotcom





my use of double quotes, quotes, ()'s, []'s, CAPS. I'z has a very....unique writing pattern. :/ esTeeEphYoo

(I have orig. saved in .txt + pics, if you'd care for me to post that as well..)

================================================== ========


I DO expect an apology. You have 0 proof and you were wrong.


================================================== ========
Looks legit to me..
I'm an ass, but I do not lie brotha.


edit:

^^ 1 minute ago, literally. 8:30 am my time. No reply to my pm. I guess he's doing research, now. ?
Good job posting this, fairly in-depth and well written. Even if you did leech this(I doubt you did, no worries), you still do the community a solid by posting this. Not to mention the time it would take to copy this here, still quite a bit of work lol. Good job.
I was trying to read a memory address in a game I got here, but I couldn't, even though I kept looking for the changed ones the one I wanted didn't show up (and I searched for all types).

Do you know how I can look for the address I want? The game uses BugTrap as its anti-hack solution (it says loading Anti-Hack Protection and then BugTrapU.dll gets loaded).
@timeshift
--Is this a question about CE, or my code? You posted 2 problems.
"Do you know how I can look for the address I want?"
No sorry. If it's trouble with the code then I'll try to help. I'm not a CE / anti-hack guru.

"The game uses BugTrap as its anti-hack solution (it says loading Anti-Hack Protection and then BugTrapU.dll gets loaded)."
^^ok. If CE can scan/work correctly, then apparently the anti-hack isn't stopping rpm. idk bugtrap.

-what value are you searching for? Maybe try using "changed / unchanged" instead of exact value...all depends on the game tho.

-So you said you found an address here at the forum, and tried to read it, but couldn't.
Please post code. Did it "attach" correctly ? Or did only rpm fail? or did rpm return 0 ?

@t7
" Even if you did leech this(I doubt you did, no worries), you still do the community a solid by posting this. "
I don't like it, but whatever. (no worries, NOT LEECHED) - If you say "I doubt you did", don't also say "Even if you did" please
It is not leeched - unless you're saying it is/isn't, please don't insult me by saying it might be. - and thank you for compliment. It was some work..I spent a few hours writing the original (& part2), inside a .txt file with <html> markup, then after joining forum changed it all to [] code. Not hard, but I took the time to do it, and try* to explain.
I have a question,

How do you make sure the programm find the right adress eatch time the process restarts?
If you could explain that to me I would be very greatfull.
Quote Originally Posted by hardbassbv View Post
I have a question,

How do you make sure the programm find the right adress eatch time the process restarts?
If you could explain that to me I would be very greatfull.
You talking about codeshifting or a dynamic address?

With codeshifting, just get the module base + offset.
With dynamic addresses you could use pointers/injection/aobscan to get the address.
Posts 113 of 13 · Page 1 of 1

Post a Reply

Similar Threads

Tags for this Thread

None

Need help?