Tutorial [Theory] Native Windows API (NTAPI)

Hello and welcome to my first thread on Programming with C++. :)

[DISCLAIMER]
The code presented at the bottom of this thread is NOT dangerous if used correctly, of course if you are stupid with it then it can cause problems… E.g. if you use the last code part to force BSoD your system then it can cause data loss/corruption (since this is what BSOD crashes can do), or if you use the NtTerminateProcess code to terminate a critical windows process then a BSOD crash can occur… Please use it responsibly and for good purposes.

If you don’t know what you’re doing but are a programmer and want to learn then just stick to using a Virtual Machine so you avoid the possibility of messing stuff up.

I have intentionally set the font size to 3 because this thread is very long and with the normal font size it may actually be even more of a pain to go through... I highly recommend you zoom in once in your browser, and it will appear much nicer to read the text (as opposed to reading it without zooming in once or as it is by default).

If you find any false information/mistakes in this thread then please let me know so I can fix them.
[/DISCLAIMER]

In this thread we will be learning about the Native API (NTAPI) and how we can go about using NTAPI functions from within C++. Throughout this tutorial, I will be working with Visual Studio 2015 Community Edition with an Empty project template (C++). This thread will have multiple parts to it: the first part will evolve around theory and then the second part will be source code to using some NTAPI functions which have been commented.


Part 1 - Theory
In Windows we have 4 different rings: ring 0; ring 1; ring 2; and ring 3.

Ring 0 is the lowest ring available and the purpose of this ring is to hold the kernel (of the Operating System) in memory. Whereas, the third ring (ring 3) is used for user-mode on our systems (e.g. Windows kernel will be executed from within Ring 0, however our standard programs will be executing from within Ring 3). The reason we have different rings is for the protection of our system (memory, ports, and the safety of other resources in general), since it restricts what our standard programs can do on the system, but grants kernel-mode the highest privileges which it needs to control and operate the system correctly. The kernel must be executed from ring 0 because it needs to be able to access our hardware, it’s a layer to the hardware. However, our standard programs do not need such privileges and giving it to them can cause big problems, they are restricted by being executed in ring 3 which does not have access directly to hardware… Not to mention the fact that if Ring 3 software (user-mode software) was running from ring 0 then it can cause conflicts with the kernel.

Ring 0 needs to be protected because if a crash occurs it will result in a crash of the entire system and the reason for this is down to the kernel being executed there in memory. Thankfully, when a standard program crashes in user-mode (ring 3), our systems don’t need to be completely restarted and this is down to memory management – each piece of software executing from ring 3 has its own memory allocated to it, therefore when something goes wrong (the memory for that software messes up) only that memory is affected, thus only that program crashes. In ring 0 it’s a different story, you have access to all the memory on the system, complete control… Therefore, a crash will affect the entire system, and all the memory. Ring 0 is essentially an abstract layer to access the hardware, and ring 3 is essentially the place for


In Windows there is something called the NTAPI (Native API) and it’s basically an Application Programming Interface which is more lower-level than the Win32 which I’m sure we are all familiar with. The way it works is when you call specific Win32 API functions, it becomes passed down and traces back to the Native API equivalent of the function, and then this proceeds by transitioning over to kernel-mode (ring 0).

There is a lot of misinformation about ring 1 and 2 and honestly I used to be wrong about it as well due to the misinformation I had read previously online whilst learning a few years back. The purposes of ring 1 and 2 is actually for device drivers, they do not actually become loaded in ring 0 itself. The reason for ring 1 and 2 being used for device drivers is simply down to the fact that they may need to access microcontrollers and the such of hardware, and if they were loaded from ring 3 then they wouldn’t have the guaranteed control to do what they really need to do. However, device drivers in ring 1 and 2 have the most control next to the kernel in ring 0. Device drivers stay in ring 1 and 2 to grant them the privileges they require without giving them too much freedom and control which they don’t really need, and if they don’t need the same control as ring 0, then putting them in ring 0 can just cause problems. Ring 0 is for the kernel only, device drivers are to work from ring 1 and 2 (and work closely with the kernel of course), and then ring 3 is for user-mode.

Now we’ve covered some basic theory about the OS rings and their purposes we can start to talk about the Native Windows API (NTAPI) and the Windows API (Win32 API).

Within the Windows Kernel we have something known as the Native API (NTAPI) and this is essentially a bunch of functions used within the Windows Kernel and the software running on our systems. As an alternate to using the NTAPI, we have the Win32 API, which is more tuned for less sophisticated software. In fact, Microsoft recommend you use the Win32 API as opposed to directly using the NTAPI because the Win32 is much more stable and tuned for general usage ad using the NTAPI can cause unexpected results and instability. Regardless of the NTAPI not being such a good idea to use for everything, it has some very nice uses when it comes to security (and generally speaking, for security software) since it’s a bit lower-level compared to the Win32 API.

When we use the Win32 API and call specific functions, these can trace back to the NTAPI equivalent of the function. To explain this a bit better, let’s take a look at the Win32 API function called TerminateProcess, which is found in the module Kernel32.dll. Below I will post the function syntax (also available at MSDN from the above link):

Code:
BOOL WINAPI TerminateProcess(
  _In_ HANDLE hProcess,
  _In_ UINT   uExitCode
);

First off, we notice that the function type is of a Boolean (BOOL), which means the function will return TRUE or FALSE (based on the function success).

After this we have a greeting of WINAPI (but we already know this) and then the function name, along with the parameters that the function takes in (a HANDLE to the target process we want to terminate and then an exit code of a type UINT (Unsigned Integer)).

This is actually the exact same function that Task Manager calls from the Details tab when you try and terminate a function - *just a quick fact I should point out.

Anyway, moving on, this function will eventually land at the stub of the Native API equivalent of the function, which is Nt*/ZwTerminateProcess. (a stub is basically a part of code, in this case the function of ZwTerminateProcess, located within NTDLL.DLL).

Once the function call has traced back down to the NTAPI function equivalent it will end up transitioning the function call into kernel-mode (ring 0) and the work will be finished there.

In kernel-mode we have something known as the SSDT (System Service Dispatch/Descriptor Table) and this is basically a huge table containing the Kernel Functions and the addresses to where the functions reside in memory, and this table is exported by ntoskrnl.exe (KeDescriptorTable - on x86 systems only, it’s not exported on x64 systems due to PatchGuard/Kernel Patch Protection purposes). When you call a function it will go through the SSDT I believe.

As another example, let’s look at the function OpenProcess (Kernel32.dll once again, Win32 API function):
Code:
HANDLE WINAPI OpenProcess(
  _In_ DWORD dwDesiredAccess,
  _In_ BOOL  bInheritHandle,
  _In_ DWORD dwProcessId
);

This function will end up at its NTAPI equivalent which is Nt*/ZwOpenProcess:
Code:
NTSTATUS ZwOpenProcess(
  _Out_    PHANDLE            ProcessHandle,
  _In_     ACCESS_MASK        DesiredAccess,
  _In_     POBJECT_ATTRIBUTES ObjectAttributes,
  _In_opt_ PCLIENT_ID         ClientId
);

As we can see, NTAPI syntax is now being used since it’s a Native API function. Instead of getting the WINAPI and BOOL/Win32 data structures, we’re getting the NTAPI ones.

All Native API functions will return an NTSTATUS value – there are so many that I cannot list them, but to list the most common ones: STATUS_SUCCESS (0x00000000); STATUS_UNSUCCESSFUL (0xC0000001); and STATUS_ACCESS_DENIED (0xC0000022).

I can think of a few reasons as to why using the NTAPI instead of just passing through the Win32 API can be useful, such as needing to use an NTAPI function without needing the use of the Win32 API function which may use other functions as well, or to bypass any Win32 hooks (but NTAPI function stubs can also be hooked anyway), so I recommend you just use the Win32 APIs unless you really need to use the NTAPI instead since it can cause instability depending on what you need to do.

For example, instead of using ZwSuspendThread or ZwSuspendProcess (both undocumented NTAPI functions), you can just use the Win32 API function SuspendThread (enumerate through all threads in the process and pass the thread handle to this function), which would potentially be much more stable depending on specific circumstances.


I believe that this is enough theory for the moment, now we will move onto Part 2.

Part 2 – using the NTAPI within C++
Firstly, I will be using Visual Studio 2015 (Community Edition) and I will create a new C++ project (Empty Project template) and add a new source file (main.cpp).

Below is the code for using NTAPI functions (check the comments in the code which are after the “//” for explanations).

NtTerminateProcess:
Code:
// This code works by obtaining the address of the NtTerminateProcess function, if it's valid then we open a handle to our target process with the Win32 API function OpenProcess (kernel32.dll) and then we call NtTerminatProcess. Afterwards we check the NTSTATUS result so we know if it was succesful or not.

// At the start we define the function structure with typedef for NtTerminateProcess so it knows the parameters we will be putting in.

// REMEMBER TO CHANGE THE PID IN THE OpenProcess function call
#include <iostream>
#include <Windows.h>
#include <winternl.h>
using namespace std;

// error codes for NtTerminateProcess
#define STATUS_SUCCESS 0x00000000
#define STATUS_ACCESS_DENIED 0xC0000022
#define STATUS_INVALID_HANDLE 0xC0000008
#define STATUS_OBJECT_TYPE_MISMATCH 0xC0000024
#define STATUS_PROCESS_IS_TERMINATING 0xC000010A

typedef NTSTATUS(NTAPI *pdef_NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);

int main()
{
       cout << "Attempting to obtain the address of NtTerminateProcess" << endl;

       // get the address of NtTerminateProcess from ntdll
       LPVOID lpFunctionAddress = GetProcAddress(LoadLibraryA("ntdll.dll"), "NtTerminateProcess");

       if (lpFunctionAddress == 0) // if the address is NULL (0) then we cannot use it
       {
              cout << "Error: could not obtain the address of NtTerminateProcess!" << endl;
              return 0; // exit the program
       }
       cout << "Attempting to obtain a handle to the target process" << endl;
       HANDLE ProcessHandle = OpenProcess(PROCESS_TERMINATE, FALSE, 13408); // open a handle to the process with the PID we passed in (13952)
       if (ProcessHandle == 0) // compare if the handle is 0, if it is then we cannot use it (fail)
       {
              cout << "Error: could not obtain a handle to the target process!" << endl;
              return 0; // exit the program
       }
       pdef_NtTerminateProcess NtCall = (pdef_NtTerminateProcess)lpFunctionAddress; // set structure and to the address
       NTSTATUS NtRet = NtCall(ProcessHandle, 0); // call the function and pass in the handle we created earlier, the 0 is for the ExitStatus however this can stay as 0
 
if (NtRet == STATUS_SUCCESS) // check if the termination was a success
       {
              cout << "The process was successfully terminated from memory!" << endl;
       }
       else if (NtRet == STATUS_ACCESS_DENIED) // access denied
       {
              cout << "The process could not be terminated: Access Denied" << endl;
       }
       else if (NtRet == STATUS_INVALID_HANDLE) // incorrect handle
       {
              cout << "The process could not be terminated: invalid handle was passed as ProcessHandle parameter" << endl;
       }
       else if (NtRet == STATUS_OBJECT_TYPE_MISMATCH) // object mismatch
       {
              cout << "The process could not be terminated: object type mismatch" << endl;
       }
       else if (NtRet == STATUS_PROCESS_IS_TERMINATING) // process is already terminating
       {
              cout << "The process could not be terminated: the process is already terminating" << endl;
       }
       else // termination failed for another reason
       {
              // print out the error code with GetLastError() so we can look up the error code and find details on why termination failed
              cout << "The process could not be terminated: error code " << GetLastError() << endl;
       }
       getchar(); // wait for us to respond before it exits program
       return 0; // execution success
}


NtRaiseHardError/RtlAdjustPrivilege:
Code:
#include <iostream> // Input and Output stream
#include <Windows.h> // Win32 API
#include <winternl.h> // this includes NTAPI data structures and other information

using namespace std;

#define SeShutdownPrivilege 19 // we need SeShutdownPrvilege to cause the BSoD

// First off we will define the structure of the functions we will be using (NtRaiseHardError + RtlAdjustPrivilege).
// We will be using RtlAdjustPrivilege to enable the SeShutdownPrvilege so we have permission to shutdown the system, and then we will use NtRaiseHardError to force BSOD the system via NTAPI.
// After we have defined our functions we will use the main() function which is called by the CRT Startup function to get the addresses of the functions we will be using and then we will check if the addresses are valid or not (if !=0 then it is fine).
// Then we call the functions.

// function definitions
typedef NTSTATUS(NTAPI *pdef_NtRaiseHardError)(NTSTATUS ErrorStatus, ULONG NumberOfParameters, ULONG UnicodeStringParameterMask OPTIONAL, PULONG_PTR Parameters, ULONG ResponseOption, PULONG Response);

typedef NTSTATUS(NTAPI *pdef_RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);

int main()

{
       // get the address of RtlAdjustPrivilege
       LPVOID lpFuncAddress = GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlAdjustPrivilege");
       if (!lpFuncAddress)
       {
              cout << "Could not get the address of RtlAdjustPrivilege" << endl;
              getchar();
              return 0;
       }

       BOOLEAN bEnabled;
       pdef_RtlAdjustPrivilege NtCall = (pdef_RtlAdjustPrivilege)lpFuncAddress;
       NTSTATUS NtRet = NtCall(SeShutdownPrivilege, TRUE, FALSE, &bEnabled); // Enable SeShutdownPrivilege via RtlAdjustPrivilege

       // here you can check if NtRet == 0x00000000 (STATUS_SUCCESS) if you want
       LPVOID lpFuncAddress2 = GetProcAddress(GetModuleHandle("ntdll.dll"), "NtRaiseHardError"); // GetModuleHandle as ntdll.dll was loaded earlier with first GetProcAddress function use
       if (!lpFuncAddress2)
       {
              cout << "Could not get the address of NtRaiseHardError" << endl;
              getchar();
              return 0;
       }

       ULONG uResp;
       pdef_NtRaiseHardError NtCall2 = (pdef_NtRaiseHardError)lpFuncAddress2;
       NTSTATUS NtRet2 = NtCall2(STATUS_ASSERTION_FAILURE, 0, 0, 0, 6, &uResp); // cause the BSoD crash
       if (!NtRet2)
       {
              cout << "The force BSoD crash failed" << endl;
              getchar();
       }
       return 0;
}

// THE ABOVE CODE WILL FORCE BSOD YOUR SYSTEM.. USE VM PLEASE!

If you want code for a specific function like ZwSuspendProcess (which is actually an undocumented function) then feel free to let me know and I’ll PM it to you, but only if you have over 300+ spam-free posts.


Damn I've just read through my post and honestly I am disappointed because it could be so much better, hopefully it's enough for someone to learn some things though.

Hope this helped.
Wave. :)
 
Last edited by a moderator:

ttyssh

New Member
Aug 3, 2018
2
Hi,
I have a few questions.
So if i understand correctly:
Example:
OpenProcess is a function Win32 API (wrapper?) that calls ntdll.dll (NATIVE API) which in turn calls the corresponding function in the kernel right? what function does it call in the kernel?
the prefix Nt (Example: NtOpenProcess) and Zw (Example: ZwOpenProcess) are aliases?
NtOpenProcess / ZwOpenProcess is just usermode?
what function need to use in the kernel?

PS: sry for bad english >.>
 

maka

Level 1
Jul 1, 2018
22
Hi
OpenProcess is a function Win32 API (wrapper?) that calls ntdll.dll (NATIVE API) which in turn calls the corresponding function in the kernel right?
As far as I know, you're right. If you open kernel32.dll on W10 with IDA (which has a free version, IDA Free) you'll see this:
openprocess-kernel32.png


OpenProcess from kernel32.dll will call OpenProcess from KernelBase.dll (on W10):
openprocess-KernelBase.png

OpenProcess (kernel32.dll) => OpenProcess (KernelBase.dll) => NtOpenProcess (ntdll.dll)


the prefix Nt (Example: NtOpenProcess) and Zw (Example: ZwOpenProcess) are aliases?
Yes, NtOpenProcess and ZwOpenProcess in usermode are the same:
nt-zw-Openprocess.png



what function does it call in the kernel?
if I'm not mistaken, when you call NtOpenProcess/ZwOpenprocess from usermode it will call the real NtOpenProcess from ntoskrnl.exe and if you call ZwOpenProcess from kernel mode it will call ZwOpenProcess (ntoskrnl.exe).

what function need to use in the kernel?
In kernel mode use ZwOpenProcess

sry for bad english
Welcome to the club :)

It's a pity that users like Wave and Opcode are no longer in the forum because they have a great knowledge about reversing and Windows Internals. I hope that one day they will return.
PS: I may be wrong in some of my affirmations. If someone find false information in my post, please let me know.
 

Attachments

  • openprocess-KernelBase.png
    openprocess-KernelBase.png
    46.4 KB · Views: 582
Last edited:
  • Like
Reactions: ttyssh

Andy Ful

Level 68
Verified
Trusted
Content Creator
Dec 23, 2014
5,780
Hi

As far as I know, you're right. If you open kernel32.dll on W10 with IDA (which has a free version, IDA Free) you'll see this:
View attachment 194398

OpenProcess from kernel32.dll will call OpenProcess from KernelBase.dll (on W10):
View attachment 194400
OpenProcess (kernel32.dll) => OpenProcess (KernelBase.dll) => NtOpenProcess (ntdll.dll)



Yes, NtOpenProcess and ZwOpenProcess in usermode are the same:View attachment 194401



if I'm not mistaken, when you call NtOpenProcess/ZwOpenprocess from usermode it will call the real NtOpenProcess from ntoskrnl.exe and if you call ZwOpenProcess from kernel mode it will call ZwOpenProcess (ntoskrnl.exe).


In kernel mode use ZwOpenProcess


Welcome to the club :)

It's a pity that users like Wave and Opcode are no longer in the forum because they have a great knowledge about reversing and Windows Internals. I hope that one day they will return.
PS: I may be wrong in some of my affirmations. If someone find false information in my post, please let me know.
kram7750, Wave, and Opcode (Deleted member 65228), there are the names of the same smart & knowledgeable guy. There are some very interesting tutorials posted by him over some years. I also hope to see him again here on MalwareTips. You both seem to have the similar interest in programming, so it would be interesting to see your discussions.
 

maka

Level 1
Jul 1, 2018
22
kram7750, Wave, and Opcode (Deleted member 65228), there are the names of the same smart & knowledgeable guy. There are some very interesting tutorials posted by him over some years.
Honestly, I suspected that they were the same user due to some similarities in their posts. Now I have no doubt.
I respect this user a lot and I consider that his tutorials are a very valuable resource.

You both seem to have the similar interest in programming, so it would be interesting to see your discussions.
Thanks for your words :)
Yes sure we have similar interest in programming/reversing/Windows internals but, being humble, this guy is on another level.

Regards! (y)
 

ttyssh

New Member
Aug 3, 2018
2
ohh thanks for the help.

Im trying to learn how to program drivers in kernel mode,
Can i use all the functions of ntdll.dll in the driver (kernel mode)? or only functions of ntoskrnl?

EDIT:
Why NtOpenProcess don't have arguments? There are many function without arguments..
OpenProcess.png
 
Last edited:

thepown3der

New Member
Apr 21, 2021
2
Nice topic ...
Win32 api for software programing is better than Ntapi
but Ntapi for security however evading security or defeting it's more than better
 
Top