System Calls in Windows & Native API?
Asked Answered
I

5

31

Recently I've been using lot of assembly language in *NIX operating systems. I was wondering about the Windows domain.


Calling convention in Linux:

mov $SYS_Call_NUM, %eax
mov $param1 , %ebx
mov $param2 , %ecx
int $0x80

Thats it. That is how we should make a system call in Linux.

Reference of all system calls in Linux:

Regarding which $SYS_Call_NUM & which parameters we can use this reference : http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html

OFFICIAL Reference : http://kernel.org/doc/man-pages/online/dir_section_2.html


Calling convention in Windows:

???

Reference of all system calls in Windows:

???

Unofficial : http://www.metasploit.com/users/opcode/syscalls.html , but how do I use these in assembly unless I know the calling convention.

OFFICIAL : ???

  • If you say, they didn't documented it. Then how is one going to write libc for windows without knowing system calls? How is one gonna do Windows Assembly programming? Atleast in the driver programming one needs to know these. right?

Now, whats up with the so called Native API? Is Native API & System calls for windows both are different terms referring to same thing? In order to confirm I compared these from two UNOFFICIAL Sources

System Calls: http://www.metasploit.com/users/opcode/syscalls.html

Native API: http://undocumented.ntinternals.net/aindex.html

My observations:

  1. All system calls are beginning with letters Nt where as Native API is consisting of lot of functions which are not beginning with letters Nt.
  2. System Call of windows are subset of Native API. System calls are just part of Native API.

Can any one confirm this and explain.

EDIT:

There was another answer. It was a 2nd answer. I really liked it but I don't know why answerer has deleted it. I request him to repost his answer.

Indecorous answered 22/3, 2010 at 3:31 Comment(4)
Also read this: #919551Indecorous
Your metasploit links are broken...Dinsmore
"how is one going to write libc for windows" - By using the documented API. Linux doesn't have an API, and syscalls are the closest thing, so that's what you need to use on Linux. Not because that's a terribly great way of interacting with the OS, but because it's the only way there is.Deuteranope
how is one going to write libc for windows - by calling the WinAPI functions. How do you implement those yourself? By working at Microsoft and consulting their internal documentation and code with call-numbers. For anyone else, reverse-engineering MS's existing libraries if you really want to make code that will break on future Windows versions when they change the private interface between their DLLs and their kernel.Audraaudras
S
41

If you're doing assembly programming under Windows you don't do manual syscalls. You use NTDLL and the Native API to do that for you.

The Native API is simply a wrapper around the kernelmode side of things. All it does is perform a syscall for the correct API.

You should NEVER need to manually syscall so your entire question is redundant.

Linux syscall codes do not change, Windows's do, that's why you need to work through an extra abstraction layer (aka NTDLL).

EDIT:

Also, even if you're working at the assembly level, you still have full access to the Win32 API, there's no reason to be using the NT API to begin with! Imports, exports, etc all work just fine in assembly programs.

EDIT2:

If you REALLY want to do manual syscalls, you're going to need to reverse NTDLL for each relevant Windows version, add version detection (via the PEB), and perform a syscall lookup for each call.

However, that would be silly. NTDLL is there for a reason.

People have already done the reverse-engineering part: see https://j00ru.vexillium.org/syscalls/nt/64/ for a table of system-call numbers for each Windows kernel. (Note that the later rows do change even between versions of Windows 10.) Again, this is a bad idea outside of personal-use-only experiments on your own machine to learn more about asm and/or Windows internals. Don't inline system calls into code that you distribute to anyone else.

Schaab answered 22/3, 2010 at 4:7 Comment(13)
+1 & thank you. Can you kindly show some example code demonstrating how to use WIN32 API & NT API from Assembly language.Indecorous
What assembler are you using? If you're using MASM for example the easiest way would be to just use 'INVOKE'. The first Google result I found that outlined it is: movsd.com/masm.htmSchaab
A short & concise article that is a great supplement to this answer: codeproject.com/KB/system/Win32.aspxIndecorous
That article is not what you're looking for. It shows how to perform manual syscalls, which you should not be doing because the numbers change across Windows versions and service packs. As I've already stated, you should be using the Windows API to perform the dispatching for you. NTDLL is mapped into all processes automatically. So even if for some reason you can't use imports (I can't think of any reasonable times where this would be the case) you can still get a handle to NTDLL and enumerate the EAT manually to get the necessary function pointers (though you should never have to).Schaab
Of course not. I was just trying to make it clear that the particular information you were referring to is not relevant to your question because you don't need to know how the system dispatch process works in order to make system calls in Windows. That's why the high-level Win32 API (or heck, even the NT API) is there, to abstract away such irrelevant details.Schaab
@Indecorous The article is a very interesting read, thanks a lotHeterotaxis
This isn't really directed at the author of this answer specifically, but I don't understand why these "don't do this" answers are so prevalent on SO, isn't this supposed to be a site "for professional and enthusiast programmers"? Why on one hand does this site encourage extreme research effort but then when you get to these low level curiosity questions I see a constant stream of "you don't need to know that!!!". Who is the real target audience for this site again? People who need their hands held or people who really are willing to dig deep?Appetite
@jrh: 100% agreed. Knowing how something works under the hood is interesting even if you're never going to write code that uses it directly. I edited this answer to add a link to j00ru.vexillium.org/syscalls/nt/64. I have no idea why people that answer Windows asm questions are so reluctant to even give pointers to info like that. All that's required is an appropriate caution that it's a bad idea to use it outside of personal experiments and learning exercises, just like a lot of silly computer tricks you can do with asm!Audraaudras
Here is the new location of the arcticle that was originally linked by claws on Mar 22'10 at 5:09: codeproject.com/Articles/33870/…Triaxial
@Appetite It's not about "you don't need to know that", it's about "you're asking the wrong question". In this case specifically, the asker is clearly expecting Windows to be similar to Linux, so this answer is perfect. There is zero practical reason anyone would ever actually want to do manual syscalls in Windows, so a response to the question "how do I do manual syscalls in Windows" of "you don't, you use the API instead" is entirely correct. I hardly consider it handholding to say "you can't do that without a hack, so do this correct way instead".Coffeecolored
@Coffeecolored - "There is zero practical reason anyone would ever actually want to do manual syscalls in Windows". There is one - writing a shellcode for WindowsTurney
@Turney Why would you need to bypass ntdll?Coffeecolored
@Coffeecolored at least because with syscalls you could use an interrupt and the number of syscall you want to use. with ntdll, you would need exact pointers to its functions, and getting them dynamically is tricky and makes shellcode very large (they could be hardcoded though, but minor change in system configs would make the shellcode stop working)Turney
D
10

The other thing you need to know about the windows syscall convention is that as I understand it the syscall tables are generated as part of the build process. This means that they can simply change - no one tracks them. If someone adds a new one at the top of the list, it doesn't matter. NTDLL still works, so everyone else who calls NTDLL still works.

Even the mechanism used to perform syscalls (which int, or sysenter) is not fixed in stone and has changed in the past, and I think that once upon a time the same version of windows used different DLLs which used different entry mechanisms depending on the CPU in the machine.

Diskin answered 30/4, 2010 at 19:28 Comment(2)
+1 because I found this very interesting. Do you have any resources (internet or books) where one can learn more about that kind of Win32 API / syscall internals?Wishbone
It is a bit out of date, but Inside Windows 2000 covers this I think. nynaeve.net/?p=48 has a nice discussion and also demonstrates that there are at last three syscall conventions in use on windows on x86 and x64. IA64 probably does something totally different again, and then of course there was Alpha, PowerPC, MIPS and probably others. Of course, usual caveat applies - all undocumented and likely to change.Diskin
D
4

I was interested in doing a windows API call in assembly with no imports (as an educational exercise), so I wrote the following FASM assembly to do what NtDll!NtCreateFile does. It's a rough demonstration on my 64-bit version of Windows (Win10 1803 Version 10.0.17134), and it crashes out after the call, but the return value of the syscall is zero so it is successful. Everything is set up per the Windows x64 calling convention, then the system call number is loaded into RAX, and then it's the syscall assembly instruction to run the call. My example creates the file c:\HelloWorldFile_FASM, so it has to be run "as administrator".

format PE64 GUI 4.0


entry start


section '.text' code readable executable


 start: 
 ;puting the first four parameters into the right registers

                            mov rcx, _Handle
                            mov rdx, [_access_mask]
                            mov r8, objectAttributes
                            mov r9, ioStatusBlock

 ;I think we need 1 stack word of padding:

                            push 0x0DF0AD8B


 ;pushing the other params in reverse order:

                            push [_eaLength]
                            push [_eaBuffer]
                            push [_createOptions]
                            push [_createDisposition]
                            push [_shareAcceses]
                            push [_fileAttributes]
                            push [_pLargeInterger]

 ;adding the shadow space (4x8)

 ;                               push 0x0
 ;                               push 0x0
 ;                               push 0x0
 ;                               push 0x0

 ;pushing the 4 register params into the shadow space for ease of debugging

                            push r9
                            push r8
                            push rdx
                            push rcx

 ;now pushing the return address to the stack:

                            push endOfProgram

                            mov r10, rcx ;copied from ntdll!NtCreateFile, not sure of the reason for this
                            mov eax, 0x55
                            syscall

 endOfProgram:
                            retn




 section '.data' data readable writeable

 ;parameters------------------------------------------------------------------------------------------------

 _Handle                         dq      0x0
 _access_mask                    dq      0x00000000c0100080
 _pObjectAttributes              dq      objectAttributes        ; at 00402058
 _pIoStatusBlock                 dq           ioStatusBlock
 _pLargeInterger                 dq      0x0
 _fileAttributes                 dq      0x0000000000000080
 _shareAcceses                   dq      0x0000000000000002
 _createDisposition              dq      0x0000000000000005
 _createOptions                  dq      0x0000000000000060
 _eaBuffer                       dq      0x0000000000000000       ; "optional" param
 _eaLength                       dq      0x0000000000000000

 ;----------------------------------------------------------------------------------------------------------


                            align   16
 objectAttributes:
 _oalength                       dq      0x30
 _rootDirectory                  dq      0x0
 _objectName                     dq           unicodeString
 _attributes                     dq      0x40
 _pSecurityDescriptor            dq      0x0
 _pSecurityQualityOfService      dq      securityQualityOfService


 unicodeString:
 _unicodeStringLength            dw      0x34
 _unicodeStringMaxumiumLength    dw      0x34, 0x0, 0x0
 _pUnicodeStringBuffer           dq      _unicodeStringBuffer


 _unicodeStringBuffer            du      '\??\c:\HelloWorldFile_FASM'       ; may need to "run as adinistrator" for the file create to work.



 ioStatusBlock:
 _status_pointer                 dq      0x0
 _information                    dq      0x0


 securityQualityOfService:
 _sqlength                       dd      0xC
 _impersonationLevel             dd      0x2
 _contextTrackingMode            db      0x1
 _effectiveOnly                  db      0x1, 0x0, 0x0

I used the documentation for Ntdll!NtCreateFile, and I also used the kernel debugger to look at and copy a lot of the params.

__kernel_entry NTSTATUS NtCreateFile(
  OUT PHANDLE                      FileHandle,
  IN ACCESS_MASK                   DesiredAccess,
  IN POBJECT_ATTRIBUTES            ObjectAttributes,
  OUT PIO_STATUS_BLOCK             IoStatusBlock,
  IN PLARGE_INTEGER AllocationSize OPTIONAL,
  IN ULONG                         FileAttributes,
  IN ULONG                         ShareAccess,
  IN ULONG                         CreateDisposition,
  IN ULONG                         CreateOptions,
  IN PVOID EaBuffer                OPTIONAL,
  IN ULONG                         EaLength
);
Deplorable answered 23/7, 2018 at 15:35 Comment(11)
mov r10, rcx is probably for the same reason as Linux: syscall itself clobbers rcx (and r11) before the kernel can see it, so the kernel syscall calling convention uses r10 instead of rcx. What version(s) of Windows does this code work on? The syscall ABI is not stable on Windows, so different parameters or even a code other than eax = 0x55 might be the right way to invoke it on other versions of Windows.Audraaudras
Thanks for the info about the mov r10, rcx line; I hadn't looked into that yet. My version of windows is 10.0.17134 Build 17134 .Deplorable
For details about syscall and RCX, see Why do x86-64 Linux system calls modify RCX, and what does the value mean? and Difference in ABI between x86_64 Linux functions and syscalls. And Why Assembly x86_64 syscall parameters are not in alphabetical order like i386 for an example of a wrapper function like Linux/glibc mmap that only has to mov r10,rcx)Audraaudras
Thanks. I've looked at the syscall documentation and I see that it saves the next address into RCX. I might improve my demonstration above because I noticed that after creating the file it crashes out with an access violation exception. I didn't want to do another call to properly end the process.Deplorable
@PeterCordes I'm not saying I recommend using syscall numbers directly (as they do change), but the syscall numbers are unofficially documented. Windows Version 10.0.17134 is Windows(1803) and the syscall for NtCreateFile is 0x55. All 5 released builds of Win 10 use 0x55 thus far. (Pre Win10 have used system call numbers other than 0x55)Wilderness
The mov r10,rcx is because the syscall interface passes the first parameter via r10 instead of RCX. A regular 64-bit function call uses RCX, RDX, R8, and R9. Syscall uses R10, RDX, R8, and R9. The reason for this is that the syscall instruction instruction overwrites RCX (it also overwrites R11).Microsoft chose to use R10 for the first parameter of the system call instead thus why you will see mov r10, rcxWilderness
The reason for the extra padding (ie push 0x0DF0AD8B) is to maintain stack alignment for performance reasons. You should add 8 bytes (a quadword) of padding IF there are an equivalent of an even number of pushes done before the syscall (otherwise you don't need the padding). This is because the stack is misaligned by 8 at the point start is executed because the return address was on the stack.Wilderness
As to why your code is failing, it is because you didn't clean up the stack after the syscall. You pushed the equivalent of 13 quadwords to set up the syscall you need to adjust the stack after the syscall to restore the stack pointer to its previous state before you do the ret. 13*8=104 bytes put on the stack prior to syscall. After the syscall you can simply do add rsp, 104 to clean up the stack.Wilderness
Because the first parameter to the syscall is in R10 instead of RCX you could replace mov rcx, _Handle with mov r10, _Handle and then you can remove the mov r10, rcx altogether. You'd also change push rcx to push r10Wilderness
A revised version of your code that should still work and take all this into account is here: capp-sysware.com/misc/stackoverflow/2489889/winsyscall.asmWilderness
Since you are forming a syscall directly (without parameters passed to start) the return address address doesn't have to be any specific value. syscall won't use it to return to (syscall will return to the instruction after syscall). So instead of push endOfProgram you can simply push any register (its value doesn't matter) like push rbp (or push rax register doesn't matter)Wilderness
P
1

Windows system calls are performed by calling into system DLLs such as kernel32.dll or gdi32.dll, which is done with ordinary subroutine calls. The mechanisms for trapping into the OS privileged layer is undocumented, but that is okay because DLLs like kernel32.dll do this for you.

And by system calls, I'm referring to documented Windows API entry points like CreateProcess() or GetWindowText(). Device drivers will generally use a different API from the Windows DDK.

Philanthropy answered 22/3, 2010 at 3:37 Comment(2)
1. How am I gonna use these in Assembly language programming? 2. Windows API and system calls are not the same thing. WINDOWS API use system calls. The similar analogy on linux domain would be POSIX API (WINDOWS API) use system calls provided by linux kernel (windows kernel). So, my concern is about the true system calls.Indecorous
I understand that there's a difference between API calls and traps into the OS privileged layer (what you call "true" system calls). These "true" system calls are an implementation detail of Windows system DLLs. You might be able to figure out how they work by disassembling the DLLs and looking at the assembly. Or you could just write your assembly code to call into the DLLs as appropriate... it's possible.Philanthropy
I
-3

OFFICIAL Calling convention in Windows: http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx

(hope this link survives in the future; if it doesn't, just search for "x64 Software Conventions" on MSDN).

The function calling convention differs in Linux & Windows x86_64. In both ABIs, parameters are preferably passed via registers, but the registers used differ. More on the Linux ABI can be found at http://www.x86-64.org/documentation/abi.pdf

Indecorous answered 23/3, 2010 at 19:23 Comment(1)
This doesn't answer the question, which was about the convention for calling into kernel mode. This is for the user mode function calling convention, which is quite different. And fully documented.Diskin

© 2022 - 2024 — McMap. All rights reserved.