add send packet function
This commit is contained in:
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
type GameReader struct {
|
||||
offset Offset
|
||||
Process
|
||||
*Process
|
||||
|
||||
monstersLastUpdate time.Time
|
||||
inventoryLastUpdate time.Time
|
||||
@@ -39,7 +39,7 @@ var WidgetStateFlags = map[string]uint64{
|
||||
"WeaponSwap": 0xF2D7CF8E9CC08212,
|
||||
}
|
||||
|
||||
func NewGameReader(process Process) *GameReader {
|
||||
func NewGameReader(process *Process) *GameReader {
|
||||
return &GameReader{
|
||||
offset: calculateOffsets(process),
|
||||
Process: process,
|
||||
|
||||
@@ -19,7 +19,7 @@ type Offset struct {
|
||||
KeyBindingsSkillsOffset uintptr
|
||||
}
|
||||
|
||||
func calculateOffsets(process Process) Offset {
|
||||
func calculateOffsets(process *Process) Offset {
|
||||
// ignoring errors, always best practices
|
||||
memory, _ := process.getProcessMemory()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@@ -18,27 +19,29 @@ type Process struct {
|
||||
pid uint32
|
||||
moduleBaseAddressPtr uintptr
|
||||
moduleBaseSize uint32
|
||||
sendPacket *sendPacketState
|
||||
sendPacketMu sync.Mutex
|
||||
}
|
||||
|
||||
const (
|
||||
Int8 = 1 // signed 8-bit integer
|
||||
Int16 = 2 // signed 16-bit integer
|
||||
Int32 = 4 // signed 32-bit integer
|
||||
Int64 = 8 // signed 64-bit integer
|
||||
Int8 = 1
|
||||
Int16 = 2
|
||||
Int32 = 4
|
||||
Int64 = 8
|
||||
)
|
||||
|
||||
func NewProcess() (Process, error) {
|
||||
func NewProcess() (*Process, error) {
|
||||
module, err := getGameModule()
|
||||
if err != nil {
|
||||
return Process{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := windows.OpenProcess(0x0010, false, module.ProcessID)
|
||||
if err != nil {
|
||||
return Process{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Process{
|
||||
return &Process{
|
||||
handler: h,
|
||||
pid: module.ProcessID,
|
||||
moduleBaseAddressPtr: module.ModuleBaseAddress,
|
||||
@@ -46,18 +49,18 @@ func NewProcess() (Process, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewProcessForPID(pid uint32) (Process, error) {
|
||||
func NewProcessForPID(pid uint32) (*Process, error) {
|
||||
module, found := getMainModule(pid)
|
||||
if !found {
|
||||
return Process{}, errors.New("no module found for the specified PID")
|
||||
return nil, errors.New("no module found for the specified PID")
|
||||
}
|
||||
|
||||
h, err := windows.OpenProcess(0x0010, false, module.ProcessID)
|
||||
if err != nil {
|
||||
return Process{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Process{
|
||||
return &Process{
|
||||
handler: h,
|
||||
pid: module.ProcessID,
|
||||
moduleBaseAddressPtr: module.ModuleBaseAddress,
|
||||
@@ -65,7 +68,7 @@ func NewProcessForPID(pid uint32) (Process, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p Process) Close() error {
|
||||
func (p *Process) Close() error {
|
||||
return windows.CloseHandle(p.handler)
|
||||
}
|
||||
|
||||
@@ -101,7 +104,7 @@ func getMainModule(pid uint32) (ModuleInfo, bool) {
|
||||
return ModuleInfo{}, false
|
||||
}
|
||||
|
||||
func (p Process) getProcessMemory() ([]byte, error) {
|
||||
func (p *Process) getProcessMemory() ([]byte, error) {
|
||||
var data = make([]byte, p.moduleBaseSize)
|
||||
err := windows.ReadProcessMemory(p.handler, p.moduleBaseAddressPtr, &data[0], uintptr(p.moduleBaseSize), nil)
|
||||
if err != nil {
|
||||
@@ -111,7 +114,7 @@ func (p Process) getProcessMemory() ([]byte, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (p Process) ReadBytesFromMemory(address uintptr, size uint) []byte {
|
||||
func (p *Process) ReadBytesFromMemory(address uintptr, size uint) []byte {
|
||||
var data = make([]byte, size)
|
||||
windows.ReadProcessMemory(p.handler, address, &data[0], uintptr(size), nil)
|
||||
|
||||
@@ -127,7 +130,7 @@ const (
|
||||
Uint64 = 8
|
||||
)
|
||||
|
||||
func (p Process) ReadUInt(address uintptr, size IntType) uint {
|
||||
func (p *Process) ReadUInt(address uintptr, size IntType) uint {
|
||||
bytes := p.ReadBytesFromMemory(address, uint(size))
|
||||
|
||||
return bytesToUint(bytes, size)
|
||||
@@ -168,7 +171,7 @@ func bytesToInt(bytes []byte, size IntType) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p Process) ReadStringFromMemory(address uintptr, size uint) string {
|
||||
func (p *Process) ReadStringFromMemory(address uintptr, size uint) string {
|
||||
if size == 0 {
|
||||
for i := 1; true; i++ {
|
||||
data := p.ReadBytesFromMemory(address, uint(i))
|
||||
@@ -181,7 +184,7 @@ func (p Process) ReadStringFromMemory(address uintptr, size uint) string {
|
||||
return string(bytes.Trim(p.ReadBytesFromMemory(address, size), "\x00"))
|
||||
}
|
||||
|
||||
func (p Process) findPattern(memory []byte, pattern, mask string) int {
|
||||
func (p *Process) findPattern(memory []byte, pattern, mask string) int {
|
||||
patternLength := len(pattern)
|
||||
for i := 0; i < int(p.moduleBaseSize)-patternLength; i++ {
|
||||
found := true
|
||||
@@ -200,7 +203,7 @@ func (p Process) findPattern(memory []byte, pattern, mask string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p Process) FindPattern(memory []byte, pattern, mask string) uintptr {
|
||||
func (p *Process) FindPattern(memory []byte, pattern, mask string) uintptr {
|
||||
if offset := p.findPattern(memory, pattern, mask); offset != 0 {
|
||||
return p.moduleBaseAddressPtr + uintptr(offset)
|
||||
}
|
||||
@@ -208,7 +211,7 @@ func (p Process) FindPattern(memory []byte, pattern, mask string) uintptr {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p Process) FindPatternByOperand(memory []byte, pattern, mask string) uintptr {
|
||||
func (p *Process) FindPatternByOperand(memory []byte, pattern, mask string) uintptr {
|
||||
if offset := p.findPattern(memory, pattern, mask); offset != 0 {
|
||||
// Adjust the address based on the operand value
|
||||
operandAddress := p.moduleBaseAddressPtr + uintptr(offset)
|
||||
@@ -220,7 +223,7 @@ func (p Process) FindPatternByOperand(memory []byte, pattern, mask string) uintp
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p Process) GetPID() uint32 {
|
||||
func (p *Process) GetPID() uint32 {
|
||||
return p.pid
|
||||
}
|
||||
|
||||
@@ -280,7 +283,7 @@ func (p *Process) ReadPointer(address uintptr, size int) (uintptr, error) {
|
||||
return uintptr(*(*uint64)(unsafe.Pointer(&buffer[0]))), nil
|
||||
}
|
||||
|
||||
func (p Process) ReadIntoBuffer(address uintptr, buffer []byte) error {
|
||||
func (p *Process) ReadIntoBuffer(address uintptr, buffer []byte) error {
|
||||
return windows.ReadProcessMemory(p.handler, address, &buffer[0], uintptr(len(buffer)), nil)
|
||||
}
|
||||
|
||||
|
||||
633
pkg/memory/send_packet.go
Normal file
633
pkg/memory/send_packet.go
Normal file
@@ -0,0 +1,633 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
d2gsSendPacketPattern = "\xE8\x00\x00\x00\x00\x0F\xB6\x85\x00\x00\x00\x00\x48\x03\xF0"
|
||||
d2gsSendPacketMask = "x????xxx????xxx"
|
||||
maxPacketSize = 65536
|
||||
)
|
||||
|
||||
var (
|
||||
sendPacketStubBase = []byte{
|
||||
0x48, 0x89, 0xC8,
|
||||
0x49, 0x89, 0xC0,
|
||||
0x48, 0x83, 0xEC, 0x28,
|
||||
0x48, 0x8B, 0x48, 0x08,
|
||||
0x48, 0x8B, 0x50, 0x10,
|
||||
0x48, 0x8B, 0x00,
|
||||
0xFF, 0xD0,
|
||||
0x41, 0xC7, 0x40, 0x18,
|
||||
0x01, 0x00, 0x00, 0x00,
|
||||
0xB8, 0x01, 0x00, 0x00, 0x00,
|
||||
0x48, 0x83, 0xC4, 0x28,
|
||||
0xC3,
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
d2gsCachedFn uintptr
|
||||
d2gsCacheMu sync.RWMutex
|
||||
d2gsCachePID uint32
|
||||
|
||||
metaBufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new([sendPacketMetaSize]byte)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
sendPacketProcessAccess = windows.PROCESS_VM_OPERATION | windows.PROCESS_VM_READ | windows.PROCESS_VM_WRITE | windows.PROCESS_QUERY_INFORMATION
|
||||
|
||||
THREAD_SUSPEND_RESUME = 0x0002
|
||||
THREAD_SET_CONTEXT = 0x0010
|
||||
THREAD_QUERY_INFORMATION = 0x0040
|
||||
sendPacketThreadAccess = THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION
|
||||
|
||||
sendPacketStatusOffset = 24
|
||||
sendPacketMetaSize = 32
|
||||
|
||||
threadStillActive = 259
|
||||
)
|
||||
|
||||
type sendPacketState struct {
|
||||
mu sync.Mutex
|
||||
handle windows.Handle
|
||||
stub uintptr
|
||||
meta uintptr
|
||||
packet uintptr
|
||||
packetCap uintptr
|
||||
fn uintptr
|
||||
thread windows.Handle
|
||||
threadID uint32
|
||||
processPID uint32
|
||||
threadLastValidated time.Time
|
||||
leakedBuffers []uintptr
|
||||
}
|
||||
|
||||
func findPatternOffset(memory []byte, pattern, mask string) int {
|
||||
if len(pattern) != len(mask) {
|
||||
return -1
|
||||
}
|
||||
patternLength := len(pattern)
|
||||
limit := len(memory) - patternLength
|
||||
for i := 0; i <= limit; i++ {
|
||||
match := true
|
||||
for j := 0; j < patternLength; j++ {
|
||||
if mask[j] == 'x' && memory[i+j] != pattern[j] {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s *sendPacketState) ensureHandle(pid uint32) (windows.Handle, error) {
|
||||
if s.handle != 0 && s.processPID == pid {
|
||||
return s.handle, nil
|
||||
}
|
||||
|
||||
if s.handle != 0 && s.processPID != pid {
|
||||
if s.packet != 0 {
|
||||
virtualFreeEx(s.handle, s.packet)
|
||||
}
|
||||
if s.meta != 0 {
|
||||
virtualFreeEx(s.handle, s.meta)
|
||||
}
|
||||
if s.stub != 0 {
|
||||
virtualFreeEx(s.handle, s.stub)
|
||||
}
|
||||
|
||||
windows.CloseHandle(s.handle)
|
||||
s.handle = 0
|
||||
s.processPID = 0
|
||||
s.stub = 0
|
||||
s.meta = 0
|
||||
s.packet = 0
|
||||
s.packetCap = 0
|
||||
s.fn = 0
|
||||
}
|
||||
|
||||
h, err := windows.OpenProcess(sendPacketProcessAccess, false, pid)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("open process %d: %w", pid, err)
|
||||
}
|
||||
s.handle = h
|
||||
s.processPID = pid
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (s *sendPacketState) ensureStub(handle windows.Handle) error {
|
||||
if s.stub != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
stubSize := uintptr(len(sendPacketStubBase))
|
||||
|
||||
addr, err := virtualAllocEx(handle, stubSize, windows.PAGE_EXECUTE_READWRITE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("allocate remote stub: %w", err)
|
||||
}
|
||||
|
||||
if err := writeRemoteMemory(handle, addr, sendPacketStubBase); err != nil {
|
||||
virtualFreeEx(handle, addr)
|
||||
return fmt.Errorf("write remote stub: %w", err)
|
||||
}
|
||||
|
||||
s.stub = addr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sendPacketState) ensureMeta(handle windows.Handle) error {
|
||||
if s.meta != 0 {
|
||||
return nil
|
||||
}
|
||||
addr, err := virtualAllocEx(handle, sendPacketMetaSize, windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("allocate remote metadata: %w", err)
|
||||
}
|
||||
s.meta = addr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sendPacketState) ensurePacketBuffer(handle windows.Handle, size uintptr) error {
|
||||
if size == 0 {
|
||||
return errors.New("packet size must be greater than zero")
|
||||
}
|
||||
|
||||
if size > maxPacketSize {
|
||||
return fmt.Errorf("packet too large: %d bytes (max %d)", size, maxPacketSize)
|
||||
}
|
||||
|
||||
if size <= s.packetCap && s.packet != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
allocSize := size
|
||||
if size < 4096 {
|
||||
allocSize = 4096
|
||||
} else {
|
||||
allocSize = (size + 4095) &^ 4095
|
||||
}
|
||||
|
||||
newPacket, err := virtualAllocEx(handle, allocSize, windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("allocate packet buffer of %d bytes: %w", allocSize, err)
|
||||
}
|
||||
|
||||
if s.packet != 0 {
|
||||
if err := virtualFreeEx(handle, s.packet); err != nil {
|
||||
log.Printf("Warning: failed to free old packet buffer at 0x%X: %v", s.packet, err)
|
||||
s.leakedBuffers = append(s.leakedBuffers, s.packet)
|
||||
}
|
||||
}
|
||||
|
||||
s.packet = newPacket
|
||||
s.packetCap = allocSize
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sendPacketState) ensureFunction(p *Process) (uintptr, error) {
|
||||
if s.fn != 0 {
|
||||
return s.fn, nil
|
||||
}
|
||||
|
||||
fn, err := p.GetD2GSSendPacketFn()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if fn == 0 {
|
||||
return 0, errors.New("D2GS_SendPacket function pointer is null")
|
||||
}
|
||||
|
||||
s.fn = fn
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
func (s *sendPacketState) ensureThreadHandle(p *Process) (windows.Handle, error) {
|
||||
if s.thread != 0 && s.processPID == p.pid {
|
||||
if err := validateThreadActive(s.thread); err == nil {
|
||||
s.threadLastValidated = time.Now()
|
||||
return s.thread, nil
|
||||
}
|
||||
windows.CloseHandle(s.thread)
|
||||
s.thread = 0
|
||||
s.threadID = 0
|
||||
s.processPID = 0
|
||||
} else if s.thread != 0 && s.processPID != p.pid {
|
||||
windows.CloseHandle(s.thread)
|
||||
s.thread = 0
|
||||
s.threadID = 0
|
||||
s.processPID = 0
|
||||
}
|
||||
|
||||
threadID, err := findMainThreadID(p.pid)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
handle, err := windows.OpenThread(sendPacketThreadAccess, false, threadID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("open thread %d: %w", threadID, err)
|
||||
}
|
||||
|
||||
s.thread = handle
|
||||
s.threadID = threadID
|
||||
s.processPID = p.pid
|
||||
s.threadLastValidated = time.Now()
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func validateThreadActive(thread windows.Handle) error {
|
||||
var exitCode uint32
|
||||
ret, _, err := procGetExitCodeThread.Call(
|
||||
uintptr(thread),
|
||||
uintptr(unsafe.Pointer(&exitCode)),
|
||||
)
|
||||
if ret == 0 {
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetExitCodeThread: %w", err)
|
||||
}
|
||||
return errors.New("GetExitCodeThread failed")
|
||||
}
|
||||
|
||||
if exitCode != threadStillActive {
|
||||
return fmt.Errorf("thread is not active (exit code: %d)", exitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sendPacketState) dispatchAPC(thread windows.Handle, start, parameter uintptr) error {
|
||||
if thread == 0 {
|
||||
return errors.New("thread handle is zero")
|
||||
}
|
||||
|
||||
if err := suspendThread(thread); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queued := false
|
||||
defer func() {
|
||||
if !queued {
|
||||
_ = resumeThread(thread)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := queueUserAPC(start, thread, parameter); err != nil {
|
||||
return err
|
||||
}
|
||||
queued = true
|
||||
|
||||
if err := resumeThread(thread); err != nil {
|
||||
queued = false
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func queueUserAPC(start uintptr, thread windows.Handle, parameter uintptr) error {
|
||||
ret, _, err := procQueueUserAPC.Call(start, uintptr(thread), parameter)
|
||||
if ret == 0 {
|
||||
if err != nil {
|
||||
return fmt.Errorf("QueueUserAPC: %w", err)
|
||||
}
|
||||
return errors.New("QueueUserAPC failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func suspendThread(thread windows.Handle) error {
|
||||
ret, _, err := procSuspendThread.Call(uintptr(thread))
|
||||
if ret == ^uintptr(0) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("SuspendThread: %w", err)
|
||||
}
|
||||
return errors.New("SuspendThread failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resumeThread(thread windows.Handle) error {
|
||||
ret, _, err := procResumeThread.Call(uintptr(thread))
|
||||
if ret == ^uintptr(0) {
|
||||
if err != nil {
|
||||
return fmt.Errorf("ResumeThread: %w", err)
|
||||
}
|
||||
return errors.New("ResumeThread failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getThreadCreationTime gets the creation time of a thread
|
||||
func getThreadCreationTime(thread windows.Handle) (int64, error) {
|
||||
var creationTime, exitTime, kernelTime, userTime windows.Filetime
|
||||
ret, _, err := procGetThreadTimes.Call(
|
||||
uintptr(thread),
|
||||
uintptr(unsafe.Pointer(&creationTime)),
|
||||
uintptr(unsafe.Pointer(&exitTime)),
|
||||
uintptr(unsafe.Pointer(&kernelTime)),
|
||||
uintptr(unsafe.Pointer(&userTime)),
|
||||
)
|
||||
if ret == 0 {
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("GetThreadTimes: %w", err)
|
||||
}
|
||||
return 0, errors.New("GetThreadTimes failed")
|
||||
}
|
||||
|
||||
return int64(creationTime.HighDateTime)<<32 | int64(creationTime.LowDateTime), nil
|
||||
}
|
||||
|
||||
func findMainThreadID(pid uint32) (uint32, error) {
|
||||
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPTHREAD, 0)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CreateToolhelp32Snapshot: %w", err)
|
||||
}
|
||||
defer windows.CloseHandle(snapshot)
|
||||
|
||||
var entry windows.ThreadEntry32
|
||||
entry.Size = uint32(unsafe.Sizeof(entry))
|
||||
if err := windows.Thread32First(snapshot, &entry); err != nil {
|
||||
return 0, fmt.Errorf("Thread32First: %w", err)
|
||||
}
|
||||
|
||||
var mainThreadID uint32
|
||||
var earliestCreationTime int64 = 0x7FFFFFFFFFFFFFFF
|
||||
var found bool
|
||||
|
||||
for {
|
||||
if entry.OwnerProcessID == pid {
|
||||
thread, err := windows.OpenThread(sendPacketThreadAccess, false, entry.ThreadID)
|
||||
if err == nil {
|
||||
creationTime, err := getThreadCreationTime(thread)
|
||||
windows.CloseHandle(thread)
|
||||
|
||||
if err == nil && creationTime < earliestCreationTime {
|
||||
earliestCreationTime = creationTime
|
||||
mainThreadID = entry.ThreadID
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := windows.Thread32Next(snapshot, &entry); err != nil {
|
||||
if errors.Is(err, windows.ERROR_NO_MORE_FILES) {
|
||||
break
|
||||
}
|
||||
return 0, fmt.Errorf("Thread32Next: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return 0, fmt.Errorf("no threads found for process %d", pid)
|
||||
}
|
||||
|
||||
return mainThreadID, nil
|
||||
}
|
||||
|
||||
func validatePatternMatch(memory []byte, offset int) bool {
|
||||
if offset >= len(memory) || memory[offset] != 0xE8 {
|
||||
return false
|
||||
}
|
||||
|
||||
if offset+5 > len(memory) {
|
||||
return false
|
||||
}
|
||||
|
||||
if offset+14 < len(memory) {
|
||||
if memory[offset+5] != 0x0F || memory[offset+6] != 0xB6 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Process) GetD2GSSendPacketFn() (uintptr, error) {
|
||||
if p == nil {
|
||||
return 0, errors.New("process is nil")
|
||||
}
|
||||
|
||||
if p.handler == 0 {
|
||||
return 0, errors.New("process handle is invalid")
|
||||
}
|
||||
|
||||
d2gsCacheMu.RLock()
|
||||
if d2gsCachedFn != 0 && d2gsCachePID == p.pid {
|
||||
fn := d2gsCachedFn
|
||||
d2gsCacheMu.RUnlock()
|
||||
return fn, nil
|
||||
}
|
||||
d2gsCacheMu.RUnlock()
|
||||
|
||||
modules, err := GetProcessModules(p.pid)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("enumerate modules: %w", err)
|
||||
}
|
||||
|
||||
for _, module := range modules {
|
||||
name := strings.ToLower(module.ModuleName)
|
||||
|
||||
if strings.Contains(name, "windows") || strings.Contains(name, "system32") {
|
||||
continue
|
||||
}
|
||||
|
||||
if module.ModuleBaseSize == 0 || module.ModuleBaseSize > 100*1024*1024 {
|
||||
continue
|
||||
}
|
||||
|
||||
memory := make([]byte, int(module.ModuleBaseSize))
|
||||
if err := windows.ReadProcessMemory(p.handler, module.ModuleBaseAddress, &memory[0], uintptr(module.ModuleBaseSize), nil); err != nil {
|
||||
log.Printf("Warning: failed to read module %s: %v", module.ModuleName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
offset := findPatternOffset(memory, d2gsSendPacketPattern, d2gsSendPacketMask)
|
||||
if offset < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !validatePatternMatch(memory, offset) {
|
||||
log.Printf("Warning: false positive pattern match at offset 0x%X in %s", offset, module.ModuleName)
|
||||
continue
|
||||
}
|
||||
|
||||
patternAddr := module.ModuleBaseAddress + uintptr(offset)
|
||||
relOffset := int32(binary.LittleEndian.Uint32(memory[offset+1 : offset+5]))
|
||||
absolute := uintptr(int64(patternAddr+5) + int64(relOffset))
|
||||
|
||||
if absolute == 0 || absolute < module.ModuleBaseAddress {
|
||||
log.Printf("Warning: invalid computed address 0x%X in %s", absolute, module.ModuleName)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("D2GS_SendPacket resolved at 0x%X in module %s", absolute, module.ModuleName)
|
||||
|
||||
d2gsCacheMu.Lock()
|
||||
d2gsCachedFn = absolute
|
||||
d2gsCachePID = p.pid
|
||||
d2gsCacheMu.Unlock()
|
||||
|
||||
return absolute, nil
|
||||
}
|
||||
|
||||
return 0, errors.New("D2GS_SendPacket pattern not found in any module")
|
||||
}
|
||||
|
||||
func (p *Process) SendPacket(packet []byte) (err error) {
|
||||
if p == nil {
|
||||
return errors.New("process is nil")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Printf("SendPacket(%d bytes) error: %v", len(packet), err)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(packet) == 0 {
|
||||
return errors.New("packet payload cannot be empty")
|
||||
}
|
||||
|
||||
if len(packet) > maxPacketSize {
|
||||
return fmt.Errorf("packet too large: %d bytes (max %d)", len(packet), maxPacketSize)
|
||||
}
|
||||
|
||||
p.sendPacketMu.Lock()
|
||||
if p.sendPacket == nil {
|
||||
p.sendPacket = &sendPacketState{}
|
||||
}
|
||||
state := p.sendPacket
|
||||
state.mu.Lock()
|
||||
p.sendPacketMu.Unlock()
|
||||
defer state.mu.Unlock()
|
||||
|
||||
fnAddr, err := state.ensureFunction(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve D2GS_SendPacket: %w", err)
|
||||
}
|
||||
|
||||
handle, err := state.ensureHandle(p.pid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open process: %w", err)
|
||||
}
|
||||
|
||||
if err := state.ensureStub(handle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := state.ensureMeta(handle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := state.ensurePacketBuffer(handle, uintptr(len(packet))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeRemoteMemory(handle, state.packet, packet); err != nil {
|
||||
return fmt.Errorf("write remote packet: %w", err)
|
||||
}
|
||||
|
||||
metaBuf := metaBufPool.Get().(*[sendPacketMetaSize]byte)
|
||||
defer func() {
|
||||
*metaBuf = [sendPacketMetaSize]byte{}
|
||||
metaBufPool.Put(metaBuf)
|
||||
}()
|
||||
|
||||
binary.LittleEndian.PutUint64(metaBuf[0:], uint64(fnAddr))
|
||||
binary.LittleEndian.PutUint64(metaBuf[8:], uint64(state.packet))
|
||||
binary.LittleEndian.PutUint64(metaBuf[16:], uint64(len(packet)))
|
||||
binary.LittleEndian.PutUint32(metaBuf[sendPacketStatusOffset:], 0)
|
||||
|
||||
if err := writeRemoteMemory(handle, state.meta, metaBuf[:]); err != nil {
|
||||
return fmt.Errorf("write remote metadata: %w", err)
|
||||
}
|
||||
|
||||
threadHandle, err := state.ensureThreadHandle(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve main thread: %w", err)
|
||||
}
|
||||
|
||||
if err := state.dispatchAPC(threadHandle, state.stub, state.meta); err != nil {
|
||||
return fmt.Errorf("dispatch APC: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeRemoteMemory(handle windows.Handle, address uintptr, data []byte) error {
|
||||
if address == 0 {
|
||||
return errors.New("attempt to write to null address")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return windows.WriteProcessMemory(handle, address, &data[0], uintptr(len(data)), nil)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user