pInvoke and NTAPI
Intro
As a continuation of the first part , you can find it » here. Here i will make progress to a more advanced technique of calling the functions i need from NTAPI, which reside in ntdll.dll.
Windows applications commonly interact with the operating system through documented Win32 APIs such as OpenProcess, VirtualAllocEx, and WriteProcessMemory. Under the hood, many Windows operations eventually cross into the native NT layer exposed through ntdll.dll.
The functions exported by ntdll.dll are commonly referred to as the Native API or NTAPI. They sit much closer to the operating-system internals than the higher-level Win32 APIs, and their interfaces are generally less developer-friendly and less comprehensively documented by Microsoft.
This makes working with NTAPI an interesting exercise in Windows internals and native interoperability: instead of relying entirely on the managed or Win32 abstractions, we have to understand the native function signatures, structures, pointer types, calling conventions, and memory layout expected by the Windows ABI.
For this demonstration, I’ll use C# because I’m more comfortable with it than C/C++ which are clearly languages for adults.
Another note is that this code was written by hand, you dont see tha everyday in 2026 :)
As mentioned in the last article we need 3 or (4 in this case) functions
-
OpenProcessthis is used to hook to out target process by supplying a PID - this will translate toNtOpenProcessin ntdll.dll -
VirtualAllocExto allocate memory regions to write out payloads - this will translate toNtAllocateVirtualMemoryExin ntdll.dll -
WriteProcessMemoryto write out shellcode to memory of process - this will translate toNtWriteVirtualMemoryin ntdll.dll -
CreateRemoteThreadExto start a execute the shellcode in the target process new thread - this will translate toNtCreateThreadEx
First things first since ntdll.dll is a lower level in order to call this functions we need to create delegates for them , delegates are type-safe representation of a method/function signature. This is necessary since i wont be importing the functions directly from ntdll.dll rather i will be using the function address to call them, you may be asking why , this is a means of stealth for the malware this technique is known as “dynamic resolution of NTAPI functions”. But this is not a fool proof method mature EDR solutions will definitely detect this , this demo is just for learning.
The end goal is to inject shellcode and execute it in a running process.
The base code we will be stating with is » here
The code above uses OpenProcess, WriteProcessMemory , CreateRemoteThreadEx, VirtualAllocEx.
To start us off ill create a function to get the function addresses from ntdll.dll,
|
|
These 2 function GetModuleHandleW and GetProcAddress are the only function we will use pinvoke to call from kernel32.dll
Read more about pinvoke » here
Heres a function , the argument need is the function name and it will return the functionaddress
|
|
Delegates
A C# delegate is a type-safe representation of a method or function signature. In normal C# development, delegates are commonly used to reference methods that have a particular parameter list and return type.
For example:
|
|
This defines the shape of a function:
-
It returns an
int -
It accepts two
intparameters
In this demonstration, the delegate allows a native function pointer obtained at runtime to be represented as a callable C# method.
When GetProcAddress returns an address, .NET knows only that it has received a pointer-sized value. It does not automatically know what parameters the function expects or what it returns.
We therefore define a delegate whose signature matches the native function:
|
|
We can then convert the native function address into an instance of that delegate:
|
|
Conceptually, the process is:
Get the DLL - locate the exported function - obtain its address - describe its ABI using a delegate - convert the address into a callable method.
Since windows Native api is undocumented by microslop inorder to be able to know the ABI for the function we can use these documentation created by other security researchers » here
This can be seen in the image below.

The documenation above is written for C so we will need to translate that to C#, i have a hard time doing this since i’m no really good in both languages so i promted AI to create for me this cheat sheet » (see appendix) so that i can understand how to map each data type to csharp equivalent.
Here are a the ABI/Structures of the functions we need.
|
|
Structs
The next problem is that some native functions don`t accept plain arguments. They also expect structures for these arguments.
For example, NtOpenProcess expects an OBJECT_ATTRIBUTES structure and a CLIENT_ID structure.
In C, the operating system defines the layout of these structures. When calling the function from C#, our structure needs to represent that layout correctly.
|
|
StructLayout(LayoutKind.Sequential) tells .NET to lay the fields out sequentially in memory in the order in which they are declared.
For example we only need the following structs for arguments for NtOpenProcess . The argument structs don’t need to be created if the argument will be null or wont be used. Since these will not be null , we need to define them.
|
|
Methods
After we map out the Delegates and Structs we can proceed to get the function addresses in the main function . We will use Marshal.GetDelegateForFunctionPointer to be able to use the raw pointer values returned by my GetProc value we defined above as callable methods.
|
|
We will also need to initialize the structs we defined above and mapping values we will be supplying , in this case “pid” which is the process id of my target process in this case i used notepad , the pid in my case will be supplied as an argument when im running this program.
|
|
Lastly we can call the functions with the necessary parameters , one thing to note , these functions return a int 0 NTSTATUS value if they were successfully called else they return a non-zero value, this logic can be used when error handling , due to this you have to ensure that the ABI/Structure mapping , data types of arguments , number of arguments and arrangement of arguments are correct since debugging wont be easy.
Here’s the code of where the functions are called
|
|
Result
Adding all these up and compiling the program and running it will inject shellcode to the target process which is notepad , the shellcode i used will pop calc.exe , but that shellcode can be anything including c2 beacons , meterpreter shellcode , reverseshell etc
The difficult part was reproducing the native functions ABI interface correctly in C#.
I had to understand:
- how native C types map to C# types
- how pointer levels translate into
IntPtr,ref, andout - how delegates describe native function signatures
- how
Marshal.GetDelegateForFunctionPointerturns that pointer into a callable method

I will probably proceed to a more advanced technique in the next one.
The end.
Disclaimer
This is for educational purposes