OffSetFix-91403

This commit is contained in:
unknown
2026-01-22 10:09:49 -05:00
committed by guiyomu-dev
parent ec04b2f00d
commit 207eaf12bb
5 changed files with 450 additions and 94 deletions

View File

@@ -318,7 +318,7 @@ func (gd *GameReader) InCharacterSelectionScreen() bool {
}
func (gd *GameReader) GetSelectedCharacterName() string {
return gd.Process.ReadStringFromMemory(gd.Process.moduleBaseAddressPtr+0x2120FF4, 0)
return gd.Process.ReadStringFromMemory(gd.Process.moduleBaseAddressPtr+0x1C3D694, 16)
}
func (gd *GameReader) LegacyGraphics() bool {

View File

@@ -8,8 +8,8 @@ import (
)
func (gd *GameReader) GetKeyBindings() data.KeyBindings {
blob := gd.ReadBytesFromMemory(gd.moduleBaseAddressPtr+0x1DE4CC4, 0x500)
blobSkills := gd.ReadBytesFromMemory(gd.offset.KeyBindingsSkillsOffset, 0x500)
blob := gd.ReadBytesFromMemory(gd.moduleBaseAddressPtr+0x18C2894, 0x500)
blobSkills := gd.ReadBytesFromMemory(gd.moduleBaseAddressPtr+0x1CE8510, 0x500)
skillsKB := [16]data.SkillBinding{}
for i := 0; i < 7; i++ {
@@ -199,13 +199,5 @@ func (gd *GameReader) GetKeyBindings() data.KeyBindings {
Key1: [2]byte{blob[0x488], blob[0x489]},
Key2: [2]byte{blob[0x492], blob[0x493]},
},
PreviousStashPage: data.KeyBinding{
Key1: [2]byte{blob[0x4c4], blob[0x4c5]},
Key2: [2]byte{blob[0x4ce], blob[0x4cf]},
},
NextStashPage: data.KeyBinding{
Key1: [2]byte{blob[0x4d8], blob[0x4d9]},
Key2: [2]byte{blob[0x4e2], blob[0x4e3]},
},
}
}

View File

@@ -1,9 +1,5 @@
package memory
import (
"encoding/binary"
)
type Offset struct {
GameData uintptr
UnitTable uintptr
@@ -26,119 +22,70 @@ type Offset struct {
}
func calculateOffsets(process *Process) Offset {
// ignoring errors, always best practices
memory, _ := process.getProcessMemory()
// GameReader
pattern := process.FindPattern(memory, "\x44\x88\x25\x00\x00\x00\x00\x66\x44\x89\x25\x00\x00\x00\x00", "xxx????xxxx????")
bytes := process.ReadBytesFromMemory(pattern+0x3, 4)
offsetInt := uintptr(binary.LittleEndian.Uint32(bytes))
gameDataOffset := (pattern - process.moduleBaseAddressPtr) - 0x121 + offsetInt
memory, err := process.getProcessMemory()
if err != nil || len(memory) == 0 {
// Return empty offsets - will be recalculated when game is ready
return Offset{}
}
// UnitTable
pattern = process.FindPattern(memory, "\x48\x03\xC7\x49\x8B\x8C\xC6", "xxxxxxx")
bytes = process.ReadBytesFromMemory(pattern+7, 4)
unitTableOffset := uintptr(binary.LittleEndian.Uint32(bytes))
unitTableOffset := uintptr(0x1D95AF0)
// UI
pattern = process.FindPattern(memory, "\x40\x84\xed\x0f\x94\x05", "xxxxxx")
uiOffset := process.ReadUInt(pattern+6, Uint32)
uiOffsetPtr := (pattern - process.moduleBaseAddressPtr) + 10 + uintptr(uiOffset)
uiOffsetPtr := uintptr(0x1DA57DA)
// Hover
pattern = process.FindPattern(memory, "\xc6\x84\xc2\x00\x00\x00\x00\x00\x48\x8b\x74", "xxx?????xxx")
hoverOffset := process.ReadUInt(pattern+3, Uint32) - 1
hoverOffset := uintptr(0x1CE8400)
// Expansion
pattern = process.FindPattern(memory, "\x48\x8B\x05\x00\x00\x00\x00\x48\x8B\xD9\xF3\x0F\x10\x50\x00", "xxx????xxxxxxx?")
offsetPtr := uintptr(process.ReadUInt(pattern+3, Uint32))
expOffset := pattern - process.moduleBaseAddressPtr + 7 + offsetPtr
expOffset := uintptr(0x1CE78D0)
// Party members offset
pattern = process.FindPattern(memory, "\x02\x45\x33\xD2\x4D\x8B", "xxxxxx")
offsetPtr = uintptr(process.ReadUInt(pattern-3, Uint32))
rosterOffset := pattern - process.moduleBaseAddressPtr + 1 + offsetPtr
rosterOffset := uintptr(0x1DABD60)
// PanelManagerContainer
pattern = process.FindPatternByOperand(memory, "\x48\x89\x05\x00\x00\x00\x00\x48\x85\xDB\x74\x1E", "xxx????xxxxx")
bytes = process.ReadBytesFromMemory(pattern, 8)
panelManagerContainerOffset := (pattern - process.moduleBaseAddressPtr) // uintptr(binary.LittleEndian.Uint64(bytes))
panelManagerContainerOffset := uintptr(0x1D00968)
// WidgetStates
pattern = process.FindPattern(memory, "\x48\x8B\x0D\x00\x00\x00\x00\x4C\x8D\x44\x24\x00\x48\x03\xC2", "xxx????xxxx?xxx")
WidgetStatesPtr := process.ReadUInt(pattern+3, Uint32)
WidgetStatesOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(WidgetStatesPtr)
WidgetStatesOffset := uintptr(0x1DCDD40)
// Waypoints
pattern = process.FindPattern(memory, "\x48\x89\x05\x00\x00\x00\x00\x0F\x11\x00", "xxx????xxx")
offsetBuffer := process.ReadUInt(pattern+3, Uint32)
WaypointTableOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(offsetBuffer)
WaypointTableOffset := uintptr(0x1C468B0)
// FPS
pattern = process.FindPattern(memory, "\x8B\x1D\x00\x00\x00\x00\x48\x8D\x05\x00\x00\x00\x00\x48\x8D\x4C\x24\x40", "xx????xxx????xxxxx")
fpsOffsetPtr := uintptr(process.ReadUInt(pattern+2, Uint32))
fpsOffset := pattern - process.moduleBaseAddressPtr + 6 + fpsOffsetPtr
// Keybindings
pattern = process.FindPattern(memory, "\x48\x8D\x05\xAF\xEE", "xxxxx")
bytes = process.ReadBytesFromMemory(pattern+3, 4)
relativeOffset := int32(binary.LittleEndian.Uint32(bytes))
keyBindingsOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(relativeOffset)
fpsOffset := uintptr(0x1C46894)
// KeyBindings Skills
pattern = process.FindPattern(memory, "\x0F\x10\x04\x24\x48\x6B\xC8\x1C\x48\x8D\x05", "xxxxxxxxxxx")
var keyBindingsSkillsOffset uintptr
bytes = process.ReadBytesFromMemory(pattern+11, 4)
relativeOffset = int32(binary.LittleEndian.Uint32(bytes))
keyBindingsSkillsOffset = uintptr(int64(pattern) + 15 + int64(relativeOffset))
keyBindingsSkillsOffset := uintptr(0x1CE8510)
// QuestInfo
pattern = process.FindPattern(memory, "\x48\x8B\x0D\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x45\x33\xE4", "xxx????x????xxx")
questInfoOffsetPtr := uintptr(process.ReadUInt(pattern+3, Uint32))
questInfoOffset := pattern - process.moduleBaseAddressPtr + 7 + questInfoOffsetPtr
questInfoOffset := uintptr(0)
// Terror Zones
tzOffset := uintptr(0x29B2DF0)
// Quest Bytes Data
pattern = process.FindPattern(memory, "\x42\xc6\x84\x28\x00\x00\x00\x00\x00\x49\xff\xc5\x49\x83\xfd\x29", "xxxx?????xxxxxxx")
bytes = process.ReadBytesFromMemory(pattern+4, 4)
questOffset := uintptr(binary.LittleEndian.Uint32(bytes))
questDataOffset := questOffset + 1
tzOffset := uintptr(0x248D4C8)
// Ping
pattern = process.FindPattern(memory, "\x48\x8B\x0D\xCC\xCC\xCC\xCC\x49\x2B\xC7", "xxx????xxx")
bytes = process.ReadBytesFromMemory(pattern+3, 4)
relativeOffset = int32(binary.LittleEndian.Uint32(bytes))
pingOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(relativeOffset)
pingOffset := uintptr(0)
// LegacyGraphics
pattern = process.FindPattern(memory, "\x80\x3D\x00\x00\x00\x00\x00\x48\x8D\x54\x24\x30", "xx?????xxxxx")
legacyGfxPtr := uintptr(process.ReadUInt(pattern+2, Uint32))
legacyGfxOffset := pattern - process.moduleBaseAddressPtr + 7 + legacyGfxPtr
legacyGfxOffset := uintptr(0x1DB263E)
// CharData
pattern = process.FindPattern(memory, "\x48\x8D\x05\x00\x00\x00\x00\x89\x93\xF4\x0C\x00\x00", "xxx????xxxxxx")
bytes = process.ReadBytesFromMemory(pattern+3, 4)
relativeOffset = int32(binary.LittleEndian.Uint32(bytes))
charDataOffset := pattern - process.moduleBaseAddressPtr + 7 + uintptr(relativeOffset)
charDataOffset := uintptr(0x1CED5E8)
return Offset{
GameData: gameDataOffset,
UnitTable: unitTableOffset,
UI: uiOffsetPtr,
Hover: uintptr(hoverOffset),
Hover: hoverOffset,
Expansion: expOffset,
RosterOffset: rosterOffset,
PanelManagerContainerOffset: panelManagerContainerOffset,
WidgetStatesOffset: WidgetStatesOffset,
WaypointTableOffset: WaypointTableOffset,
FPS: fpsOffset,
KeyBindingsOffset: keyBindingsOffset,
KeyBindingsSkillsOffset: keyBindingsSkillsOffset,
QuestInfo: questInfoOffset,
TZ: tzOffset,
Quests: questDataOffset,
Ping: pingOffset,
LegacyGraphics: legacyGfxOffset,
CharData: charDataOffset,

View File

@@ -105,10 +105,44 @@ func getMainModule(pid uint32) (ModuleInfo, bool) {
}
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 {
return nil, err
// Use chunked reading as primary method since VirtualQueryEx is often blocked
return p.getProcessMemoryChunked()
}
// getProcessMemoryChunked reads memory in small chunks, skipping protected regions
func (p *Process) getProcessMemoryChunked() ([]byte, error) {
return ReadMemoryChunked(p.handler, p.moduleBaseAddressPtr, p.moduleBaseSize)
}
// ReadMemoryChunked reads memory in small chunks, skipping protected regions
// This is useful for reading large modules where a single ReadProcessMemory call may fail
func ReadMemoryChunked(handle windows.Handle, baseAddress uintptr, size uint32) ([]byte, error) {
var data = make([]byte, size)
const pageSize = uintptr(4096)
successfulReads := 0
failedReads := 0
for offset := uintptr(0); offset < uintptr(size); offset += pageSize {
address := baseAddress + offset
chunkSize := pageSize
// Adjust last chunk
if offset+pageSize > uintptr(size) {
chunkSize = uintptr(size) - offset
}
// Try to read, but don't fail if it's protected
err := windows.ReadProcessMemory(handle, address, &data[offset], chunkSize, nil)
if err != nil {
failedReads++
// Fill with zeros and continue (pattern matching will fail gracefully)
for i := offset; i < offset+chunkSize; i++ {
data[i] = 0
}
} else {
successfulReads++
}
}
return data, nil

View File

@@ -47,6 +47,11 @@ var (
procGetThreadTimes = kernel32.NewProc("GetThreadTimes")
procVirtualProtectEx = kernel32.NewProc("VirtualProtectEx")
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
@@ -57,6 +62,7 @@ var (
return new([sendPacketMetaSize]byte)
},
}
sendPacketCallCount int
)
const (
@@ -88,6 +94,9 @@ type sendPacketState struct {
processPID uint32
threadLastValidated time.Time
leakedBuffers []uintptr
lastSendTime time.Time
sendCount int
isExiting bool // Set to true when game is exiting to skip all packets
}
type cfgCallTargetInfo struct {
@@ -118,6 +127,8 @@ func findPatternOffset(memory []byte, pattern, mask string) int {
func (s *sendPacketState) ensureHandle(pid uint32) (windows.Handle, error) {
if s.handle != 0 && s.processPID == pid {
// Reset exit flag even if handle is reused - we're in a new game
s.isExiting = false
return s.handle, nil
}
@@ -148,6 +159,10 @@ func (s *sendPacketState) ensureHandle(pid uint32) (windows.Handle, error) {
}
s.handle = h
s.processPID = pid
// Reset exiting flag when starting new game
s.isExiting = false
return h, nil
}
@@ -228,6 +243,74 @@ func (s *sendPacketState) ensurePacketBuffer(handle windows.Handle, size uintptr
s.packet = newPacket
s.packetCap = allocSize
// Periodically attempt to clean up leaked buffers
if len(s.leakedBuffers) > 0 {
s.cleanupLeakedBuffers(handle)
}
return nil
}
// cleanupLeakedBuffers attempts to free previously leaked buffers
func (s *sendPacketState) cleanupLeakedBuffers(handle windows.Handle) {
if len(s.leakedBuffers) == 0 {
return
}
cleaned := s.leakedBuffers[:0]
for _, addr := range s.leakedBuffers {
if err := virtualFreeEx(handle, addr); err != nil {
// Still can't free, keep it in the list
cleaned = append(cleaned, addr)
}
}
s.leakedBuffers = cleaned
if len(s.leakedBuffers) == 0 {
log.Printf("Successfully cleaned up all leaked packet buffers")
}
}
// Cleanup sets exit flag to cancel all future packet sends
// Pending APCs in queue will timeout naturally, which is fine since game is exiting
// Also resets buffer pointers so they will be re-allocated on next game
// IMPORTANT: We do NOT free remote memory here because pending APCs may still be
// executing and trying to use that memory. Freeing it would cause D2R to crash.
// The memory will be freed when the process exits, or orphaned if game continues.
func (s *sendPacketState) Cleanup() error {
s.mu.Lock()
defer s.mu.Unlock()
// Set exiting flag to skip all future packet sends immediately
s.isExiting = true
log.Printf("Packet system marked as exiting - all future packet sends will be cancelled")
// DO NOT free remote buffers - pending APCs may still be using them!
// Just reset local pointers so they will be re-allocated on next game.
// The remote memory will be orphaned but that's acceptable:
// - If D2R exits/crashes, memory is freed with the process
// - If game returns to menu, it's a small leak that won't accumulate
// (next game allocates new buffers with fresh pointers)
if s.handle != 0 {
// Reset pointers WITHOUT freeing - memory is orphaned but safe
s.packet = 0
s.packetCap = 0
s.meta = 0
s.stub = 0
// Reset function pointer as it may be invalid after game exit
s.fn = 0
// Close thread handle - the main thread may change between games
if s.thread != 0 {
windows.CloseHandle(s.thread)
s.thread = 0
s.threadID = 0
}
}
return nil
}
@@ -251,10 +334,15 @@ func (s *sendPacketState) ensureFunction(p *Process) (uintptr, error) {
func (s *sendPacketState) ensureThreadHandle(p *Process) (windows.Handle, error) {
if s.thread != 0 && s.processPID == p.pid {
// Validate thread is still active and belongs to the correct process
if err := validateThreadActive(s.thread); err == nil {
s.threadLastValidated = time.Now()
return s.thread, nil
// Additional validation: verify thread still belongs to this process
if err := validateThreadBelongsToProcess(s.thread, p.pid); err == nil {
s.threadLastValidated = time.Now()
return s.thread, nil
}
}
// Thread is invalid, close and reset
windows.CloseHandle(s.thread)
s.thread = 0
s.threadID = 0
@@ -276,6 +364,12 @@ func (s *sendPacketState) ensureThreadHandle(p *Process) (windows.Handle, error)
return 0, fmt.Errorf("open thread %d: %w", threadID, err)
}
// Validate the newly opened thread belongs to the correct process
if err := validateThreadBelongsToProcess(handle, p.pid); err != nil {
windows.CloseHandle(handle)
return 0, fmt.Errorf("thread %d validation failed: %w", threadID, err)
}
s.thread = handle
s.threadID = threadID
s.processPID = p.pid
@@ -303,6 +397,31 @@ func validateThreadActive(thread windows.Handle) error {
return nil
}
// validateThreadBelongsToProcess verifies the thread belongs to the specified process
// Uses basic process handle comparison for validation
func validateThreadBelongsToProcess(thread windows.Handle, expectedPID uint32) error {
// Try to get process ID of thread by checking thread times
// If we can get thread times, the thread is valid
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 fmt.Errorf("thread validation failed (GetThreadTimes): %w", err)
}
return errors.New("thread validation failed: GetThreadTimes returned 0")
}
// Thread is valid and accessible, which means we have permission
// This is a basic check - we already validated the thread ID when opening it
return nil
}
func (s *sendPacketState) dispatchAPC(thread windows.Handle, start, parameter uintptr) error {
if thread == 0 {
return errors.New("thread handle is zero")
@@ -483,8 +602,8 @@ func (p *Process) GetD2GSSendPacketFn() (uintptr, error) {
continue
}
memory := make([]byte, int(module.ModuleBaseSize))
if err := windows.ReadProcessMemory(p.handler, module.ModuleBaseAddress, &memory[0], uintptr(module.ModuleBaseSize), nil); err != nil {
memory, err := ReadMemoryChunked(p.handler, module.ModuleBaseAddress, module.ModuleBaseSize)
if err != nil {
log.Printf("Warning: failed to read module %s: %v", module.ModuleName, err)
continue
}
@@ -599,7 +718,171 @@ func (p *Process) SendPacket(packet []byte) (err error) {
return fmt.Errorf("dispatch APC: %w", err)
}
return nil
// Wait for completion with default timeout
return p.waitForPacketCompletion(handle, state, 100*time.Millisecond)
}
// SendPacketWithTimeout sends a packet with a custom APC timeout
// Use this for high-ping connections where 100ms may not be enough
func (p *Process) SendPacketWithTimeout(packet []byte, apcTimeout time.Duration) (err error) {
if p == nil {
return errors.New("process is nil")
}
sendPacketCallCount++
packetID := byte(0)
if len(packet) > 0 {
packetID = packet[0]
}
// Log every increment packet send (0x3A = stat, 0x3B = skill)
if packetID == 0x3A || packetID == 0x3B {
packetType := "UNKNOWN"
if packetID == 0x3A {
packetType = "STAT"
} else if packetID == 0x3B {
packetType = "SKILL"
}
log.Printf("[D2GO SEND #%d] Sending %s packet (0x%02x) - %d bytes", sendPacketCallCount, packetType, packetID, len(packet))
}
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()
// Ensure handle is open (this resets isExiting flag if new game)
handle, err := state.ensureHandle(p.pid)
if err != nil {
return fmt.Errorf("open process: %w", err)
}
// If we're exiting, skip all packet sends immediately
if state.isExiting {
return errors.New("packet send cancelled: game is exiting")
}
// Rate limiting: prevent overwhelming the APC queue
const maxPacketsPerSecond = 100
now := time.Now()
if !state.lastSendTime.IsZero() {
elapsed := now.Sub(state.lastSendTime)
if elapsed < time.Second {
if state.sendCount >= maxPacketsPerSecond {
return fmt.Errorf("rate limit exceeded: %d packets/sec (max %d)", state.sendCount, maxPacketsPerSecond)
}
state.sendCount++
} else {
state.sendCount = 1
state.lastSendTime = now
}
} else {
state.lastSendTime = now
state.sendCount = 1
}
// Additional protection: minimum delay between consecutive packets
if !state.lastSendTime.IsZero() && now.Sub(state.lastSendTime) < time.Millisecond {
time.Sleep(time.Millisecond - now.Sub(state.lastSendTime))
}
fnAddr, err := state.ensureFunction(p)
if err != nil {
return fmt.Errorf("resolve D2GS_SendPacket: %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 p.waitForPacketCompletion(handle, state, apcTimeout)
}
// waitForPacketCompletion waits for the APC to complete with the given timeout
func (p *Process) waitForPacketCompletion(handle windows.Handle, state *sendPacketState, apcTimeout time.Duration) error {
timeout := time.After(apcTimeout)
ticker := time.NewTicker(1 * time.Millisecond)
defer ticker.Stop()
timeoutMs := int(apcTimeout.Milliseconds())
var statusBuf [4]byte
for {
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 {
status := binary.LittleEndian.Uint32(statusBuf[:])
if status == 1 {
return nil
}
}
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 {
continue
}
status := binary.LittleEndian.Uint32(statusBuf[:])
if status == 1 {
return nil // Success - packet was sent
}
}
}
}
func writeRemoteMemory(handle windows.Handle, address uintptr, data []byte) error {
@@ -718,3 +1001,103 @@ func alignUp(value, alignment uintptr) uintptr {
mask := alignment - 1
return (value + mask) &^ mask
}
func createRemoteThread(handle windows.Handle, startAddr, parameter uintptr) (windows.Handle, uint32, error) {
var threadID uint32
ret, _, err := procCreateRemoteThread.Call(
uintptr(handle),
0, // lpThreadAttributes
0, // dwStackSize (default)
startAddr, // lpStartAddress
parameter, // lpParameter
0, // dwCreationFlags (start immediately)
uintptr(unsafe.Pointer(&threadID)),
)
if ret == 0 {
if err != nil {
return 0, 0, err
}
return 0, 0, errors.New("CreateRemoteThread failed")
}
return windows.Handle(ret), threadID, nil
}
// CONTEXT64 is the AMD64 thread context structure
type CONTEXT64 struct {
P1Home uint64
P2Home uint64
P3Home uint64
P4Home uint64
P5Home uint64
P6Home uint64
ContextFlags uint32
MxCsr uint32
SegCs uint16
SegDs uint16
SegEs uint16
SegFs uint16
SegGs uint16
SegSs uint16
EFlags uint32
Dr0 uint64
Dr1 uint64
Dr2 uint64
Dr3 uint64
Dr6 uint64
Dr7 uint64
Rax uint64
Rcx uint64
Rdx uint64
Rbx uint64
Rsp uint64
Rbp uint64
Rsi uint64
Rdi uint64
R8 uint64
R9 uint64
R10 uint64
R11 uint64
R12 uint64
R13 uint64
R14 uint64
R15 uint64
Rip uint64
_ [512]byte // FltSave (XSAVE_FORMAT)
VectorRegister [26][16]byte
VectorControl uint64
DebugControl uint64
LastBranchToRip uint64
LastBranchFromRip uint64
LastExceptionToRip uint64
LastExceptionFromRip uint64
}
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
}