(same thing as part 1: re-posting from another forum I go to, incase anyone needs it. Some info may be wrong/incorrect, I'm not an expert)
The goal
To be able to scan all the memory being used by a process. Eventually being able to read all of the data and comparing it to some value. That is, to be able to find the specific location of a known value in a process's memory! ie. Scan(), and eventually FindPattern()
Basic Assumptions
You read part 1.
Probably not 64 bit compatible
If you remember from the last tutorial, each address in a process's ram has access rights associated with it. But that's not quite right. Actually the ram
(0-0xFFFFFFFF) is divided up into many chunks of many different sizes. Each chunk has specific access rights, and all addresses in that chunk have the same rights.
For example
10000 - 20000 READ_ONLY 0x10000, 0x10001, 0x1**** are all READ_ONLY
20000 - 30000 READ_ONLY
30000 - 60000 EXECUTE_READ are all EXECUTE_READWRITE (ie.can't call WriteProcessMemory on these addr)
60000 - 80000 READ_ONLY (ie.can't call WriteProcessMemory on these addr)
80000 - 10000 EXECUTE_READWRITE
10000 - FFFFF READ_WRITE are all READ_WRITE
That is totally made up. But you can see there are chunks (start address to end address) which each have access rights.Also note that each chunk, or 'region', has more properties than just Protection. Windows has a nice structure for us called MEMORY_BASIC_INFORMATION.
Basically it represents a chunk/region and has useful properties like Size. So how do we fill this nice structure with info? Another API of course, coming soon.
The MEMORY_BASIC_INFORMATION structure
Code:
Private Structure MEMORY_BASIC_INFORMATION
Dim BaseAddress As IntPtr Pretty obvious. The lowest address of the page
Dim AllocationBase As IntPtr
Dim AllocationProtect As UInt32 Same as protect. When the region was first created
Dim RegionSize As IntPtr
Dim State As UInt32 COMMIT is the only one we care about
Dim Protect As UInt32 Access rights. EXECUTE_READWRITE, READONLY etc.
Dim zType As UInt32 IMAGE, MAPPED, PRIVATE.
End Structure
And now to use our new struture. But how do we fill it with useful data? That api. VirtualQueryEx().
Code:
Private Declare Function VirtualQueryEx Lib "kernel32.dll" (ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByRef lpBuffer As MEMORY_BASIC_INFORMATION, ByVal dwLength As UInt32) As Int32
hProcess Valid process handle from OpenProcess(). In my code, _targetProcessHandle
lpAddress The address we want info on. (it automatically figures out
which region that address is in,and returns the rights for that region, via lpBuffer)
lpBuffer An object of our MEMORY_BASIC_MEMORY structure. Passed BYREF.
dwLength The size of our MEMORY_BASIC_MEMORY structure in bytes
dwLength is slightly tricky. We could count all the variables in the structure, and because we know what type they are, and how big each type is in bytes, it's easy to figure out the overall size of the structure. There is also a .Net function to do this for us. In the Marshal class.
Code:
Dim _mbi As MEMORY_BASIC_INFORMATION
Dim _mbiSize as Int32 = System.Runtime.InteropServices.Marshal.SizeOf(_mbi)
'or Marshal.SizeOf(New MEMORY_BASIC_INFO) and u don't need to have _mbi declared.
'which we will use as
VirtualQueryEx(_targetProcessHandle, _addr, _mbi, _mbiSize) 'Now _mbi's properties are filled with data.
MsgBox("Region BaseAddress: 0x" & _mbi.BaseAddress.ToString("X") & ", size: " & _mbi.RegionSize.ToString("X"))
We will use Marshal.SizeOf(mbi) several times. This size shouldn't change so we can calculate then store it to avoid unnecessary function calls.
It's not _mbi = VirtualQuery(etc) because _mbi (lpBuffer) is passed ByRef. I hope you understand ByRef.
Ok, great. Now we can know the access rights of any address (and therefore it's region). Let's take this a step further and find out the access rights for every region. This involves looping over the entire memory range and figuring out where each region is, but how do we do that? Actually it's pretty simple. The trick is, when you add Region.Base + Region.Size you get the next Region's .Base. Seems obvious. It wasn't 2 seconds ago Here is the code to do just that.
Code:
Public Sub xxx
Dim _addr As IntPtr = IntPtr.Zero
Do
VirtualQueryEx(_targetProcessHandle, _addr, _mbi, _mbiSize)
''increment _addr to next region
_addr = _mbi.BaseAddress + _mbi.RegionSize
Loop While _addr.ToInt32 < 0xFFFFFFFF
End Sub
Admittedly, it doesn't actually do anything. Here is a loop that does *something*.
Code:
Public Sub yyy
'Don't actually run this loop. LOTS of message boxes.
Dim _addr As IntPtr = IntPtr.Zero
Do
VirtualQueryEx(_targetProcessHandle, _addr, _mbi, _mbiSize)
MsgBox("Region BaseAddress: " & _mbi.BaseAddress & " , size: " & _mbi.RegionSize)
''increment _addr to next region
_addr = _mbi.BaseAddress + _mbi.RegionSize
Loop While _addr.ToInt32 < 0xFFFFFFFF
End Sub
note. Instead of using 0 and 0xFFFFFFFF for our start and stop addresses, I
recomend using SystemInfo.lpMinAddress and .lpMaxAddress.
Just like MEMORY_BASIC_INFO and VirtualQueryEx, we create a structure
called SYTEM_INFO and fill it with the api GetSystemInfo(), that has
useful properties like # of processors. See below.
Some notes
If mbi.State isn't COMMITTED, the target app ISN'T using it. You probably don't care about reading it.
mbi.Type IMAGE, SYSTEM, MAPPED. I don't know. Depending on what you need you may ignore,or focus, on some types.
Generally you can read all 3 without problems? Specific cases of not being able to??
Here is a loop that will "Analyze" every region of a process's ram.
Code:
Public Sub AnalyzeRam
Dim _addr As IntPtr = IntPtr.Zero
Dim _mbi as MEMORY_BASIC_INFORMATION
Dim _mbiSize As Int32 = System.Runtime.InteropServices.Marshal.SizeOf(_mbi)
Dim _sysInfo As SYSTEM_INFORMATION
Dim _outputString as String = ""
GetSystemInfo(_sysInfo)
_addr = _sysInfo.lpMinimumApplicationAddress
Do
VirtualQueryEx(_targetProcessHandle, _addr, _mbi, _mbiSize)
If _mbi.State = MemoryAllocationState.Commit Then
'This region actively being used by process
_outputString += "Base: 0x" & _mbi.BaseAddress.ToString("X") & Environment.NewLine
_outputString += "Size: " & _mbi.BaseAddress.ToString("X") & Environment.NewLine 'Also in hex!
If _mbi.zType = MemoryAllocationType.MEM_PRIVATE Then
_outputString += "[private]" & Environment.NewLine
ElseIf _mbi.zType = MemoryAllocationType.MEM_IMAGE
_outputString += "[image]" & Environment.NewLine
Else
_outputString += "[mapped]" & Environment.NewLine
End If
If _mbi.Protect And MemoryAllocationType.PAGE_GUARD Then
'guard-ed. Reading will result in exception by OS.
_outputString += "---GUARDED---"
End If
End If
'MessageBox.Show(_outputString)
_addr = _mbi.BaseAddress + _mbi.RegionSize ''point _addr to next region
Loop While _addr.ToInt32 < _sysInfo.lpMaximumApplicationAddress
End Sub
note. MBI.Type has been renamed to mbi.zType because of keyword name collision.
I assume you added this function to the MemoryManager class from part 1,
and that you've already called OpenProcess() successfully.
You also need these...
Code:
Private Declare Sub GetSystemInfo Lib "kernel32.dll" (ByRef lpSystemInfo As SYSTEM_INFO)
Code:
Private Structure SYSTEM_INFO
Dim wrocessorArchitecture As Int16
Dim wReserved As Int16
Dim dwPageSize As Int32 'Useful
Dim lpMinimumApplicationAddress As Int32 'Useful
Dim lpMaximumApplicationAddress As Int32 'Useful
Dim dwActiveProcessorMask As Int32
Dim dwNumberOfProcessors As Int32
Dim dwProcessorType As Int32
Dim dwAllocationGranularity As Int32
Dim wProcessorLevel As Int16
Dim wProcessorRevision As Int16
End Structure
Code:
Private Enum MemoryAllocationProtectionType As UInt32
PAGE_NOACCESS = &H1
PAGE_READONLY = &H2
PAGE_READWRITE = &H4
PAGE_WRITECOPY = &H8
PAGE_EXECUTE = &H10
PAGE_EXECUTE_READ = &H20
PAGE_EXECUTE_READWRITE = &H40
PAGE_EXECUTE_WRITECOPY = &H80
PAGE_GUARD = &H100
PAGE_NOCACHE = &H200
PAGE_WRITECOMBINE = &H400
PAGE_CANREAD = PAGE_READONLY Or PAGE_READWRITE Or PAGE_EXECUTE_READ Or PAGE_EXECUTE_READWRITE
PAGE_CANEXECUTE = PAGE_EXECUTE Or PAGE_EXECUTE_READ Or PAGE_EXECUTE_READWRITE 'Or PAGE_WRITECOPY?
PAGE_CANWRITE = PAGE_READWRITE Or PAGE_EXECUTE_READWRITE OR PAGE_WRITECOMBINE
End Enum
Private Enum MemoryAllocationType As UInt32
MEM_IMAGE = &H1000000
MEM_MAPPED = &H40000
MEM_PRIVATE = &H20000
End Enum
Private Enum MemoryAllocationState As UInt32
Commit = &H1000
Reserve = &H2000
Decommit = &H4000
Release = &H8000
Reset = &H80000
Physical = &H400000
TopDown = &H100000
WriteWatch = &H200000
LargePages = &H20000000
End Enum
Actually reading out all of the data (or as much as possible)
Now that we have a framework which well let us loop over each memory region, we need to actually read the data from the regions. If you remember, ReadProcessMemory() takes a Size parameter. Reading from 0-0xffffffff only Int32 or Int64 at a time would take LOTS and lots of calles to ReadProcessMemory() and that can't be efficient. If we increase the read size, then we can call it a lot less times and improve performance. How big can you make ReadProcessMemory() go? Well, I'm not sure(Look into it), but the SYSTEM_INFO structure has a property called PageSize. On my 32 bit Windows Xp with only 2g of ram, this is 4096. So apparently ReadProcessMemory() can read upto/more than 4096 bytes at a time. This kind of seems like a lot! But really it isn't. I assume this is most efficient?
My strategy is this
Code:
if mbi.RegionSize <= pageSize then
bytes = ReadProcessMemory(entire_region)
else
bytes = SecondFunctionToGetLargerPages()
end if
I'm not going to fully explain the code that copies the regions. Now that we can use ReadProcessMemory and have MemoryManager.GetBytes(), it's
just a matter of creating the correct byte arrays and how to pass them back and forth. For now we can only read regions that are marked readable (EXECUTE_READ, READWRITE, etc). Obviously. There is actually another API to change the permissions, we'll cover that later. Notice the If _mbi.Protect AND MemoryAllocation.PAGE_CANREAD? The .Protect property is actually a bit mask, so it can store several values simultaneously. I use bitmasking to check if certain values are set: readable, writable, executable.
I'm not going to explain it too much further, you have what you need.
Code:
Private Sub AnalyzeAndReadAll
Dim _mbi As MEMORY_BASIC_INFORMATION, _sysInfo As SYSTEM_INFO
Dim _mbiSize As Int32 = System.Runtime.InteropServices.Marshal.SizeOf(_mbi)
GetSystemInfo(_sysInfo)
Dim _addr As IntPtr = IntPtr.Zero
Dim _readBuff(_sysInfo.dwPageSize - 1) As Byte 'read small pages
Dim _bigBuff(0) As Byte 'read big pages
Dim _actualBytesRead As Int32 = 0 'actual length of bytes copied during ReadProcessMemory()
Do
VirtualQueryEx(_targetProcessHandle, _addr, _mbi, _mbiSize)
If _mbi.State = MemoryAllocationState.Commit Then
'this region of ram actively being used by process (commited at least..)
If (_mbi.Protect And MemoryAllocationProtectionType.PAGE_CANREAD) And Not (_mbi.Protect And MemoryAllocationProtectionType.PAGE_GUARD)) Then 'bitmask (checks for any readable type)
''READable and not GUARDed
''Read the data.
If _mbi.RegionSize.ToInt32 <= _sysInfo.dwPageSize Then
'small page. Read entire page into buffer.
If ReadProcessMemory(_targetProcessHandle, _mbi.BaseAddress, _readBuff, _mbi.RegionSize, _actualBytesRead) Then
If _actualBytesRead <> _mbi.RegionSize Then
'not able to read all data, handle gracefully. do nothing :)
'MsgBox("ScanForBytes() RPM->ActualBytesRead too low!" & _addr.ToString("X"))
Else
'do something with the data
'ie. compare to another byte arrar for equality ;)
End If
Else
'MsgBox("ScanForBytes::RPM FAIL 0x" & _mbi.BaseAddress.ToString("X"))
End If
Else 'large page. Read page in chunks
_bigBuff = ReadLargeRamChunk(_addr, IntPtr.Add(_addr, _mbi.RegionSize.ToInt32))
'do something with the data
'ie. compare to another byte arrar for equality ;)
End If ''//page size
End If
_addr = _mbi.BaseAddress + _mbi.RegionSize ''increment _addr to next region
Loop While _addr.ToInt32 < _systemInfo.lpMaximumApplicationAddress
End Sub
Private Function ReadLargeRamRegion(ByVal aStart As IntPtr, ByVal aStop As IntPtr) As Byte()
Dim _rtnBuffSize As Int32 = aStop.ToInt32 - aStart.ToInt32 'theoretical max size: may be smaller due to Read failures
Dim _sizeRemaining As Int32 = _rtnBuffSize
Dim _byteBuff(_rtnBuffSize - 1) As Byte
Dim _byteBuffCurrIndex As Int32 = 0 'actual size of data to be returned
Dim _curAddr As IntPtr = aStart
Dim _readBuff(_systemInfo.dwPageSize -1) As Byte
Dim _actualBytesRead As Int32 = 0 '' Actual count of bytes read by ReadProcessMemory()
'start reading
Do
If _sizeRemaining >= _systemInfo.dwPageSize Then
If ReadProcessMemory(_targetProcessHandle, _curAddr, _readBuff, _systemInfo.dwPageSize, _actualBytesRead) Then
If _actualBytesRead <> _systemInfo.dwPageSize Then
'didn't read all data?! Don't append _readBuff to byteBuff. or do..
'MsgBox("ReadLargeRamChunk() RPM->ActualBytesRead too low! 0x" & _curAddr.ToString("X"))
Else
Array.Copy(_readBuff, 0, _byteBuff, _byteBuffCurrIndex, _actualBytesRead)
_byteBuffCurrIndex += _systemInfo.dwPageSize
End If
Else
'MsgBox("ReadLargeRamChunk() RPM->FAIL! 0x" & _curAddr.ToString("X"))
End If
_curAddr += _systemInfo.dwPageSize
_sizeRemaining -= _systemInfo.dwPageSize
If _sizeRemaining = 0 Then Exit Do
Else
'almost at end of mem scan. 1 small piece left
If ReadProcessMemory(_targetProcessHandle, _curAddr, _readBuff, _sizeRemaining, _actualBytesRead) Then
If (_actualBytesRead <> _sizeRemaining) Then
'not able to read entire area
'MsgBox("ReadLargeRamChunk() RPM->ActualBytesRead too low! (final chunk) 0x" & _curAddr.ToString("X"))
Else
Array.Copy(_readBuff, 0, _byteBuff, _byteBuffCurrIndex, _actualBytesRead)
'success
Exit Do
End If
Else
'MsgBox("ReadLargeRamChunk() RPM->FAIL! (final chunk) 0x" & _curAddr.ToString("X"))
End If
End If
Loop
If _byteBuffCurrIndex < _rtnBuffSize Then
'not all data read. _byteBuff is too large. chop off extra, unused byte.
Redim Preserve _byteBuff(_byteBuffCurrIndex + 1) 'efficiency? only occurs if read fails occur.
Return _byteBuff
End Function
You're now reading all the readable data from the process! Big stuff.
Notice the PAGE_CANREAD. You don't actually have to make that check, but depending on the access type of the memory region, rpm may fail. If you check CANREAD, it should* succeed.
Also, the second choice..you can call OpenProcess() every time! you want to read, or you can 'attach' once, and keep track of the handle. Obviously I chose to keep track of it, so none of my Read() functions call OpenProcess(), they used a local variable _targetProcessHandle.
The goal
To be able to scan all the memory being used by a process. Even those not marked as readable
It's actually really easy. There is yet another windows api that will change the access rights of a memory region. VirtualProtectEx().
VirtualProtectEx is just like VirtualQueryEx except that instead of reading the access rights, we're settings them! Simple, right?
Code:
Private Declare Function VirtualProtectEx Lib "kernel32.dll" (ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As IntPtr, ByVal flNewProtect As UInt32, ByRef lpfoldProtect As UInt32) As Boolean
hProcess Same as all the others
lpAddress
dwSize
fwNewProtect New protection rights
fwOldProtect Old protection rights (byref. for restoring when finished)
Code:
'assuming _mbi has been set
Dim _oldProtects as Uint32 = 0 'BYREF. Save so we can restore rights later.
VirtualProtectEx(_targetProcessHandle, _mbi.BaseAddress, _mbi.RegionSize,MemoryAllocationProtectionType.EXECUTE_READWRITE, _oldProtect)
One thing to note: you need to be careful of the new Protect value. If the region was marked as EXECUTEable, you better make the new protection be
executable or the program will crash when it tries to run it's code there. Same situation with WRITEable. If the region was writeable, and you change
it to read_only, the program will crash when it tries to write to it. Seems pretty obvious, but I didn't think about it my first time.
That's really all there is to it. Now inside our scan, if the memory region isn't readable, we can make it readable. One last word of advice. Remember how MBI.Protect uses bitmasks to store several values. This means it can be PAGE_READ and PAGE_GUARD at the same time. I think PAGE_GUARD is like an anti-cheat mechanism. It will flag the OS if there is an attempt to Read() if GUARD is set to true. (not necessarily anti-cheat, but can be used this way)
---------- Post added at 02:49 AM ---------- Previous post was at 02:11 AM ----------
edit: I posted that tutorial a while ago, there were quite a few (obvious) bugs, and I've since change some code. Hopefully you can spot the differences / bugs / improvements.
Scan
Code:
#Region "Scan"
''' <summary>
''' Scans a process's entire memory range for the specified hex string.
''' <param name="hexString">Standard search/mask technique. (** denotes a masked byte.)</param>
''' <param name="returnOnFirstOccurance">End scan early if value found?</param>
''' </summary>
Public Function FindPattern(ByVal hexString As String, Optional ByVal returnOnFirstOccurance As Boolean = True) As IntPtr()
''format example: 64 8b 15 ** ** ** ** 8b 34 ** 8b 0d ** ** ** ** 89 81
Dim _hexStringChunks() As String = hexString.Split(" ")
Dim _hexStringAsBytes(_hexStringChunks.Length - 1) As Byte
Dim _mask(_hexStringChunks.Length - 1) As Byte
For xx As Int32 = 0 To _hexStringChunks.Length - 1
If _hexStringChunks(xx) = "**" Then
_hexStringAsBytes(xx) = &H0
_mask(xx) = &H0 'unimportant
Else
_hexStringAsBytes(xx) = Byte.Parse(_hexStringChunks(xx), Globalization.NumberStyles.HexNumber)
_mask(xx) = &H1 'important
End If
Next
Dim _results() As IntPtr = ScanForByteMask(_hexStringAsBytes, _mask, returnOnFirstOccurance)
Return _results
End Function
''' <summary>
''' Scan a process's entire memory range for the specified array of bytes. Function always return an array(). (may specify to return when first occurance is found. If returnOnFirstOccurance=true, array.length will be 1)
''' </summary>
Private Function ScanForBytes(ByVal byteBuff() As Byte, Optional ByVal returnOnFirstOccurance As Boolean = False) As IntPtr()
If IsAttachedToProcess() = False Then
Return New IntPtr() {IntPtr.Zero} 'user fail
End If
Dim _rtns As New List(Of IntPtr) 'locations (memory addresses) where the buff() was found at
If Not returnOnFirstOccurance Then _rtns.Capacity = 1000 'could be larger. depends how many results you expect..
Dim _mbi As MEMORY_BASIC_INFORMATION, _sysInfo As SYSTEM_INFO
Dim _mbiSize As Int32 = Marshal.SizeOf(_mbi) ' size of mbi struct, in bytes.
GetSystemInfo(_sysInfo)
Dim _addr As IntPtr = IntPtr.Zero 'counter/loop control.
Dim _readBuff(_sysInfo.dwPageSize - 1) As Byte 'small buffer to read small mem regions
Dim _bigBuff(0) As Byte 'large buffer to read large mem regions
Dim _actualBytesRead As Int32 = 0 ''actual length of bytes copied during ReadProcessMemory()
Dim _origPageProtection As UInt32 = 0 ''To preserve/restore original protection values
Dim _hasAlreadyFoundOnce As Boolean = False 'possibly end the scan early when first result found
Dim _oldProtects As String = "" 'displayed after errorrs. as string of '0' padded bits.
Do
VirtualQueryEx(_targetProcessHandle, _addr, _mbi, _mbiSize)
_oldProtects = ConvertMBIProtectToBinaryString(_mbi.Protect)
If _mbi.State = MemoryAllocationState.Commit Then
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_CANREAD Then 'bitmask check for any readable type
''EASY READ
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_GUARD Then
DoOutput("avoided the guard at 0x" & _addr.ToString("X"))
GoTo badLabelSkipThisMemRegion 'BUT ITS GUARDED
End If
Else
''CANT READ YET
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_COPYFORWARD Then
''CANT READ AT ALL (ignore non-readable copyforward unless you really* know what you're doing. usually fails anyway)
DoOutput("avoided OS managed file (dll?) at 0x" & _addr.ToString("X"))
GoTo badLabelSkipThisMemRegion
Else
''Enable Read
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_CANEXECUTE Then
'it should remain executable!
If VirtualProtectEx(_targetProcessHandle, _mbi.BaseAddress, _mbi.RegionSize, MemoryAllocationProtectionType.PAGE_EXECUTE_READWRITE, _origPageProtection) Then
'DoOutput("execPatching OK 0x" & _addr.ToString("X"))
Else
DoOutput("SFB() execPatching FAIL 0x" & _addr.ToString("X"))
DoOutput("protect: " & _oldProtects)
Threading.Thread.Sleep(2500)
GoTo badLabelSkipThisMemRegion
End If
Else
If VirtualProtectEx(_targetProcessHandle, _mbi.BaseAddress, _mbi.RegionSize, MemoryAllocationProtectionType.PAGE_READWRITE, _origPageProtection) Then
'DoOutput("readPatching OK 0x" & _mbi.BaseAddress.ToString("X"))
Else
DoOutput("SFB() readPatching FAIL 0x" & _mbi.BaseAddress.ToString("X"))
DoOutput("protect: " & _oldProtects)
Threading.Thread.Sleep(2500)
GoTo badLabelSkipThisMemRegion
End If
End If
End If
End If
''READ THE DATA
If _mbi.RegionSize.ToInt32 <= _sysInfo.dwPageSize Then '4096 bytes. 4kb. see windows memory management for info.
''READ THE DATA. small region
If ReadProcessMemory(_targetProcessHandle, _mbi.BaseAddress, _readBuff, _mbi.RegionSize, _actualBytesRead) Then
If (_actualBytesRead <> _mbi.RegionSize) Then
'not able to read all data, handle gracefully. do nothing :)
modPublic.DoOutput("SFB() RPM->ActualBytesRead too low! 0x" & _mbi.BaseAddress.ToString("X"))
Else
''COMPARE VALUE
For xx As Int32 = 0 To _mbi.RegionSize.ToInt32 - byteBuff.Length 'todo: align4?
For yy As Int32 = 0 To byteBuff.Length - 1
If byteBuff(yy) <> _readBuff(xx + yy) Then
GoTo badLabelNoSuccess
End If
Next
_rtns.Add(_addr.ToInt32 + xx) 'found it
If returnOnFirstOccurance Then
_hasAlreadyFoundOnce = True
Exit For
End If
badLabelNoSuccess:
Next
End If
Else
modPublic.DoOutput("SFB() RPM FAIL 0x" & _mbi.BaseAddress.ToString("X"))
End If
Else
' large region
_bigBuff = ReadLargeRamPage(_addr, _addr.ToInt32 + _mbi.RegionSize.ToInt32)
For xx As Int32 = 0 To _bigBuff.Length - byteBuff.Length 'todo: align4?
''COMPARE VALUE
For yy As Int32 = 0 To byteBuff.Length - 1
If byteBuff(yy) <> _bigBuff(xx + yy) Then
GoTo badLabelNoMoreSuccess
End If
Next
_rtns.Add(_addr.ToInt32 + xx) 'found it
If returnOnFirstOccurance Then
_hasAlreadyFoundOnce = True
Exit For
End If
badLabelNoMoreSuccess:
Next
End If ''end region size
'' RESTORE PROTECTION
If _origPageProtection Then
VirtualProtectEx(_targetProcessHandle, _mbi.BaseAddress, _mbi.RegionSize, _origPageProtection, _origPageProtection)
_origPageProtection = 0
End If
If _hasAlreadyFoundOnce AndAlso returnOnFirstOccurance Then Exit Do
End If ''//state=committed
badLabelSkipThisMemRegion:
_addr = _mbi.BaseAddress.ToInt32 + _mbi.RegionSize.ToInt32 ''increment _addr to next region
Loop While _addr.ToInt32 < _sysInfo.lpMaximumApplicationAddress
If _rtns.Count = 0 Then _rtns.Add(IntPtr.Zero) 'pattern not found!
Return _rtns.ToArray
End Function
Private Function ScanForByteMask(ByVal buff() As Byte, ByVal mask() As Byte, Optional ByVal returnOnFirstOccurance As Boolean = False) As IntPtr()
If IsAttachedToProcess() = False Or mask.Length <> buff.Length Then
Return New IntPtr() {IntPtr.Zero} 'user fail
End If
Dim _rtns As New List(Of IntPtr) 'locations (memory addresses) where the buff() was found at
If Not returnOnFirstOccurance Then _rtns.Capacity = 1000 'could be larger. depends how many results you expect..
Dim _mbi As MEMORY_BASIC_INFORMATION, _sysInfo As SYSTEM_INFO
Dim _mbiSize As Int32 = Marshal.SizeOf(_mbi) ''size of memory_basic_region struct in bytes.
GetSystemInfo(_sysInfo)
Dim _addr As IntPtr = IntPtr.Zero 'counter/loop control.
Dim _readBuff(_sysInfo.dwPageSize - 1) As Byte 'small buffer to read small mem regions
Dim _bigBuff(0) As Byte 'large buffer to read large mem regions
Dim _actualBytesRead As Int32 = 0 ''actual length of bytes copied during ReadProcessMemory()
Dim _origPageProtection As UInt32 = 0 ''To preserve/restore original protection values
Dim _hasAlreadyFoundOnce As Boolean = False
Dim _oldProtects As String = "" 'displayed after errorrs. as string of bits.
Do
VirtualQueryEx(_targetProcessHandle, _addr, _mbi, _mbiSize)
_oldProtects = ConvertMBIProtectToBinaryString(_mbi.Protect)
If _mbi.State = MemoryAllocationState.Commit Then
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_CANREAD Then 'bitmask check for any readable type
''EASY READ
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_GUARD Then
'DoOutput("avoided the guard at 0x" & _addr.ToString("X"))
GoTo badLabelSkipThisMemRegion 'BUT ITS GUARDED
End If
Else
''CANT READ YET
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_COPYFORWARD Then
''CANT READ AT ALL (ignore non-readable copyforward unless you really* know what you're doing. usually fails anyway)
DoOutput("avoided OS managed file (dll?) at 0x" & _addr.ToString("X"))
GoTo badLabelSkipThisMemRegion
Else
''Enable Read
If _mbi.Protect And MemoryAllocationProtectionType.PAGE_CANEXECUTE Then
'it should remain executable!
If VirtualProtectEx(_targetProcessHandle, _mbi.BaseAddress, _mbi.RegionSize, MemoryAllocationProtectionType.PAGE_EXECUTE_READWRITE, _origPageProtection) Then
'DoOutput("execPatching OK 0x" & _addr.ToString("X"))
Else
DoOutput("SFBM() execPatching FAIL 0x" & _addr.ToString("X"))
DoOutput("protect: " & _oldProtects)
Threading.Thread.Sleep(2500)
GoTo badLabelSkipThisMemRegion
End If
Else
If VirtualProtectEx(_targetProcessHandle, _mbi.BaseAddress, _mbi.RegionSize, MemoryAllocationProtectionType.PAGE_READWRITE, _origPageProtection) Then
'DoOutput("readPatching OK 0x" & _mbi.BaseAddress.ToString("X"))
Else
DoOutput("SFBM() readPatching FAIL 0x" & _mbi.BaseAddress.ToString("X"))
DoOutput("protect: " & _oldProtects)
Threading.Thread.Sleep(2500)
GoTo badLabelSkipThisMemRegion
End If
End If
End If
End If
''READ THE DATA
If _mbi.RegionSize.ToInt32 <= _sysInfo.dwPageSize Then '4096 bytes. 4kb. see windows memory management for info.
''READ THE DATA. small region
If ReadProcessMemory(_targetProcessHandle, _mbi.BaseAddress, _readBuff, _mbi.RegionSize, _actualBytesRead) Then
If (_actualBytesRead <> _mbi.RegionSize) Then
'not able to read all data, handle gracefully. do nothing :)
modPublic.DoOutput("SFBM() RPM->ActualBytesRead too low! 0x" & _mbi.BaseAddress.ToString("X"))
Else
''COMPARE VALUE
For xx As Int32 = 0 To _mbi.RegionSize.ToInt32 - buff.Length 'todo: align4?
For yy As Int32 = 0 To buff.Length - 1
If mask(yy) <> 0 Then
If buff(yy) <> _readBuff(xx + yy) Then
GoTo badLabelNoSuccess
End If
End If
Next
_rtns.Add(_addr.ToInt32 + xx) 'found it
If returnOnFirstOccurance Then
_hasAlreadyFoundOnce = True
Exit For
End If
badLabelNoSuccess:
Next
End If
Else
modPublic.DoOutput("SFBM() RPM FAIL 0x" & _mbi.BaseAddress.ToString("X"))
End If
Else
' large region
_bigBuff = ReadLargeRamPage(_addr, _addr.ToInt32 + _mbi.RegionSize.ToInt32)
For xx As Int32 = 0 To _bigBuff.Length - buff.Length 'todo: align4?
''COMPARE VALUE
For yy As Int32 = 0 To buff.Length - 1
If mask(yy) <> 0 Then
If buff(yy) <> _bigBuff(xx + yy) Then
GoTo badLabelNoMoreSuccess
End If
End If
Next
_rtns.Add(_addr.ToInt32 + xx) 'found it
If returnOnFirstOccurance Then
_hasAlreadyFoundOnce = True
Exit For
End If
badLabelNoMoreSuccess:
Next
End If ''end region size
'' RESTORE PROTECTION
If _origPageProtection Then
VirtualProtectEx(_targetProcessHandle, _mbi.BaseAddress, _mbi.RegionSize, _origPageProtection, _origPageProtection)
_origPageProtection = 0
End If
If _hasAlreadyFoundOnce AndAlso returnOnFirstOccurance Then Exit Do
End If ''//state=committed
badLabelSkipThisMemRegion:
_addr = _mbi.BaseAddress.ToInt32 + _mbi.RegionSize.ToInt32 ''increment _addr to next region
Loop While _addr.ToInt32 < _sysInfo.lpMaximumApplicationAddress
If _rtns.Count = 0 Then _rtns.Add(IntPtr.Zero) 'pattern not found!
Return _rtns.ToArray
End Function
''' <summary>
''' Reads a region of ram (startAddress to stopAddress). CALLING code is responsible for checking memory_allocation_protection_type before calling this function.
''' </summary>
''' <param name="aStart">Beginning of scan range. (usually a memory_basic_information.BaseAddress, doesn't have to be).</param>
''' <param name="aStop">End of scan range. (usually memory_basic_information.BaseAddress + iSize, doesn't have to be) </param>
''' <returns></returns>
''' <remarks>Can span multiple regions, but caller should verity access rights first!</remarks>
Private Function ReadLargeRamPage(ByVal aStart As IntPtr, ByVal aStop As IntPtr) As Byte()
Dim _rtnBuffSize As Int32 = aStop.ToInt32 - aStart.ToInt32 'theoretical max size: may be smaller due to read failure
Dim _returnByteBuff(_rtnBuffSize - 1) As Byte ' return results
Dim _byteBuffCurrIndex As Int32 = 0 'counter
Dim _readBuff(_systemInfo.dwPageSize - 1) As Byte 'temporary storage for ReadProcessMemory()
Dim _actualBytesRead As Int32 = 0 '' actual length of bytes returned by ReadProcessMemory()
Dim _curAddr As IntPtr = aStart
Dim _sizeRemaining As Int32 = _rtnBuffSize '
'start reading
Do
If _sizeRemaining >= _systemInfo.dwPageSize Then
If ReadProcessMemory(_targetProcessHandle, _curAddr, _readBuff, _systemInfo.dwPageSize, _actualBytesRead) Then
If (_actualBytesRead <> _systemInfo.dwPageSize) Then
''not able to read entire area
If _actualBytesRead > 0 Then 'append what little data we did read
Array.Copy(_readBuff, 0, _returnByteBuff, _byteBuffCurrIndex, _actualBytesRead)
_byteBuffCurrIndex += _actualBytesRead
End If
modPublic.DoOutput("ReadLargeRamChunk() RPM->ActualBytesRead too low! 0x" & _curAddr.ToString("X"))
Else
Array.Copy(_readBuff, 0, _returnByteBuff, _byteBuffCurrIndex, _systemInfo.dwPageSize)
_byteBuffCurrIndex += _systemInfo.dwPageSize
End If
Else
modPublic.DoOutput("ReadLargeRamChunk() RPM->FAIL! 0x" & _curAddr.ToString("X"))
End If
_sizeRemaining -= _systemInfo.dwPageSize '
Else
'almost at end of mem scan. 1 small piece left
If ReadProcessMemory(_targetProcessHandle, _curAddr, _readBuff, _sizeRemaining, _actualBytesRead) Then
If (_actualBytesRead <> _sizeRemaining) Then
'not able to read entire area
If _actualBytesRead > 0 Then 'append what little data we did read
Array.Copy(_readBuff, 0, _returnByteBuff, _byteBuffCurrIndex, _actualBytesRead)
_byteBuffCurrIndex += _actualBytesRead
End If
modPublic.DoOutput("ReadLargeRamChunk() RPM->ActualBytesRead too low! (final chunk) 0x" & _curAddr.ToString("X"))
Else
Array.Copy(_readBuff, 0, _returnByteBuff, _byteBuffCurrIndex, _sizeRemaining)
_byteBuffCurrIndex += _sizeRemaining
End If
Else
modPublic.DoOutput("ReadLargeRamChunk() RPM->FAIL! (final chunk!) 0x" & _curAddr.ToString("X"))
End If
_sizeRemaining = 0
End If
If _sizeRemaining = 0 Then Exit Do
_curAddr = _curAddr.ToInt32 + _systemInfo.dwPageSize
Loop
If _byteBuffCurrIndex < _rtnBuffSize Then ' ie. actual_read_length < expected_read_length
'_returnBuff was declared too large. Shrink it.
ReDim Preserve _returnByteBuff(_byteBuffCurrIndex - 1) ' only occurs on read failures
End If
Return _returnByteBuff
End Function
''' <summary>
''' Will return a string of 0's and 1's representing a uint32 value.
''' </summary>
''' <param name="mbiProtect">Protect value of MEMORY_BASIC_INFORMATION.</param>
''' <returns>Return string will be left-padded with 0's so that the final length of the string is a multiple of 8</returns>
Private Function ConvertMBIProtectToBinaryString(ByVal mbiProtect As UInt32) As String
''only used for user output. easier to read if they're all 8-padded
Dim _rtnStr As String = Convert.ToString(mbiProtect, 2) 'binary string
Dim _padNeeded As Int32 = _rtnStr.Length Mod 8 'add padding if needed
If _padNeeded <> 0 Then
_padNeeded = 8 - _padNeeded
_rtnStr = New String("0", _padNeeded) & _rtnStr '' .net has a string function .Pad(). o well, already made the code.
End If
Return _rtnStr
End Function
#End Region
struct/enums maybe have posted in part 1. sry. descriptions taken from msdn.com
Code:
#Region "Structs/Enum"
Private Structure SYSTEM_INFO
Dim wrocessorArchitecture As Int16
Dim wReserved As Int16
Dim dwPageSize As Int32
Dim lpMinimumApplicationAddress As Int32
Dim lpMaximumApplicationAddress As Int32
Dim dwActiveProcessorMask As Int32
Dim dwNumberOfProcessors As Int32
Dim dwProcessorType As Int32
Dim dwAllocationGranularity As Int32
Dim wProcessorLevel As Int16
Dim wProcessorRevision As Int16
End Structure
Private Structure MEMORY_BASIC_INFORMATION
Dim BaseAddress As IntPtr
Dim AllocationBase As IntPtr
Dim AllocationProtect As UInt32
Dim RegionSize As IntPtr
Dim State As UInt32
''' <summary>
''' See the MemoryAllocationProtectionType enum. for values
''' </summary>
''' <remarks>no remarks anywhere.</remarks>
Dim Protect As UInt32
Dim zType As UInt32
End Structure
Public Enum MemoryAllocationProtectionType As UInt32
''' <summary>
''' Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.
''' </summary>
PAGE_NOACCESS = &H1 'ALWAYS LEAVE ALONE
''' <summary>
''' Enables read-only access to the committed region of pages. An attempt to write to the committed region results in an access violation.
''' </summary>
PAGE_READONLY = &H2 'HACKRW
''' <summary>
''' Enables read-only or read/write access to the committed region of pages.
''' </summary>
PAGE_READWRITE = &H4 'HACKRW
''' <summary>
''' Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to a committed copy-on-write page results in a private copy of the page being made for the process.
''' </summary>
PAGE_WRITECOPY = &H8 'DONT phux with writeoncopy
''' <summary>
''' Enables execute access to the committed region of pages. An attempt to read from or write to the committed region results in an access violation. -microsof*****m
''' </summary>
PAGE_EXECUTE = &H10 'HACKRW
''' <summary>
''' Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region results in an access violation.
''' </summary>
PAGE_EXECUTE_READ = &H20 'HACKRW
''' <summary>
''' Enables execute, read-only, or read/write access to the committed region of pages.
''' </summary>
PAGE_EXECUTE_READWRITE = &H40 'HACKRW
''' <summary>
''' Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to write to a committed copy-on-write page results in a private copy of the page being made for the process.
''' </summary>
PAGE_EXECUTE_WRITECOPY = &H80 'DONT phux with writeoncopy
''' <summary>
''' Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time access alarm.
''' </summary>
'''
PAGE_GUARD = &H100 'HACKRW
''' <summary>
''' Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required for a device.
''' </summary>
PAGE_NOCACHE = &H200
''' <summary>
''' Sets all pages to be write-combined.Applications should not use this attribute except when explicitly required for a device.
''' </summary>
PAGE_WRITECOMBINE = &H400
''' <summary>
''' Allowed to call ReadProcessMemory() without needing VirtualProtectEx().
''' </summary>
PAGE_CANREAD = PAGE_READONLY Or PAGE_READWRITE Or PAGE_WRITECOPY Or PAGE_EXECUTE_READ Or PAGE_EXECUTE_READWRITE Or PAGE_EXECUTE_WRITECOPY
PAGE_CANEXECUTE = PAGE_EXECUTE Or PAGE_EXECUTE_READ Or PAGE_EXECUTE_READWRITE Or PAGE_EXECUTE_WRITECOPY
PAGE_CANWRITE = PAGE_READWRITE Or PAGE_EXECUTE_READWRITE 'excludes 'copy on write' regions.
''' <summary>
''' Generally we should leave copy-forward memory alone, except maybe for reading. But only if it is originally marked as readable! Careful with VirtualProtectEx...it can/will fail, and/or cause misc. (memory related) bugs.
''' </summary>
PAGE_COPYFORWARD = PAGE_WRITECOPY Or PAGE_EXECUTE_WRITECOPY Or PAGE_WRITECOMBINE
End Enum
Private Enum MemoryAllocationType As UInt32
MEM_IMAGE = &H1000000
MEM_MAPPED = &H40000
MEM_PRIVATE = &H20000
End Enum
Private Enum MemoryAllocationState As UInt32
Commit = &H1000
Reserve = &H2000
Decommit = &H4000
Release = &H8000
Reset = &H80000
Physical = &H400000
TopDown = &H100000
WriteWatch = &H200000
LargePages = &H20000000
End Enum
Private Enum ProcessAccessProtectionType As UInt32
''' <summary>
''' Required to terminate a process using TerminateProcess.
''' </summary>
PROCESS_TERMINATE = &H1
''' <summary>
''' Required to create a thread.
''' </summary>
''' <remarks></remarks>
PROCESS_CREATE_THREAD = &H2
''' <summary>
''' Required to perform an operation on the address space of a process (see VirtualProtectEx and WriteProcessMemory).
''' </summary>
PROCESS_VM_OPERATION = &H8
''' <summary>
''' Required to read memory in a process using ReadProcessMemory.
''' </summary>
PROCESS_VM_READ = &H10
''' <summary>
''' Required to read memory in a process using ReadProcessMemory.
''' </summary>
PROCESS_VM_WRITE = &H20
''' <summary>
''' Required to duplicate a handle using DuplicateHandle.
''' </summary>
PROCESS_DUP_HANDLE = &H40
''' <summary>
''' Required to create a process.
''' </summary>
PROCESS_CREATE_PROCESS = &H80
''' <summary>
''' Required to set memory limits using SetProcessWorkingSetSize.
''' </summary>
PROCESS_SET_QUOTA = &H100
''' <summary>
''' Required to set certain information about a process, such as its priority class (see SetPriorityClass).
''' </summary>
PROCESS_SET_INFORMATION = &H200
''' <summary>
''' Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken).
''' </summary>
PROCESS_QUERY_INFORMATION = &H400
''' <summary>
''' Required to suspend or resume a process.
''' </summary>
PROCESS_SUSPEND_RESUME = &H800
''' <summary>
''' Required to retrieve certain information about a process (see GetExitCodeProcess, GetPriorityClass, IsProcessInJob, QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted PROCESS_QUERY_LIMITED_INFORMATION.
''' </summary>
PROCESS_QUERY_LIMITED_INFORMATION = &H1000
''' <summary>
''' Required to wait for the process to terminate using the wait functions.
''' </summary>
PROCESS_SYNCHRONIZE = &H100000
''' <summary>
''' Enables read/write to most of the process. Exclueds copyforward, non-readable regions.
''' </summary>
''' <remarks></remarks>
PROCESS_ALLACCESS = &H1F0FFF
End Enum
and of course the
write functions
Code:
Public Function WriteByte(ByVal addr As IntPtr, ByVal aByte As Byte) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, New Byte() {aByte}, 1, vbNull) 'awk array decl :/
End Function
Public Function WriteInt16(ByVal addr As IntPtr, ByVal data As Int16) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 2, vbNull)
End Function
Public Function WriteInt32(ByVal addr As IntPtr, ByVal data As Int32) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 4, vbNull)
End Function
Public Function WriteInt64(ByVal addr As IntPtr, ByVal data As Int64) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, vbNull)
End Function
Public Function WriteUInt16(ByVal addr As IntPtr, ByVal data As UInt16) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 2, vbNull)
End Function
Public Function WriteUInt32(ByVal addr As IntPtr, ByVal data As UInt32) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 4, vbNull)
End Function
Public Function WriteUInt64(ByVal addr As IntPtr, ByVal data As UInt64) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, vbNull)
End Function
Public Function WriteFloat(ByVal addr As IntPtr, ByVal data As Single) As Boolean
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 4, vbNull)
End Function
Public Function WriteDouble(ByVal addr As IntPtr, ByVal data As Double) As String
Return WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, vbNull)
End Function
Public Function WriteIntPtr(ByVal addr As IntPtr, ByVal ptr As IntPtr) As Boolean
Dim _bytes(IntPtr.Size - 1) As Byte
If IntPtr.Size = 4 Then
_bytes = BitConverter.GetBytes(Convert.ToUInt32(ptr))
Return WriteProcessMemory(_targetProcessHandle, addr, _bytes, 4, vbNull)
Else
_bytes = BitConverter.GetBytes(Convert.ToUInt64(ptr))
Return WriteProcessMemory(_targetProcessHandle, addr, _bytes, 8, vbNull)
End If
End Function
Public Function WriteUnicodeString(ByVal addr As IntPtr, ByVal str As String) As Boolean
Dim _bytes() As Byte = System.Text.Encoding.Unicode.GetBytes(str)
Return WriteProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, vbNull)
End Function
Public Function WriteAsciiString(ByVal addr As IntPtr, ByVal str As String) As Boolean
Dim _bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(str)
Return WriteProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, vbNull)
End Function
Public Function WriteBytes(ByVal addr As IntPtr, ByVal bytes() As Byte) As Boolean
Dim _writeLength As Int32 = 0
If WriteProcessMemory(_targetProcessHandle, addr, bytes, bytes.Length, _writeLength) Then
If _writeLength = bytes.Length Then
Return True
Else
DoOutput("MemoryManager::WriteBytes() writeLength < buff.size")
Return False
End If
Else
Return False 'wpm failed!
End If
End Function
DoOutput() is just a public Sub. Just adds the (msg) to a multi-line text box. Debugging output basically, which I highly recomend for stuff like this.
Code:
Public Sub DoOutput(ByVal msg As String, Optional ByVal printTime As Boolean = True)
If frmMain.txtOutput.TextLength > 5000 Then '' change 50k to something lower to consume less ram.
Dim _bracLoc As Int32 = frmMain.txtOutput.Text.IndexOf("[", 2500)
'remove (about) the first 1/2 of the text, keeping the most recent messages.
frmMain.txtOutput.Text = "******************" & Environment.NewLine & Mid(frmMain.txtOutput.Text, _bracLoc) ''append old msgs
End If
If printTime Then
frmMain.txtOutput.AppendText("[" & Date.Now.ToString("h-m-s") & "]" & msg & Environment.NewLine)
Else
frmMain.txtOutput.AppendText(msg & Environment.NewLine)
End If
Application.DoEvents()
End Sub
'set displayTime = false for exact formatting (when seconds goes from 60 to 0, a jagged effect happens)
'Don't credit me in your apps, I don't want to be associated with your 'hacks'. Including my name in the source
'would be nice, but, as I found 1/2 of this info on google and made the other half up..all to the good.
'I did work hard on ScanForBytes and FindPattern tho.
If you have any (relevant) questions/comments, feel free.