英文:
Calling kernel32's ReadProcessMemory in Go
问题
我正在尝试使用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)
}
英文:
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)
}
答案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))) // 读取 2 个字节
这里做了以下操作:
- 声明了一个类型为 "包含两个字节的数组" 的变量;
- 取得了该数组第一个元素的地址;
- 将该地址转换为类型
unsafe.Pointer
; - 将得到的值转换为
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
中的值,只有三个引用:变量本身、a
和 p
,而不包括 i
。
在处理调用外部代码时,你应该考虑这些规则,因为你绝不能传递 unitptr
类型的值:它们仅用于将数据编组到被调用的函数中,并将其解组回来,并且必须在同一作用域中使用,即与它们从/到的值相同的作用域。
还要注意,在 Go 中,你不能只是获取整数类型变量的地址,并将该地址提供给期望相应大小的内存块指针的函数。你必须处理字节数组,并在被调用的函数写入数据后,需要显式将其转换为所需类型的值。这就是为什么 Go 中没有“类型转换”而只有“类型转换”的原因:你不能通过类型转换来重新解释值的数据类型,只有 uintptr(unsafe.Pointer)
(以及返回)是一个值得注意的例外,用于 FFI/interop 的目的,即使在这种情况下,你基本上只是将指针转换为指针,只是通过 GC 边界传递它。
要对整数类型的值进行“序列化”和“反序列化”,你可以使用 encoding/binary
标准包,或者手动编写简单的函数,进行位移和按位或等操作。
英文:
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:
- A variable of type "array of two bytes" is declared;
- The address of the first element of this array is taken;
- That address is type-converted to the type
unsafe.Pointer
; - 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
andp
, but noti
.
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论