From 8173a628b2c3024e9a98dcea81b390c6ec13cb7a Mon Sep 17 00:00:00 2001 From: vietdungdev Date: Sat, 21 Mar 2026 11:51:59 +0700 Subject: [PATCH 1/2] secure v2 --- pkg/memory/direct_syscall.go | 285 +++++++++++++++++++++++++++++++++++ pkg/memory/process.go | 16 +- pkg/memory/send_packet.go | 81 ++-------- 3 files changed, 308 insertions(+), 74 deletions(-) create mode 100644 pkg/memory/direct_syscall.go diff --git a/pkg/memory/direct_syscall.go b/pkg/memory/direct_syscall.go new file mode 100644 index 0000000..937fb9c --- /dev/null +++ b/pkg/memory/direct_syscall.go @@ -0,0 +1,285 @@ +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_READWRITE) + if err != nil { + return 0, err + } + copy(unsafe.Slice((*byte)(unsafe.Pointer(mem)), len(code)), code) + var oldProtect uint32 + err = windows.VirtualProtect(mem, uintptr(len(code)), windows.PAGE_EXECUTE_READ, &oldProtect) + if err != nil { + return 0, err + } + + 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 +} diff --git a/pkg/memory/process.go b/pkg/memory/process.go index a7f6575..29f44f2 100644 --- a/pkg/memory/process.go +++ b/pkg/memory/process.go @@ -36,7 +36,7 @@ func NewProcess() (*Process, error) { return nil, err } - h, err := windows.OpenProcess(0x0010, false, module.ProcessID) + h, err := ntOpenProcess(module.ProcessID, 0x0010) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func NewProcessForPID(pid uint32) (*Process, error) { return nil, errors.New("no module found for the specified PID") } - h, err := windows.OpenProcess(0x0010, false, module.ProcessID) + h, err := ntOpenProcess(module.ProcessID, 0x0010) if err != nil { return nil, err } @@ -69,7 +69,7 @@ func NewProcessForPID(pid uint32) (*Process, error) { } func (p *Process) Close() error { - return windows.CloseHandle(p.handler) + return ntCloseHandle(p.handler) } // ModuleBaseAddress returns the base address of the D2R module. @@ -138,7 +138,7 @@ func ReadMemoryChunked(handle windows.Handle, baseAddress uintptr, size uint32) } // Try to read, but don't fail if it's protected - err := windows.ReadProcessMemory(handle, address, &data[offset], chunkSize, nil) + err := ntReadProcessMemory(handle, address, data[offset:offset+chunkSize]) if err != nil { failedReads++ // Fill with zeros and continue (pattern matching will fail gracefully) @@ -155,7 +155,7 @@ func ReadMemoryChunked(handle windows.Handle, baseAddress uintptr, size uint32) func (p *Process) ReadBytesFromMemory(address uintptr, size uint) []byte { var data = make([]byte, size) - windows.ReadProcessMemory(p.handler, address, &data[0], uintptr(size), nil) + _ = ntReadProcessMemory(p.handler, address, data) return data } @@ -275,11 +275,11 @@ type ModuleInfo struct { } func GetProcessModules(processID uint32) ([]ModuleInfo, error) { - hProcess, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, processID) + hProcess, err := ntOpenProcess(processID, windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ) if err != nil { return nil, err } - defer windows.CloseHandle(hProcess) + defer ntCloseHandle(hProcess) var modules [1024]windows.Handle var needed uint32 @@ -323,7 +323,7 @@ func (p *Process) ReadPointer(address uintptr, size int) (uintptr, error) { } func (p *Process) ReadIntoBuffer(address uintptr, buffer []byte) error { - return windows.ReadProcessMemory(p.handler, address, &buffer[0], uintptr(len(buffer)), nil) + return ntReadProcessMemory(p.handler, address, buffer) } // ReadWidgetContainer reads the WidgetContainer structure. diff --git a/pkg/memory/send_packet.go b/pkg/memory/send_packet.go index cc44291..6a94cfd 100644 --- a/pkg/memory/send_packet.go +++ b/pkg/memory/send_packet.go @@ -38,14 +38,11 @@ var ( } kernel32 = windows.NewLazySystemDLL("kernel32.dll") - procVirtualAllocEx = kernel32.NewProc("VirtualAllocEx") - procVirtualFreeEx = kernel32.NewProc("VirtualFreeEx") procQueueUserAPC = kernel32.NewProc("QueueUserAPC") procSuspendThread = kernel32.NewProc("SuspendThread") procResumeThread = kernel32.NewProc("ResumeThread") procGetExitCodeThread = kernel32.NewProc("GetExitCodeThread") procGetThreadTimes = kernel32.NewProc("GetThreadTimes") - procVirtualProtectEx = kernel32.NewProc("VirtualProtectEx") procSetProcessValidCallTargets = kernel32.NewProc("SetProcessValidCallTargets") procCreateRemoteThread = kernel32.NewProc("CreateRemoteThread") @@ -143,7 +140,7 @@ func (s *sendPacketState) ensureHandle(pid uint32) (windows.Handle, error) { virtualFreeEx(s.handle, s.stub) } - windows.CloseHandle(s.handle) + _ = ntCloseHandle(s.handle) s.handle = 0 s.processPID = 0 s.stub = 0 @@ -153,7 +150,7 @@ func (s *sendPacketState) ensureHandle(pid uint32) (windows.Handle, error) { s.fn = 0 } - h, err := windows.OpenProcess(sendPacketProcessAccess, false, pid) + h, err := ntOpenProcess(pid, sendPacketProcessAccess) if err != nil { return 0, fmt.Errorf("open process %d: %w", pid, err) } @@ -305,7 +302,7 @@ func (s *sendPacketState) Cleanup() error { // Close thread handle - the main thread may change between games if s.thread != 0 { - windows.CloseHandle(s.thread) + _ = ntCloseHandle(s.thread) s.thread = 0 s.threadID = 0 } @@ -343,12 +340,12 @@ func (s *sendPacketState) ensureThreadHandle(p *Process) (windows.Handle, error) } } // Thread is invalid, close and reset - windows.CloseHandle(s.thread) + _ = ntCloseHandle(s.thread) s.thread = 0 s.threadID = 0 s.processPID = 0 } else if s.thread != 0 && s.processPID != p.pid { - windows.CloseHandle(s.thread) + _ = ntCloseHandle(s.thread) s.thread = 0 s.threadID = 0 s.processPID = 0 @@ -366,7 +363,7 @@ func (s *sendPacketState) ensureThreadHandle(p *Process) (windows.Handle, error) // Validate the newly opened thread belongs to the correct process if err := validateThreadBelongsToProcess(handle, p.pid); err != nil { - windows.CloseHandle(handle) + _ = ntCloseHandle(handle) return 0, fmt.Errorf("thread %d validation failed: %w", threadID, err) } @@ -509,7 +506,7 @@ func findMainThreadID(pid uint32) (uint32, error) { if err != nil { return 0, fmt.Errorf("CreateToolhelp32Snapshot: %w", err) } - defer windows.CloseHandle(snapshot) + defer ntCloseHandle(snapshot) var entry windows.ThreadEntry32 entry.Size = uint32(unsafe.Sizeof(entry)) @@ -526,7 +523,7 @@ func findMainThreadID(pid uint32) (uint32, error) { thread, err := windows.OpenThread(sendPacketThreadAccess, false, entry.ThreadID) if err == nil { creationTime, err := getThreadCreationTime(thread) - windows.CloseHandle(thread) + _ = ntCloseHandle(thread) if err == nil && creationTime < earliestCreationTime { earliestCreationTime = creationTime @@ -866,7 +863,7 @@ func (p *Process) waitForPacketCompletion(handle windows.Handle, state *sendPack select { case <-timeout: // Try to read status one last time before failing - if err := windows.ReadProcessMemory(handle, state.meta+sendPacketStatusOffset, &statusBuf[0], 4, nil); err == nil { + if err := ntReadProcessMemory(handle, state.meta+sendPacketStatusOffset, statusBuf[:]); err == nil { status := binary.LittleEndian.Uint32(statusBuf[:]) if status == 1 { return nil @@ -874,7 +871,7 @@ func (p *Process) waitForPacketCompletion(handle windows.Handle, state *sendPack } return fmt.Errorf("packet send timeout: APC not processed within %dms", timeoutMs) case <-ticker.C: - if err := windows.ReadProcessMemory(handle, state.meta+sendPacketStatusOffset, &statusBuf[0], 4, nil); err != nil { + if err := ntReadProcessMemory(handle, state.meta+sendPacketStatusOffset, statusBuf[:]); err != nil { continue } status := binary.LittleEndian.Uint32(statusBuf[:]) @@ -892,74 +889,26 @@ func writeRemoteMemory(handle windows.Handle, address uintptr, data []byte) erro if len(data) == 0 { return nil } - return windows.WriteProcessMemory(handle, address, &data[0], uintptr(len(data)), nil) + return ntWriteProcessMemory(handle, address, data) } func virtualAllocEx(handle windows.Handle, size uintptr, protect uint32) (uintptr, error) { if size == 0 { return 0, errors.New("allocation size cannot be zero") } - - addr, _, err := procVirtualAllocEx.Call( - uintptr(handle), - 0, - size, - windows.MEM_COMMIT|windows.MEM_RESERVE, - uintptr(protect), - ) - if addr == 0 { - if err != nil { - return 0, err - } - return 0, errors.New("VirtualAllocEx failed") - } - return addr, nil + return ntAllocateVirtualMemory(handle, size, windows.MEM_COMMIT|windows.MEM_RESERVE, protect) } func virtualFreeEx(handle windows.Handle, address uintptr) error { - if address == 0 { - return nil - } - - ret, _, err := procVirtualFreeEx.Call( - uintptr(handle), - address, - 0, - windows.MEM_RELEASE, - ) - if ret == 0 { - if err != nil { - return err - } - return errors.New("VirtualFreeEx failed") - } - return nil + return ntFreeVirtualMemory(handle, address, windows.MEM_RELEASE) } func virtualProtectEx(handle windows.Handle, address uintptr, size uintptr, protect uint32) error { if address == 0 || size == 0 { return errors.New("attempt to protect invalid region") } - - if err := procVirtualProtectEx.Find(); err != nil { - return nil - } - - var oldProtect uint32 - ret, _, err := procVirtualProtectEx.Call( - uintptr(handle), - address, - size, - uintptr(protect), - uintptr(unsafe.Pointer(&oldProtect)), - ) - if ret == 0 { - if err != nil { - return fmt.Errorf("VirtualProtectEx: %w", err) - } - return errors.New("VirtualProtectEx failed") - } - return nil + _, err := ntProtectVirtualMemory(handle, address, size, protect) + return err } func markCallTargetValid(handle windows.Handle, address uintptr, size uintptr) error { From deb8c37b5578f5af0da08834f67acd7d6b005686 Mon Sep 17 00:00:00 2001 From: vietdungdev Date: Wed, 25 Mar 2026 22:42:16 +0700 Subject: [PATCH 2/2] remove unused code --- pkg/memory/send_packet.go | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/pkg/memory/send_packet.go b/pkg/memory/send_packet.go index 6a94cfd..b9d3e93 100644 --- a/pkg/memory/send_packet.go +++ b/pkg/memory/send_packet.go @@ -46,10 +46,6 @@ var ( procSetProcessValidCallTargets = kernel32.NewProc("SetProcessValidCallTargets") procCreateRemoteThread = kernel32.NewProc("CreateRemoteThread") - ntdll = windows.NewLazySystemDLL("ntdll.dll") - procNtGetContextThread = ntdll.NewProc("NtGetContextThread") - procNtSetContextThread = ntdll.NewProc("NtSetContextThread") - d2gsCachedFn uintptr d2gsCacheMu sync.RWMutex d2gsCachePID uint32 @@ -1027,26 +1023,3 @@ const CONTEXT_AMD64 = 0x00100000 const CONTEXT_CONTROL = CONTEXT_AMD64 | 0x0001 const CONTEXT_INTEGER = CONTEXT_AMD64 | 0x0002 const CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER - -func getThreadContext(threadHandle windows.Handle, ctx *CONTEXT64) error { - ctx.ContextFlags = CONTEXT_FULL - ret, _, err := procNtGetContextThread.Call( - uintptr(threadHandle), - uintptr(unsafe.Pointer(ctx)), - ) - if ret != 0 { - return fmt.Errorf("NtGetContextThread failed: 0x%X, %w", ret, err) - } - return nil -} - -func setThreadContext(threadHandle windows.Handle, ctx *CONTEXT64) error { - ret, _, err := procNtSetContextThread.Call( - uintptr(threadHandle), - uintptr(unsafe.Pointer(ctx)), - ) - if ret != 0 { - return fmt.Errorf("NtSetContextThread failed: 0x%X, %w", ret, err) - } - return nil -}