Tutorial: a first session¶
This walks through one session against a Windows 11 VM: attach, look around, stop on a kernel function, read its arguments, step, and leave. Every command and output below was captured from a real session and trimmed only where marked .... It assumes a target you can attach to; the Quickstart gets you there.
The session uses the gdb backend, which needs nothing set up inside Windows. The commands are the same over kd and kdnet; what differs is where the first stop lands.
Attach¶
$ ntoseye --backend gdb
target
kernel Windows 26200
base fffff80797200000
psmods ffffe70faa662830
BREAK p01.01 hypervisor at hvix64+0x3a6bde
├─ thread Idle state Running ethread fffff807981d25c0 pid 0 tid 0
╰─ saved VTL0 nt!HalProcessorIdle+0xf
...
gdb:p01.01>
Attaching halts the VM. ntoseye finds the kernel in guest memory, loads its symbols from Microsoft’s symbol server (cached under ~/.ntoseye/symbols after the first time), and prints a stop header: the vCPU (p01.01), what it was running, and where. Below the header come the registers, a few instructions of disassembly, and the top of the stack.
This VM runs virtualization-based security, so its idle vCPU stopped inside the Windows hypervisor (hvix64), and the header’s last line says where Windows itself left off. On a VM without VBS the first stop is in Windows directly. VBS and the Windows hypervisor covers what these stops mean.
The prompt names the backend and the selected vCPU.
Look around¶
vertarget describes the target:
gdb:p01.01> vertarget
target version
target Windows 10.0 build 26200 (26100.ge_release.240331-1435)
arch AMD64
kernel fffff80797200000 size 0x1450000
processors 4
uptime 0d 05:27:42
backend gdb
...
lm lists loaded kernel modules and whether their symbols loaded:
gdb:p01.01> lm
Start End Module Version Symbols Source Image
fffff80797200000 fffff80798650000 nt - loaded cached ntoskrnl.exe
fffff80798660000 fffff80798666000 hal - loaded cached hal.dll
fffff80728e30000 fffff8072933d000 dxgkrnl 10.0.26100.9444 loaded cached dxgkrnl.sys
...
ps lists processes with the address of each _EPROCESS and its page-table root:
gdb:p01.01> ps
Name PID EPROCESS DTB Wow64
System 4 ffffe70faa6df040 00000000001ae000 -
Secure System 132 ffffe70faa7a1040 0000000118d7e000 -
smss.exe 544 ffffe70fb19a4280 0000000268526000 -
lsass.exe 960 ffffe70fb2d51080 0000000114917000 -
...
Symbols are searched with x; * and ? are wildcards:
gdb:p01.01> x nt!NtCreateFi*
fffff80797ac7930 nt!NtCreateFile
Stop on a kernel function¶
Set a breakpoint with bp and resume with g. NtCreateFile runs whenever any process opens a file, so it hits almost at once:
gdb:p01.01> bp nt!NtCreateFile
breakpoint #0 set at fffff80797ac7930 (nt!NtCreateFile) (global)
gdb:p01.01> g
VM running, waiting for stop (Ctrl+C to pause)...
BREAK p01.02 LogonUI.exe (1204) at nt!NtCreateFile
├─ breakpoint #0
╰─ thread LogonUI.exe state Running ethread ffffe70fb4c8b080 pid 1204 tid 3804
...
The header now names the process and thread that called into the kernel. Ctrl+C breaks in at any time while the VM runs.
kn shows the stack. It crosses from the kernel into the calling process’s user-mode code, and resolves that too:
gdb:p01.02> k 6
00 fffffd86b64aeda8 fffff80797ac7930 nt!NtCreateFile
01 fffffd86b64aedb0 fffff807978c1d55 nt!KiSystemServiceCopyEnd+0x25
02 0000009112efd5a8 00007ff855661864 ntdll!NtCreateFile+0x14
03 0000009112efd5b0 00007ff852e36217 kernelbase!CreateFileInternal+0x373
04 0000009112efd730 00007ff852e37887 kernelbase!CreateFileW+0x97
05 0000009112efd790 00007ff852e395f6 kernelbase!BasepLoadLibraryAsDataFileInternal+0x206
A module seen for the first time shows module+offset for a moment while its symbols download in the background; lm shows it as fetching meanwhile.
Read the arguments¶
NtCreateFile’s third argument, in r8 on x64, is an OBJECT_ATTRIBUTES whose ObjectName is the path being opened. Expressions understand types from the PDB, so a cast reads it directly, and dS prints a UNICODE_STRING:
gdb:p01.02> dS ((nt!_OBJECT_ATTRIBUTES*)@r8)->ObjectName
0000009112efd668 Length=66 MaximumLength=68 Buffer=000002702036ac90 "\\??\\C:\\WINDOWS\\SYSTEM32\\tzres.dll"
dt displays a structure, optionally only the named fields. $proc is the current process’s _EPROCESS:
gdb:p01.01> dt nt!_EPROCESS @$proc UniqueProcessId ImageFileName
_EPROCESS (2112 bytes) @ ffffe70fb6199080
+0x1d0 UniqueProcessId : void* = 0x1710
+0x338 ImageFileName : UCHAR[15] = "svchost.exe"
Expressions covers registers, pseudo-registers, casts, and members.
Step¶
p steps over one instruction, and gu runs until the current function returns. Each stop prints a header again:
gdb:p01.01> p
BREAK p01.01 svchost.exe (5904) at nt!NtCreateFile+0x7
...
gdb:p01.01> gu
VM running, waiting for stop (Ctrl+C to pause)...
BREAK p01.01 svchost.exe (5904) at nt!KiSystemServiceCopyEnd+0x25
...
Clean up and leave¶
gdb:p01.01> bc *
breakpoint #0 cleared
gdb:p01.01> q
Where to go next¶
.hhlists every command, and.hh <command>explains one; the same text is the command reference. Tab completes commands, symbols, and types.Coming from WinDbg lists what carries over and what differs.
Breakpoints covers conditions, scoping to one process or thread, and commands that run on a hit.
Python SDK does all of the above from a script.