Code:
Public Class WinApi
#Region "Windows(R) Functions"
Public Declare Sub GetSystemInfo Lib "kernel32" (ByRef lpSystemInfo As SystemInfo)
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As IntPtr) As Boolean
Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAcess As UInt32, ByVal bInheritHandle As Boolean, ByVal dwProcessId As Int32) As IntPtr
Public Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer() As Byte, ByVal iSize As Int32, ByRef lpNumberOfBytesRead As Integer) As Boolean
Public Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpbaseAddress As IntPtr, ByVal lpBuffer As Byte(), ByVal nSize As Int32, ByRef dwNumberOfBytesWritten As Int32) As Boolean
Public Declare Function VirtualProtectEx Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As Int32, ByVal dwNewProtect As UInt32, ByRef dwOldProtect As UInt32) As Boolean
Public Declare Function VirtualQueryEx Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByRef lpMemoryBasicInformation As MemoryBasicInformation, ByVal dwLength As Int32) As UInt32
#End Region
#Region "Structs"
Public Structure SystemInfo
Dim wProcessorArchitecture As Int16
Dim wReserved As Int16
Dim dwPageSize As Int32
Dim lpMinimumApplicationAddress As IntPtr
Dim lpMaximumApplicationAddress As IntPtr
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
Public Structure MemoryBasicInformation
Dim BaseAddress As IntPtr
Dim AllocationBase As IntPtr
Dim AllocationProtect As UInt32
Dim RegionSize As IntPtr
Dim State As UInt32
Dim Protect As UInt32
Dim AllocationType As UInt32
End Structure
#End Region
#Region "Enums"
Public Enum MemoryAllocationProtection 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
End Enum
Public Enum MemoryAllocationType As UInt32
MEM_IMAGE = &H1000000
MEM_MAPPED = &H40000
MEM_PRIVATE = &H20000
End Enum
Public Enum MemoryAllocationState As UInt32
COMMIT = &H1000
RESERVE = &H2000
DECOMMIT = &H4000
RELEASE = &H8000
RESET = &H80000
PHYSICAL = &H400000
TOP_DOWN = &H100000
WRITE_WATCH = &H200000
LARGE_PAGES = &H20000000
End Enum
Public Enum ProcessAccess As UInt32
''Function descriptions taken from msdn.com
''' <summary>
''' Required to terminate a process using TerminateProcess.
''' </summary>
PROCESS_TERMINATE = &H1
''' <summary>
''' Required to create a thread.
''' </summary>
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 write to memory in a process using WriteProcessMemory.
''' </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. Windows Server 2003 and Windows XP: This access right is not supported.
''' </summary>
PROCESS_QUERY_LIMITED_INFORMATION = &H1000
''' <summary>
''' Required to wait for the process to terminate using the wait functions.
''' </summary>
PROCESS_SYNCHRONIZE = &H100000
End Enum
#End Region
End Class
Code:
Public Class MemoryManager
Implements IDisposable
#Region "Private"
Private _isAttached As Boolean = False ''required for most non-shared functions.
Private _targetProcessId As Int32 = 0
Private _targetProcessHandle As IntPtr = IntPtr.Zero ''Returned from winapi.OpenProcess()
Private _mainModuleBase As IntPtr = IntPtr.Zero
Private _systemInfo As WinApi.SystemInfo '' Useful information about cpu and ram. Stored to avoid excessive lookup.
Private _mbiSize As Int32 = 0 '' SizeOf(WinAPI.MEMORY_BASIC_INFORMATION) in bytes. Stored to avoid excessive lookup.
Public Sub New()
''Store these values in the class so they don't have to be calculated/looked up each time they are used.
WinApi.GetSystemInfo(_systemInfo) '' Useful info about cpu and ram.
_mbiSize = System.Runtime.InteropServices.Marshal.SizeOf(New WinApi.MemoryBasicInformation) ''size of the struct in bytes
End Sub
#End Region
Public Function AttachToProcess(ByVal processId As Int32) As Boolean
If _isAttached Then
Return False ''Already attached... True would be misleading because processId might not == _targetProcessId.
Else
For Each proc As Process In Process.GetProcesses()
If proc.Id = processId Then
_targetProcessHandle = WinApi.OpenProcess(WinApi.ProcessAccess.PROCESS_VM_WRITE Or WinApi.ProcessAccess.PROCESS_VM_READ Or WinApi.ProcessAccess.PROCESS_VM_OPERATION Or WinApi.ProcessAccess.PROCESS_QUERY_INFORMATION, False, processId)
If _targetProcessHandle = IntPtr.Zero Then
''OpenProcess() failed. Problem with processId or OpenAccess level.
Return False
Else
''OpenProcess() success. Permissions Granted.
_isAttached = True
_targetProcessId = processId
_mainModuleBase = proc.MainModule.BaseAddress
Return True
End If
End If
Next
''If falls through, processId not found
MessageBox.Show("MemoryManager::AttachToProcess() Process Id not found: " & processId.ToString())
Return False
End If
End Function
Public Sub DetachFromProcess()
If _isAttached Then
If _targetProcessHandle <> IntPtr.Zero Then
WinApi.CloseHandle(_targetProcessHandle) '' we are done with handle. OS can free/re-use it.
End If
_isAttached = False
_targetProcessId = 0
_targetProcessHandle = IntPtr.Zero
End If
End Sub
Public ReadOnly Property IsAttached() As Boolean
Get
Return _isAttached
End Get
End Property
Public ReadOnly Property MainModuleBase As IntPtr
Get
Return _mainModuleBase
End Get
End Property
#Region "Read"
Public Function ReadByte(ByVal addr As IntPtr) As Byte
Dim _byte(0) As Byte '' Awkward. ReadProcessMemory delcared to take a byte array instead of an IntPtr. Both are an address.
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _byte, 1, New Int32)
Return _byte(0)
End Function
Public Function ReadInt16(ByVal addr As IntPtr) As Int16
Dim _bytes(1) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 2, New Int32)
Return BitConverter.ToInt16(_bytes, 0)
End Function
Public Function ReadInt32(ByVal addr As IntPtr) As Int32
Dim _bytes(3) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 4, New Int32)
Return BitConverter.ToInt32(_bytes, 0)
End Function
Public Function ReadInt64(ByVal addr As IntPtr) As Int64
Dim _bytes(7) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 8, New Int32)
Return BitConverter.ToInt64(_bytes, 0)
End Function
Public Function ReadUInt16(ByVal addr As IntPtr) As UInt16
Dim _bytes(1) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 2, New Int32)
Return BitConverter.ToUInt16(_bytes, 0)
End Function
Public Function ReadUInt32(ByVal addr As IntPtr) As UInt32
Dim _bytes(3) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 4, New Int32)
Return BitConverter.ToUInt32(_bytes, 0)
End Function
Public Function ReadUInt64(ByVal addr As IntPtr) As UInt64
Dim _bytes(7) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 8, New Int32)
Return BitConverter.ToUInt64(_bytes, 0)
End Function
Public Function ReadFloat(ByVal addr As IntPtr) As Single
Dim _bytes(3) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 4, New Int32)
Return BitConverter.ToSingle(_bytes, 0)
End Function
Public Function ReadDouble(ByVal addr As IntPtr) As Double
Dim _bytes(7) As Byte
If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 8, New Int32)
Return BitConverter.ToDouble(_bytes, 0)
End Function
''' <summary>
''' Reads an Ascii string from the target process's ram.
''' </summary>
''' <param name="addr">Beginning address of the string.</param>
''' <param name="maxLength">Max length of string if unknown. Must be > 0</param>
Public Function ReadAsciiString(ByVal addr As IntPtr, ByVal maxLength As Int32) As String
If Not (_isAttached AndAlso (maxLength > 0)) Then
Return String.Empty '' fail. not attached to any process (or maxLength = 0).
End If
Dim _bytes(maxLength - 1) As Byte
Dim _bytesRead As Int32 = 0
If WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, maxLength, _bytesRead) Then
Return System.Text.Encoding.ASCII.GetString(_bytes, 0, _bytesRead)
Else
Return String.Empty ''rpm failed
End If
End Function
''' <summary>
''' Read a Unicode string from the target process's ram.
''' </summary>
''' <param name="addr">Beginning address of the string.</param>
''' <param name="maxLength">Max length of string if unknown. Must be > 0</param>
Public Function ReadUnicodeString(ByVal addr As IntPtr, ByVal maxLength As Int32) As String
If Not (_isAttached AndAlso (maxLength > 0)) Then
Return String.Empty '' fail. not attached to any process (or maxLength = 0).
End If
maxLength = maxLength * 2 '' 2 bytes per character. TODO: "Unicode" ?
Dim _bytes(maxLength - 1) As Byte
Dim _bytesRead As Int32 = 0
If WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, _bytesRead) Then
Return System.Text.Encoding.Unicode.GetString(_bytes, 0, _bytesRead)
Else
Return String.Empty '' rpm failed.
End If
End Function
''' <summary>
''' Reads an array of bytes from the target process's ram into a pre-declared buffer.
''' </summary>
''' <param name="byteBuff">Must be large enough, will not be re-sized!</param>
''' <param name="size">Number of bytes to read.</param>
''' <param name="actualBytesRead">Byref. Returns the actual number of bytes read.</param>
Public Function ReadBytes(ByVal addr As IntPtr, ByRef byteBuff() As Byte, ByVal size As Int32, ByRef actualBytesRead As Int32) As Boolean
Return WinApi.ReadProcessMemory(_targetProcessHandle, addr, byteBuff, size, actualBytesRead)
End Function
#End Region
#Region "Write"
Public Function WriteByte(ByVal addr As IntPtr, ByVal aByte As Byte) As Boolean
Dim _bts() As Byte = {aByte} '' Awkward. Winapi function is declared as array() instead of as IntPtr
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, _bts, 1, New Int32)
End Function
Public Function WriteInt16(ByVal addr As IntPtr, ByVal data As Int16) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 2, New Int32)
End Function
Public Function WriteUInt16(ByVal addr As IntPtr, ByVal data As UInt16) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 2, New Int32)
End Function
Public Function WriteInt32(ByVal addr As IntPtr, ByVal data As Int32) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 4, New Int32)
End Function
Public Function WriteInt64(ByVal addr As IntPtr, ByVal data As Int64) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, New Int32)
End Function
Public Function WriteUInt64(ByVal addr As IntPtr, ByVal data As UInt64) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, New Int32)
End Function
Public Function WriteFloat(ByVal addr As IntPtr, ByVal data As Single) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 4, New Int32)
End Function
Public Function WriteDouble(ByVal addr As IntPtr, ByVal data As Double) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, New Int32)
End Function
Public Function WriteAsciiString(ByVal addr As IntPtr, ByVal str As String, ByRef actualBytesWritten As Int32) As Boolean
Dim _bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(str)
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, actualBytesWritten)
End Function
Public Function WriteUnicodeString(ByVal addr As IntPtr, ByVal str As String, ByRef actualBytesWritten As Int32) As Boolean
Dim _bytes() As Byte = System.Text.Encoding.Unicode.GetBytes(str)
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, actualBytesWritten)
End Function
Public Function WriteBytes(ByVal addr As IntPtr, ByVal bytes() As Byte, ByRef actualBytesWritten As Int32) As Boolean
Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, bytes, bytes.Length, actualBytesWritten)
End Function
#End Region
#Region "Hack"
Public Function GetMemRegions() As WinApi.MemoryBasicInformation()
Dim _rtnBlocks As New List(Of WinApi.MemoryBasicInformation)
Dim _curBase As Int64 = _systemInfo.lpMinimumApplicationAddress.ToInt64
Dim _curMbi As WinApi.MemoryBasicInformation
Try
Do
If WinApi.VirtualQueryEx(_targetProcessHandle, New IntPtr(_curBase), _curMbi, _mbiSize) = 0 Then
Exit Do
End If
If _curMbi.State = WinApi.MemoryAllocationState.COMMIT Then 'this page actively being used by the target process
_rtnBlocks.Add(_curMbi)
End If
_curBase += _curMbi.RegionSize.ToInt64
Loop While _curBase < _systemInfo.lpMaximumApplicationAddress.ToInt64
Catch ex As Exception
MessageBox.Show("MemoryManager::GetMemRegions() Threw an Exception. Please give this message to tech support -->" & Environment.NewLine _
& "Current memory region info:" & Environment.NewLine _
& ".baseAddress: 0x" & _curMbi.BaseAddress.ToString("X") & Environment.NewLine _
& ".size: " & _curMbi.RegionSize.ToString() & " bytes" & Environment.NewLine _
& ".protect: " & _curMbi.Protect & Environment.NewLine _
& ".type: " & _curMbi.AllocationType & Environment.NewLine _
& "state: " & _curMbi.State & Environment.NewLine & Environment.NewLine _
& "System error message (also important): " & Environment.NewLine _
& ex.Message & Environment.NewLine & "Thank you. ")
End Try
Return _rtnBlocks.ToArray()
End Function
''TODO: FindBytes(), FindPattern() and more.
#End Region
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
DetachFromProcess()
End If
End If
Me.disposedValue = True
End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
It's a few lines of code. Feel free to ask questions. A generic function would turn 100 lines of code into 15, but maybe this is more clear for people who are new/haven't used generics. Comments appreciated.