-1

How to write / read the memory of any process?

As I understand, I have to use WriteProcessMemory / ReadProcessMemory winapi functions.

Here is whith what i start:

Add-Type -TypeDefinition '
using System;
using System.Runtime.InteropServices;

public class winapi{
    [DllImport("kernel32.dll")]public static extern Boolean WriteProcessMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        IntPtr lpBuffer,
        UInt32 nSize,
        ref UInt32 lpNumberOfBytesWritten
    );
    [DllImport("kernel32.dll")]public static extern Boolean ReadProcessMemory( 
        IntPtr hProcess, 
        IntPtr lpBaseAddress,
        IntPtr lpBuffer,
        UInt32 dwSize, 
        ref UInt32 lpNumberOfBytesRead
    );
}
'

[IntPtr]$mem = [Runtime.InteropServices.Marshal]::AllocHGlobal(4)

$dataToWrite = "ABC"

[IntPtr]$hProcess = $pid
[IntPtr]$lpBaseAddress = $mem
[IntPtr]$lpBuffer
[UInt32]$nSize
[UInt32]$BytesWritten

$CallResult = [winapi]::WriteProcessMemory(
    $hProcess,
    $lpBaseAddress,
    $lpBuffer,
    $nSize,
    [ref]$BytesWritten
)
$CallResult

[IntPtr]$hProcess = $pid
[IntPtr]$lpBaseAddress  = $mem
[UInt32]$ReadSize = 4
[IntPtr]$lpBuffer = [Runtime.InteropServices.Marshal]::AllocHGlobal($ReadSize)
[UInt32]$BytesRead = 0 

$CallResult = [winapi]::ReadProcessMemory(
    $hProcess,
    $lpBaseAddress,
    $lpBuffer,
    $ReadSize,
    [ref]$BytesRead
)
$CallResult

# As I understand, here I should get "ABC"

[Runtime.InteropServices.Marshal]::FreeHGlobal($mem)
[Runtime.InteropServices.Marshal]::FreeHGlobal($lpBuffer)

I need to read some processes memory / write processes memory.

How to do it correctly?

Lexxy
  • 457
  • 2
  • 11
  • Add `$destArray = [byte[]]::new($BytesRead);[System.Runtime.InteropServices.Marshal]::Copy($lpBuffer, $destArray, 0, $BytesRead)` before the `FreeHGlobal()` calls at the buttom - will copy the read memory stored in the unmanaged `$lpBuffer` to `$destArray` - but it won't correspond to `$dataToWrite`, because you didn't actually write anything in the first place :) – Mathias R. Jessen Oct 25 '20 at 11:47
  • 1
    *"How to do it correctly?"* Er, the first step is figuring out, which address to read from/write to. That's the hard part. The question you asked about is the trivial part, that's been asked and answered literally hundreds of times. – IInspectable Oct 25 '20 at 12:10

1 Answers1

1

Let's take them one at a time.

WriteProcessMemory

In order to write something to somewhere in some process, you need to pass the correct arguments:

lpBuffer

The lpBuffer argument must point to the data that needs to be written.

There's a couple of ways to produce a safe pointer to an existing object, I prefer this approach:

$dataToWrite = "ABC"

# Encode data in byte array 
$dataArray   = [Text.Encoding]::Unicode.GetBytes($dataToWrite)

# Pin the object in memory (ensures the garbage collector doesn't move the source data around)
$pinnedArray = [Runtime.InteropServices.GCHandle]::Alloc($dataArray, [Runtime.InteropServices.GCHandleType]::Pinned)

# Now we can obtain a safe pointer to the array
[IntPtr]$lpBuffer = $pinnedArray.AddrOfPinnedObject()

But the far easier approach would be to just declare lpBuffer a byte[] in the first place and have the runtime take care of all that (sans the string encoding):

[DllImport("kernel32.dll")]public static extern Boolean WriteProcessMemory(
    IntPtr hProcess,
    IntPtr lpBaseAddress,
    byte[] lpBuffer,
    UInt32 nSize,
    ref UInt32 lpNumberOfBytesWritten
);

hProcess

For the hProcess argument, you need a process handle - passing the PID of the owning process directly won't work.

For the current process (ie. the executing PowerShell host application), you can use Get-Process:

# Obtain handle to current process
$hProcess = (Get-Process -Id $PID).Handle

But for a different process, you'll need to call Kernel32!OpenProcess() - this answer has an excellent example of how to import and declare the access flag parameter type:

[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(
    ProcessAccessFlags dwDesiredAccess, 
    [MarshalAs(UnmanagedType.Bool)]
    bool bInheritHandle,
    int dwProcessId);
[Flags]
public enum ProcessAccessFlags : uint
{
    All = 0x001F0FFF,
    Terminate = 0x00000001,
    CreateThread = 0x00000002,
    VMOperation = 0x00000008,
    VMRead = 0x00000010,
    VMWrite = 0x00000020,
    DupHandle = 0x00000040,
    SetInformation = 0x00000200,
    QueryInformation = 0x00000400,
    Synchronize = 0x00100000
}

lpBaseAddress

[Marshal]::AllocHGlobal($size) will only help you when writing to the current process.

For allocation in a different process, you'll need to pass a writeable process handle obtained via OpenProcess() to Kernel32!VirtualAllocEx():

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true) ]
public extern IntPtr VirtualAllocEx(
    IntPtr hProcess,
    IntPtr lpAddress,
    UInt32 dwSize,
    AllocationType flAllocationType,
    MemoryProtection flProtect);
[Flags]
public enum AllocationType
{
     Commit = 0x1000,
     Reserve = 0x2000,
     Decommit = 0x4000,
     Release = 0x8000,
     Reset = 0x80000,
     Physical = 0x400000,
     TopDown = 0x100000,
     WriteWatch = 0x200000,
     LargePages = 0x20000000
}

[Flags]
public enum MemoryProtection
{
     Execute = 0x10,
     ExecuteRead = 0x20,
     ExecuteReadWrite = 0x40,
     ExecuteWriteCopy = 0x80,
     NoAccess = 0x01,
     ReadOnly = 0x02,
     ReadWrite = 0x04,
     WriteCopy = 0x08,
     GuardModifierflag = 0x100,
     NoCacheModifierflag = 0x200,
     WriteCombineModifierflag = 0x400
}

VirtualAllocEx will write the base address of the allocation to the lpAddress pointer on success.


ReadProcessMemory

Finally, to read the resulting data, you'll need to copy the data referenced by the lpBuffer argument to an array:

$CallResult = [winapi]::ReadProcessMemory(
    $hProcess,
    $lpBaseAddress,
    $lpBuffer,
    $ReadSize,
    [ref]$BytesRead
)

if($CallResult){
  $destArray = [byte[]]::new($BytesRead)
  [System.Runtime.InteropServices.Marshal]::Copy($lpBuffer, $destArray, 0, $BytesRead)

  $stringRead = [Text.Encoding]::Unicode.GetString($destArray)
  Write-Host "Read data from process memory: $stringRead"
}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206