The windows Native API (Ntdll.dll) is a user-mode library. However, it is the final user-mode library that interfaces with the kernel. How does it actually interface with the kernel and bridge the gap from user-mode into kernel-mode? For example, if I write a standard usermode application, I cannot directly call ntoskrnl.exe functions. Does Ntdll.dll in fact directly call ntoskrnl.exe functions? If so, how does it have permissions to do so being a user-mode library?
-
Can any application make such a call to invoke SSDT funcs or just Ntdll?– the_endianAug 12, 2017 at 6:20
-
@blabb I don't view it as the answer to this question. I viewed your other answer and while it is related, there is no mention of int 2e or sysenter/syscall in it. This is fine as they are 2 separate questions and your answer was for the other one. However, you did answer this one in the comments. If you want to elaborate as an answer, feel free!– the_endianAug 13, 2017 at 3:36
1 Answer
ntdll.dll
is not privileged in any sense and can't do anything that your user mode library can't. So no, it doesn't have "permission" do directly call kernel-mode code and it doesn't do that.
What it does is pass through one special interface the kernel provides in order to receive user mode calls. Depending on the version and on the CPU, the ntdll
functions load a couple of registers with (a) a number representing the id of the requested operation, and (b) the address of the stack, containing arguments for this operation. The it does something special (see below) that causes the CPU to transition into kernel-mode, and start executing the entry point for system calls.
This code is basically a switch statement on the index, which calls the correct appropriate kernel-mode function (and copies arguments).
The "something special" ntdll
does is either invoke a software interrupt (int 0x2e
) or, in modern versions of Windows, use a specialized instruction for system calls (syscall
or sysenter
). What both these things do is transition into ring 0 and start executing a given address from a given stack. The difference is that dispatching the interrupt requires consulting a table stored in memory (the IDT), while the specialized instructions don't.
That's where the magic happens. Nothing really prevents you from preparing the right arguments on the stack, loading eax and edx with the right values and performing an int 2e
(or the appropriate stuff for x64).