280 lines
6.3 KiB
Go
280 lines
6.3 KiB
Go
package memory
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
type objectAttributes struct {
|
|
Length uint32
|
|
RootDirectory windows.Handle
|
|
ObjectName uintptr
|
|
Attributes uint32
|
|
SecurityDescriptor uintptr
|
|
SecurityQualityOfService uintptr
|
|
}
|
|
|
|
type clientID struct {
|
|
UniqueProcess uintptr
|
|
UniqueThread uintptr
|
|
}
|
|
|
|
var (
|
|
dsInitOnce sync.Once
|
|
dsInitErr error
|
|
|
|
ntOpenProcessStub uintptr
|
|
ntReadVMStub uintptr
|
|
ntWriteVMStub uintptr
|
|
ntCloseStub uintptr
|
|
ntAllocVMStub uintptr
|
|
ntProtectVMStub uintptr
|
|
ntFreeVMStub uintptr
|
|
)
|
|
|
|
func initDirectSyscalls() {
|
|
var err error
|
|
ntOpenProcessStub, err = buildNtStub("NtOpenProcess")
|
|
if err != nil {
|
|
dsInitErr = err
|
|
return
|
|
}
|
|
ntReadVMStub, err = buildNtStub("NtReadVirtualMemory")
|
|
if err != nil {
|
|
dsInitErr = err
|
|
return
|
|
}
|
|
ntWriteVMStub, err = buildNtStub("NtWriteVirtualMemory")
|
|
if err != nil {
|
|
dsInitErr = err
|
|
return
|
|
}
|
|
ntCloseStub, err = buildNtStub("NtClose")
|
|
if err != nil {
|
|
dsInitErr = err
|
|
return
|
|
}
|
|
ntAllocVMStub, err = buildNtStub("NtAllocateVirtualMemory")
|
|
if err != nil {
|
|
dsInitErr = err
|
|
return
|
|
}
|
|
ntProtectVMStub, err = buildNtStub("NtProtectVirtualMemory")
|
|
if err != nil {
|
|
dsInitErr = err
|
|
return
|
|
}
|
|
ntFreeVMStub, err = buildNtStub("NtFreeVirtualMemory")
|
|
if err != nil {
|
|
dsInitErr = err
|
|
return
|
|
}
|
|
}
|
|
|
|
func ensureDirectSyscalls() error {
|
|
dsInitOnce.Do(initDirectSyscalls)
|
|
return dsInitErr
|
|
}
|
|
|
|
func buildNtStub(name string) (uintptr, error) {
|
|
ntdll := windows.NewLazySystemDLL("ntdll.dll")
|
|
proc := ntdll.NewProc(name)
|
|
if err := ntdll.Load(); err != nil {
|
|
return 0, fmt.Errorf("load ntdll.dll: %w", err)
|
|
}
|
|
|
|
addr := proc.Addr()
|
|
if addr == 0 {
|
|
return 0, fmt.Errorf("%s address is zero", name)
|
|
}
|
|
sysID, err := extractSysID(addr)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("extract %s sysid: %w", name, err)
|
|
}
|
|
return makeSyscallStub(sysID)
|
|
}
|
|
|
|
func extractSysID(procAddr uintptr) (uint32, error) {
|
|
b := unsafe.Slice((*byte)(unsafe.Pointer(procAddr)), 64)
|
|
if len(b) < 11 {
|
|
return 0, errors.New("short Nt stub")
|
|
}
|
|
if b[0] != 0x4C || b[1] != 0x8B || b[2] != 0xD1 || b[3] != 0xB8 {
|
|
return 0, errors.New("unexpected Nt stub prefix")
|
|
}
|
|
found := false
|
|
for i := 0; i+2 < len(b); i++ {
|
|
if b[i] == 0x0F && b[i+1] == 0x05 && b[i+2] == 0xC3 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return 0, errors.New("syscall;ret not found")
|
|
}
|
|
return binary.LittleEndian.Uint32(b[4:8]), nil
|
|
}
|
|
|
|
func makeSyscallStub(sysID uint32) (uintptr, error) {
|
|
code := []byte{0x4C, 0x8B, 0xD1, 0xB8, 0, 0, 0, 0, 0x0F, 0x05, 0xC3}
|
|
binary.LittleEndian.PutUint32(code[4:8], sysID)
|
|
mem, err := windows.VirtualAlloc(0, uintptr(len(code)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
copy(unsafe.Slice((*byte)(unsafe.Pointer(mem)), len(code)), code)
|
|
return mem, nil
|
|
}
|
|
|
|
func ntStatusErr(op string, status uintptr) error {
|
|
return fmt.Errorf("%s failed: NTSTATUS=0x%08X", op, uint32(status))
|
|
}
|
|
|
|
func ntOpenProcess(pid uint32, access uint32) (windows.Handle, error) {
|
|
if err := ensureDirectSyscalls(); err != nil {
|
|
return 0, err
|
|
}
|
|
var h windows.Handle
|
|
oa := objectAttributes{Length: uint32(unsafe.Sizeof(objectAttributes{}))}
|
|
cid := clientID{UniqueProcess: uintptr(pid)}
|
|
status, _, _ := syscall.SyscallN(
|
|
ntOpenProcessStub,
|
|
uintptr(unsafe.Pointer(&h)),
|
|
uintptr(access),
|
|
uintptr(unsafe.Pointer(&oa)),
|
|
uintptr(unsafe.Pointer(&cid)),
|
|
)
|
|
if status != 0 {
|
|
return 0, ntStatusErr("NtOpenProcess", status)
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
func ntReadProcessMemory(handle windows.Handle, address uintptr, out []byte) error {
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
if err := ensureDirectSyscalls(); err != nil {
|
|
return err
|
|
}
|
|
var bytesRead uintptr
|
|
status, _, _ := syscall.SyscallN(
|
|
ntReadVMStub,
|
|
uintptr(handle),
|
|
address,
|
|
uintptr(unsafe.Pointer(&out[0])),
|
|
uintptr(len(out)),
|
|
uintptr(unsafe.Pointer(&bytesRead)),
|
|
)
|
|
if status != 0 {
|
|
return ntStatusErr("NtReadVirtualMemory", status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ntWriteProcessMemory(handle windows.Handle, address uintptr, in []byte) error {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
if err := ensureDirectSyscalls(); err != nil {
|
|
return err
|
|
}
|
|
var bytesWritten uintptr
|
|
status, _, _ := syscall.SyscallN(
|
|
ntWriteVMStub,
|
|
uintptr(handle),
|
|
address,
|
|
uintptr(unsafe.Pointer(&in[0])),
|
|
uintptr(len(in)),
|
|
uintptr(unsafe.Pointer(&bytesWritten)),
|
|
)
|
|
if status != 0 {
|
|
return ntStatusErr("NtWriteVirtualMemory", status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ntCloseHandle(handle windows.Handle) error {
|
|
if handle == 0 {
|
|
return nil
|
|
}
|
|
if err := ensureDirectSyscalls(); err != nil {
|
|
return err
|
|
}
|
|
status, _, _ := syscall.SyscallN(ntCloseStub, uintptr(handle))
|
|
if status != 0 {
|
|
return ntStatusErr("NtClose", status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ntAllocateVirtualMemory(handle windows.Handle, size uintptr, allocType, protect uint32) (uintptr, error) {
|
|
if err := ensureDirectSyscalls(); err != nil {
|
|
return 0, err
|
|
}
|
|
base := uintptr(0)
|
|
regionSize := size
|
|
status, _, _ := syscall.SyscallN(
|
|
ntAllocVMStub,
|
|
uintptr(handle),
|
|
uintptr(unsafe.Pointer(&base)),
|
|
0,
|
|
uintptr(unsafe.Pointer(®ionSize)),
|
|
uintptr(allocType),
|
|
uintptr(protect),
|
|
)
|
|
if status != 0 {
|
|
return 0, ntStatusErr("NtAllocateVirtualMemory", status)
|
|
}
|
|
return base, nil
|
|
}
|
|
|
|
func ntProtectVirtualMemory(handle windows.Handle, address uintptr, size uintptr, newProtect uint32) (uint32, error) {
|
|
if err := ensureDirectSyscalls(); err != nil {
|
|
return 0, err
|
|
}
|
|
base := address
|
|
regionSize := size
|
|
var oldProtect uint32
|
|
status, _, _ := syscall.SyscallN(
|
|
ntProtectVMStub,
|
|
uintptr(handle),
|
|
uintptr(unsafe.Pointer(&base)),
|
|
uintptr(unsafe.Pointer(®ionSize)),
|
|
uintptr(newProtect),
|
|
uintptr(unsafe.Pointer(&oldProtect)),
|
|
)
|
|
if status != 0 {
|
|
return 0, ntStatusErr("NtProtectVirtualMemory", status)
|
|
}
|
|
return oldProtect, nil
|
|
}
|
|
|
|
func ntFreeVirtualMemory(handle windows.Handle, address uintptr, freeType uint32) error {
|
|
if address == 0 {
|
|
return nil
|
|
}
|
|
if err := ensureDirectSyscalls(); err != nil {
|
|
return err
|
|
}
|
|
base := address
|
|
regionSize := uintptr(0) // required for MEM_RELEASE
|
|
status, _, _ := syscall.SyscallN(
|
|
ntFreeVMStub,
|
|
uintptr(handle),
|
|
uintptr(unsafe.Pointer(&base)),
|
|
uintptr(unsafe.Pointer(®ionSize)),
|
|
uintptr(freeType),
|
|
)
|
|
if status != 0 {
|
|
return ntStatusErr("NtFreeVirtualMemory", status)
|
|
}
|
|
return nil
|
|
}
|