# Windows System Calls

<details>

<summary>Table of Contents</summary>

* [Foreword](#foreword)
  * [Acknowledgements](#acknowledgement)
* [TL;DR](#tl-dr)
* [Introduction](#introduction)
* [Kernel Debugging](#kernel-debugging)
  * [Local Machine](#local-machine)
  * [Remote Machine](#remote-machine)
  * [Virtual Machine](#virtual-machine)
* [Kernel Debugging (Contd.)](#kernel-debugging-contd)
* [Recap](#recap)
* [The Invocation](#the-invocation)
  * [Model-Specific Registers](#model-specific-registers)
  * [CPUID Enumeration](#cpuid-enumeration)
  * [Finding MSRs For Your Processor](#finding-msrs-for-your-processor)
* [The Invocation (Contd.)](#the-invocation-contd)
  * [KiSystemCall64](#kisystemcall64)
  * [The System Service Number (SSN)](#the-system-service-number-ssn)
  * [Table Identifier](#table-identifier)
  * [The SSDT](#the-ssdt)
* [The Return](#the-return)
* [References](#references)

</details>

## Foreword

{% hint style="danger" %}
This post is currently under construction. Expect errors, unfinished thoughts, and much much more. I'm just keeping this public because I love being 100% transparent with what I'm doing/working on and documenting my progress. <mark style="background-color:orange;">With that being said, you've officially been warned</mark>. I *am* working on it :)

![](/files/ejPSiNpoP2SUSZlTrOOU)
{% endhint %}

Although the video below isn't entirely about system calls; more so the exploitative puppeteering of syscalls, I mention the internals of syscalls (especially in the post-invocation phase). So, it's still worth a watch. Plus, I'll be directing the viewers to come to this blog for the kernel debugging portion I had to omit (although very badly wanted to include but couldn't—time reasons, you know, as you can see from the sheer size of this blog). Either way, video or not, strap in because this might be my largest and most complicated blog post to date but I promise you'll come out the other end feeling like you've awakened your third-eye chakra or something. This is insane low-level wizardry.

{% embed url="<https://www.youtube.com/watch?v=-M2_mZg_2Ew>" %}
Malware Development: System Calls
{% endembed %}

### Acknowledgement

I cannot emphasize enough how pivotal [Alice Climent-Pommeret's](https://alice.climent-pommeret.red/) [blog](https://alice.climent-pommeret.red/posts/a-syscall-journey-in-the-windows-kernel/), and [Xeno Kovah's](https://twitter.com/XenoKovah) free courses on [OST2](https://p.ost2.fyi/courses), namely: [x86-64 OS Internals](https://p.ost2.fyi/courses/course-v1:OpenSecurityTraining2+Arch2001_x86-64_OS_Internals+2021_v1/about), [Windows Kernel Internals 2](https://p.ost2.fyi/courses/course-v1:OpenSecurityTraining2+Arch2821_Windows_Kernel_Internals_2+2023_v1/about), as well as the [Intel SDM](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) have been throughout my deep dive into the nuanced/hyper-niched world of system calls and kernel debugging. These resources served as both a compass and a beacon, guiding my understanding and unraveling the complex layers that make up this topic. It is atop the shoulders of *these* giants on which I stand. All credit and my deepest gratitude go to them and the many others whom I've included in my references, for without their amazing work and insightful contributions, this exploration would not have been nearly as in-depth or fulfilling.

## TL;DR

I don't like including TL;DR sections in my blogs because I feel like the ability to sit through and read something 'till the end is at an all-time low—however, for this blog post, it *may* be warranted because it's genuinely enormous. &#x20;

1. After a syscall has been invoked, the value inside of our instruction pointer `RIP` is saved inside of the `RCX` register so that once we're in kernel-land we can return to the user-space via the sysexit instruction (which will rebalance everything syscall does).&#x20;
2. Then, the `IA32_LSTAR` Model Specific Register (MSR) places the value it holds (it gets this value at boot from the `nt!KiInitializeBootStructures` routine. `nt!KiInitializeBootStructures` is also responsible for setting the values of the other involved MSRs by using the privileged `WRMSR` instruction) into the instruction pointer, causing us to run the `nt!KiSystemCall(32|64)` (or `nt!KiSystemCall(32|64)Shadow` if your processor has enabled the mitigation for the "meltdown" vulnerability and therefore requires calls to "shadow routines" — this is also determined by another routine that `nt!KiInitializeBootStructures` calls: `nt!KiEnableKvaShadowing`, which sets "`KiKvaShadow`" to one (`1`), thus, enabling KVAS (which is [Microsoft's implementation of KPTI](https://msrc.microsoft.com/blog/2018/03/kva-shadow-mitigating-meltdown-on-windows/))).
3. `KiSystemCall64` calls `KiSystemServiceStart` which uses the SSN that was placed in `EAX` and extracts the syscall index as well as the table identifier.&#x20;
4. Then, in `KiSystemServiceRepeat` this extracted index is used as an actual index against the long array of kernel routines which are found in the SSDT.
5. ...&#x20;

## Introduction

As malware (developers | analysts), reverse engineers, security researchers, or whatever else, we've become rather acquainted with; and strutted alongside—*harmoniously* and *synchronously*, certain complex mechanisms that allow our devices to work as we expect them to. These mechanisms are the beating heart of our worlds which we invest so much time into; *pumping as the clock cycles tick*...&#x20;

The system call instruction (hereafter, "syscall") is one of these little complexities that we've definitely utilized for our "[malicious](/nest/mal/dev/inject/syscalls.md)" purposes, but still haven't fully grasped the complete, unadulterated, demystified, and raw beauty of. I think personally, the reason I love syscalls so much is for the sole reason that they are the interfaces to the kernel space. The gates to hell, so to speak. Because of this, I've started to develop a borderline unhealthy obsession with them (which you'll no doubt see as the blog post goes on) as well as trying to understand every last bit of the deep internal quirks at play in our operating systems as a whole.&#x20;

I've only fallen deeper in love with C-programming, paying special attention to writing the best code I can muster up. Moreover, my love and resolve for assembly remain aflame. Today, we're looking at what happens after a syscall is invoked. Firstly, we need to set up an adequate environment so that we can play to our heart's desire. Then, we'll have a short recap of a typical WinAPI function's journey from the user-space to kernel-land, just so we're all on the same page and so that we all share the same vocabulary (if you're already comfortable with a typical syscall stub and the basics of what it does in the user space, then click [here](#the-invocation) to skip the recap).

## Kernel Debugging

Ah, kernel debugging. A persistent source of trauma for many and an unlocker of cosmic verity for others. It is no doubt an integral process one must become *very* familiar with once they've reached a certain threshold if they're to become even *more* divine. Today, we begin that trek.

{% hint style="warning" %}
I'll teach you how to create a kernel debugging environment but I won't go over all the individual WinDbg commands in depth here. It's assumed that you're at least *somewhat* familiar with the breakpoint, execution, and display commands. If not, you're in luck! Click [here](https://github.com/repnz/windbg-cheat-sheet).&#x20;
{% endhint %}

Kernel debugging; at least the "setting up the environment" part, isn't as difficult as it may seem. However, there are definitely things to note when setting up or using a kernel debugging environment, *especially* when you're debugging "for real." To figure out what's necessary for you,  the first thing you want to understand is if you want to debug a:

* [Local Machine](#local-machine)
* [Remote Machine](#remote-machine)
* [Virtual Machine](#virtual-machine)

### Local Machine

Debugging a local machine (i.e. your host machine) is arguably and objectively *the worst* choice out of the three above. The reason for this is that we practically can't do anything—anything *cool*, that is. Aside from reading and writing to and from kernel memory (the latter of which can still be extremely dangerous if not done carefully—resulting in potential corruption or crashes), we can't set breakpoints, restart, or execute any commands that stop the machine, *even momentarily*. This is because if we halt the execution of the machine, there's no way for us to continue execution again - everything will be hung. Microsoft confirms this as well, based on what they say in their [documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/performing-local-kernel-debugging#commands-that-are-not-available):

<figure><img src="/files/SDBQQlt3H3CCEx49x8cR" alt=""><figcaption><p>Prohibited debugging commands for <em>local</em> debugging from <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/performing-local-kernel-debugging#commands-that-are-not-available">MSDN</a></p></figcaption></figure>

### Remote Machine

This would be the ideal scenario, and you *can* do it. As the incredible [Pavel Yosifovich](https://scorpiosoftware.net/) says:

> In this final level, you’re debugging a physical machine, which provides the most “authentic” experience. Setting this up is the trickiest. Full description of how to set it up is described in the debugger documentation. In general, it’s similar to the previous case, but network debugging might not work for you depending on the network card type your target and host machines have.
>
> — [Pavel Yosifovich](https://scorpiosoftware.net/), [Levels of Kernel Debugging](https://scorpiosoftware.net/2023/03/07/levels-of-kernel-debugging/).

However, not all of us have spare machines lying around and the ones we do have are probably some geriatric relics revitalized with some sort of Linux distribution anyway. Therefore, it's safe to say that most of us will proceed with scenario [three](#virtual-machine).

### Virtual Machine

You get all the control you would with a remote machine setup and you don't need to go out and dish out some big bucks (or little bucks, who knows, tech is pretty crazy and versatile nowadays) for a new hot piece of hardware. Instead, we can make as many virtual machines as our exhausted computers can handle! If something crashes, borks, breaks, or corrupts, then we have snapshots! So, I would recommend this approach to most who start their fruitful kernel debugging journeys. I'm sure remote debugging has its niche pros in there as well. Right now, however, this shall suffice.

***

## Kernel Debugging (Contd.)

{% hint style="warning" %}
It's assumed you have a working Windows virtual machine set up and ready to go. If not, countless tutorials can teach you how to install and configure a virtual machine for your specific virtualization software. I'll be using [VMware Workstation](https://www.vmware.com/ca/products/workstation-pro.html) but it really doesn't matter in the slightest.
{% endhint %}

The first step is to download [WinDbg Preview](https://apps.microsoft.com/detail/9PGJGD53TN86?rtc=1\&hl=en-ca\&gl=CA) from the Microsoft store (don't worry, you don't need to make an account—or at least, I don't *think* you do. I previously set it up on my virtual machine and I never once logged in to anything, so it should be the same for you):

<figure><img src="/files/XmJFjwnvcO7ucDyMxVB2" alt=""><figcaption><p>Installed WinDbg Preview</p></figcaption></figure>

Next, we need to turn on our virtual machine if it's not already on. Make sure that your machine can reach your host machine since we're going to be connecting to it through the network. If you're worried because you're using your malware analysis or development VM, then you should create a new VM solely for kernel debugging (this is also great because if something gets corrupted or broken, it's just an empty throwaway VM anyway) or go back to a known clean snapshot and do it from that point. Remember to make new/constant snapshots in case shit hits the fan. Now, in an elevated command prompt execute the following commands:

{% code overflow="wrap" %}

```powershell
bcdedit /debug on
bcdedit /dbgsettings net hostip:<ip> port:<port> key:<key>
```

{% endcode %}

{% hint style="info" %}
The "`hostip`" is the IP of your virtual machine. The "`port`" can be set to any port between 49151 and 65536 (49151 < port < 65536). Finally, the "`key`" can be whatever you want, as long as it follows a four-octet pattern. For example, "`1.2.3.4`", "`hey.vsauce.michael.here`", etc., are all valid keys you can use.
{% endhint %}

<figure><img src="/files/hJ1NmxgxTXT8nLQkE6S1" alt=""><figcaption><p>Running the commands successfully</p></figcaption></figure>

If we read the program's help page, we can see that this utility is responsible for modifying the boot configuration data store:

```powershell
C:\WINDOWS\system32>bcdedit /?

BCDEDIT - Boot Configuration Data Store Editor

The Bcdedit.exe command-line tool modifies the boot configuration data store.
The boot configuration data store contains boot configuration parameters and
controls how the operating system is booted. These parameters were previously
in the Boot.ini file (in BIOS-based operating systems) or in the nonvolatile
RAM entries (in Extensible Firmware Interface-based operating systems). You can
use Bcdedit.exe to add, delete, edit, and append entries in the boot
configuration data store.
```

Furthermore, the commands we're interested in are the ones related to debugging:

```powershell
Command that control debugging
==============================
/bootdebug      Enables or disables boot debugging for a boot application.
/dbgsettings    Sets the global debugger parameters.
/debug          Enables or disables kernel debugging for an operating system
                entry.
/hypervisorsettings  Sets the hypervisor parameters.
```

You can get even more information by issuing `bcdedit /dbgsettings /?`:

```powershell
C:\Users\hepha>bcdedit /dbgsettings /?

This command sets or displays the global debugger settings for the system.

This command does not enable or disable the debugger for any particular boot
entry.  To enable or disable the debugger for a particular boot entry, use
"bcdedit /debug < identifier> ON".  For information about identifiers, run
"bcdedit /? ID".

To set an individual global debugger setting, use
"bcdedit /set {dbgsettings} <type> <value>".  For information about valid
types, run "bcdedit /? TYPES".

bcdedit /dbgsettings [ <debugtype> [DEBUGPORT:<comport>] [BAUDRATE:<baud>]
                        [CHANNEL:<channel>] [TARGETNAME:<targetname>]
                        [HOSTIP:<ip>] [PORT:<port>] [KEY:<key>] [nodhcp]
                        [newkey] [/start <startpolicy>] [/noumex] ]

    <debugtype>     Specifies the type of debugger. <debugtype> can be one of
                    SERIAL, 1394, USB, NET or LOCAL.
                    
    <comport>       For SERIAL debugging, specifies the serial port to use as
                    the debugging port. This is an optional setting.
                    
    <baud>          For SERIAL debugging, specifies the baud rate to be used
                    for debugging. This is an optional setting.
                    
    <channel>       For 1394 debugging, specifies the 1394 channel to be used
                    for debugging.
                    
    <targetname>    For universal serial bus (USB) debugging, specifies the USB
                    target name to be used for debugging.
                    
    <ip>            For network debugging, specifies the IPv4 address of the
                    host debugger.
                    
    <port>          For network debugging, specifies the port to communicate
                    with on the host debugger.  Should be 49152 or higher.
                    
    <key>           For network debugging, specifies the key with which to
                    encrypt the connection.  [0-9] and [a-z] allowed only.
                    
    [...]

Examples:

    [...]

The following command sets the global debugger settings to network debugging
using IPv4 with a debugger host at 192.168.1.2 communicating on port 50000:

    bcdedit /dbgsettings NET HOSTIP:192.168.1.2 PORT:50000

    [...]
```

Honestly, that's about the hardest part of it. Now, we're ready to open up our debugger and connect to our machine. We want to navigate to the main menu of this program and select `Attach to kernel`.

<figure><img src="/files/tW5jblCGx43rJrdJCk1l" alt=""><figcaption><p>Attach to kernel </p></figcaption></figure>

After selecting that, you want to go in and populate all of the entries with what you supplied in the command line like the following (because we have `Break on connection` set, the machine will halt once we connect, keep this in mind for future debugging sessions):

<figure><img src="/files/1MZHMaa3tOg3DOU0zQHr" alt=""><figcaption><p>Populating the entries</p></figcaption></figure>

Now, we press `OK`, and see what happens.

<figure><img src="/files/WefxVrL7aOVYxcbyxneQ" alt=""><figcaption><p>Current view</p></figcaption></figure>

We can see that it's currently "`Waiting to reconnect...`" and the way we *actually* instantiate a connection from our VM is by restarting it. So, let's restart it, and see what happens.

<figure><img src="/files/KmRfb2qU8qfAUJnRcMdi" alt=""><figcaption><p>Restarting the BAT-PC</p></figcaption></figure>

{% hint style="danger" %}
If you haven't updated the VM in a while, be prepared to get stun-locked by Windows updates. Literal hell on earth. Note that there are workarounds to disable automatic updates and the like.&#x20;

![](/files/5IUlfHGMHTyjsuKr4poR)
{% endhint %}

<figure><img src="/files/BXGZQ55zf9OG2yXRkKEC" alt=""><figcaption><p>Kernel debugger connection established</p></figcaption></figure>

We see that we have hit our first initial breakpoint automatically, and on our VM side, we can see that it's hung here:

<figure><img src="/files/hBzIKhPVjo9gu4M5kiOG" alt=""><figcaption><p>Windows hung on boot</p></figcaption></figure>

We can continue the execution by hitting the `Go` button or entering `g` in the command window. In order to debug more efficiently, we need to get some symbols which will give us a *much* better time. So, in the command window, issue the following commands and wait until everything's finished downloading and refreshing:

```powershell
.sympath SRV*C:\DebugSymbols*http://msdl.microsoft.com/download/symbols
.reload /f
```

{% hint style="info" %}
There are other ways to set up your debug symbols. Some people use [environment variables](https://stackoverflow.com/a/30019890) to set this up.
{% endhint %}

You'll see many program database (`pdb`) files being installed, which will be placed in the directory you specify after `SRV*`, so in this case, `C:\DebugSymbols`. This will give us a ton of debugging information, most of it crucial or mandatory like symbol and structure names. So, this step is critical if you wish to spend any interval of any time unit doing kernel debugging.

```powershell
1: kd> .sympath SRV*C:\DebugSymbols*http://msdl.microsoft.com/download/symbols
Symbol search path is: SRV*C:\DebugSymbols*http://msdl.microsoft.com/download/symbols
Expanded Symbol search path is: srv*c:\debugsymbols*http://msdl.microsoft.com/download/symbols

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       SRV*C:\DebugSymbols*http://msdl.microsoft.com/download/symbols
1: kd> .reload /f
Connected to Windows 10 19041 x64 target at (Wed Jan 24 05:13:39.514 2024 (UTC - 5:00)), ptr64 TRUE
Loading Kernel Symbols
...........

Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.
Run !sym noisy before .reload to track down problems loading symbols.

....................................................
................................................................
................................................................
......
Loading User Symbols

Loading unloaded module list
......Unable to enumerate user-mode unloaded modules, Win32 error 0n30

************* Symbol Loading Error Summary **************
Module name            Error
clipsp                 The system cannot find the file specified
vsock                  The system cannot find the file specified
vmci                   The system cannot find the file specified
vmrawdsk               The system cannot find the file specified
BasicDisplay           The system cannot find the file specified
truecrypt              The system cannot find the file specified
vmmouse                The system cannot find the file specified
vm3dmp_loader          The system cannot find the file specified
vm3dmp                 The system cannot find the file specified
drmk                   The system cannot find the file specified
vmusbmouse             The system cannot find the file specified
vmmemctl               The system cannot find the file specified
peauth                 The system cannot find the file specified
vmhgfs                 The system cannot find the file specified

You can troubleshoot most symbol related issues by turning on symbol loading diagnostics (!sym noisy) and repeating the command that caused symbols to be loaded.
You should also verify that your symbol search path (.sympath) is correct.
```

Don't worry about the missing files. They're not important to us here. With all that said and done, you've officially set up your kernel debugger! It's now ready to go whenever you need it! 'Gratz! We'll be set for the future sections of this blog (and for the future of your career as a very *promising* researcher or malware developer/analyst)! Now, let's move on to the recap, if you're already familiar with syscalls on the user-land side, click [here](#the-invocation) to skip the recap.

## Recap

If you've done the [direct](/nest/mal/dev/inject/syscalls/direct-syscalls.md)/[indirect](/nest/mal/dev/inject/syscalls/indirect-syscalls.md) syscall malware implementations, that's where we're starting our foundation. So, to reiterate:

<figure><img src="/files/M3Rrj8aj9wSikmLuGDRY" alt=""><figcaption><p>WinAPI function flowchart</p></figcaption></figure>

<figure><img src="/files/ikd92ZkKJOjVEEDMOEDm" alt=""><figcaption><p><code>NtOpenProcess</code> syscall stub</p></figcaption></figure>

1. Our program calls a function—let's say `OpenProcess`, for example.
2. `OpenProcess`, which is exported from `Kernel32`, makes its way down to `Kernelbase`; which we know is a library meant to forward the calls down to `NTDLL`.
3. The WinAPI, once in `NTDLL`, consults its NTAPI counterpart; in this case, `NtOpenProcess`.
4. Once we're in the `NtOpenProcess` syscall stub, we move the "System Service Number" (SSN); or in familiar terms, the "syscall number", into the `EAX` register.&#x20;
5. A `test` (which is a bitwise `AND` operation) is performed on the byte value present in the `KUSER_SHARED_DATA+0x308` (`SystemCall`) field against a value of `1`. As we already know, this is a check done to determine if a syscall instruction is to be used or if the "legacy" `0x2E` interrupt is to be used.&#x20;
6. Finally, we either invoke the `syscall` instruction or trigger the `0x2E` interrupt depending on the outcome of the `test` operation.

{% hint style="warning" %}
Although we've come to understand that a syscall instruction is invoked if we're on 64-bit, and the `0x2E` interrupt is used when we're on 32-bit, this isn't the *complete* picture. Remember that there exists the [sysenter](https://www.felixcloutier.com/x86/sysenter)/[sysexit](https://www.felixcloutier.com/x86/sysexit) pair for 32-bit systems which is actually *preferred* for the 32-bit architecture because they're *both* supported on Intel and AMD (and they're much - *much* faster than the legacy interrupt; we're talking *an order of magnitude* faster).

> Everything is OK, the process runs without any problem. Regarding performances issues, we tried a simple benchmark based on the `rdtsc` instruction (`rdtsc`/`NtQuerySystemInformation`/`rdtsc` loops, with hardcoded `INT 2E` or `SYSCALL` instructions), and found that the large majority of the NtQuerySystemInformation performed with the `SYSCALL` instruction are faster of about \~1.000 clock ticks than `INT 2E` ones (`rdtsc` instructions should not be impacted by Hyper-V virtualization, and should also contain « non-VTL0 » ticks counts). The code may be found at the end of this blog post.
>
> — Adrien Chevalier, [The Windows 10 TH2 INT 2E mystery](https://www.amossys.fr/insights/blog-technique/windows10-th2-int2e-mystery/).

It does get a little muddy here when we start talking about specific omissions for manufacturer-specific (AMD and Intel) CPU instructions. You can read more about this [here](https://reverseengineering.stackexchange.com/a/16511). There's also a free [OpenSecurityTraining course](https://p.ost2.fyi/courses/course-v1:OpenSecurityTraining2+Arch2001_x86-64_OS_Internals+2021_v1/about) which also dives into this; please check it out, it's free and it, along with the entire platform, is a genuine treasure trove of information. Major respects to OST2.&#x20;
{% endhint %}

And that's around the point where we are. Moreover, this is around the point that some malware developers just pack their bags and never reach deeper. This is where it *starts* getting interesting! You should always try to go deeper if you can—if for nothing, you'll feel like a Skyrim mage reading through crypt-kept elder scrolls when you go deeper and deeper into the pseudo-"uncharted" territory that is the Kernel (anyone who's at some point needed to consult the AMD or Intel manuals knows the feeling I'm failing to describe :wink:).

## The Invocation

I'll choose a common function like `NtOpenProcess` or something that's guaranteed to run to place a breakpoint on. Actually, before that, I'll check to see if `ntdll` has even been loaded as a module with `lm`. We can further filter the output of the `lm` command with `m <module name>`:

```nasm
1: kd> lm m ntdll
Browse full module list
start             end                 module name
00007ffd`65e10000 00007ffd`66008000   ntdll    # (pdb symbols)          c:\debugsymbols\ntdll.pdb\A372A05ADCAA11CA41FD4A931AB93BB31\ntdll.pdb
```

Looks like `ntdll` has been loaded! Now, I'll set a breakpoint on `NtOpenProcess` and continue the execution of the VM:

```nasm
1: kd> bp ntdll!NtOpenProcess
1: kd> g
Breakpoint 0 hit
ntdll!NtOpenProcess:
0033:00007ffd`65ead490 4c8bd1          mov     r10,rcx
```

The breakpoint triggers immediately. Of course, handles are being opened all the damn time so it's expected that we get a lot of breakpoint triggers whenever we continue the execution of the machine. This is fantastic. Our disassembly window should look like the following:

<figure><img src="/files/hOJvCYNDlZGqWGBhkzfJ" alt=""><figcaption><p>Disassembly window</p></figcaption></figure>

Yeah, so we get the typical syscall stub which we're very familiar with. I'll step four (`4`) instructions so that we land on the syscall instruction (you could also just set a breakpoint on the syscall instruction itself, but let's exercise our WinDbg command skills a bit):

```nasm
1: kd> p 4
ntdll!NtOpenProcess+0x3:
0033:00007ffd`65ead493 b826000000      mov     eax,26h
ntdll!NtOpenProcess+0x8:
0033:00007ffd`65ead498 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
ntdll!NtOpenProcess+0x10:
0033:00007ffd`65ead4a0 7503            jne     ntdll!NtOpenProcess+0x15 (00007ffd`65ead4a5)
ntdll!NtOpenProcess+0x12:
0033:00007ffd`65ead4a2 0f05            syscall
```

<figure><img src="/files/XaxeY9HTkPcNCjXYfwYL" alt=""><figcaption><p>Stepped to syscall</p></figcaption></figure>

We're currently on the syscall instruction as shown by the chartreuse/lime-ish bar, and our red line is the breakpoint we've set on `ntdll!NtOpenProcess`. Now, let's examine our register values before we step forward with an instruction.&#x20;

```nasm
1: kd> r
rax=0000000000000026 rbx=000001937897f700 rcx=00000067fa37bf18
rdx=0000000000101000 rsi=0000000000000000 rdi=00000000000023ac
rip=00007ffd65ead4a2 rsp=00000067fa37be88 rbp=0000000000100000
 r8=00000067fa37bec0  r9=00000067fa37beb0 r10=00000067fa37bf18
r11=00000067fa37bef8 r12=00000067fa37c150 r13=0000000000000000
r14=00000067fa37c050 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!NtOpenProcess+0x12:
0033:00007ffd`65ead4a2 0f05            syscall
```

Remember, these values are here for a reason and it's especially easy to deduce some of them because some of these values have been placed into the registers directly from the syscall stub we're in. The syscall sub begins by moving the value of the `RCX` register into the `R10` register. We can definitely see this from the listing above that `r11=rcx=00000067fa37bf18`. Next, we move the SSN into the `EAX` register or in this case, the `RAX` register. `RAX` extends the `EAX` register, meaning that whatever's in `EAX`, we'll be able to see it in `RAX`. The SSN of `NtOpenProcess` is `26h=0x26`, and `rax=0000000000000026`, so we're looking good here! That's all of the directly modified registers and their subsequent instructions covered (not including the `ZF` flag that may get set or not by the `test byte ptr [7FFE0308h], 1` happening after the `mov eax, 26h` instruction). Now, let us step forward once and spill the contents of the registers once more.

```nasm
1: kd> p
ntdll!NtOpenProcess+0x14:
0033:00007ffd`65ead4a4 c3              ret
1: kd> r
rax=0000000000000000 rbx=000001937897f700 rcx=00007ffd65ead4a4
rdx=0000000000000000 rsi=0000000000000000 rdi=00000000000023ac
rip=00007ffd65ead4a4 rsp=00000067fa37be88 rbp=0000000000100000
 r8=00000067fa37be88  r9=0000000000100000 r10=0000000000000000
r11=0000000000000346 r12=00000067fa37c150 r13=0000000000000000
r14=00000067fa37c050 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!NtOpenProcess+0x14:
0033:00007ffd`65ead4a4 c3              ret
```

Something cool happens here! The address in our `RIP` register, which is the address that's right after the syscall instruction (the `ret` instruction), is placed in the `RCX` register.

<figure><img src="/files/isNZT0l0CUXwlFMNimoH" alt=""><figcaption><p>Return address placed in <code>rcx</code></p></figcaption></figure>

This is done so that once we're in the kernel, we can leave it and come back to user-land once the kernel issues out the `sysret` instruction, as we'll soon see. Furthermore, this is the expected behaviour which we can confirm by looking at the 5,000-page arcanist's grimoire that is the [Intel® 64 and IA-32 Architectures Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html):

<figure><img src="/files/mOcD6DWVUg3AWSky4uKb" alt=""><figcaption><p><a href="https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html">Intel® 64 and IA-32 Architectures Software Developer Manuals</a>, Vol. 2B 4-687</p></figcaption></figure>

{% hint style="info" %}
If you don't want to download the Intel manual for some reason, or just want an easy web-based reference of some instructions and more present in the manual, then [here](https://www.felixcloutier.com/x86/) you go! The author does state, *several times*, that you shouldn't rely on the site for anything serious but it's still convenient to have, so bookmark it or something.
{% endhint %}

We see that after this instruction, this strange-looking "`IA32_LSTAR`" register places the value that it holds into our `RIP` register. Now, before continuing, we must understand what this register is, what class/family of registers it belongs to, and why it's used here seemingly out of nowhere. To get an answer to all of these questions, we must first understand mode-specific registers (MSRs).

### Model-Specific Registers

From the first sentence in the second chapter of the Intel manual, we read:

> This chapter lists MSRs across Intel processor families. All MSRs listed can be read with the `RDMSR` and written with the `WRMSR` instructions.
>
> — [Intel® 64 and IA-32 Architectures Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html), Vol. 4 2-1

And, we can also read that these MSRs—which are used for performance monitoring, debugging, CPU feature toggling, controlling hardware functions, etc., were first introduced by the Pentium processor which as a consequence, also required the addition of two (`2`) new instructions in order to read/write to them:

> The Pentium processor introduced a set of model-specific registers (MSRs) for use in controlling hardware functions and performance monitoring. To access these MSRs, two new instructions were added to the IA-32 architecture: read MSR (RDMSR) and write MSR (WRMSR). The MSRs in the Pentium processor are not guaranteed to be duplicated or provided in the next generation IA-32 processors.
>
> — [Intel® 64 and IA-32 Architectures Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html), 23-36 Vol. 3B

So, all we know so far is that these specific kinds of registers, which seem to be pretty important, can be read with `RDMSR` (read model-specific register) and written to with `WRMSR` (write model-specific register). What are these instructions? Can *anyone* just invoke them... even from user-land? Well, no. You see, these instructions belong to a specific group of instructions that will only work in kernel-land; i.e., they're called "system" or "privileged" instructions.&#x20;

<figure><img src="/files/LHz3JIq5kptJbtAQ9AUY" alt=""><figcaption><p>List of privileged instructions, Vol. 3A 5-23</p></figcaption></figure>

Okay, well that's nice to know, for sure. If we try to execute one of these instructions when we're not in kernel-land (CPL 0), then a "general-protection exception" is generated. You might be under the impression that whatever these MSRs are (we still haven't actually seen/come across one yet, aside from `IA32_LSTAR`), there are only a few of them. Well... you see, they're called "model-specific" for a reason, and there are a lot of Intel CPU models. So, not only is there a lot of MSRs, but there's enough to warrant Intel dedicating an *entire volume* of their manual to them... Alas, volume four (`4`) of the Intel manual was born:

<figure><img src="/files/Bddg8kkSyYF4aa202Xl9" alt=""><figcaption><p>Exhibit A: WTF (I mean, what did we <em>really</em> expect)</p></figcaption></figure>

Right so, how the hell can we figure out which MSRs our specific model uses? Isn't that the first step? Seeing what we're able to utilize? Yeah, you'll definitely gain an understanding of MSRs and how intricate all of these things are by seeing what you can run. So, let's explore some insane functionality that comes from a single instruction and a bundle of registers that will tell us everything we need to know and seemingly, so much more as well.

### CPUID Enumeration

Intel themselves state that it's very important to determine the processor type of a system and the processor features of said system when developing software that's intended to run on the IA-32 architecture.&#x20;

> When writing software intended to run on IA-32 processors, it is necessary to identify the type of processor present in a system and the processor features that are available to an application.
>
> — [Intel® 64 and IA-32 Architectures Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html), Vol. 1 20-1

To enumerate or identify our specific processor or CPU, we can utilize an instruction made by Intel. Here, we introduce the insanely cool "`CPUID`" instruction.

<figure><img src="/files/sPnYfK2bb4CemnTfzKKd" alt=""><figcaption><p><code>CPUID</code>—CPU Identification, Vol. 2A 3-217</p></figcaption></figure>

This instruction wasn't always available for us to use. It was introduced by Intel in the 90's.&#x20;

> The primary means of identifying a modern x86 or x64 processor is the cpuid instruction. This was developed in the early 1990s for what was then Intel’s new Pentium processor but it also exists in some models of Intel’s 80486 processor and of 80486 look-alikes from other manufacturers.
>
> — Geoff Chappell, [The CPUID Instruction](https://www.geoffchappell.com/studies/windows/km/cpu/cpuid/index.htm)

From the manual, we can also see the way you determine if your processor can invoke the `CPUID` command (realistically, if you have a CPU that wasn't made on Pangea, you'll probably be able to run it) is by seeing if you can set or clear the `ID` flag (which is the 21st bit in the `EFLAGS`/`RFLAGS` register):

<figure><img src="/files/iu1fwdc0nNISFJgoNmPD" alt=""><figcaption><p>Figure 2-5. System Flags in the <code>EFLAGS</code> Register, 2-10 Vol. 3A</p></figcaption></figure>

<figure><img src="/files/tvRKle7lpgd8m2tMEBr0" alt=""><figcaption><p>Vol. 1 3-17</p></figcaption></figure>

<figure><img src="/files/mVyMxWqOlhInH23aajFu" alt=""><figcaption><p>Vol. 1 20-1</p></figcaption></figure>

From the image(s) above, we can see that the `CPUID` instruction, based on what's inside of `EAX`, or sometimes in `ECX` as well, will return a variety of information, which it'll store in the `EAX`, `EBX`, `ECX`, and `EDX` registers! This is incredible! So, even though the `CPUID` instruction itself doesn't take in any operands, it does seem to work based on two (`2`) pseudo-inputs that *we* can *definitely* write to. Those being the `EAX` and `ECX` registers! Unlike certain instructions, the `CPUID` instruction also doesn't modify any of the `EFLAGS` when invoked, which we can see from the tables below:

<figure><img src="/files/H2Gc7dKMKjSKE5s8nOTn" alt=""><figcaption><p>Table A-1. Codes Describing Flags, Vol. 1 A-1</p></figcaption></figure>

<figure><img src="/files/eRJ8nrE2LV3ORxJBoHN4" alt=""><figcaption><p>Table A-2. <code>EFLAGS</code> Cross-Reference, Vol. 1 A-1</p></figcaption></figure>

The same can be said about our best friends, the `RDMSR`/`WRMSR` instructions:

<figure><img src="/files/xICL1588uCN3MKbXIb0U" alt=""><figcaption><p>Table A-2. <code>EFLAGS</code> Cross-Reference (Contd.), Vol. 1 A-3</p></figcaption></figure>

<figure><img src="/files/eT74unbSk0ylrR7YxecV" alt=""><figcaption><p>Table A-2. <code>EFLAGS</code> Cross-Reference (Contd.), Vol. 1 A-4</p></figcaption></figure>

Furthermore, there exists a WinDbg "`CPUID`" command that we can issue. Now, since we're virtualizing this, it's most likely not going to be the *exact* same values we'd get on our host machine. However, since the CPU is the same, we should definitely see at least some overlap (the biggest disparity is the cores and threads return values since we can selectively choose this for our virtual machines). Since the `CPUID` instruction itself isn't a privileged/system instruction, we can invoke it through WinDbg from user-mode or kernel-mode with [`!cpuid`](https://learn.microsoft.com/en-us/windows-hardware/drivers/debuggercmds/-cpuid):

```nasm
1: kd> !cpuid
CP  F/M/S  Manufacturer     MHz
 0  6,165,5  GenuineIntel    3792
 1  6,165,5  GenuineIntel    3792
 2  6,165,5  GenuineIntel    3792
 3  6,165,5  GenuineIntel    3792
```

These counts and values seem to match what I've set for the VM, so this is looking good:

<figure><img src="/files/HHOv5UI6UIT6OCnNAQuA" alt=""><figcaption><p>VM processor configuration</p></figcaption></figure>

> The **CP** column gives the processor number. (These numbers are always sequential, starting with zero). The **Manufacturer** column specifies the processor manufacturer. The **MHz** column specifies the processor speed, if it is available.
>
> — [MSDN](https://learn.microsoft.com/en-us/windows-hardware/drivers/debuggercmds/-cpuid)

From the Intel manual, we're actually told to look for this "GenuineIntel" string first and foremost:

<figure><img src="/files/BUbFsy5cgIKetiqGDCmE" alt=""><figcaption><p>Intel's tasks for <code>CPUID</code> usage</p></figcaption></figure>

Moreover, from [here](https://en.wikipedia.org/wiki/CPUID), we can see that "GenuineIntel" is a known ID string for processor manufacturers, "soft CPU cores", and VMs alike (It seems like in the beginning, AMD's processors had the "AMDisbetter!" manufacturer ID string, haha):

<figure><img src="/files/O8tEE46GLzZnXUlfzo5F" alt=""><figcaption><p>CPU Manufacturer ID strings</p></figcaption></figure>

Although this is really cool and we're unlocking an expansive understanding of hyper-niche things most people wouldn't ever care about, this still leaves us with the ever-beckoning question:&#x20;

> "What now?"&#x20;
>
> — Everyone

I mean, we have our `CPUID` instruction, but how does this help us figure out what MSRs we're capable of using? Secondly, from the image above where we're told to always look for "GenuineIntel", but there was also a second point about testing "feature identification flags individually" and that we should not "make assumptions about undefined bits." Does this second portion unlock that piece of the puzzle for us? Well, hold on a little longer, my litle dorks. The `CPUID` instruction is actually an incredible gift to us. I took the [`U_CPUID`](https://gitlab.com/opensecuritytraining/arch2001_x86-64_os_internals_code_for_class/-/tree/master/U_CPUID?ref_type=heads) project created by the incredible [Xeno Kovah](https://opensecuritytraining.info/About.html) and reformatted it into a program that utilizes inline assembly (Thanks to the love of my life, [x0reaxeax](http://x0reaxe.ax/), for blessing my life by showing me this compiler). Anyways, from whatever values you set for `EAX` or `ECX`, you'll get the subsequent outputs in the `E[A-D]X` registers. So, let's see what we're able to find!

<figure><img src="/files/eSwEZFluJF5lQycXUnI9" alt=""><figcaption><p>Program's output</p></figcaption></figure>

There are so many possible input values you can set for the `EAX` register, although, you may also be limited by that first value that we look for; the "maximum input value", which for my CPU (this is all done on my host machine, by the way) is 22. Showcasing more of the splendid information we gather from `CPUID`, and the `EAX`/`ECX` inputs, in the screenshot below, we can see that I've set the `EAX` register to seven (`7`) and set the `ECX` register to zero (`0`) (it was set to a non-zero value before this point, which is why I'm resetting it) to return some security-related features present (or not present) on the processor (which is what was originally showcased by Xeno):

<figure><img src="/files/zTS9OwGPp9ZephMo73TB" alt=""><figcaption><p><code>EAX</code>/<code>ECX</code> input values for <code>CPUID</code></p></figcaption></figure>

Looking in the manual, we'll see that this gives us an output for the "Structured Extended Feature Flags Enumeration Leaf."

<figure><img src="/files/xzgncWzBJS5g9oWOEH9F" alt=""><figcaption><p>Table 3-8. Information Returned by <code>CPUID</code> Instruction (Contd.), Vol. 2A 3-221</p></figcaption></figure>

There's so much more information and it goes past the `EBX` register as well. We can see that there are more things to enumerate based on the bits of other registers:

<figure><img src="/files/cEAn1kyItEofTojBTRly" alt=""><figcaption><p>Table 3-8. Information Returned by <code>CPUID</code> Instruction (Contd.), 3-222 Vol. 2A</p></figcaption></figure>

And all of this is for one (`1`) single input of `EAX=0x7`! I've even decided to look into the `ECX` outputs while pasting this screenshot, and it's super easy to bitshift for our specific bit and then see if it's set with a logical `AND` operation (`&`) as well!

<figure><img src="/files/Rvg4syuX2gmLBd6VF4xA" alt=""><figcaption><p><code>ECX</code> outputs from <code>CPUID</code></p></figcaption></figure>

I was interested in looking for the `PKU` (`Bit 03: PKU. Supports protection keys for user-mode pages if 1`) and the `CET_SS` (`Bit 07: CET_SS. Supports CET shadow stack features if 1`):

<figure><img src="/files/NH2gWsmmkovZdrB5PzNQ" alt=""><figcaption><p>Final output</p></figcaption></figure>

{% hint style="info" %}
If you're curious about what `SMEP`, `SMAP`, and `KVAS` are, you're urged to read the following [blog post](https://www.welivesecurity.com/2022/01/11/signed-kernel-drivers-unguarded-gateway-windows-core/) for a really nice introduction.
{% endhint %}

Please feel free to play around with your own input values or whatever, and thank you once again to the amazing [Xeno Kovah](https://opensecuritytraining.info/About.html) and [OS2](https://opensecuritytraining.info/Home.html) for all of their hard work and for keeping education and information free (please check them out, you won't regret it — everything you read here, they've covered in their free [Architecture 2001: x86-64 OS Internals](https://p.ost2.fyi/courses/course-v1:OpenSecurityTraining2+Arch2001_x86-64_OS_Internals+2021_v1/course/) class)! You can find the source code attached below if you'd like to tinker around with this more:

{% hint style="danger" %}
If you're intending to use these files, make sure you compile them with the Intel compiler. You can install it from [here](https://www.intel.com/content/www/us/en/developer/articles/tool/oneapi-standalone-components.html#dpcpp-cpp). Once you've installed the compiler, you can set it in Visual Studio by right-clicking on your solution in the Solution Explorer and then going to `Intel Compiler` `>` `Use Intel oneAPI DPC++/C++ Compiler`. Then, you're ready to compile and use the program. Have fun!
{% endhint %}

{% file src="/files/W9dUZ8zAIgAX24c9aO1Q" %}

{% file src="/files/EnIkjFMktnPyk4W2D7j4" %}

{% file src="/files/GfzieRb2FsgkUbg6asVn" %}

### Finding MSRs For Your Processor

The way we can find which MSRs are utilized by or available to our processor(s) is by enumerating the "Processor Brand String" and then doing a lookup on it in the manual. Although, note that this brand string isn't available on *all* processors. We can find the processor brand string; which looks like this: `Intel(R) Core(TM) i7-10700K CPU`, by once again, using our lovely `CPUID` instruction. Luckily, there exists a flow chart and some logic for us to determine if we're able to retrieve this brand string as seen in the following picture:

<figure><img src="/files/d1Qa8DGKSHAnaZ1ld7Tj" alt=""><figcaption><p>Figure 3-9. Determination of Support for the Processor Brand String, Vol. 2A 3-255</p></figcaption></figure>

We input `0x80000000` as the input in `EAX`, and if the returned value in `EAX` is $$\ge$$ `0x80000004`, then we know we have processor brand string support. Otherwise, we don't. I'll quickly perform this test to see what I get:

```c
	eax_input = 0x80000000;
	exec_cpuid(
		eax_input,
		ecx_input,
		&eax_output,
		&ebx_output,
		&ecx_output,
		&edx_output
	);
	(eax_output >= 0x80000004) 
		? OKAY("[Processor Brand String] Supported!") 
		: WARN("[Processor Brand String] Not Supported!");
```

Setting a breakpoint on the `(eax_output >= 0x80000004)` line, I'm able to see that my `EAX` return value was  `>0x80000004` which means I have processor brand string support:

<figure><img src="/files/2SoXS9tvrQxqAdXZXR7H" alt=""><figcaption><p><code>EAX</code> return value</p></figcaption></figure>

<figure><img src="/files/pTdKuuxL405BPzw5FSE2" alt=""><figcaption><p>Program output</p></figcaption></figure>

> To use the brand string method, execute `CPUID` with `EAX` input of `8000002H` through `80000004H`. For each input value, `CPUID` returns 16 ASCII characters using `EAX`, `EBX`, `ECX`, and `EDX`. The returned string will be NULL-terminated.
>
> — [Intel® 64 and IA-32 Architectures Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html), Vol. 2A 3-255

And they graciously give us a table that shows us what we should be expecting for each iteration of the input values and how the brand string builds up little by little until it's finally null-terminated as a proper string:

<figure><img src="/files/Fyn2EIaujgTYD0RrLwoh" alt=""><figcaption><p>Table 3-13. Processor Brand String Returned with Pentium 4 Processor + (Contd.), Vol. 2A 3-255 - 3-256 Vol. 2A</p></figcaption></figure>

Furthermore, down below, we can see the strings getting constructed and finally, an input of `0x80000005`, seems to be null-terminating the strings! This is so cool.

<figure><img src="/files/dmhgzVcatYgytZkSWvYl" alt=""><figcaption><p>Table 3-8. Information Returned by CPUID Instruction (Contd.), Vol. 2A 3-239</p></figcaption></figure>

So, I very hackily programmed out a simple iteration that will run the `CPUID` instruction with `EAX` values going from `0x80000002` to `0x80000005` and append the register's return values (which are going to be hexadecimal representations of strings) to our `brand_string` array:

```c
eax_input = 0x80000000;
exec_cpuid(
	eax_input,
	ecx_input,
	&eax_output,
	&ebx_output,
	&ecx_output,
	&edx_output
);
(eax_output >= 0x80000004) 
	? OKAY("[Processor Brand String] Supported!") 
	: WARN("[Processor Brand String] Not Supported!");

for (int i = 0, j = 0; i < 4; i++) {
	exec_cpuid(
		0x80000002 + i, 
		0, 
		&eax_output, 
		&ebx_output, 
		&ecx_output, 
		&edx_output
	);
	memcpy(brand_string + j, &eax_output, sizeof(uint32));
	j += sizeof(uint32);
	memcpy(brand_string + j, &ebx_output, sizeof(uint32));
	j += sizeof(uint32);
	memcpy(brand_string + j, &ecx_output, sizeof(uint32));
	j += sizeof(uint32);
	memcpy(brand_string + j, &edx_output, sizeof(uint32));
	j += sizeof(uint32);
}
brand_string[48] += '\0';
OKAY("[Processor Brand String] %s", brand_string);
```

Running this, we get the following output:

<figure><img src="/files/KDiQ3mehgdQoI3XguFOn" alt=""><figcaption><p>Processor brand string output</p></figcaption></figure>

We've now figured out what we could've done in like two seconds had we just run any processor information command in a terminal or from a Windows menu somewhere—but hey, we did it all manually... from assembly! We now see that our (or in this case, *my*) processor belongs to the "Core(TM)" family as well as belonging to the 10th generation (the "10" in "10700K") of the I7 processors, which I can now cross-reference in the manual to see which specific MSRs are available to me.  Searching for a second or two, I came across this section, which seems to be the right place for my processor:

<figure><img src="/files/ccg7VtimRxNxgdbnW0x7" alt=""><figcaption><p>2-330 Vol. 4</p></figcaption></figure>

Using a tool like [`HWiNFO`](https://www.hwinfo.com/), we can confirm this to be true:

<figure><img src="/files/NBLtTx3LDmkPHtcaPrpN" alt=""><figcaption><p>Microarchitecture checks out!</p></figcaption></figure>

And, even better, we can actually see all of those security-related values we were manually finding with `EAX`, like `SMEP`, `SMAP`, `SGX`, etc. This is a very useful tool, indeed!

<figure><img src="/files/JKrB5cP8EvRTsPAzZTBO" alt=""><figcaption><p>Revalidating our previous findings </p></figcaption></figure>

***

To make an `unsigned` `long` story `short`, after finding out the respective processor section(s) that are dedicated to our processor *specifically*, we can list all the MSRs supported by our CPU. In my case, here are *some* of them (the list is still long):

<figure><img src="/files/UNHIRCEPxkuoEPU6zD1y" alt=""><figcaption><p>Table 2-44. MSRs Supported by the 10th Generation Intel® Core™ Processors (Ice Lake Microarchitecture), Vol. 4 2-363</p></figcaption></figure>

## The Invocation (Contd.)

Now that we're somewhat familiar with these MSRs and how to find them for our specific processor, we still have the `IA32_LSTAR` MSR to contemplate. It's one of three (`3`) MSRs which are used by the syscall (entering the kernel-space) and sysret (leaving the kernel-space) instructions, as seen in the figure below:

<figure><img src="/files/Y4P58PexqbkMsHgQPujS" alt=""><figcaption><p>Figure 5-14. MSRs Used by SYSCALL and SYSRET, Vol. 3A 5-23</p></figcaption></figure>

Our microarchitecture (along with many others as well) has support for these three MSRs; which I know is obvious to us reading it, especially since the three MSRs are all architectural MSRs, but we can see that it's *explicitly* defined in the following table and we're given the register addresses of these MSRs so that we can read/write to them with those privileged/system `RDMSR`/`WRMSR` instructions:

<figure><img src="/files/VSun8k90PCFMgNnoFwvc" alt=""><figcaption><p>Table 2-3. MSRs in Processors Based on Intel® Core™ Microarchitecture (Contd.), 2-80 Vol. 4</p></figcaption></figure>

So, let's try this out! I'm very curious to see what the respective values of these registers are. Especially the `IA32_LSTAR` register since it's the one that changes our `RIP` register to whatever it's pointing to—meaning that it's directly responsible and directly influences what we start executing in kernel-land first! In our debugger, I've reached the syscall instruction again, and now, I'll issue the `rdmsr` instruction with the register address of the `IA32_LSTAR` MSR, which from the table above, we know is `0xC0000082`.&#x20;

{% hint style="danger" %}
Sometimes, the symbols for `ntdll` don't load properly, which has been a well-documented issue with WinDbg. It's an extremely annoying problem which is consequently threatening to derail this entire operation and I don't have the patience (for once) to debug this and residually accumulate grey hairs because of it. Instead, I've just shut down the VM and exited WinDbg Preview. Upon starting, I immediately got a hit on the `NtOpenProcess` breakpoint (I don't even remember setting this breakpoint, LOL), so thankfully, resetting both the debugger and VM seemed to have resolved the issue for me. But yeah, it's a common issue which you can read about below
{% endhint %}

{% embed url="<https://stackoverflow.com/questions/35979342/symbols-for-ntdll-dll-could-not-be-downloaded-from-microsoft-symbol-servers>" %}
`ntdll` symbols problem
{% endembed %}

***

```nasm
kd> g
KDTARGET: Refreshing KD connection
Breakpoint 0 hit
ntdll!NtOpenProcess:
0033:00007ffb`eb08d490 4c8bd1          mov     r10,rcx
```

Now, let's see what that `IA32_LSTAR` MSR was pointing to by issuing the following privileged `rdmsr <address>` instruction:

```nasm
0: kd> rdmsr 0xc0000082
msr[c0000082] = fffff803`63610d40
```

Huh, so the `IA32_LSTAR` MSR *is* pointing to an address; Intel didn't lie to us! This will be the first thing we run in the kernel after we invoke a syscall, so, let's get some more information about this address, what *is* this?

<figure><img src="/files/WxTRRitftYyrIHyzw2o8" alt=""><figcaption><p><code>IA32_LSTAR</code> is set to <code>nt!KiSystemCall64</code></p></figcaption></figure>

Incredible! So, it seems like the first thing we run once we're in kernel-land is the `KiSystemCall64` kernel routine, which is exported by `nt`. Before continuing, I had to settle a curiosity I had about the value of `IA32_LSTAR`. *Where* or *when* does it get the address value of "`0xfffff80363610d40`"? Well, after doing a bit of searching online and reading (and pretending to understand) way too many 中国語とロシア語で書かれているブログ記事, I was directed towards the `KiInitializeBootStructures` routine. Which, as we can deduce from the name, is a kernel routine that is responsible for initializing boot structures. These are amongst the first things that get initialized in our computers.&#x20;

> The entry point of the NT Kernel is called `KiSystemStartup`**,** and is invoked by the OS loader. `KiSystemStartup` in turn performs basic initialization of several things, one of which is the calling of `KiInitializeBootStructures`**.**
>
> — [Minh Tran](https://www.fortinet.com/blog/search?author=Minh+Tran), [A Deep Dive Analysis of Microsoft’s Kernel Virtual Address Shadow Feature](https://www.fortinet.com/blog/threat-research/a-deep-dive-analysis-of-microsoft-s-kernel-virtual-address-shadow-feature)

Not only that, but the other MSRs we've come across seem to get their values set here as well. We see this routine regularly utilizing the privileged `WRMSR` instruction (which is definitely expected since we're initializing very important kernel things) to give our beloved MSRs their values.

<pre class="language-nasm"><code class="lang-nasm">nt!KiInitializeBootStructures+0x3ad:
fffff803`63b9ebed 33c0            xor     eax,eax
fffff803`63b9ebef ba10002300      mov     edx,230010h
<strong>fffff803`63b9ebf4 b9810000c0      mov     ecx,0C0000081h
</strong>fffff803`63b9ebf9 0f30            wrmsr   ; takes EAX:EDX -> MSR[ECX]
fffff803`63b9ebfb 488bd3          mov     rdx,rbx
fffff803`63b9ebfe 488bc3          mov     rax,rbx
fffff803`63b9ec01 48c1ea20        shr     rdx,20h
<strong>fffff803`63b9ec05 b9830000c0      mov     ecx,0C0000083h
</strong>fffff803`63b9ec0a 0f30            wrmsr
fffff803`63b9ec0c 488bd6          mov     rdx,rsi
fffff803`63b9ec0f 488bc6          mov     rax,rsi
fffff803`63b9ec12 48c1ea20        shr     rdx,20h
<strong>fffff803`63b9ec16 b9820000c0      mov     ecx,0C0000082h
</strong>fffff803`63b9ec1b 0f30            wrmsr
fffff803`63b9ec1d b800470000      mov     eax,4700h
fffff803`63b9ec22 33d2            xor     edx,edx
<strong>fffff803`63b9ec24 b9840000c0      mov     ecx,0C0000084h
</strong>fffff803`63b9ec29 0f30            wrmsr
fffff803`63b9ec2b 8bd5            mov     edx,ebp
fffff803`63b9ec2d 488bcf          mov     rcx,rdi
fffff803`63b9ec30 e833020000      call    nt!KiInitPrcb (fffff803`63b9ee68)
fffff803`63b9ec35 e84e000000      call    nt!ExInitPoolLookasidePointers (fffff803`63b9ec88)
fffff803`63b9ec3a 85ed            test    ebp,ebp
fffff803`63b9ec3c 7528            jne     nt!KiInitializeBootStructures+0x426 (fffff803`63b9ec66)  Branch
</code></pre>

{% hint style="info" %}
The [`WRMSR`](https://www.felixcloutier.com/x86/wrmsr) instruction works by taking the values inside of `EAX` and `EDX` and placing them into the MSR we specify inside of `ECX`. Similarly, the [`RDMSR`](https://www.felixcloutier.com/x86/rdmsr) instruction works by reading the MSR we specify inside of `ECX` into `EDX` and `EAX`.
{% endhint %}

So, there's that mystery solved, the `IA32_STAR` (`0xC0000081`), `IA32_LSTAR` (`0xC0000082`), `IA32_CSTAR` (`0xC0000083`), `IA32_FMASK` (`0xC0000084`) are all being set during boot time by `KiInitializeBootStructures`.

### KiSystemCall64

I think this is where we really start getting into the meat and bones of this entire process. The `KiSystemCall64` function is responsible for taking our SSN and finding the appropriate kernel routine to run. In other words, its main task is to dispatch syscall requests (although it *can* do more than that but that's a topic for another time). The way it does this is really interesting and it's what we'll explore in this section. However, a quick note about "KVA Shadowing" and its role in deciding which dispatching routine you'll be running.

{% hint style="info" %}
[KVA Shadowing](https://msrc.microsoft.com/blog/2018/03/kva-shadow-mitigating-meltdown-on-windows/) is a mitigation for the devastating "[meltdown](https://meltdownattack.com/meltdown.pdf)" vulnerability discovered in 2018. I'd *really* like to get into the details of KPTI, KVAS (which is the Windows implementation of KPTI), meltdown/spectre, and all of that, but I'm forcing myself to be as brief as I can so that I can maybe talk about it in another post. However, if you're interested, check out the links: [here](https://labs.bluefrostsecurity.de/blog/2020/06/30/meltdown-reloaded-breaking-windows-kaslr/), [here](https://www.fortinet.com/blog/threat-research/a-deep-dive-analysis-of-microsoft-s-kernel-virtual-address-shadow-feature), and [here](https://msrc.microsoft.com/blog/2018/03/kva-shadow-mitigating-meltdown-on-windows/). So, there are a lot of kernel routines that are either the "normal" routines or the routines appended with "Shadow" to indicate that your processor requires KVA Shadowing because it's vulnerable to meltdown. `KiSystemCall(32|64)` is no different in this regard; you may see `KiSystemCall(32|64)Shadow` in its stead. The way the kernel decides if it's going to use the shadow routines (that sounds so cool, btw) or not is based on if `KiKvaShadow` is set to one (`1`) or not. And what sets this value, you may ask? Well, we've already met the thing responsible! `KiEnableKvaShadowing` is responsible for setting `KiKvaShadow`, but `KiEnableKvaShadowing` itself is run by `KiInitializeBootStructures`! So, yeah. It's determined during boot!
{% endhint %}

If we examine the address we had returned by our `rdmsr` command, we get the following:

<pre class="language-nasm"><code class="lang-nasm">2: kd> rdmsr 0xc0000082
msr[c0000082] = fffff804`16a10d40

2: kd> u fffff804`16a10d40
<strong>nt!KiSystemCall64:
</strong>fffff804`16a10d40 0f01f8          swapgs
fffff804`16a10d43 654889242510000000 mov   qword ptr gs:[10h],rsp
fffff804`16a10d4c 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]
fffff804`16a10d55 6a2b            push    2Bh
fffff804`16a10d57 65ff342510000000 push    qword ptr gs:[10h]
fffff804`16a10d5f 4153            push    r11
fffff804`16a10d61 6a33            push    33h
fffff804`16a10d63 51              push    rcx

2: kd> ln fffff804`16a10d40
Browse module
Set bu breakpoint

(fffff804`16a10d40)   nt!KiSystemCall64   |  (fffff804`16a10f7a)   nt!KiSystemServiceUser
Exact matches:
</code></pre>

Alright, so `KiSystemCall64` is the first thing we run once we're in the Kernel! The function itself is incredible and begins the coolest process of this entire trek. You see, to dispatch our syscall request to the kernel, the kernel actually needs to be given the right syscall index so that we can match it against a list of other possible kernel routines; obviously, so we run the function we're dispatching instead of some random function. That would be disastrous, to say the least. So, to begin this process, we must first extract the SSN that we had in `EAX`, and supply it as an index to the array of kernel routines there are.

We'll get to that shortly, but first, we need to understand what the `KiSystemCall64` instruction does. First, it invokes the `swapgs` instruction, which from the screenshot below, we can see just exchanges the `IA32_GS_BASE` (`0xC0000101`) MSR with the value in the `IA32_KERNEL_GS_BASE` (`0xC0000102`) MSR. Okay, so reading the MSR value here with `rdmsr`, we'll see which address gets put in here.&#x20;

<figure><img src="/files/FU5DdwxPDbQfnpXYFen4" alt=""><figcaption><p>coming soon, pals.</p></figcaption></figure>

### The System Service Number (SSN)

### Table Identifier

### The SSDT

## The Return

## References

{% tabs %}
{% tab title="System Calls References" %}
{% embed url="<https://alice.climent-pommeret.red/posts/a-syscall-journey-in-the-windows-kernel/>" %}

{% embed url="<https://p.ost2.fyi/courses/course-v1:OpenSecurityTraining2+Arch2001_x86-64_OS_Internals+2021_v1/about>" %}

{% embed url="<https://stl-tec.de/tutorials/WinReverseEng/syscalls/>" %}

{% embed url="<https://codemachine.com/articles/system_call_instructions.html>" %}

{% embed url="<https://www.amossys.fr/insights/blog-technique/windows10-th2-int2e-mystery/>" %}

{% embed url="<https://kristal-g.github.io/2021/05/08/SYSRET_Shellcode.html>" %}

{% embed url="<https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/glimpse-into-ssdt-in-windows-x64-kernel>" %}

{% embed url="<https://community.osr.com/discussion/230820/finding-the-active-gs-base-in-win-7-x64-kernel>" %}

{% embed url="<https://www.unknowncheats.me/forum/c-and-c-/478794-hook-kisystemcall64-kisystemcall64shadow-bugcheck-double-fault.html#post3302868>" %}

{% embed url="<https://medium.com/tenable-techblog/api-series-setthreadcontext-d08c9f84458d>" %}

{% embed url="<https://www.welivesecurity.com/2022/01/11/signed-kernel-drivers-unguarded-gateway-windows-core/>" %}

{% embed url="<https://www.fortinet.com/blog/threat-research/a-deep-dive-analysis-of-microsoft-s-kernel-virtual-address-shadow-feature>" %}

{% embed url="<https://www.matteomalvica.com/minutes/windows_kernel/>" %}

{% embed url="<https://wiki.osdev.org/SWAPGS>" %}

{% embed url="<https://resources.infosecinstitute.com/topics/hacking/kernel-exploitation-part-2/>" %}
{% endtab %}

{% tab title="CPUID References" %}
{% embed url="<https://www.hugi.scene.org/online/coding/hugi%2016%20-%20corawhd4.htm>" %}

{% embed url="<https://www.geoffchappell.com/studies/windows/km/cpu/cpuid/index.htm>" %}

{% embed url="<https://en.wikipedia.org/wiki/CPUID>" %}
{% endtab %}

{% tab title="Kernel Debugging References" %}
{% embed url="<https://fishilico.github.io/generic-config/windows/windbg-kd.html>" %}

{% embed url="<https://voidsec.com/windows-drivers-reverse-engineering-methodology/#remote-kernel-debugging>" %}

{% embed url="<https://scorpiosoftware.net/2023/03/07/levels-of-kernel-debugging/>" %}

{% embed url="<https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/performing-local-kernel-debugging#commands-that-are-not-available>" %}
{% endtab %}

{% tab title="Manuals" %}
{% embed url="<https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html>" %}

{% embed url="<https://www.felixcloutier.com/x86/>" %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://archive.crow.rip/nest/os/internals/syscalls.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
