System Calls in windows & Native API?
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
);
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.
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.
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.