new plugins system
This commit is contained in:
@@ -5,7 +5,7 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@@ -15,59 +15,96 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
|
|||||||
return nil, errors.New("empty plugin binary")
|
return nil, errors.New("empty plugin binary")
|
||||||
}
|
}
|
||||||
|
|
||||||
mm, err := LoadMemoryModule(data)
|
type initResult struct {
|
||||||
if err != nil {
|
dp *dllPlugin
|
||||||
return nil, fmt.Errorf("pe load: %w", err)
|
err error
|
||||||
}
|
}
|
||||||
|
ch := make(chan initResult, 1)
|
||||||
|
|
||||||
if err := mm.CallEntryPoint(dllProcessAttach); err != nil {
|
go func() {
|
||||||
mm.Free()
|
runtime.LockOSThread()
|
||||||
return nil, fmt.Errorf("DllMain init: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad, err := mm.GetExport("PluginOnLoad")
|
mm, err := LoadMemoryModule(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mm.Free()
|
ch <- initResult{err: fmt.Errorf("pe load: %w", err)}
|
||||||
return nil, err
|
runtime.UnlockOSThread()
|
||||||
}
|
return
|
||||||
onEvent, err := mm.GetExport("PluginOnEvent")
|
}
|
||||||
if err != nil {
|
|
||||||
mm.Free()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
onUnload, err := mm.GetExport("PluginOnUnload")
|
|
||||||
if err != nil {
|
|
||||||
mm.Free()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
setCallback, _ := mm.GetExport("PluginSetCallback")
|
if err := mm.CallEntryPoint(dllProcessAttach); err != nil {
|
||||||
|
mm.Free()
|
||||||
|
ch <- initResult{err: fmt.Errorf("DllMain init: %w", err)}
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
runtime := "go"
|
onLoad, err := mm.GetExport("PluginOnLoad")
|
||||||
if getRuntimeAddr, err := mm.GetExport("PluginGetRuntime"); err == nil {
|
if err != nil {
|
||||||
ret, _, _ := syscall.SyscallN(getRuntimeAddr)
|
mm.Free()
|
||||||
if ret != 0 {
|
ch <- initResult{err: err}
|
||||||
var buf [32]byte
|
runtime.UnlockOSThread()
|
||||||
for i := range buf {
|
return
|
||||||
b := *(*byte)(unsafe.Pointer(ret + uintptr(i)))
|
}
|
||||||
if b == 0 {
|
onEvent, err := mm.GetExport("PluginOnEvent")
|
||||||
runtime = string(buf[:i])
|
if err != nil {
|
||||||
break
|
mm.Free()
|
||||||
|
ch <- initResult{err: err}
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onUnload, err := mm.GetExport("PluginOnUnload")
|
||||||
|
if err != nil {
|
||||||
|
mm.Free()
|
||||||
|
ch <- initResult{err: err}
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCallback, _ := mm.GetExport("PluginSetCallback")
|
||||||
|
|
||||||
|
rt := "go"
|
||||||
|
if getRuntimeAddr, err := mm.GetExport("PluginGetRuntime"); err == nil {
|
||||||
|
ret, _, _ := syscall.SyscallN(getRuntimeAddr)
|
||||||
|
if ret != 0 {
|
||||||
|
var buf [32]byte
|
||||||
|
for i := range buf {
|
||||||
|
b := *(*byte)(unsafe.Pointer(ret + uintptr(i)))
|
||||||
|
if b == 0 {
|
||||||
|
rt = string(buf[:i])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf[i] = b
|
||||||
}
|
}
|
||||||
buf[i] = b
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
dp := &dllPlugin{
|
dp := &dllPlugin{
|
||||||
mem: mm,
|
mem: mm,
|
||||||
onLoadAddr: onLoad,
|
onLoadAddr: onLoad,
|
||||||
onEventAddr: onEvent,
|
onEventAddr: onEvent,
|
||||||
onUnloadAddr: onUnload,
|
onUnloadAddr: onUnload,
|
||||||
setCallbackAddr: setCallback,
|
setCallbackAddr: setCallback,
|
||||||
pluginRuntime: runtime,
|
pluginRuntime: rt,
|
||||||
|
workCh: make(chan pluginWork),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- initResult{dp: dp}
|
||||||
|
|
||||||
|
dp.workerLoop()
|
||||||
|
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
}()
|
||||||
|
|
||||||
|
res := <-ch
|
||||||
|
if res.err != nil {
|
||||||
|
return nil, res.err
|
||||||
}
|
}
|
||||||
return dp, nil
|
return res.dp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pluginWork struct {
|
||||||
|
fn func() error
|
||||||
|
done chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
type dllPlugin struct {
|
type dllPlugin struct {
|
||||||
@@ -78,76 +115,94 @@ type dllPlugin struct {
|
|||||||
setCallbackAddr uintptr
|
setCallbackAddr uintptr
|
||||||
callbackHandle uintptr // prevent GC of the callback closure
|
callbackHandle uintptr // prevent GC of the callback closure
|
||||||
pluginRuntime string // "go", "c", "cpp", etc.
|
pluginRuntime string // "go", "c", "cpp", etc.
|
||||||
mu sync.Mutex
|
workCh chan pluginWork
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dllPlugin) workerLoop() {
|
||||||
|
for w := range p.workCh {
|
||||||
|
w.done <- w.fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *dllPlugin) runOnWorker(fn func() error) error {
|
||||||
|
done := make(chan error, 1)
|
||||||
|
p.workCh <- pluginWork{fn: fn, done: done}
|
||||||
|
return <-done
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *dllPlugin) Load(send func(string, []byte), hostInfo []byte) error {
|
func (p *dllPlugin) Load(send func(string, []byte), hostInfo []byte) error {
|
||||||
p.mu.Lock()
|
return p.runOnWorker(func() error {
|
||||||
defer p.mu.Unlock()
|
// Create a stdcall callback the DLL can invoke to send events to the host.
|
||||||
|
cb := syscall.NewCallback(func(eventPtr, eventLen, payloadPtr, payloadLen uintptr) uintptr {
|
||||||
|
event := make([]byte, eventLen)
|
||||||
|
if eventLen > 0 {
|
||||||
|
copy(event, unsafe.Slice((*byte)(unsafe.Pointer(eventPtr)), eventLen))
|
||||||
|
}
|
||||||
|
payload := make([]byte, payloadLen)
|
||||||
|
if payloadLen > 0 {
|
||||||
|
copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(payloadPtr)), payloadLen))
|
||||||
|
}
|
||||||
|
send(string(event), payload)
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
p.callbackHandle = cb
|
||||||
|
|
||||||
// Create a stdcall callback the DLL can invoke to send events to the host.
|
if p.setCallbackAddr != 0 {
|
||||||
cb := syscall.NewCallback(func(eventPtr, eventLen, payloadPtr, payloadLen uintptr) uintptr {
|
syscall.SyscallN(p.setCallbackAddr, cb)
|
||||||
event := make([]byte, eventLen)
|
|
||||||
if eventLen > 0 {
|
|
||||||
copy(event, unsafe.Slice((*byte)(unsafe.Pointer(eventPtr)), eventLen))
|
|
||||||
}
|
}
|
||||||
payload := make([]byte, payloadLen)
|
|
||||||
if payloadLen > 0 {
|
var infoPtr uintptr
|
||||||
copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(payloadPtr)), payloadLen))
|
infoLen := uintptr(len(hostInfo))
|
||||||
|
if len(hostInfo) > 0 {
|
||||||
|
infoPtr = uintptr(unsafe.Pointer(&hostInfo[0]))
|
||||||
}
|
}
|
||||||
send(string(event), payload)
|
ret, _, _ := syscall.SyscallN(p.onLoadAddr, infoPtr, infoLen, cb)
|
||||||
return 0
|
if int32(ret) != 0 {
|
||||||
|
return errors.New("PluginOnLoad returned non-zero")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
p.callbackHandle = cb
|
|
||||||
|
|
||||||
if p.setCallbackAddr != 0 {
|
|
||||||
syscall.SyscallN(p.setCallbackAddr, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
var infoPtr uintptr
|
|
||||||
infoLen := uintptr(len(hostInfo))
|
|
||||||
if len(hostInfo) > 0 {
|
|
||||||
infoPtr = uintptr(unsafe.Pointer(&hostInfo[0]))
|
|
||||||
}
|
|
||||||
ret, _, _ := syscall.SyscallN(p.onLoadAddr, infoPtr, infoLen, cb)
|
|
||||||
if int32(ret) != 0 {
|
|
||||||
return errors.New("PluginOnLoad returned non-zero")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *dllPlugin) Event(event string, payload []byte) error {
|
func (p *dllPlugin) Event(event string, payload []byte) error {
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
eventBytes := []byte(event)
|
eventBytes := []byte(event)
|
||||||
var eventPtr, payloadPtr uintptr
|
payloadCopy := make([]byte, len(payload))
|
||||||
eventLen := uintptr(len(eventBytes))
|
copy(payloadCopy, payload)
|
||||||
payloadLen := uintptr(len(payload))
|
|
||||||
if len(eventBytes) > 0 {
|
|
||||||
eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
|
|
||||||
}
|
|
||||||
if len(payload) > 0 {
|
|
||||||
payloadPtr = uintptr(unsafe.Pointer(&payload[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
ret, _, _ := syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
|
go func() {
|
||||||
if int32(ret) != 0 {
|
runtime.LockOSThread()
|
||||||
return errors.New("PluginOnEvent returned non-zero")
|
defer runtime.UnlockOSThread()
|
||||||
}
|
|
||||||
|
cleanup := p.mem.SetupThreadTLS()
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
var eventPtr, payloadPtr uintptr
|
||||||
|
eventLen := uintptr(len(eventBytes))
|
||||||
|
payloadLen := uintptr(len(payloadCopy))
|
||||||
|
if len(eventBytes) > 0 {
|
||||||
|
eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
|
||||||
|
}
|
||||||
|
if len(payloadCopy) > 0 {
|
||||||
|
payloadPtr = uintptr(unsafe.Pointer(&payloadCopy[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *dllPlugin) Unload() {
|
func (p *dllPlugin) Unload() {
|
||||||
p.mu.Lock()
|
_ = p.runOnWorker(func() error {
|
||||||
defer p.mu.Unlock()
|
if p.onUnloadAddr != 0 {
|
||||||
if p.onUnloadAddr != 0 {
|
syscall.SyscallN(p.onUnloadAddr)
|
||||||
syscall.SyscallN(p.onUnloadAddr)
|
}
|
||||||
}
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *dllPlugin) Close() error {
|
func (p *dllPlugin) Close() error {
|
||||||
p.Unload()
|
p.Unload()
|
||||||
|
close(p.workCh)
|
||||||
if p.pluginRuntime != "go" {
|
if p.pluginRuntime != "go" {
|
||||||
if p.mem != nil {
|
if p.mem != nil {
|
||||||
p.mem.Free()
|
p.mem.Free()
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func currentTEB() uintptr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
imageDOSSignature = 0x5A4D // "MZ"
|
imageDOSSignature = 0x5A4D // "MZ"
|
||||||
imageNTSignature = 0x00004550 // "PE\0\0"
|
imageNTSignature = 0x00004550 // "PE\0\0"
|
||||||
@@ -123,14 +125,18 @@ 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
|
||||||
tlsData uintptr
|
tlsData uintptr
|
||||||
|
tlsArrayAlloc uintptr // our VirtualAlloc'd ThreadLocalStoragePointer array
|
||||||
|
tlsTemplSrc uintptr // PE template raw data start (for copying to new threads)
|
||||||
|
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) {
|
||||||
@@ -293,8 +299,8 @@ func (mm *MemoryModule) Free() {
|
|||||||
if mm.tlsIndex != 0xFFFFFFFF {
|
if mm.tlsIndex != 0xFFFFFFFF {
|
||||||
if mm.tlsData != 0 {
|
if mm.tlsData != 0 {
|
||||||
_ = windows.VirtualFree(mm.tlsData, 0, windows.MEM_RELEASE)
|
_ = windows.VirtualFree(mm.tlsData, 0, windows.MEM_RELEASE)
|
||||||
|
mm.tlsData = 0
|
||||||
}
|
}
|
||||||
procTlsFree.Call(uintptr(mm.tlsIndex))
|
|
||||||
}
|
}
|
||||||
for _, h := range mm.importDLLs {
|
for _, h := range mm.importDLLs {
|
||||||
_ = windows.FreeLibrary(h)
|
_ = windows.FreeLibrary(h)
|
||||||
@@ -431,30 +437,78 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
|
|||||||
|
|
||||||
tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress)))
|
tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress)))
|
||||||
|
|
||||||
idx, _, _ := procTlsAlloc.Call()
|
|
||||||
if idx == 0xFFFFFFFF {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mm.tlsIndex = uint32(idx)
|
|
||||||
|
|
||||||
if tls.AddressOfIndex != 0 {
|
|
||||||
*(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = mm.tlsIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
if totalSize > 0 {
|
if totalSize > 0 {
|
||||||
tlsData, err := windows.VirtualAlloc(0, totalSize,
|
var err error
|
||||||
|
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 {
|
||||||
if dataSize > 0 {
|
return
|
||||||
copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
|
}
|
||||||
}
|
if dataSize > 0 {
|
||||||
procTlsSetValue.Call(uintptr(mm.tlsIndex), tlsData)
|
copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
|
||||||
mm.tlsData = tlsData
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tebAddr := currentTEB()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
*(*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
|
||||||
|
}
|
||||||
|
|
||||||
|
if tls.AddressOfIndex != 0 {
|
||||||
|
*(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = newIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
mm.tlsIndex = newIndex
|
||||||
|
mm.tlsData = tlsData
|
||||||
|
mm.tlsTemplSrc = uintptr(tls.StartAddressOfRawData)
|
||||||
|
mm.tlsTemplSize = dataSize
|
||||||
|
mm.tlsTotalSize = totalSize
|
||||||
|
|
||||||
if tls.AddressOfCallBacks != 0 {
|
if tls.AddressOfCallBacks != 0 {
|
||||||
cbAddr := uintptr(tls.AddressOfCallBacks)
|
cbAddr := uintptr(tls.AddressOfCallBacks)
|
||||||
for {
|
for {
|
||||||
@@ -468,6 +522,61 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mm *MemoryModule) SetupThreadTLS() func() {
|
||||||
|
if mm.tlsIndex == 0xFFFFFFFF || mm.tlsTotalSize == 0 {
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsData, err := windows.VirtualAlloc(0, mm.tlsTotalSize,
|
||||||
|
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
||||||
|
if err != nil || tlsData == 0 {
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
if mm.tlsTemplSize > 0 {
|
||||||
|
copyMem(tlsData, (*byte)(unsafe.Pointer(mm.tlsTemplSrc)), mm.tlsTemplSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
ptrSize := unsafe.Sizeof(uintptr(0))
|
||||||
|
tebAddr := currentTEB()
|
||||||
|
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() {
|
||||||
|
_ = 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 := currentTEB()
|
||||||
|
field := (*uintptr)(unsafe.Pointer(tebNow + 0x58))
|
||||||
|
*field = oldArray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func peString(addr uintptr) string {
|
func peString(addr uintptr) string {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
for i := 0; i < 4096; i++ { // safety limit
|
for i := 0; i < 4096; i++ { // safety limit
|
||||||
@@ -480,45 +589,42 @@ func peString(addr uintptr) string {
|
|||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func copyMem(dst uintptr, src *byte, size uintptr) {
|
||||||
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
for i := uintptr(0); i < size; i++ {
|
||||||
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
|
*(*byte)(unsafe.Pointer(dst + i)) = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(src)) + i))
|
||||||
procTlsAlloc = modKernel32.NewProc("TlsAlloc")
|
|
||||||
procTlsSetValue = modKernel32.NewProc("TlsSetValue")
|
|
||||||
procTlsFree = modKernel32.NewProc("TlsFree")
|
|
||||||
)
|
|
||||||
|
|
||||||
func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
|
|
||||||
r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
|
|
||||||
if r == 0 {
|
|
||||||
return 0, fmt.Errorf("GetProcAddress ordinal %d: %w", ordinal, err)
|
|
||||||
}
|
}
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sectionProtection(chars uint32) uint32 {
|
func sectionProtection(chars uint32) uint32 {
|
||||||
exec := chars&imageSCNMemExecute != 0
|
r := chars&imageSCNMemRead != 0
|
||||||
read := chars&imageSCNMemRead != 0
|
w := chars&imageSCNMemWrite != 0
|
||||||
write := chars&imageSCNMemWrite != 0
|
x := chars&imageSCNMemExecute != 0
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case exec && write:
|
case x && w:
|
||||||
return windows.PAGE_EXECUTE_READWRITE
|
return windows.PAGE_EXECUTE_READWRITE
|
||||||
case exec && read:
|
case x && r:
|
||||||
return windows.PAGE_EXECUTE_READ
|
return windows.PAGE_EXECUTE_READ
|
||||||
case exec:
|
case x:
|
||||||
return windows.PAGE_EXECUTE
|
return windows.PAGE_EXECUTE
|
||||||
case write:
|
case w:
|
||||||
return windows.PAGE_READWRITE
|
return windows.PAGE_READWRITE
|
||||||
case read:
|
case r:
|
||||||
return windows.PAGE_READONLY
|
return windows.PAGE_READONLY
|
||||||
default:
|
default:
|
||||||
return windows.PAGE_NOACCESS
|
return windows.PAGE_NOACCESS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyMem(dst uintptr, src *byte, size uintptr) {
|
var (
|
||||||
dstSlice := unsafe.Slice((*byte)(unsafe.Pointer(dst)), size)
|
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
srcSlice := unsafe.Slice(src, size)
|
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
|
||||||
copy(dstSlice, srcSlice)
|
)
|
||||||
|
|
||||||
|
func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
|
||||||
|
r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
|
||||||
|
if r == 0 {
|
||||||
|
return 0, fmt.Errorf("pe: GetProcAddress ordinal %d: %w", ordinal, err)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// func currentTEB() uintptr
|
||||||
|
TEXT ·currentTEB(SB),NOSPLIT,$0-8
|
||||||
|
// mov rax, gs:[0x30] — read the TEB self-pointer
|
||||||
|
BYTE $0x65; BYTE $0x48; BYTE $0x8B; BYTE $0x04; BYTE $0x25
|
||||||
|
LONG $0x00000030
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
@@ -39,7 +39,7 @@ import { isAuthorizedAgentRequest } from "./server/agent-auth";
|
|||||||
import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils";
|
import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils";
|
||||||
import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils";
|
import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils";
|
||||||
import { CORS_HEADERS } from "./server/http-security";
|
import { CORS_HEADERS } from "./server/http-security";
|
||||||
import { mimeType, secureHeaders, securePluginHeaders } from "./server/http-utils";
|
import { mimeType, secureHeaders } from "./server/http-utils";
|
||||||
import { sanitizePluginId } from "./server/plugin-utils";
|
import { sanitizePluginId } from "./server/plugin-utils";
|
||||||
import { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch";
|
import { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch";
|
||||||
import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch";
|
import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch";
|
||||||
@@ -421,7 +421,6 @@ async function startServer() {
|
|||||||
enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent,
|
enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent,
|
||||||
drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents,
|
drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents,
|
||||||
secureHeaders,
|
secureHeaders,
|
||||||
securePluginHeaders,
|
|
||||||
mimeType,
|
mimeType,
|
||||||
},
|
},
|
||||||
fileShare: {
|
fileShare: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function securePluginHeaders() {
|
|||||||
return {
|
return {
|
||||||
...SECURITY_HEADERS,
|
...SECURITY_HEADERS,
|
||||||
"X-Frame-Options": "SAMEORIGIN",
|
"X-Frame-Options": "SAMEORIGIN",
|
||||||
"Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
|
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' wss: ws:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ type PluginRouteDeps = {
|
|||||||
enqueuePluginEvent: (clientId: string, pluginId: string, event: string, payload: any) => void;
|
enqueuePluginEvent: (clientId: string, pluginId: string, event: string, payload: any) => void;
|
||||||
drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>;
|
drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>;
|
||||||
secureHeaders: (contentType?: string) => Record<string, string>;
|
secureHeaders: (contentType?: string) => Record<string, string>;
|
||||||
securePluginHeaders: () => Record<string, string>;
|
|
||||||
mimeType: (path: string) => string;
|
mimeType: (path: string) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -715,7 +714,6 @@ export async function handlePluginRoutes(
|
|||||||
|
|
||||||
const raw = await file.text();
|
const raw = await file.text();
|
||||||
const baseTag = `<base href="/plugins/${pluginId}/assets/" />`;
|
const baseTag = `<base href="/plugins/${pluginId}/assets/" />`;
|
||||||
const bridgeTag = `<script src="/assets/plugin-bridge.js"></script>`;
|
|
||||||
let injected = raw;
|
let injected = raw;
|
||||||
|
|
||||||
const headMatch = raw.match(/<head[^>]*>/i);
|
const headMatch = raw.match(/<head[^>]*>/i);
|
||||||
@@ -723,16 +721,8 @@ export async function handlePluginRoutes(
|
|||||||
injected = raw.replace(headMatch[0], `${headMatch[0]}\n ${baseTag}`);
|
injected = raw.replace(headMatch[0], `${headMatch[0]}\n ${baseTag}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (injected.includes("</head>")) {
|
|
||||||
injected = injected.replace("</head>", ` ${bridgeTag}\n </head>`);
|
|
||||||
} else if (injected.includes("</body>")) {
|
|
||||||
injected = injected.replace("</body>", ` ${bridgeTag}\n</body>`);
|
|
||||||
} else {
|
|
||||||
injected = `${bridgeTag}\n${injected}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(injected, {
|
return new Response(injected, {
|
||||||
headers: { ...deps.securePluginHeaders(), "Content-Type": "text/html; charset=utf-8" },
|
headers: deps.secureHeaders("text/html; charset=utf-8"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,9 +736,33 @@ export async function handlePluginRoutes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clientId = url.searchParams.get("clientId") || "";
|
const clientId = url.searchParams.get("clientId") || "";
|
||||||
const bridgeToken = uuidv4();
|
|
||||||
const origin = url.origin;
|
const htmlFile = path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.html`);
|
||||||
const iframeSrc = `/plugins/${pluginId}/frame?clientId=${encodeURIComponent(clientId)}&token=${encodeURIComponent(bridgeToken)}&origin=${encodeURIComponent(origin)}`;
|
const file = Bun.file(htmlFile);
|
||||||
|
let pluginBody = "";
|
||||||
|
let pluginHeadExtras = "";
|
||||||
|
|
||||||
|
if (await file.exists()) {
|
||||||
|
const raw = await file.text();
|
||||||
|
|
||||||
|
const bodyMatch = raw.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
||||||
|
pluginBody = bodyMatch ? bodyMatch[1] : raw;
|
||||||
|
|
||||||
|
const headMatch = raw.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
||||||
|
if (headMatch) {
|
||||||
|
const headContent = headMatch[1];
|
||||||
|
const linkTags = headContent.match(/<link[^>]*>/gi) || [];
|
||||||
|
const styleTags = headContent.match(/<style[\s\S]*?<\/style>/gi) || [];
|
||||||
|
pluginHeadExtras = [...linkTags, ...styleTags]
|
||||||
|
.map((tag) =>
|
||||||
|
tag.replace(/href="(?!https?:\/\/|\/)/g, `href="/plugins/${pluginId}/assets/`),
|
||||||
|
)
|
||||||
|
.join("\n ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsFile = Bun.file(path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.js`));
|
||||||
|
const hasPluginJs = await jsFile.exists();
|
||||||
|
|
||||||
const html = `<!doctype html>
|
const html = `<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -771,27 +785,19 @@ export async function handlePluginRoutes(
|
|||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="/assets/main.css" />
|
<link rel="stylesheet" href="/assets/main.css" />
|
||||||
<link rel="stylesheet" href="/assets/custom.css" />
|
<link rel="stylesheet" href="/assets/custom.css" />
|
||||||
|
${pluginHeadExtras}
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-slate-950 text-slate-100">
|
<body class="min-h-screen bg-slate-950 text-slate-100">
|
||||||
<header id="top-nav"></header>
|
<header id="top-nav"></header>
|
||||||
<main class="px-5 py-6">
|
<main class="px-5 py-6">
|
||||||
<div class="max-w-6xl mx-auto">
|
<div class="max-w-6xl mx-auto">
|
||||||
<div class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden">
|
<div id="plugin-container" class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden p-4">
|
||||||
<iframe
|
${pluginBody}
|
||||||
id="plugin-frame"
|
|
||||||
src="${iframeSrc}"
|
|
||||||
sandbox="allow-scripts allow-same-origin"
|
|
||||||
class="w-full h-[calc(100vh-220px)] bg-slate-950"
|
|
||||||
></iframe>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<div
|
|
||||||
id="plugin-host"
|
|
||||||
data-bridge-token="${bridgeToken}"
|
|
||||||
></div>
|
|
||||||
<script type="module" src="/assets/nav.js"></script>
|
<script type="module" src="/assets/nav.js"></script>
|
||||||
<script src="/assets/plugin-host.js"></script>
|
${hasPluginJs ? `<script src="/plugins/${pluginId}/assets/${pluginId}.js"></script>` : ""}
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user