The Big Picture: The Kitchen Window
In the restaurant picture from Section 1, the waiter is Win32. He speaks the customer's language and writes down orders neatly. Behind him is a kitchen window. The slips pass through it in a special format. Only the staff use it. The customers are not told how it works, and the restaurant may rebuild the kitchen without telling anyone.
That window is the Native API. It sits at the very bottom of the user-mode part of Windows. Every request, however it started, must pass through it on the way to the kernel.
3.1 Nt* and Zw* Functions in ntdll.dll
What it is: The Native API is the set of functions in ntdll.dll. Their names start with Nt (and often also Zw). The "NT" stands for New Technology. It is the name of the family of Windows that all modern versions (10, 11) belong to.
| Win32 function | Native API function it ends up using | Job |
|---|---|---|
CreateFileW | NtCreateFile (or NtOpenFile) | Open or create a file |
ReadFile | NtReadFile | Read data |
WriteFile | NtWriteFile | Write data |
CloseHandle | NtClose | Release a handle |
VirtualAlloc | NtAllocateVirtualMemory | Get memory |
CreateProcessW | NtCreateUserProcess | Start a program |
RegOpenKeyExW | NtOpenKey | Open a registry key |
Notice how the Native names are shorter and more "mechanical". They follow the pattern Nt + Verb + Object: NtReadFile, NtClose, NtOpenKey.
What an Nt function does in user mode: almost nothing. Each one is a tiny stub (a very short piece of code). It does three things:
- Puts a service number into a CPU register. The number says which kernel service is wanted.
- Runs the
syscallinstruction (Section 4). - Returns the result when the kernel is done.
NtCreateFile: (the idea, not exact machine code)
put service number into a register
execute "syscall" <-- crosses into kernel mode
return
The service number is different on different Windows versions. That fact is a big reason the Native API is not a safe place to build programs on (see 3.3).
What about Zw? Both NtCreateFile and ZwCreateFile exist. In user mode (your programs) they are essentially the same thing. The difference only matters inside the kernel, where the two names handle security checks differently. As a beginner you can remember:
| Name | In user mode | Inside the kernel |
|---|---|---|
Nt... | Enters the kernel via syscall | Direct call; may check that the caller is allowed |
Zw... | Same stub as Nt... | Makes the call act as if it came from trusted kernel code |
Keep it simple: in your programs, the two names do the same job. The deeper difference is for driver writers.
3.2 The Undocumented and Internal Nature
What it is: Microsoft publishes the Win32 API in full. The Native API is a different story:
| Function group | Documentation status |
|---|---|
| A few Native functions | Documented, often with a warning that they may change |
| Most Native functions | Little or no official documentation. People learn them from experience, debugging and unofficial references |
| The system call numbers | Not documented at all, and they change between Windows versions |
"Undocumented" does not mean "secret" or "illegal". It means Microsoft has not promised to keep these functions the same. You can still see their names in ntdll.dll with common tools.
A wise way to say it: Native API functions are an internal detail of Windows, like the wiring inside a wall. The light switch (Win32) is for you. The wiring is for the electrician.
What a Native function looks like:
| Typical feature | Meaning |
|---|---|
| Returns an NTSTATUS | A 32-bit result code (see 3.4) |
| Uses handles | Tickets for objects (Chapter 9) |
| Uses UNICODE_STRING | A text structure with its own length, not the usual end marker. It holds Unicode text |
| Uses OBJECT_ATTRIBUTES | A structure that describes the object name and security |
| Takes many parameters | Often 8 to 11 inputs for one call |
Look at how much more the Native function wants:
Win32 : CreateFileW( name, access, share, security, create, flags, template )
Native : NtCreateFile( &handle, access, &object_attributes, &io_status,
&size, attributes, share, disposition, options,
ea_buffer, ea_length )
You do not need to memorise these. The point is that the Native layer is more detailed and fussier, and Win32 hides that for you.
3.3 Why Win32 Wraps the Native API Instead of Exposing It
What it is: Win32 does not replace the Native API. It wraps it, like putting a gift in a nice box. Win32 sits on top, tidies up the inputs, then calls Native.
There are five reasons Windows is built this way:
1. Stability. The Native API and system call numbers can change from one Windows version to the next. If every program used them directly, every Windows update could break programs. Win32 absorbs the changes. Programs call the same CreateFileW year after year while the Native layer below adapts.
2. Simplicity. CreateFileW has friendly parameters and sensible defaults. Win32 turns them into the longer Native request.
3. Convenience and checking. Win32 converts plain file paths such as C:\notes.txt into the NT style (\??\C:\notes.txt) that the kernel understands. It also converts ANSI text to Unicode and turns NTSTATUS codes into Win32 errors.
4. Many "personalities". Windows was designed so it could run several kinds of programs on one kernel. The Native API is the neutral base. Win32 is one friendly face (the "subsystem") built on it. Others once existed.
5. Safety. Using only the official layer means a program stays within the rules Microsoft tests heavily.
Win32 (stable, friendly, documented)
│ wraps and translates
▼
Native API (precise, fussy, may change)
│
▼
Kernel
Rule of thumb for programmers: use Win32 (or a language that does). Calling
ntdll.dllyourself is only for special cases, and Microsoft warns that those functions may change. Security tools and some research tools do look at this layer, but it is not where normal programs belong.
Seeing a Native name in the wild. You can see these names on your own PC. In PowerShell:
(Get-Item C:\Windows\System32\ntdll.dll).VersionInfo.FileVersionThis prints the version of ntdll.dll. On Windows 10 and 11 it is tied to the Windows build number. Because the file changes with every update, programs must not rely on what is inside it.
3.4 Native Errors: NTSTATUS and How They Become Win32 Errors
What it is: The Native API reports results with an NTSTATUS. It is a 32-bit number. Its top bits say what kind of result it is.
| Top bits | Meaning | Example |
|---|---|---|
0x0... | Success | STATUS_SUCCESS = 0x00000000 |
0x4... | Information | (rare) |
0x8... | Warning | STATUS_BUFFER_OVERFLOW |
0xC... | Error | STATUS_ACCESS_DENIED = 0xC0000022 |
(0x simply means "the number after this is written in hexadecimal", base 16.)
When Win32 gets an NTSTATUS from ntdll.dll, it translates it into a Win32 error code (and stores it for GetLastError()):
| NTSTATUS (Native) | Win32 error code |
|---|---|
STATUS_SUCCESS (0x00000000) | 0 (ERROR_SUCCESS) |
STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) | 2 (ERROR_FILE_NOT_FOUND) |
STATUS_ACCESS_DENIED (0xC0000022) | 5 (ERROR_ACCESS_DENIED) |
STATUS_INVALID_PARAMETER (0xC000000D) | 87 (ERROR_INVALID_PARAMETER) |
So the same failure looks different depending on the layer you watch:
Kernel says: 0xC0000022 (STATUS_ACCESS_DENIED)
Native API sees: 0xC0000022
Win32 shows: error 5 (ERROR_ACCESS_DENIED)
Many tools, including Process Monitor (Section 6), show the NTSTATUS-style names, such as NAME NOT FOUND and ACCESS DENIED. Blue Screen codes and many crash messages are also NTSTATUS values. A few you may know already: 0xC0000005 is an access violation, which means a program touched memory it should not.
net helpmsgtakes Win32 numbers (for example 5), not NTSTATUS numbers.
3.5 Seeing the Native Layer: Safe Looks
You can look at ntdll.dll without changing anything.
dir C:\Windows\System32\ntdll.dll
You will see a file of around 2 MB (the exact size depends on your Windows build). It is small. It is a thin layer mostly made of the tiny stubs described in 3.1, plus helper code such as the loader that loads DLLs for every new program (Chapter 22).
Other safe facts:
| Fact | How you can see it |
|---|---|
ntdll.dll is in every user-mode process | Run tasklist /m ntdll.dll in Command Prompt (Task Manager itself does not list modules) |
| It is the first DLL every program gets | The loader starts there, before kernel32.dll |
| It exists in both 32-bit and 64-bit forms on 64-bit Windows | System32 has the 64-bit one, SysWOW64 has the 32-bit one |
Try tasklist /m ntdll.dll. The list will be very long. That is the point: everything uses it.
Summary
| Term | Plain meaning |
|---|---|
| Native API | The Nt and Zw functions in ntdll.dll |
| Stub | A tiny function that loads a service number and runs syscall |
| Nt vs Zw | The same in user mode; different only inside the kernel |
| Undocumented | Microsoft does not promise it will stay the same |
| Why wrap it | Win32 gives stability, simplicity and translation |
| NTSTATUS | A 32-bit result code from the Native layer; 0xC... means error |
| Win32 error | A small number from GetLastError, translated from NTSTATUS |
| ntdll.dll | The lowest user-mode DLL, present in every process |