Caution
I don't really care what you do with this project, but please don't use it to be nefarious. Even if you do, it is not an effective way to avoid detection by any modern antivirus or anticheat, so you'd be pretty silly.
Caution
By default, UndeadInterop reads export and syscall data directly from the in-memory copy of ntdll.dll/win32u.dll rather than cloning a pristine copy from the filesystem. This means a tampered export table can influence syscall ID resolution and hook classification. Enable filesystem cloning (see below) to mitigate this.
UndeadInterop is a C# library for reading PE and module data from a running process, primarily its own, to resolve ntdll.dll/win32u.dll syscall IDs and invoke them directly. It also inspects usermode functions for hooks, covering inline jumps and calls, stack pivots, debug interrupts, and forwarded exports.
It started after reading secret.club's writeup on BattlEye's usermode API hooks and grew into a hook detection tool and PInvoke-style syscall wrapper. It was originally the backend for UndeadHotkeys, a frontend for designing macros that never went public and no longer has any surviving source.
- Export walking - Parses the PE export directory of a loaded module to enumerate
Nt*/Zw*functions without relying on static offsets - Syscall resolution - Derives syscall IDs from export order, falling back to forwarded addresses when present
- Direct syscalls - Generates small shellcode stubs at runtime and marshals them to typed delegates, so syscalls can be called like normal .NET methods
- Hook detection - Disassembles a function's prologue with Iced and classifies the control flow into
HookType.InlineJmp,InlineCall,Returning,DebugInt,DebugPrk,Tamper, orForwarded - Shared export handling - Tracks
Nt/Zwpairs so identifiers stay consistent with the real syscall table
UndeadInterop doesn't provide any type interop for syscalls, so you'll need to define your own delegates, and compatibility with the Windows syscall ABI and architecture is your responsibility.
Define a delegate matching the syscall signature and attribute it with NtImport:
[NtImport]
public delegate int NtSuspendProcess(nint handle);
var suspendProcess = NtApi.PrepCall<NtSuspendProcess>();
suspendProcess(processHandle);If the delegate name doesn't match the export name, pass the real entry point:
[NtImport("NtProtectVirtualMemory")]
public delegate int ProtectMemory(nint handle, ref nint baseAddress, ref nint size, uint newProtect, out uint oldProtect);For cases where a compile-time delegate isn't available, resolve one dynamically:
dynamic call = NtApi.PrepDynamic("NtClose", typeof(int), typeof(nint));
call(handle);Check whether any ntdll/win32u export in the current process is hooked:
if (NtApi.IsUserApiHooked())
{
// at least one Nt/Zw export has inline, stack, debug, or forwarding hooks
}By default, UndeadInterop reads from the in-memory module copy. Enable filesystem cloning to resolve syscall IDs and compare hook data against a pristine on-disk copy:
NtApi.UseCloneForSyscalls = true; // resolve syscall IDs from the clone
NtApi.UseCloneForHooks = true; // compare hook data against the clone
NtApi.ClonePath = null; // defaults to Environment.SystemDirectoryWhen UseCloneForHooks is enabled, functions whose bytes differ from the clone without a specific hook being classified will return HookType.Tamper.
NtApi.cs- Export enumeration, syscall ID resolution, shellcode generation, and delegate cachingNtImport.cs- Attribute used to mark a delegate as a syscall importNtStatus.cs- NTSTATUS values returned by native callsMeta/ExportData.cs/Meta/ExportFunction.cs- Raw PE export directory data and per-function recordsMeta/ModuleMap.cs- Abstracts over live and cloned modules with base address, size, and nameMeta/FunctionAnalyzer.cs/Meta/FunctionBlock.cs- Disassembles a function's code region into instruction blocks for analysisMeta/Hooking/HookAnalyzer.cs/Meta/Hooking/HookType.cs- Classifies hook types from the disassembled instructions
Targets net8.0, Windows only, x64 (the generated syscall stub is x86_64 only), and depends on Iced for disassembly.
UndeadInterop is licensed under Apache-2.0.