
Windows X86-64 System Call Table - badrabbit
https://j00ru.vexillium.org/syscalls/nt/64/
======
quotemstr
It's important to note that unlike Linux, NT system call numbers are not
stable. That's a very good thing --- it effectively forces all system calls to
go through ntdll, which can coordinate closely with userspace in interesting
ways. It's as if NT's "vdso" covered all interaction with the kernel.

For example, NT has no native 32-bit ABI like Linux does. Instead, _userspace_
translates 32-bit to 64-bit system calls, handling the switch from long and
back to long mode transparently. This elegant approach is possible because
applications never make system calls directly.

~~~
rocqua
How does that work with regards to privilege separation. It sounds like
bypassing nt.dll might be an exploit vector that is easily forgotten.

~~~
mehrdadn
Just a note, it's not nt.dll, it's ntdll.dll.

~~~
crehn
Heh, interesting. Any specific reason for the stuttering? Or just arbitrary
inconsistent historical decision?

~~~
windozeee
I'll give you a better one. On a 64 bit Windows:

\- the 64 bit binaries are stored in C:\Windows\System32

\- the 32 bit binaries are stored in C:\Windows\SysWOW64

~~~
mehrdadn
Haha, I think WOW64 means "Windows [32] on Windows 64" which kind of makes
sense if you squint at it just right...

~~~
cjarrett
That's a bingo.

------
mehrdadn
Note that this page doesn't show the full set of syscalls, just a subset of
them (seems to be the ones ntdll calls). The Win32k stuff, for one, don't seem
to be there (edit: they're on this page instead:
[https://j00ru.vexillium.org/syscalls/win32k/64/](https://j00ru.vexillium.org/syscalls/win32k/64/)),
let alone more obscure ones that I expect exist but that I don't know about
(like WSL stuff).

~~~
monocasa
The WSL stuff doesn't have system calls AFAIK. All the work is either kernel
mode indirection to the kernel driver, and IPC (ALPC I think on WSL1?).

~~~
mehrdadn
Well for starters WSL implements Linux system calls, so it definitely has its
own system calls!

But even beyond that, I was thinking of one thing they added for performance
reasons (see 3rd bullet):
[https://github.com/Microsoft/WSL/issues/873#issuecomment-425...](https://github.com/Microsoft/WSL/issues/873#issuecomment-425272829)

However I'm not entirely sure if it's a concrete "syscall" or if it's
implemented as a new option through an existing one. (It's not clear to me how
they could reasonably fit this into anything that already exists, but it
might.) Though I would be mildly surprised if they didn't implement at least
one syscall specifically for WSL...

~~~
monocasa
> Well for starters WSL implements Linux system calls, so it definitely has
> its own system calls!

Sure, but not in the NT system call table.

> But even beyond that, I was thinking of one thing they added for performance
> reasons (see 3rd bullet):
> [https://github.com/Microsoft/WSL/issues/873#issuecomment-425...](https://github.com/Microsoft/WSL/issues/873#issuecomment-425..).

> However I'm not entirely sure if it's a concrete "syscall" or if it's
> implemented as a new option through an existing one. (It's not clear to me
> how they could reasonably fit this into anything that already exists, but it
> might.) Though I would be mildly surprised if they didn't implement at least
> one syscall specifically for WSL...

It's neither, it's an API inside kernel space. They didn't expose it through
the NT syscall table, but instead how the linux transtalion layer calls
directly into NT kernel functions.

~~~
mehrdadn
Ah I see, okay thanks.

~~~
zaszrespawned
[https://devblogs.microsoft.com/commandline/announcing-
wsl-2/](https://devblogs.microsoft.com/commandline/announcing-wsl-2/)

WSL is now a VM. So its really hypervisor that runs the linux syscalls on its
virtual processor

~~~
mehrdadn
I'm aware of WSL2 but we've been talking about WSL1.

------
ninkendo
How do they deal with the syscall numbers being different from release to
release, while still maintaining binary compatibility? From what I can tell,
(in Linux at least) the syscall numbers are _the_ thing that needs to stay
constant for the kernel to not break user space.

~~~
mehrdadn
> How do they deal with the syscall numbers being different from release to
> release, while still maintaining binary compatibility

By not calling syscalls based on their numbers on Windows. That might sound
crazy, but actually to me coming from Windows, calling them by ordinals is
what sounds crazy. :-) You're supposed to call exported functions in shared
libraries (ntdll.dll etc.) that call the syscalls for you.

~~~
ninkendo
So that means ntdll.dll and family can never be statically linked? I guess
that makes sense if the only responsibility of the dll is to expose syscalls,
but does ntdll expose any other functionality? Functionality that may want to
be overridden by a different implementation, etc?

In the Linux world, since syscalls are exposed by libc, other non-C runtimes
(for example, Go) end up needing to make system calls on their own without the
libc wrappers. Is such a system just not a thing in Windows?

~~~
quotemstr
Go doesn't _need_ to bypass libc on Linux. There's no technical reason to do
that. Go doesn't bypass libc on other operating systems. Go going straight to
the kernel is just a fashion statement, a symbol repudiation of C, not
something that makes actual life better for users.

~~~
brianpgordon
Hm, I thought there was some issue with glibc not wanting to provide wrappers
for certain Linux syscalls. What if the Go runtime wanted to use one of those
syscalls for some reason? It seems like then it'd be forced to go straight to
the kernel.

~~~
quotemstr
Yes, it'd have to bypass libc for those system calls, just like any C program
would. How is that an excuse for bypassing libc for things like read(2)?

No, making system call interposition harder isn't a security feature. A user
who can LD_PRELOAD you can already do arbitrary things to your program.

~~~
jart
Sometimes when folks talk to the kernel, they want to know they're talking to
the actual kernel. glibc by design lets random stuff on the system MiTM
symbols like read (even security critical ones like getrandom) and the LGPL
effectively forbids many projects from using static linking as an escape
hatch. The Chrome guys had to write a whole system call library from scratch
because of it. On Windows it tends to get much more intense, where dynamic
symbol interposition is brought to its logical conclusion, and you've layers
upon layers of antivirus hooks and spyware sitting between you and the system.
There, pretty much the only thing you can do is just SSL the heck out of
everything.

~~~
dooglius
> glibc by design lets random stuff on the system MiTM symbols like read

Can you elaborate on this? I'm aware of things like LD_PRELOAD, but if an
adversary controls the environment, he could just change PATH to point to a
rooted version of chrome anyway. That also has to do with the capabilities of
the system linker, not glibc.

>the LGPL effectively forbids many projects from using static linking as an
escape hatch

The LGPL explicitly allows static linking, that's the main difference versus
the GPL.

~~~
jart
Are you in control of your network connection? Packets aren't that much
different from calls between DSOs. It's just a big onion ring. If you make a
conscious decision to trust Chrome with your data, then does Chrome have a
moral obligation to ensure that choice, in reality, ends up being
You<->Chrome, rather than You<->SysAdmin<->Comcast<->NSA<->Hacker<->Chrome? Or
maybe they just want to protect IP holders. Or maybe they just don't want
folks filing bugs about performance when the root cause turns out to be some
poorly written system library. At the end of the day, it's all about
minimizing unknowns.

LGPL allows dynamic linking. Only way it'll allow static is if your releases
are accompanied by tools for decompiling and recompiling your binaries with
the LGPL bits interchanged. But that actually might not be allowed either,
since GCC 4.3+ headers and runtimes (e.g. libstdc++) kind of prohibit you from
changing binaries on your own, after they've been compiled.

~~~
cesarb
> Are you in control of your network connection? Packets aren't that much
> different from calls between DSOs.

The difference is that calls between dynamic libraries are on the same side of
the "airtight hatchway" (as explained by Raymond Chen at
[https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31...](https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31283)),
while there's a security boundary at the network connection.

~~~
jart
Imagine the modern PC as being just another substrate of this great
battlefield where big corporations duke it out with each other and the
individual owners are becoming less and less relevant to the security story.
It's unpleasant. It seems to get raunchier each year. I've even seen household
names retool things like graphics drivers to inject data harvesting code into
command-line programs at runtime. Then the big companies who don't play dirty
like that, usually react by demanding greater authoritarianism in tools, until
the rest of us devs find ourselves smothered with -fpic aslr retpoline code-
signed bubblewrapping, which isn't so much keeping us safe, but rather keeping
big company A safe from big company B. Everything that the guy you linked said
is correct. But locks aren't that relevant anymore, once the wolves have been
invited in to live.

~~~
quotemstr
You've used a lot of rhetoric, but you haven't described a threat model. Both
this comment and your previous one, the GP, use language of moral duty when
talking about a specific software security measure. That's a category error.

What _specific_ _threat_ is bypassing libc supposed to protect against? I
don't think you have an argument here. As the poster to whom you're replying
mentioned, anyone who can do symbol interposition already has full control
over your program.

~~~
jart
Perhaps you could share specific details on the use cases that dynamic symbol
interposition is intended to serve? Because the only one I think I've seen so
far is setbuf(1) and I can imagine more than a few alternatives that could
have been considered.

~~~
quotemstr
Enumerating specific interposition user cases has nothing to do with the point
I made, which is about threat modeling. You still have not articulated any
specific threat model; you've instead tried to distract the conversation and
move it in a different direction.

~~~
jart
You're welcome to complain about how I talk, but this isn't about me. This is
about serving the user and making sure tech behaves the way they expect it to.
We need to raise awareness of infrastructural weaknesses that folks may need
to consider, in order to stay safe.

I consider symbolic interposition to be one of those things. Linux users might
not feel comfortable about the fact that glibc currently makes it so easy to
intercept system calls to Linux Kernel's RNG, that your upstream dependencies
might actually compromise your key generator unintentionally.

Don't you think that's worth discussing? We could also talk about the concerns
surrounding Layered Service Providers, which is another great example of
userspace libraries misrepresenting APIs that are generally believed to be
talking to the operating system.

~~~
quotemstr
It's not "how [you] talk". It's that you're not making an actual point. There
is no "safety" threat here, nor is there an "infrastructural weakness[]".
Fretting about "safety" without identifying a threat model is vacuous. What
is, specifically, your threat model? What security guarantees can a program
make only through bypassing libc? Why aren't the vast majority of programs ---
the ones that call libc --- vulnerable to this ineffable threat?

Users get to control how programs execute. They can interpose symbols. They
can disassemble programs. They can run programs under a debugger. They can
modify the kernel. They can run programs in a VM. A program can't detect this
intermediation; nor does it have any right to do so. A program has no business
breaking random OS visibility and control features. We generally call the ones
that try malware.

Bypassing libc when making system calls doesn't give a program any insurance
against environmental changes. It just inconveniences users while providing no
"safety" guarantees. If you want full control over a program's execution
environment, ship an appliance.

------
moyix
One odd bit of trivia: the Windows system call mechanism supports up to four
different tables. The first two are what you'll usually see - the first is the
basic kernel services, and the second is for Win32k, the graphical subsystem.
The final two slots are up for grabs and historically could be used by drivers
to implement custom system calls.

The system call number determines what table is used. Calls in the 0x0-0xFFF
range are handled by the first table, 0x1000-0x1FFF by the second, and so on.

As far as I know, the ability to add another system call table was only ever
used by the IIS web server kernel-mode component (spud.sys).

~~~
crusader1099
I don't doubt your interesting bit of trivia, but I couldn't find anything
online about. Do you have a source for this? I would certainly love to read
more about it.

~~~
moyix
There's a bit more information in the indispensable Windows Internals book
(quoting from the 4th edition, which is a little old now):

> As you’ll see in Chapter 6, each thread has a pointer to its system service
> table. Windows has two built-in system service tables, but up to four are
> supported. The system service dispatcher determines which table contains the
> requested service by interpreting a 2-bit field in the 32-bit system service
> number as a table index. The low 12 bits of the system service number serve
> as the index into the table specified by the table index.

[...]

> A primary default array table, _KeServiceDescriptorTable_ , defines the core
> executive system services implemented in Ntosrknl.exe. The other table
> array, _KeServiceDescriptorTableShadow_ , includes the Windows USER and GDI
> services implemented in the kernel-mode part of the Windows subsystem,
> Win32k.sys. The first time a Windows thread calls a Windows USER or GDI
> service, the address of the thread’s system service table is changed to
> point to a table that includes the Windows USER and GDI services. The
> _KeAddSystemServiceTable_ function allows Win32k.sys and other device
> drivers to add system service tables. If you install Internet Information
> Services (IIS) on Windows 2000, its support driver (Spud.sys) upon loading
> defines an additional service table, leaving only one left for definition by
> third parties. With the exception of the Win32k.sys service table, a service
> table added with _KeAddSystemServiceTable_ is copied into both the
> _KeServiceDescriptorTable_ array and the _KeServiceDescriptorTableShadow_
> array. Windows supports the addition of only two system service tables
> beyond the core and Win32 tables.

------
pedrocx486
Sorry if this is an dumb question I could easily find on Google (been a webdev
for most of my career), buy I'm curious why some syscalls exist on Vista SP1
for example, but don't exist on the versions before and after it.

Edit: I meant SP0, not SP1, sorry. It was this SysCall: NtListTransactions

~~~
mehrdadn
Could you give an example? I don't see anything that's on Vista SP1 but not on
SP2.

~~~
pedrocx486
Sorry, in my case I saw this on SP0 not SP1: NtListTransactions

~~~
PeCaN
that's probably part of TxF (aka transactional ntfs) which got deprecated
basically as soon as it was released (though i think it's still used
internally for e.g. system restore) so it's likely that got moved around
somehow or the TxF API got reimplemented in userspace

~~~
mehrdadn
I don't believe their deprecation claims on that. It's too deeply ingrained
into the OS for them to ever remove it, and too useful and difficult to
replace. Really, it just doesn't have enough users, is all. And there's more
to transactions than just the file system (TxF) so I'm not even sure it's
related to this either.

Also you can't implement TxF in userspace. It has to detect conflicts with
other applications and roll back in the case of an unsuccessful commit (power
loss etc.) before the file system is used again. Any userspace implementation
would leave stuff in a corrupted state until it's re-run.

~~~
PeCaN
> I don't believe their deprecation claims on that.

I don't either, although the API is kind of overkill for most use cases so I'm
not too surprised they discourage people from using it.

> And there's more to transactions than just the file system (TxF) so I'm not
> even sure it's related to this either.

True, I just assumed it was related to the txfs_list_transactions ioctl.

> Also you can't implement TxF in userspace. It has to detect conflicts with
> other applications and […]

I think the Kernel Transaction Manager already takes care of that. I think???
TxF could be implemented as a userspace library on top of KTM, but I'm not
particularly familiar with either facility. Though if it was possible perhaps
they would've done it that way in the first place, since TxF uses KTM
regardless.

I wonder what did happen to it between SP0 and SP1.

------
jolmg
Interesting that on a linux machine

    
    
      man syscalls \
      | grep -Po '\w+(?=\(2\))' \
      | sort \
      | uniq \
      | wc -l
    

gives the same number of 481 as the number of rows in that table. Though, the
grep includes man-pages that don't correspond with a syscall, like intro(2).
It's just a curious coincidence.

------
october_sky
Could someone provide examples of what to type in the field, with perhaps the
relevance? (I'm not a Windows user, but this looks cool enough to test)

------
kuroguro
Has anyone ever done a mapping of which regular API functions map to which
syscalls?

------
sbierwagen
Huh. Why is this so slow? According to Chrome, it took 704 milliseconds from
clicking the "show" button on "Windows XP" to displaying the results. No
network calls at all. A whole 525ms of script evaluation. Modern computer,
thousand row table, wouldn't expect it to take that long.

~~~
lucb1e
Works fine for me on an average laptop with latest stable Firefox.

~~~
mehrdadn
Try "Show All".

~~~
wvenable
Seems instantaneous on Firefox for me.

~~~
mehrdadn
Really?! It isn't instantaneous for me, even on Firefox 62 (Windows). Takes
some half a second or so.

~~~
wvenable
I'm on Firefox 68.0.2 on Windows 10.

I can click "Show All" and "Hide All" over and over and there is hardly any
delay. Maybe you should upgrade to the latest and see if it makes a
difference.

~~~
mehrdadn
Interesting, thanks for mentioning this! It seems to be something with my
profile, because a fresh profile is also definitely faster (maybe 200-300ms
just eyeballing). The latest unbranded build I got [1] also seems about the
same if not faster (maybe around 200ms). Definitely not "instantaneous", but
I'll definitely track this down.

UPDATE: At least 3 of the culprits seem to be MutationObservers from my own
extensions (as in, from extensions that I have written for myself) that have
{childList: true, subtree: true}! I guess I will have to optimize them. :-)

[1]
[https://queue.taskcluster.net/v1/task/Uht_zkkPThu384OAHNPwdA...](https://queue.taskcluster.net/v1/task/Uht_zkkPThu384OAHNPwdA/runs/0/artifacts/public/build/target.zip)

------
bediger4000
Windows NT has been around since what, 1993 or 94?

Why is this even news? Doesn't Microsoft document this stuff? How does anybody
use a system that doesn't have this documented?

PS
[http://stratigery.com/nt.sekrits.html](http://stratigery.com/nt.sekrits.html)

~~~
derpherpsson
I also ask myself this question. How can anyone use an OS where the creators
deliberately hide some of the documentation?

I would love to read up on windows internals, but there are no real resources
out there. A few books that at first glance appears to cover it, but then just
deals with the application layer and up to point-and-click.

This is why I don't use windows anymore. I don't ever again want a job where I
have to debug why old program P stopped working after an update. I prefer _to
be able_ to learn stuff.

~~~
kyberias
Oh come on!

[https://www.amazon.co.uk/Windows-Internals-Part-
architecture...](https://www.amazon.co.uk/Windows-Internals-Part-architecture-
management/dp/0735684189)

Certainly all systems have undocumented stuff that are left undocumented
because the users are not supposed to use it. If they would, they couldn't
change it.

~~~
bediger4000
System calls seems like the very definition of what should be documented for
users. Particularly if you want someone to write compilers for your operating
system.

~~~
yyyk
The stable program-kernel interface is not the kernel syscalls, but library
API. That's exactly the same as every other non-Linux OS out there including
other Unixes and other Free OSs.

~~~
bediger4000
Pretty sure that isn't true of FreeBSD, NetBSD or OpenBSD. It most certainly
wasn't true of any Unix.

They all have files that end up as /usr/include/sys/syscall.h and define well-
known numerical constants for each (documented!) system call.

[https://github.com/openbsd/src/blob/master/sys/sys/syscall.h](https://github.com/openbsd/src/blob/master/sys/sys/syscall.h)

[https://github.com/lattera/freebsd/blob/master/sys/sys/sysca...](https://github.com/lattera/freebsd/blob/master/sys/sys/syscall.h)

[http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/sys/syscall.h?re...](http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/sys/syscall.h?rev=1.307&content-
type=text/x-cvsweb-markup&only_with_tag=MAIN)

Oh, look! They even share the exact same numerical values for the old school
system calls. #1 is exit(), #2 is fork(), so on and so forth.

Gosh, on my linux laptop, here in /usr/include/asm/unistd_64.h is pretty much
the same numbers, but objectively hidden a little bit more than in the direct
Unix descendants. I interpret this as "less documented than in Unixes".

Wow, even Minix source code has the same numbers:
[https://minixnitc.github.io/posix.html](https://minixnitc.github.io/posix.html)

I wonder why that is? Maybe it's because the system call numbers are in fact
documented, part of the POSIX API, and descend from Bell Labs Unix source
code. Here's V7's list of system call numbers as proof:

[https://minnie.tuhs.org/cgi-
bin/utree.pl?file=V7/usr/include...](https://minnie.tuhs.org/cgi-
bin/utree.pl?file=V7/usr/include/sys.s)

~~~
yyyk
Documenting syscalls does not mean a guarantee of stability. I can't recall
any other Unixes except Linux which has committed to maintaining syscalls,
which means this interface cannot be relied on.

e.g.

OpenBSD deletes "relic from the past" syscall:

[https://cvsweb.openbsd.org/src/sys/sys/syscall.h?rev=1.199&c...](https://cvsweb.openbsd.org/src/sys/sys/syscall.h?rev=1.199&content-
type=text/x-cvsweb-markup)

Solaris documenting deleted syscalls (note however the libc interface is
guaruaneed):

[https://docs.oracle.com/cd/E23824_01/html/E22973/gkzlf.html](https://docs.oracle.com/cd/E23824_01/html/E22973/gkzlf.html)

MacOS X is so unstable that even go developers had to go through libSystem:

[https://golang.org/doc/go1.12](https://golang.org/doc/go1.12)

~~~
Volt
> MacOS X is so unstable that even go developers had to go through libSystem

Just to be clear to anyone else, the Mac OS X syscall interface is
_guaranteed_ to be _unstable_ by Apple. Going through libSystem is the only
way.

