add send packet function
This commit is contained in:
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
type GameReader struct {
|
type GameReader struct {
|
||||||
offset Offset
|
offset Offset
|
||||||
Process
|
*Process
|
||||||
|
|
||||||
monstersLastUpdate time.Time
|
monstersLastUpdate time.Time
|
||||||
inventoryLastUpdate time.Time
|
inventoryLastUpdate time.Time
|
||||||
@@ -39,7 +39,7 @@ var WidgetStateFlags = map[string]uint64{
|
|||||||
"WeaponSwap": 0xF2D7CF8E9CC08212,
|
"WeaponSwap": 0xF2D7CF8E9CC08212,
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGameReader(process Process) *GameReader {
|
func NewGameReader(process *Process) *GameReader {
|
||||||
return &GameReader{
|
return &GameReader{
|
||||||
offset: calculateOffsets(process),
|
offset: calculateOffsets(process),
|
||||||
Process: process,
|
Process: process,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type Offset struct {
|
|||||||
KeyBindingsSkillsOffset uintptr
|
KeyBindingsSkillsOffset uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateOffsets(process Process) Offset {
|
func calculateOffsets(process *Process) Offset {
|
||||||
// ignoring errors, always best practices
|
// ignoring errors, always best practices
|
||||||
memory, _ := process.getProcessMemory()
|
memory, _ := process.getProcessMemory()
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -18,27 +19,29 @@ type Process struct {
|
|||||||
pid uint32
|
pid uint32
|
||||||
moduleBaseAddressPtr uintptr
|
moduleBaseAddressPtr uintptr
|
||||||
moduleBaseSize uint32
|
moduleBaseSize uint32
|
||||||
|
sendPacket *sendPacketState
|
||||||
|
sendPacketMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Int8 = 1 // signed 8-bit integer
|
Int8 = 1
|
||||||
Int16 = 2 // signed 16-bit integer
|
Int16 = 2
|
||||||
Int32 = 4 // signed 32-bit integer
|
Int32 = 4
|
||||||
Int64 = 8 // signed 64-bit integer
|
Int64 = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewProcess() (Process, error) {
|
func NewProcess() (*Process, error) {
|
||||||
module, err := getGameModule()
|
module, err := getGameModule()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Process{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := windows.OpenProcess(0x0010, false, module.ProcessID)
|
h, err := windows.OpenProcess(0x0010, false, module.ProcessID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Process{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Process{
|
return &Process{
|
||||||
handler: h,
|
handler: h,
|
||||||
pid: module.ProcessID,
|
pid: module.ProcessID,
|
||||||
moduleBaseAddressPtr: module.ModuleBaseAddress,
|
moduleBaseAddressPtr: module.ModuleBaseAddress,
|
||||||
@@ -46,18 +49,18 @@ func NewProcess() (Process, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProcessForPID(pid uint32) (Process, error) {
|
func NewProcessForPID(pid uint32) (*Process, error) {
|
||||||
module, found := getMainModule(pid)
|
module, found := getMainModule(pid)
|
||||||
if !found {
|
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)
|
h, err := windows.OpenProcess(0x0010, false, module.ProcessID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Process{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return Process{
|
return &Process{
|
||||||
handler: h,
|
handler: h,
|
||||||
pid: module.ProcessID,
|
pid: module.ProcessID,
|
||||||
moduleBaseAddressPtr: module.ModuleBaseAddress,
|
moduleBaseAddressPtr: module.ModuleBaseAddress,
|
||||||
@@ -65,7 +68,7 @@ func NewProcessForPID(pid uint32) (Process, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Process) Close() error {
|
func (p *Process) Close() error {
|
||||||
return windows.CloseHandle(p.handler)
|
return windows.CloseHandle(p.handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +104,7 @@ func getMainModule(pid uint32) (ModuleInfo, bool) {
|
|||||||
return ModuleInfo{}, false
|
return ModuleInfo{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Process) getProcessMemory() ([]byte, error) {
|
func (p *Process) getProcessMemory() ([]byte, error) {
|
||||||
var data = make([]byte, p.moduleBaseSize)
|
var data = make([]byte, p.moduleBaseSize)
|
||||||
err := windows.ReadProcessMemory(p.handler, p.moduleBaseAddressPtr, &data[0], uintptr(p.moduleBaseSize), nil)
|
err := windows.ReadProcessMemory(p.handler, p.moduleBaseAddressPtr, &data[0], uintptr(p.moduleBaseSize), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,7 +114,7 @@ func (p Process) getProcessMemory() ([]byte, error) {
|
|||||||
return data, nil
|
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)
|
var data = make([]byte, size)
|
||||||
windows.ReadProcessMemory(p.handler, address, &data[0], uintptr(size), nil)
|
windows.ReadProcessMemory(p.handler, address, &data[0], uintptr(size), nil)
|
||||||
|
|
||||||
@@ -127,7 +130,7 @@ const (
|
|||||||
Uint64 = 8
|
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))
|
bytes := p.ReadBytesFromMemory(address, uint(size))
|
||||||
|
|
||||||
return bytesToUint(bytes, size)
|
return bytesToUint(bytes, size)
|
||||||
@@ -168,7 +171,7 @@ func bytesToInt(bytes []byte, size IntType) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Process) ReadStringFromMemory(address uintptr, size uint) string {
|
func (p *Process) ReadStringFromMemory(address uintptr, size uint) string {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
for i := 1; true; i++ {
|
for i := 1; true; i++ {
|
||||||
data := p.ReadBytesFromMemory(address, uint(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"))
|
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)
|
patternLength := len(pattern)
|
||||||
for i := 0; i < int(p.moduleBaseSize)-patternLength; i++ {
|
for i := 0; i < int(p.moduleBaseSize)-patternLength; i++ {
|
||||||
found := true
|
found := true
|
||||||
@@ -200,7 +203,7 @@ func (p Process) findPattern(memory []byte, pattern, mask string) int {
|
|||||||
return 0
|
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 {
|
if offset := p.findPattern(memory, pattern, mask); offset != 0 {
|
||||||
return p.moduleBaseAddressPtr + uintptr(offset)
|
return p.moduleBaseAddressPtr + uintptr(offset)
|
||||||
}
|
}
|
||||||
@@ -208,7 +211,7 @@ func (p Process) FindPattern(memory []byte, pattern, mask string) uintptr {
|
|||||||
return 0
|
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 {
|
if offset := p.findPattern(memory, pattern, mask); offset != 0 {
|
||||||
// Adjust the address based on the operand value
|
// Adjust the address based on the operand value
|
||||||
operandAddress := p.moduleBaseAddressPtr + uintptr(offset)
|
operandAddress := p.moduleBaseAddressPtr + uintptr(offset)
|
||||||
@@ -220,7 +223,7 @@ func (p Process) FindPatternByOperand(memory []byte, pattern, mask string) uintp
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Process) GetPID() uint32 {
|
func (p *Process) GetPID() uint32 {
|
||||||
return p.pid
|
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
|
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)
|
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