在Go中调用kernel32的ReadProcessMemory

5

我正在使用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)
}
1个回答

6

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

请注意,Go语言不是C语言,您不能真正地在其中使用指针进行危险操作,或者说,您可以通过使用内置的unsafe包及其Pointer类型来进行操作,该类型类似于C语言中的void*(指向数据内存块中的某个位置)。

您需要的是类似于以下内容:

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

观察这里所做的事情:

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

最后两步是必要的,因为Go语言具有垃圾回收功能:

  • 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.

处理调用外部代码时,您应该考虑这些规则,因为绝不能传递unitptr类型的值:它们仅用于将数据编组到被调用的函数中并将其解组回来,并且必须在相同的范围内使用 - 它们是从/转换为类型的值所在的范围。
此外,请注意,在Go语言中,您不能只取整数类型变量的地址,并将该地址提供给期望指向适当大小的内存块的指针的函数。 你必须处理字节数组,并在被调用的函数写入数据后,需要显式地将其转换为所需的类型值。这就是为什么Go没有“类型强制转换”,而只有“类型转换”的原因:您不能通过类型转换重新解释值的数据类型,而uintptr(unsafe.Pointer)(以及返回)作为FFI / interop的一个值得注意的例外情况,在这种情况下,您基本上将指针转换为指针,只是通过GC边界传输它。
要“序列化”和“反序列化”整数类型的值,您可以使用encoding /binary标准包或手动编写简单的函数,其中进行位移和或运算等操作 ;-)
请注意,函数返回后,如果ret表示没有错误,则必须检查length变量的值。
2015-10-05,根据James Henstridge的建议进行更新。

小问题:a := &data[2] 不是 data 的有效偏移量,因为它的长度只有 2。除此之外,回答很棒,+1。 - thwd
1
看起来最后一个参数也应该是指向 int32 的指针(鉴于 LPDWORD 类型)——它看起来是一个输出参数,用于表示实际读入缓冲区的字节数。 - James Henstridge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接