From 2bb1585ee9a043c377d95a0fe73da6d8001d9a2c Mon Sep 17 00:00:00 2001 From: Weilu Jia Date: Sat, 25 Sep 2021 19:43:29 -0700 Subject: [PATCH] windows: Add low level memory related syscalls --- windows/memory_windows.go | 11 ++++ windows/syscall_windows.go | 5 ++ windows/syscall_windows_test.go | 112 ++++++++++++++++++++++++++++++++ windows/zsyscall_windows.go | 45 +++++++++++++ 4 files changed, 173 insertions(+) diff --git a/windows/memory_windows.go b/windows/memory_windows.go index 1adb6073..6dc0920a 100644 --- a/windows/memory_windows.go +++ b/windows/memory_windows.go @@ -35,3 +35,14 @@ const ( QUOTA_LIMITS_HARDWS_MAX_DISABLE = 0x00000008 QUOTA_LIMITS_HARDWS_MAX_ENABLE = 0x00000004 ) + +type MemoryBasicInformation struct { + BaseAddress uintptr + AllocationBase uintptr + AllocationProtect uint32 + PartitionId uint16 + RegionSize uintptr + State uint32 + Protect uint32 + Type uint32 +} diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go index ff2d45d4..b99b6f62 100644 --- a/windows/syscall_windows.go +++ b/windows/syscall_windows.go @@ -274,6 +274,11 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys VirtualAlloc(address uintptr, size uintptr, alloctype uint32, protect uint32) (value uintptr, err error) = kernel32.VirtualAlloc //sys VirtualFree(address uintptr, size uintptr, freetype uint32) (err error) = kernel32.VirtualFree //sys VirtualProtect(address uintptr, size uintptr, newprotect uint32, oldprotect *uint32) (err error) = kernel32.VirtualProtect +//sys VirtualProtectEx(process Handle, address uintptr, size uintptr, newProtect uint32, oldProtect *uint32) (err error) = kernel32.VirtualProtectEx +//sys VirtualQuery(address uintptr, buffer *MemoryBasicInformation, length uintptr) (err error) = kernel32.VirtualQuery +//sys VirtualQueryEx(process Handle, address uintptr, buffer *MemoryBasicInformation, length uintptr) (err error) = kernel32.VirtualQueryEx +//sys ReadProcessMemory(process Handle, baseAddress uintptr, buffer *byte, size uint32, numberOfBytesRead *uintptr) (err error) = kernel32.ReadProcessMemory +//sys WriteProcessMemory(process Handle, baseAddress uintptr, buffer *byte, size uint32, numberOfBytesWritten *uintptr) (err error) = kernel32.WriteProcessMemory //sys TransmitFile(s Handle, handle Handle, bytesToWrite uint32, bytsPerSend uint32, overlapped *Overlapped, transmitFileBuf *TransmitFileBuffers, flags uint32) (err error) = mswsock.TransmitFile //sys ReadDirectoryChanges(handle Handle, buf *byte, buflen uint32, watchSubTree bool, mask uint32, retlen *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) = kernel32.ReadDirectoryChangesW //sys FindFirstChangeNotification(path string, watchSubtree bool, notifyFilter uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.FindFirstChangeNotificationW diff --git a/windows/syscall_windows_test.go b/windows/syscall_windows_test.go index bc11750c..9ceb1a41 100644 --- a/windows/syscall_windows_test.go +++ b/windows/syscall_windows_test.go @@ -777,3 +777,115 @@ func TestProcessModules(t *testing.T) { t.Fatalf("module size does not match executable: %v != %v", moduleInfo.SizeOfImage, peSizeOfImage) } } + +func TestReadWriteProcessMemory(t *testing.T) { + testBuffer := []byte{0xBA, 0xAD, 0xF0, 0x0D} + + process, err := windows.GetCurrentProcess() + if err != nil { + t.Fatalf("unable to get current process: %v", err) + } + + buffer := make([]byte, len(testBuffer)) + err = windows.ReadProcessMemory(process, uintptr(unsafe.Pointer(&testBuffer[0])), &buffer[0], uint32(len(buffer)), nil) + if err != nil { + t.Errorf("ReadProcessMemory failed: %v", err) + } + if !bytes.Equal(testBuffer, buffer) { + t.Errorf("bytes read does not match buffer: 0x%X != 0x%X", testBuffer, buffer) + } + + buffer = []byte{0xDE, 0xAD, 0xBE, 0xEF} + err = windows.WriteProcessMemory(process, uintptr(unsafe.Pointer(&testBuffer[0])), &buffer[0], uint32(len(buffer)), nil) + if err != nil { + t.Errorf("WriteProcessMemory failed: %v", err) + } + if !bytes.Equal(testBuffer, buffer) { + t.Errorf("bytes written does not match buffer: 0x%X != 0x%X", testBuffer, buffer) + } +} + +func TestVirtualProtect(t *testing.T) { + testBuffer := []byte{0xBA, 0xAD, 0xF0, 0x0D} + + memBasicInfo := windows.MemoryBasicInformation{} + + err := windows.VirtualQuery(uintptr(unsafe.Pointer(&testBuffer[0])), &memBasicInfo, unsafe.Sizeof(memBasicInfo)) + if err != nil { + t.Fatalf("VirtualQuery failed: %v", err) + } + + origProtect := memBasicInfo.Protect + + var oldProtect uint32 + var newProtect uint32 = windows.PAGE_EXECUTE_READWRITE + err = windows.VirtualProtect(uintptr(unsafe.Pointer(&testBuffer[0])), uintptr(len(testBuffer)), newProtect, &oldProtect) // NB: Golang does not like it if you remove permissions + if err != nil { + t.Fatalf("VirtualProtect failed: %v", err) + } + + if origProtect != oldProtect { + t.Errorf("VirtualQuery Protect does not match oldProtect: 0x%X != 0x%X", origProtect, oldProtect) + } + + err = windows.VirtualQuery(uintptr(unsafe.Pointer(&testBuffer[0])), &memBasicInfo, unsafe.Sizeof(memBasicInfo)) + if err != nil { + t.Errorf("VirtualQuery failed: %v", err) + } + + if memBasicInfo.Protect != newProtect { + t.Errorf("VirtualQuery Protect does not match newProtect: 0x%X != 0x%X", memBasicInfo.Protect, newProtect) + } + + // Restore state + newProtect = oldProtect + err = windows.VirtualProtect(uintptr(unsafe.Pointer(&testBuffer[0])), uintptr(len(testBuffer)), newProtect, &oldProtect) + if err != nil { + t.Fatalf("VirtualProtect failed during restore: %v", err) + } +} + +func TestVirtualProtectEx(t *testing.T) { + testBuffer := []byte{0xBA, 0xAD, 0xF0, 0x0D} + + process, err := windows.GetCurrentProcess() + if err != nil { + t.Fatalf("unable to get current process: %v", err) + } + + memBasicInfo := windows.MemoryBasicInformation{} + + err = windows.VirtualQueryEx(process, uintptr(unsafe.Pointer(&testBuffer[0])), &memBasicInfo, unsafe.Sizeof(memBasicInfo)) + if err != nil { + t.Fatalf("VirtualQueryEx failed: %v", err) + } + + origProtect := memBasicInfo.Protect + + var oldProtect uint32 + var newProtect uint32 = windows.PAGE_EXECUTE_READWRITE + err = windows.VirtualProtectEx(process, uintptr(unsafe.Pointer(&testBuffer[0])), uintptr(len(testBuffer)), newProtect, &oldProtect) // NB: Golang does not like it if you remove permissions + if err != nil { + t.Fatalf("VirtualProtectEx failed: %v", err) + } + + if origProtect != oldProtect { + t.Errorf("VirtualQueryEx Protect does not match oldProtect: 0x%X != 0x%X", origProtect, oldProtect) + } + + err = windows.VirtualQueryEx(process, uintptr(unsafe.Pointer(&testBuffer[0])), &memBasicInfo, unsafe.Sizeof(memBasicInfo)) + if err != nil { + t.Errorf("VirtualQueryEx failed: %v", err) + } + + if memBasicInfo.Protect != newProtect { + t.Errorf("VirtualQueryEx Protect does not match newProtect: 0x%X != 0x%X", memBasicInfo.Protect, newProtect) + } + + // Restore state + newProtect = oldProtect + err = windows.VirtualProtectEx(process, uintptr(unsafe.Pointer(&testBuffer[0])), uintptr(len(testBuffer)), newProtect, &oldProtect) + if err != nil { + t.Fatalf("VirtualProtectEx failed during restore: %v", err) + } +} diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go index e282e246..91410a3c 100644 --- a/windows/zsyscall_windows.go +++ b/windows/zsyscall_windows.go @@ -303,6 +303,7 @@ var ( procReadConsoleW = modkernel32.NewProc("ReadConsoleW") procReadDirectoryChangesW = modkernel32.NewProc("ReadDirectoryChangesW") procReadFile = modkernel32.NewProc("ReadFile") + procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") procReleaseMutex = modkernel32.NewProc("ReleaseMutex") procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW") procResetEvent = modkernel32.NewProc("ResetEvent") @@ -345,12 +346,16 @@ var ( procVirtualFree = modkernel32.NewProc("VirtualFree") procVirtualLock = modkernel32.NewProc("VirtualLock") procVirtualProtect = modkernel32.NewProc("VirtualProtect") + procVirtualProtectEx = modkernel32.NewProc("VirtualProtectEx") + procVirtualQuery = modkernel32.NewProc("VirtualQuery") + procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx") procVirtualUnlock = modkernel32.NewProc("VirtualUnlock") procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId") procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects") procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject") procWriteConsoleW = modkernel32.NewProc("WriteConsoleW") procWriteFile = modkernel32.NewProc("WriteFile") + procWriteProcessMemory = modkernel32.NewProc("WriteProcessMemory") procAcceptEx = modmswsock.NewProc("AcceptEx") procGetAcceptExSockaddrs = modmswsock.NewProc("GetAcceptExSockaddrs") procTransmitFile = modmswsock.NewProc("TransmitFile") @@ -2636,6 +2641,14 @@ func ReadFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) ( return } +func ReadProcessMemory(process Handle, baseAddress uintptr, buffer *byte, size uint32, numberOfBytesRead *uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procReadProcessMemory.Addr(), 5, uintptr(process), uintptr(baseAddress), uintptr(unsafe.Pointer(buffer)), uintptr(size), uintptr(unsafe.Pointer(numberOfBytesRead)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func ReleaseMutex(mutex Handle) (err error) { r1, _, e1 := syscall.Syscall(procReleaseMutex.Addr(), 1, uintptr(mutex), 0, 0) if r1 == 0 { @@ -2990,6 +3003,30 @@ func VirtualProtect(address uintptr, size uintptr, newprotect uint32, oldprotect return } +func VirtualProtectEx(process Handle, address uintptr, size uintptr, newProtect uint32, oldProtect *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procVirtualProtectEx.Addr(), 5, uintptr(process), uintptr(address), uintptr(size), uintptr(newProtect), uintptr(unsafe.Pointer(oldProtect)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func VirtualQuery(address uintptr, buffer *MemoryBasicInformation, length uintptr) (err error) { + r1, _, e1 := syscall.Syscall(procVirtualQuery.Addr(), 3, uintptr(address), uintptr(unsafe.Pointer(buffer)), uintptr(length)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func VirtualQueryEx(process Handle, address uintptr, buffer *MemoryBasicInformation, length uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procVirtualQueryEx.Addr(), 4, uintptr(process), uintptr(address), uintptr(unsafe.Pointer(buffer)), uintptr(length), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func VirtualUnlock(addr uintptr, length uintptr) (err error) { r1, _, e1 := syscall.Syscall(procVirtualUnlock.Addr(), 2, uintptr(addr), uintptr(length), 0) if r1 == 0 { @@ -3046,6 +3083,14 @@ func WriteFile(handle Handle, buf []byte, done *uint32, overlapped *Overlapped) return } +func WriteProcessMemory(process Handle, baseAddress uintptr, buffer *byte, size uint32, numberOfBytesWritten *uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procWriteProcessMemory.Addr(), 5, uintptr(process), uintptr(baseAddress), uintptr(unsafe.Pointer(buffer)), uintptr(size), uintptr(unsafe.Pointer(numberOfBytesWritten)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func AcceptEx(ls Handle, as Handle, buf *byte, rxdatalen uint32, laddrlen uint32, raddrlen uint32, recvd *uint32, overlapped *Overlapped) (err error) { r1, _, e1 := syscall.Syscall9(procAcceptEx.Addr(), 8, uintptr(ls), uintptr(as), uintptr(unsafe.Pointer(buf)), uintptr(rxdatalen), uintptr(laddrlen), uintptr(raddrlen), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(overlapped)), 0) if r1 == 0 {