Files
2020-02-17 14:05:02 +01:00

213 lines
8.3 KiB
Markdown

# Intro
PoC gdb debugging the Windows 10 secure kernel, with examples of use:
* Tracing secure calls
* Tracing `Skpg*` calls
* Debugging `LsaIso.exe` (Credential Guard)
At the time of writing, it works on a Win10 1903, Windows Server 2019 and Win10 January 2020 Insider preview (Build 19550).
# Setup
:warning: this PoC has been done on an Ubuntu 19.04. Bugs and working elements
are only relevant at the time of writing.
Requirements:
* QEMU-KVM: version from the package does not include the possibility to read /
write physical memory (support for `qemu.PhyMemMode` packets). We'll then use the version
from [the repository](https://github.com/qemu/qemu)
* Win10 image
* A kernel supporting some KVM extensions. The current `5.0.0-38` version
crashes qemu while booting Win10 with VSM. For the experiments, the
`4.15.0-1066-oem` is used (available from Ubuntu repository)
* Secure-boot, with OVMF. The Ubuntu version (from repository) does not include variables with preconfigured windows Key. The fedora version can be used (or the one in this repository)
Steps:
1. Get a working version of Win10 on QEMU-KVM
2. Extracts `securekernel.exe` from this installation. One can use qemu-nbd:
```
rmmod nbd
modprobe nbd max_part=16
qemu-nbd -c /dev/nbd0 drive.vdi
mount /dev/nbd0p1 /mnt
cp /mnt/.../System32/securekernel.exe /dest
qemu-nbd -d /dev/nbd0
```
3. Extracts a few interesting addresses from this binary. It can be done manually, or using an IDA script (`extract_ida.py`)
4. Run Win10 with QEMU-KVM, enabling secureboot, KVM. Some englightments can be added, but are not required. One can even disable the hypervisor advertising (`hypervisor=off`). Example of command:
```
qemu/x86_64-softmmu/qemu-system-x86_64 -snapshot -drive file=/path/to/win10.qcow2,format=qcow2,if=none,id=drive-sata0-0-0 -device ide-hd,bus=ide.0,drive=drive-sata0-0-0,id=sata0-0-0,bootindex=1 -cpu host,hv_time,kvm=off,hv_vendor_id=null,hypervisor=off -enable-kvm -m 4096 -global driver=cfi.pflash01,property=secure,value=on -net none -pflash /path/to/OVMF_CODE.secboot.fd -pflash /path/to/OVMF_VARS.secboot.copy.fd -machine pc-q35-3.1,smm=on -global driver=cfi.pflash01,property=secure,value=on -device pcie-root-port,port=0x10,chassis=1,id=pci.1,bus=pcie.0,multifunction=on,addr=0x2 -device pcie-root-port,port=0x11,chassis=2,id=pci.2,bus=pcie.0,addr=0x2.0x1 -device pcie-root-port,port=0x12,chassis=3,id=pci.3,bus=pcie.0,addr=0x2.0x2 -device pcie-root-port,port=0x13,chassis=4,id=pci.4,bus=pcie.0,addr=0x2.0x3 -device qemu-xhci,p2=15,p3=15,id=usb,bus=pci.2,addr=0x0 -s`
```
The `-s` is used to automatically starts the `gdb stub` on `127.0.0.1:1234`. One can start it at runtime using the qemu console (command `gdbserver`).
5. Once the Win10 correctly starts, ensure Virtual Based Security capabilities are enabled. Depending on the experiments, one can enable Device Guard (through the local console), HVCI (through the "Core memory protection"), ...
Once these steps have been done, one can now attach to the secure kernel.
First, once the VM is running, attach to it with `gdb`, disable gdb pagination and display the current instruction:
```
(gdb) target remote 127.1:1234
(gdb) set pagination off
(gdb) display /i $pc
```
The difficulty is now to retrieve the virtual address of the secure kernel. There are several way to do it:
* Inspect the memory, looking for VMCS structures
* Inspect the memory, looking for the securekernel, then rebuilding back Pagination tables and associated addresses
* Breaking early during the startup, in `hvloader`: the securekernel will report back its address
* Breaking in Hyper-V, looking for VTL transition (hint: handling of hypercalls 0x11 and 0x12)
This PoC is based on a more pragmatic / ulgy way to do it.
First, let's load the `find_patch_phys.py` script in gdb (after patching the
constants inside, like the `securekernel.exe` path):
```
(gdb) source path/to/find_patch_phys.py
```
Now, let's find `securekernel` in the physical memory, patching the
`IumInvokeSecureService` entry point with an infinite loop. This function is
called to perform "secure calls", and is at least called by the normal world
every second to perform "periodic" work.
```
(gdb) find_patch_ium_ep
Look for 'IumInvokeSecureService' in physical memory...
0x0
0x1000000
0x2000000
0x3000000
0x4000000
0x5000000
0x6000000
Found at 0x6c4a0b0!
Replace with infinite loop...
```
Once the memory has been patched, we let the VM continue for a while (a few seconds is enough):
```
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0xfffff80608a130b0 in ?? ()
1: x/i $pc
=> 0xfffff80608a130b0: jmp 0xfffff80608a130b0
```
We can check we actually ends on an infinite loop. If so, we note the virtual address, and fix the memory:
```
(gdb) fix_ium_ep
Base VA: 0xfffff804c8a00000
```
At this stage, we have the virtual address used by the secure kernel. Then, we
can break on it from anywhere using hardware breakpoints, or use software
breakpoints when we are in the secure kernel context (QEMU gdb stub can support
any of them).
If, while using a script, one need to get back in the context of the secure kernel, the following warning will be printed:
```
(gdb) my_command
Please launch this command in the context of the secure kernel
hint: hbreak *(0xfffff8040d0900b0)
```
One can do:
```
(gdb) hbreak *(0xfffff8040d0900b0)
Hardware assisted breakpoint 14 at 0xfffff8040d0900b0
(gdb) c
Continuing.
Breakpoint 14, 0xfffff8040d0900b0 in ?? ()
1: x/i $pc
=> 0xfffff8040d0900b0: mov %rbx,0x18(%rsp)
(gdb) del 14
```
# Examples
## Logging SECURE CALLs
We'll break on `IumInvokeSecureService` and inspect its argument to get the secure call number.
The translation is made thanks to the `_SKSECURESERVICE` enum, available in `ntoskernel.pdb`.
One can extract it with IDA or WinDbg, and convert it to a JSON file thanks to
the `log_securecalls/convert_sknum.py` script.
Then, we can import the script and run it:
```
(gdb) source log_securecalls/log_securecalls.py
(gdb) log_securecall /path/to/sknums.json
Breakpoint 15 at 0xfffff8040d0900b0
(gdb) c
Continuing.
[IumInvokeSecureService] SECURESERVICE_FAST_FLUSH_RANGE_LIST
[IumInvokeSecureService] SECURESERVICE_FAST_FLUSH_RANGE_LIST
[IumInvokeSecureService] SECURESERVICE_FAST_FLUSH_RANGE_LIST
...
```
![IumInvokeSecureService](screen_ium.png?raw=true)
## Logging Hyperguard
Thanks to the support of many breakpoint, we'll break on all `securekernel.exe` functions starting with "Skpg".
To do so:
```
(gdb) source /path/to/log_skpg.py
(gdb) log_skpg
Breakpoint 19 at 0xfffff8040d0a99f8
Breakpoint 20 at 0xfffff8040d0f35d8
Breakpoint 21 at 0xfffff8040d0a6248
Breakpoint 22 at 0xfffff8040d0f3804
Breakpoint 23 at 0xfffff8040d07e1a0
Breakpoint 24 at 0xfffff8040d089518
Breakpoint 25 at 0xfffff8040d08938c
Breakpoint 26 at 0xfffff8040d07e6e4
Breakpoint 27 at 0xfffff8040d0a52f8
...
(gdb) c
Continuing.
[SkpgPeriodicEvent]
[SkpgPeriodicEvent]
[SkpgPeriodicEvent]
[SkpgPeriodicEvent]
```
![Skpg log](screen_skpg.png?raw=true)
## Debugging LsaIso
Ium binaries specifies if their are debuggable or not. When attaching to one of
this process, the secure kernel function `SkpsIsProcessDebuggingEnabled` is
called to retrieve if the debugging is enabled or not.
Here, we simply always return 1.
```
(gdb) source /path/to/enable_debugging.py
(gdb) enable_ium_debug
Breakpoint 1 at 0xfffff80608a7961c
(gdb) c
Continuing.
```
Now, we can attach to `LsaIso` in WinDbg and observes the patch in the gdb output:
```
[SkpsIsProcessDebuggingEnabled] Enter
Breakpoint 2 at 0xfffff80608a133e0
[SkpsIsProcessDebuggingEnabled] Exit - Fix RAX
```
![LsaIso](screen_lsaiso.png?raw=true)
# References
* Hyper-V debugging for beginners, from [@gerhart_x](https://twitter.com/gerhart_x) [Link](http://hvinternals.blogspot.com/2015/10/hyper-v-debugging-for-beginners.html)
* BlackHat 2015: BATTLE OF SKM AND IUM: HOW WINDOWS 10 REWRITES OSARCHITECTURE, from Ionescu [Link](http://www.alex-ionescu.com/blackhat2015.pdf)
* BlackHat 2017: Fractured Backbone: Breaking Modern OS Defenses with Firmware Attacks, from Bulygin and al. [Link](https://www.blackhat.com/docs/us-17/wednesday/us-17-Bulygin-Fractured-Backbone-Breaking-Modern-OS-Defenses-With-Firmware-Attacks.pdf)
* [LiveCloudKd](https://github.com/comaeio/LiveCloudKd)
* [Hypervisor Top Level Functional Specification](https://docs.microsoft.com/fr-fr/virtualization/hyper-v-on-windows/reference/tlfs)