Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling kernel32's ReadProcessMemory in Go

I'm trying to manipulate processes on Windows using Go language, and I'm starting off by reading other process' memory by using ReadProcessMemory.

However, for most of the addresses I get Error: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. error. Maybe my list of arguments is wrong, but I can't find out why.

Can anyone point out what I am doing wrong here?

package main

import (
  "fmt"
)

import (
  windows "golang.org/x/sys/windows"
)

func main() {
  handle, _ := windows.OpenProcess(0x0010, false, 6100) // 0x0010 PROCESS_VM_READ, PID 6100
  procReadProcessMemory := windows.MustLoadDLL("kernel32.dll").MustFindProc("ReadProcessMemory")

  var data uint   = 0
  var length uint = 0

  for i := 0; i < 0xffffffff; i += 2 {
    fmt.Printf("0x%x\n", i)

    // BOOL ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead)
    ret, _, e := procReadProcessMemory.Call(uintptr(handle), uintptr(i), uintptr(data), 2, uintptr(length)) // read 2 bytes
    if (ret == 0) {
        fmt.Println("  Error:", e)          
    } else {
        fmt.Println("  Length:", length)
        fmt.Println("  Data:", data)                        
    }
  }

  windows.CloseHandle(handle)
}
like image 530
Hongweng Avatar asked Oct 02 '15 05:10

Hongweng


1 Answers

uintptr(data) is incorrect: it takes the value from data (0 of type uint) and converts that to unitptr type — yielding the same value converted to another type — producing, on x86, a null pointer.

Note that Go is not C, and you can't really play dirty games with pointers in it, or, rather, you can, but only through using the unsafe built-in package and its Pointer type which is like void* (pointing somewhere in a data memory block) in C.

What you need is something like

import "unsafe"

var (
    data   [2]byte
    length uint32
)
ret, _, e := procReadProcessMemory.Call(uintptr(handle), uintptr(i),
    uintptr(unsafe.Pointer(&data[0])),
    2, uintptr(unsafe.Pointer(&length))) // read 2 bytes

Observe what was done here:

  1. A variable of type "array of two bytes" is declared;
  2. The address of the first element of this array is taken;
  3. That address is type-converted to the type unsafe.Pointer;
  4. The obtained value is then type-converted to uintptr.

The last two steps are needed because Go features garbage collection:

  • In Go, when you take an address of a value in memory and store it in a variable, the GC knows about this "implicit" pointer and the value which address was taken won't be garbage-collected even if it becomes unreachable with that value holding its address being the only reference left.
  • Even if you make that address value lose the type information it maintains — through type-converting it to unsafe.Pointer, the new value is still considered by GC and behaves like "normal" values containing addresses — as explained above.
  • By type-converting such value to uintptr you make GC stop considering it as a pointer. Hence this type is there only for FFI/interop.

    In other words, in

    var data [2]byte
    a := &data[0]
    p := unsafe.Pointer(a)
    i := uintptr(p)
    

    there are only three references to the value in data: that variable itself, a and p, but not i.

You should consider these rules when dealing with calling outside code because you should never ever pass around unitptr-typed values: they're only for marshaling data to the called functions and unmarshaling it back, and have to be used "on the spot" — in the same scope as the values they are type-converted from/to.

Also observe that in Go, you can't just take the address of a variable of an integer type and supply that address to a function which expects a pointer to a memory block of an appropriate size. You have to deal with byte arrays and after the data has been written by the called function, you need to explicitly convert it to a value of the type you need. That's why there's no "type casts" in Go but only "type conversions": you can't reinterpret the data type of a value through type-conversion, with the uintptr(unsafe.Pointer) (and back) being a notable exception for the purpose of FFI/interop, and even in this case you basically convert a pointer to a pointer, just transfer it through the GC boundary.

To "serialize" and "deserialize" a value of an integer type you might use the encoding/binary standard package or hand-roll no-brainer simple functions which do bitwise shifts and or-s and so on ;-)


2015-10-05, updated as per the suggestion of James Henstridge.

Note that after the function returns, and ret signalizes there's no error you have to check the value of the length variable.

like image 73
kostix Avatar answered Sep 30 '22 03:09

kostix