在Go中调用kernel32的ReadProcessMemory

Hon*_*eng 5 interop unsafe ffi go kernel32

我正在尝试使用Go语言操作Windows上的进程,我开始通过使用来阅读其他进程的内存ReadProcessMemory.

但是,对于大多数地址,我都会收到Error: Only part of a ReadProcessMemory or WriteProcessMemory request was completed.错误.也许我的论据清单是错误的,但我找不到原因.

谁能指出我在这里做错了什么?

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)
}
Run Code Online (Sandbox Code Playgroud)

kos*_*tix 6

uintptr(data)是不正确的:它取值data(0的类型uint)并将其转换为unitptr类型 - 产生转换为另一种类型的相同值 - 在x86上产生一个空指针.

注意Go不是C,你不能真正玩带有指针的脏游戏,或者说,你可以,但只能通过使用unsafe内置包及其Pointer类型void*(指向数据存储器中的某处)在C.中

你需要的是什么

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
Run Code Online (Sandbox Code Playgroud)

观察这里做了什么:

  1. 声明了一个"两个字节的数组"类型的变量;
  2. 取这个数组的第一个元素的地址;
  3. 该地址被类型转换为该类型unsafe.Pointer;
  4. 然后将获得的值进行类型转换为uintptr.

最后两个步骤是必需的,因为Go功能垃圾收集:

  • 在Go中,当你在内存中获取一个值的地址并将其存储在一个变量中时,GC知道这个"隐式"指针,并且即使它无法访问,也不会对所采用的地址进行垃圾收集.保持其地址为唯一参考的值.
  • 即使您使该地址值丢失了它所维护的类型信息 - 通过类型转换它unsafe.Pointer,GC仍然会考虑新值,并且行为类似于包含地址的"正常"值 - 如上所述.
  • 通过类型转换这样的值uintptr使GC停止将其视为指针.因此这种类型只适用于FFI /互操作.

    换句话说,在

    var data [2]byte
    a := &data[0]
    p := unsafe.Pointer(a)
    i := uintptr(p)
    
    Run Code Online (Sandbox Code Playgroud)

    只有三个引用的值data:该变量本身,ap,但不会i.

在处理调用外部代码时应该考虑这些规则,因为你永远不应该传递周围的unitptr值:它们只是用于将数据封送到被调用的函数并将其解组,并且必须"在现场"使用 - 在与从/到它们的类型转换的值相同的范围.

另请注意,在Go中,您不能只获取整数类型变量的地址,并将该地址提供给需要指向适当大小的内存块的指针的函数.您必须处理字节数组,并且在被调用函数写入数据之后,您需要将其显式转换为您需要的类型的值.这就是Go中没有"类型转换"但只有"类型转换"的原因:你不能通过类型转换重新解释值的数据类型,而uintptr(unsafe.Pointer)(和back)是FFI/interop目的的一个值得注意的例外,即使在这种情况下,您基本上将指针转换为指针,只需将其传递通过GC边界.

要"序列化"和"反序列化"整数类型的值,您可以使用encoding/binary标准包或手动滚动简单函数,这些函数按位移位和or-s等等;-)


2015-10-05,根据James Henstridge的建议更新.

请注意,在函数返回后,并发出ret信号表示没有错误,您必须检查length变量的值.