mfs would somehow think I'm fixing ts without AI LMFAO.
This commit is contained in:
@@ -5,7 +5,6 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"overlord-client/cmd/agent/plugins/teb"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -124,18 +123,16 @@ type imageTLSDirectory64 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MemoryModule struct {
|
type MemoryModule struct {
|
||||||
base uintptr
|
base uintptr
|
||||||
size uintptr
|
size uintptr
|
||||||
entryPoint uintptr
|
entryPoint uintptr
|
||||||
exports map[string]uintptr
|
exports map[string]uintptr
|
||||||
importDLLs []windows.Handle
|
importDLLs []windows.Handle
|
||||||
initialized bool
|
initialized bool
|
||||||
tlsIndex uint32
|
tlsIndex uint32 // index returned by TlsAlloc; 0xFFFFFFFF if no TLS dir
|
||||||
tlsData uintptr
|
tlsTemplSrc uintptr // PE template raw data start (for copying to new threads)
|
||||||
tlsArrayAlloc uintptr // our VirtualAlloc'd ThreadLocalStoragePointer array
|
tlsTemplSize uintptr // dataSize (template bytes to copy)
|
||||||
tlsTemplSrc uintptr // PE template raw data start (for copying to new threads)
|
tlsTotalSize uintptr // dataSize + zeroFill (total allocation per thread)
|
||||||
tlsTemplSize uintptr // dataSize (template bytes to copy)
|
|
||||||
tlsTotalSize uintptr // dataSize + zeroFill (total allocation per thread)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadMemoryModule(data []byte) (*MemoryModule, error) {
|
func LoadMemoryModule(data []byte) (*MemoryModule, error) {
|
||||||
@@ -176,7 +173,7 @@ func LoadMemoryModule(data []byte) (*MemoryModule, error) {
|
|||||||
base: base,
|
base: base,
|
||||||
size: imageSize,
|
size: imageSize,
|
||||||
exports: make(map[string]uintptr),
|
exports: make(map[string]uintptr),
|
||||||
tlsIndex: 0xFFFFFFFF,
|
tlsIndex: tlsOutOfIndexes,
|
||||||
}
|
}
|
||||||
|
|
||||||
headerSize := uintptr(ntHdr.OptionalHeader.SizeOfHeaders)
|
headerSize := uintptr(ntHdr.OptionalHeader.SizeOfHeaders)
|
||||||
@@ -296,11 +293,15 @@ func (mm *MemoryModule) Free() {
|
|||||||
syscall.SyscallN(mm.entryPoint, mm.base, dllProcessDetach, 0)
|
syscall.SyscallN(mm.entryPoint, mm.base, dllProcessDetach, 0)
|
||||||
mm.initialized = false
|
mm.initialized = false
|
||||||
}
|
}
|
||||||
if mm.tlsIndex != 0xFFFFFFFF {
|
if mm.tlsIndex != tlsOutOfIndexes {
|
||||||
if mm.tlsData != 0 {
|
// Free our load-thread per-thread block (held in the slot)
|
||||||
_ = windows.VirtualFree(mm.tlsData, 0, windows.MEM_RELEASE)
|
// before releasing the slot itself, so the OS can reuse it.
|
||||||
mm.tlsData = 0
|
if data := tlsGetValue(mm.tlsIndex); data != 0 {
|
||||||
|
_ = windows.VirtualFree(data, 0, windows.MEM_RELEASE)
|
||||||
|
tlsSetValue(mm.tlsIndex, 0)
|
||||||
}
|
}
|
||||||
|
tlsFree(mm.tlsIndex)
|
||||||
|
mm.tlsIndex = tlsOutOfIndexes
|
||||||
}
|
}
|
||||||
for _, h := range mm.importDLLs {
|
for _, h := range mm.importDLLs {
|
||||||
_ = windows.FreeLibrary(h)
|
_ = windows.FreeLibrary(h)
|
||||||
@@ -427,6 +428,17 @@ func (mm *MemoryModule) parseExports(dir imageDataDirectory) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupTLS handles the manually-mapped DLL's TLS directory.
|
||||||
|
//
|
||||||
|
// Critically, this MUST go through the Win32 TlsAlloc/TlsSetValue API rather
|
||||||
|
// than poking the TEB's ThreadLocalStoragePointer (gs:0x58) directly. ntdll
|
||||||
|
// owns that array and resizes it as DLLs reserve slots. If we replace the
|
||||||
|
// pointer with our own buffer sized only for our slot, every other DLL on the
|
||||||
|
// thread (d3d11.dll, dxgi.dll, ole32.dll's COM apartment slot, msvcrt's
|
||||||
|
// errno, Rust libstd's SRWLOCK pool, etc.) reads or writes past the end of
|
||||||
|
// our truncated array on its next TLS access — which manifests as random
|
||||||
|
// access violations in completely unrelated code paths (e.g.
|
||||||
|
// D3D11CreateDevice).
|
||||||
func (mm *MemoryModule) setupTLS(reason uint32) {
|
func (mm *MemoryModule) setupTLS(reason uint32) {
|
||||||
dosHdr := (*imageDOSHeader)(unsafe.Pointer(mm.base))
|
dosHdr := (*imageDOSHeader)(unsafe.Pointer(mm.base))
|
||||||
ntHdr := (*imageNTHeaders64)(unsafe.Pointer(mm.base + uintptr(dosHdr.LfaNew)))
|
ntHdr := (*imageNTHeaders64)(unsafe.Pointer(mm.base + uintptr(dosHdr.LfaNew)))
|
||||||
@@ -436,81 +448,46 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress)))
|
tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress)))
|
||||||
|
|
||||||
dataSize := uintptr(tls.EndAddressOfRawData - tls.StartAddressOfRawData)
|
dataSize := uintptr(tls.EndAddressOfRawData - tls.StartAddressOfRawData)
|
||||||
totalSize := dataSize + uintptr(tls.SizeOfZeroFill)
|
totalSize := dataSize + uintptr(tls.SizeOfZeroFill)
|
||||||
|
|
||||||
var tlsData uintptr
|
// Reserve a real TLS slot through the Win32 loader. ntdll grows the
|
||||||
|
// TEB's TLS array to fit, and this slot is interoperable with the
|
||||||
|
// plugin's compiled `gs:[58h+idx*8]` access pattern because the
|
||||||
|
// system keeps that array properly sized.
|
||||||
|
idx := tlsAlloc()
|
||||||
|
if idx == tlsOutOfIndexes {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the calling thread's per-thread TLS block (template +
|
||||||
|
// zero-fill) and bind it to our slot. Subsequent threads that need
|
||||||
|
// to call into the plugin allocate their own block via
|
||||||
|
// SetupThreadTLS, but never touch the TEB array.
|
||||||
if totalSize > 0 {
|
if totalSize > 0 {
|
||||||
var err error
|
tlsData, err := windows.VirtualAlloc(0, totalSize,
|
||||||
tlsData, err = windows.VirtualAlloc(0, totalSize,
|
|
||||||
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
||||||
if err != nil || tlsData == 0 {
|
if err != nil || tlsData == 0 {
|
||||||
|
tlsFree(idx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if dataSize > 0 {
|
if dataSize > 0 {
|
||||||
copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
|
copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
|
||||||
}
|
}
|
||||||
}
|
if !tlsSetValue(idx, tlsData) {
|
||||||
|
|
||||||
tebAddr := teb.CurrentTEB()
|
|
||||||
if tebAddr == 0 {
|
|
||||||
if tlsData != 0 {
|
|
||||||
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
||||||
}
|
tlsFree(idx)
|
||||||
return
|
|
||||||
}
|
|
||||||
tlsArrayField := (*uintptr)(unsafe.Pointer(tebAddr + 0x58)) // ThreadLocalStoragePointer
|
|
||||||
oldArray := *tlsArrayField
|
|
||||||
|
|
||||||
var newIndex uint32
|
|
||||||
ptrSize := unsafe.Sizeof(uintptr(0))
|
|
||||||
|
|
||||||
if oldArray == 0 {
|
|
||||||
newArray, err := windows.VirtualAlloc(0, ptrSize,
|
|
||||||
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
|
||||||
if err != nil || newArray == 0 {
|
|
||||||
if tlsData != 0 {
|
|
||||||
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
*(*uintptr)(unsafe.Pointer(newArray)) = tlsData
|
|
||||||
*tlsArrayField = newArray
|
|
||||||
newIndex = 0
|
|
||||||
mm.tlsArrayAlloc = newArray
|
|
||||||
} else {
|
|
||||||
var count uint32
|
|
||||||
for count = 0; count < 256; count++ {
|
|
||||||
entry := *(*uintptr)(unsafe.Pointer(oldArray + uintptr(count)*ptrSize))
|
|
||||||
if entry == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newSize := uintptr(count+1) * ptrSize
|
|
||||||
newArray, err := windows.VirtualAlloc(0, newSize,
|
|
||||||
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
|
||||||
if err != nil || newArray == 0 {
|
|
||||||
if tlsData != 0 {
|
|
||||||
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
copyMem(newArray, (*byte)(unsafe.Pointer(oldArray)), uintptr(count)*ptrSize)
|
|
||||||
}
|
|
||||||
*(*uintptr)(unsafe.Pointer(newArray + uintptr(count)*ptrSize)) = tlsData
|
|
||||||
*tlsArrayField = newArray
|
|
||||||
newIndex = count
|
|
||||||
mm.tlsArrayAlloc = newArray
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Patch the plugin's `_tls_index` so its compiled TLS accesses use
|
||||||
|
// our OS-issued slot instead of whatever index it was linked with.
|
||||||
if tls.AddressOfIndex != 0 {
|
if tls.AddressOfIndex != 0 {
|
||||||
*(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = newIndex
|
*(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = idx
|
||||||
}
|
}
|
||||||
|
|
||||||
mm.tlsIndex = newIndex
|
mm.tlsIndex = idx
|
||||||
mm.tlsData = tlsData
|
|
||||||
mm.tlsTemplSrc = uintptr(tls.StartAddressOfRawData)
|
mm.tlsTemplSrc = uintptr(tls.StartAddressOfRawData)
|
||||||
mm.tlsTemplSize = dataSize
|
mm.tlsTemplSize = dataSize
|
||||||
mm.tlsTotalSize = totalSize
|
mm.tlsTotalSize = totalSize
|
||||||
@@ -528,8 +505,12 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetupThreadTLS allocates a fresh per-thread TLS block for the plugin on
|
||||||
|
// the current thread and installs it via TlsSetValue. Returns a cleanup
|
||||||
|
// function that releases the allocation and clears the slot. As above, this
|
||||||
|
// goes through Win32 only — never touches gs:[58h] directly.
|
||||||
func (mm *MemoryModule) SetupThreadTLS() func() {
|
func (mm *MemoryModule) SetupThreadTLS() func() {
|
||||||
if mm.tlsIndex == 0xFFFFFFFF || mm.tlsTotalSize == 0 {
|
if mm.tlsIndex == tlsOutOfIndexes || mm.tlsTotalSize == 0 {
|
||||||
return func() {}
|
return func() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,50 +523,17 @@ func (mm *MemoryModule) SetupThreadTLS() func() {
|
|||||||
copyMem(tlsData, (*byte)(unsafe.Pointer(mm.tlsTemplSrc)), mm.tlsTemplSize)
|
copyMem(tlsData, (*byte)(unsafe.Pointer(mm.tlsTemplSrc)), mm.tlsTemplSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
ptrSize := unsafe.Sizeof(uintptr(0))
|
prev := tlsGetValue(mm.tlsIndex)
|
||||||
tebAddr := teb.CurrentTEB()
|
if !tlsSetValue(mm.tlsIndex, tlsData) {
|
||||||
if tebAddr == 0 {
|
|
||||||
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
||||||
return func() {}
|
return func() {}
|
||||||
}
|
}
|
||||||
tlsArrayField := (*uintptr)(unsafe.Pointer(tebAddr + 0x58))
|
|
||||||
oldArray := *tlsArrayField
|
|
||||||
|
|
||||||
var newArray uintptr
|
|
||||||
if oldArray == 0 {
|
|
||||||
arrSize := uintptr(mm.tlsIndex+1) * ptrSize
|
|
||||||
newArray, err = windows.VirtualAlloc(0, arrSize,
|
|
||||||
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
|
||||||
if err != nil || newArray == 0 {
|
|
||||||
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
|
||||||
return func() {}
|
|
||||||
}
|
|
||||||
*(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = tlsData
|
|
||||||
*tlsArrayField = newArray
|
|
||||||
} else {
|
|
||||||
needed := uintptr(mm.tlsIndex+1) * ptrSize
|
|
||||||
newArray, err = windows.VirtualAlloc(0, needed,
|
|
||||||
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
|
||||||
if err != nil || newArray == 0 {
|
|
||||||
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
|
||||||
return func() {}
|
|
||||||
}
|
|
||||||
copyMem(newArray, (*byte)(unsafe.Pointer(oldArray)), needed)
|
|
||||||
*(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = tlsData
|
|
||||||
*tlsArrayField = newArray
|
|
||||||
}
|
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
|
// Restore whatever was in the slot before (typically 0 for a
|
||||||
|
// fresh thread). Then free our per-thread block.
|
||||||
|
tlsSetValue(mm.tlsIndex, prev)
|
||||||
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
|
||||||
if newArray != 0 {
|
|
||||||
*(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = 0
|
|
||||||
_ = windows.VirtualFree(newArray, 0, windows.MEM_RELEASE)
|
|
||||||
}
|
|
||||||
tebNow := teb.CurrentTEB()
|
|
||||||
if tebNow != 0 {
|
|
||||||
field := (*uintptr)(unsafe.Pointer(tebNow + 0x58))
|
|
||||||
*field = oldArray
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,8 +579,37 @@ func sectionProtection(chars uint32) uint32 {
|
|||||||
var (
|
var (
|
||||||
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
|
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
|
||||||
|
procTlsAlloc = modKernel32.NewProc("TlsAlloc")
|
||||||
|
procTlsFree = modKernel32.NewProc("TlsFree")
|
||||||
|
procTlsSetValue = modKernel32.NewProc("TlsSetValue")
|
||||||
|
procTlsGetValue = modKernel32.NewProc("TlsGetValue")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const tlsOutOfIndexes uint32 = 0xFFFFFFFF
|
||||||
|
|
||||||
|
// tlsAlloc reserves a TLS slot via the Win32 loader so ntdll keeps the TEB's
|
||||||
|
// ThreadLocalStoragePointer correctly sized. This is critical: writing the
|
||||||
|
// TEB array directly truncates it for OTHER DLLs (like d3d11.dll), which
|
||||||
|
// then crash on TlsGetValue/TlsSetValue past the truncated tail.
|
||||||
|
func tlsAlloc() uint32 {
|
||||||
|
r, _, _ := procTlsAlloc.Call()
|
||||||
|
return uint32(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tlsFree(idx uint32) {
|
||||||
|
procTlsFree.Call(uintptr(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tlsSetValue(idx uint32, value uintptr) bool {
|
||||||
|
r, _, _ := procTlsSetValue.Call(uintptr(idx), value)
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func tlsGetValue(idx uint32) uintptr {
|
||||||
|
r, _, _ := procTlsGetValue.Call(uintptr(idx))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
|
func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
|
||||||
r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
|
r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
|
||||||
if r == 0 {
|
if r == 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user