在 Go 中调用 kernel32 的 ReadProcessMemory



我正在尝试使用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%xn", 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)
}

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. 该地址类型转换为类型unsafe.Pointer;
  3. 然后将得到的值类型转换为uintptr

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

  • 在Go中,当你在内存中获取一个值的地址并将其存储在一个变量中,GC知道这个"隐式"指针,并且即使它变得不可达,该值保持其地址是唯一的引用,也不会被垃圾收集。
  • 即使你使该地址值丢失了它所维护的类型信息—通过将其类型转换为unsafe.Pointer,新值仍然被GC考虑,并且表现得像包含地址—如上所述。
  • 通过将这样的值类型转换为uintptr,可以使GC不再将其视为指针。因此,这种类型只适用于FFI/interop。

    也就是说,在

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

    data中的值只有三个引用:变量本身,ap,但没有i

在处理调用外部代码时,你应该考虑这些规则,因为你永远不应该传递unitptr类型的值:它们只用于将数据封送给被调用的函数并将其反封送,并且必须"当场"使用—在与值相同的作用域中,它们从/类型转换为。

还需要注意的是,在Go语言中,你不能直接获取整型变量的地址,并将该地址提供给一个需要指针指向适当大小的内存块的函数。您必须处理字节数组,并且在被调用的函数写入数据之后,您需要显式地将其转换为所需类型的值。这就是为什么Go中没有"类型强制转换",只有"类型转换":你不能通过类型转换重新解释值的数据类型,uintptr(unsafe.Pointer)(和back)是FFI/互操作的一个显著例外,即使在这种情况下,你基本上将指针转换为指针,只是通过GC边界传输它。

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


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

注意,函数返回后,ret表示没有错误您必须检查length变量的值

相关内容

  • 没有找到相关文章

最新更新