Processes and modules

!dh

Show the headers of a mapped PE image.

!dh [-f] [-s] [-e] [-i] [-a] <module|address>

Also: dh

The argument is a module name, an address, or an image base. A module name is nt, hal, ntdll, or the image name of the module, and ntoseye looks for it in the .process module list first, and then in the kernel module list. An address can be any address inside a loaded module. An image base can be the base of an image that no loader list contains, if that image starts with MZ. With no options, the command shows the file and section headers, as WinDbg does. -f: the file header, the optional header (entry point, image base, subsystem, DLL characteristics, stack and heap sizes), and the data directories (RVA and size). For the security directory, the value is a file offset. -s: the section table with decoded flags, and the debug directory with its CodeView PDB name, GUID, and age. -e: the export directory and all exports (ordinal, RVA, name or forwarder). -i: each import descriptor and its imports (hint and name or ordinal), with the address that the loader bound in the IAT. If a descriptor has no import name table, the command shows only the bound addresses, because its IAT no longer contains names. -a: all of these. You can combine options (-fs, -f -i). If ntoseye cannot read a directory, it shows the directory as unavailable and shows the other data. For example, the import table of a driver is in its INIT section, and Windows discards that section after load. The same applies to an import name or a module name that ntoseye cannot read. The command reads the headers from memory as they are mapped, so the values are the ones that the loader left there.

!for_each_module

Run commands once for each loaded module that lm shows.

!for_each_module ["<command-string>"]

The loop uses the modules of the current scope, in lm order. These are the modules of the .process process or, without one, the kernel modules (or the secure kernel modules under .vtl 1). CommandString can use the following aliases, which ntoseye replaces as whole words, or anywhere as ${@#Name}. Alias names are case-sensitive. The aliases are @#ModuleIndex (the 0-based position), @#ModuleName (the module! name that lm shows), @#ImageName (the image name that lm shows), @#LoadedImageName (the full path from the loader if known, else the image name), @#SymbolFileName (the local PDB of the symbols, else the image name), @#Base, @#End, @#Size, @#TimeDateStamp, @#Checksum, @#FileVersion, @#ProductVersion, @#Flags (DEBUG_MODULE_USER_MODE for a process module), @#SymbolType (DEBUG_SYMTYPE_PDB, _DEFERRED while ntoseye gets the symbols, else _NONE), and @#ModuleNameSize, @#ImageNameSize, @#LoadedImageNameSize, @#SymbolFileNameSize (the string length plus one). If a value contains ; or a quote, ntoseye inserts it as a quoted string, so the value cannot end its command and start a new one. Numbers are hex with a 0x prefix, so they are the same in all radixes. If a module does not have a value that an alias uses (for example, no timestamp or no version resource), ntoseye shows an error and skips the module. Without CommandString, the command runs .echo @#ModuleIndex : @#Base @#End @#ModuleName @#ImageName  @#LoadedImageName. Ctrl+C stops the loop. If the session does not permit a command (for example, a resume in a breakpoint action), the loop stops and the rest of the command line does not run. If a command only shows an error, the loop continues.

!for_each_process

Run commands once for each process in the target.

!for_each_process ["<command-string>"]

In CommandString, ntoseye replaces each whole-word @#Process with the EPROCESS address of the process, and ${@#Process} anywhere. To use several commands, separate them with ; and put the string in quotes. Without CommandString, the command runs !process @#Process 0 for each process. The loop does not change the process context (.process, $proc), but .process /p @#Process in CommandString does, and this change stays after the loop. The loop uses the processes that ps shows, in the same order. Ctrl+C stops the loop. If the session does not permit a command (for example, a resume in a breakpoint action), the loop stops and the rest of the command line does not run. If a command only shows an error, the loop continues.

!for_each_thread

Run commands once for each thread in the target, or in the .process process.

!for_each_thread ["<command-string>"]

In CommandString, ntoseye replaces each whole-word @#Thread with the ETHREAD address of the thread, and ${@#Thread} anywhere. To use several commands, separate them with ; and put the string in quotes. Without CommandString, the command runs !thread @#Thread 2 for each thread. If you select a process with .process, the loop uses only the threads of that process, and otherwise each thread that threads shows. The loop does not change the thread context (.thread, $thread). Ctrl+C stops the loop. If the session does not permit a command (for example, a resume in a breakpoint action), the loop stops and the rest of the command line does not run. If a command only shows an error, the loop continues.

!hvcall

Decode the hypercall that the current vCPU’s processor handles in the Windows hypervisor.

!hvcall

For a vCPU halted in the Windows hypervisor, for example on a breakpoint on a hypercall handler (ba e1 hv!HvCallFlushVirtualAddressList), shows the hypercall of the VMCALL exit that its processor handles: the caller (a VTL of the root partition, or the guest partition’s VP that the processor serves), the call code with its TLFS name, the flags (fast, variable header size, nested), a rep call’s start and count, the GPAs of the input and output, and each field of the input as the Hyper-V TLFS lays it out, with names for special values, processor sets, flags, and the common register names, then each element of a rep call’s input list. A slow call’s input is read at its GPA through the EPT of the calling VTL. A fast call’s input is RDX and R8, then, for an XMM fast call, XMM0 to XMM5 as the hypervisor’s VM-exit entry code saved them; when they are not known, it says what is missing. A call whose layout ntoseye does not know shows its input as raw qwords. The command needs the general-purpose registers of the exit, which ntoseye reads where the hypervisor’s VM-exit entry code saved them, so it explains why when the vCPU is still on the entry or the last exit was not a VMCALL. Needs the VM’s hv-evmcs enlightenment.

!hvcalls

List the hypercalls the Windows hypervisor implements, from its hypercall table.

!hvcalls [-a]

Reads the hypervisor’s hypercall table and shows, for each call code, the name the Hyper-V TLFS gives it (when the TLFS documents it), whether it is a simple or a rep call (var marks a variable-size input header), the sizes of its fixed input and output and of each rep element, and its handler in the hv image. Codes that share the handler of the reserved code 0 are not implemented; -a lists them too. Needs the VM’s hv-evmcs enlightenment or a vCPU stopped in the hypervisor.

!hvd

Display the memory of a Windows hypervisor partition’s guest (a Hyper-V VM, WSL2, Windows Sandbox).

!hvd [-p] [-b|-d|-q] [partition-id vp-index] <address> [range]

Reads the guest’s memory through the EPT of the VTL its VP runs in, as its eVMCS names them: guest virtual memory through the VP’s page tables (its CR3), or with -p guest physical memory. Without a partition and VP it reads the guest VP that the current vCPU’s processor runs, as ~ and a stop in the hypervisor name it. -b shows bytes (the default), -d dwords, and -q qwords. The range is L, an end address, or a byte length, as for db. Unreadable pages show as ??. The memory is read-only, and ntoseye has no symbols for the guest. The numbers use the current radix. Needs the VM’s hv-evmcs enlightenment.

!hvept

Translate a guest physical address through each VTL’s EPT (second-level address translation) of a Windows hypervisor VP.

!hvept [-v] <address> [partition-id [vp-index]]

Walks the extended page tables that each enabled VTL’s eVMCS names, by default for the VP the current vCPU’s processor runs (with -v the root partition’s), or the root partition’s VP 0. The address is guest physical, or with -v virtual in the current address space (the .process or VTL1 scope), which the command translates through the guest’s page tables first; that space is the root partition’s, so -v takes only a root partition VP. Each row shows the VTL, its EPT pointer, the host physical address, the access that every level of the walk allows (r, w, and x, where x is supervisor-mode execute when the VTL uses mode-based execute control), user-mode execute under that control, the page size, and the memory type, or the level where the walk found no entry. This is how memory integrity (HVCI) and the secure kernel set page permissions that NT cannot change. The address and IDs use the current radix. Needs the VM’s hv-evmcs enlightenment. See !hvpartitions for where the objects come from.

!hveptdiff

List the guest physical ranges that VTL0’s and VTL1’s EPTs of a Windows hypervisor VP map differently.

!hveptdiff [partition-id [vp-index]]

Walks the whole EPT of VTL0 and of VTL1 of a VP, by default the one the current vCPU’s processor runs (a guest partition’s first), else the root partition’s VP 0, and lists each range of guest physical memory that the two map with different access, or that only one of them maps, with the access in each (r, w, x, and u for user-mode execute under mode-based execute control). Adjacent ranges that differ the same way are merged. These are the pages that the secure kernel and memory integrity (HVCI) protect from NT. The IDs use the current radix. Needs VBS and the VM’s hv-evmcs enlightenment.

!hvpartitions

Show the partitions of the Windows hypervisor as a tree, with their virtual processors.

!hvpartitions

Walks the hypervisor’s partition objects from its processor blocks, which come from the eVMCS pages (the VM’s hv-evmcs enlightenment) or from a vCPU stopped in the hypervisor. Each child partition is below its parent, with its partition ID and object, and below each partition are its privilege mask (HV_PARTITION_PRIVILEGE_MASK) with the TLFS names, then each VP: the processor whose current VP it is, the VTL it runs or last ran in (the other enabled VTLs after it), the guest RIP where that VTL left off, named from symbols for the root partition’s VPs, and why it last left for the hypervisor. !hvvps shows each VTL of a partition’s VPs in full. hvix64 has no public symbols, so the offsets are read off the hypervisor’s own code and each object is validated before it is shown; if the layout is not recognized, the command gives an error and does not guess offsets. Intel hosts only.

!hvr

Show the registers of a VTL of a Windows hypervisor VP, whether or not a processor runs it.

!hvr [partition-id vp-index [vtl]]

By default the VP the current vCPU’s processor runs (a guest partition’s first, else the root partition’s) and the VTL it runs in. For a VP that a vCPU runs now, they are that vCPU’s; for one whose exit a vCPU in the hypervisor handles, those of that exit; otherwise those the VP saved at its last exit, read from its register block through the descriptor of its register region, which no processor maps while the VP does not run. The VP resumes with them but for what the hypervisor writes as the exit’s result, such as a hypercall’s status in RAX. RIP, RSP, flags, control and segment registers come from the VTL’s eVMCS. The general-purpose registers are shared by a VP’s VTLs and belong to the one it runs in; another VTL shows its eVMCS state only. The IDs use the current radix. Needs the VM’s hv-evmcs enlightenment; builds before 10.0.17763 keep the saved registers on the processor’s stack, so a VP no processor runs has none there.

!hvu

Disassemble the memory of a Windows hypervisor partition’s guest (a Hyper-V VM, WSL2, Windows Sandbox).

!hvu [-p] [partition-id vp-index] <address> [range]

Reads the guest’s memory as !hvd does: guest virtual memory through the page tables (CR3) and EPT of the VTL its VP runs in, or with -p guest physical memory through the EPT. Without a partition and VP it reads the guest VP that the current vCPU’s processor runs. The code decodes in the mode the VTL left off in, by its eVMCS: 64-bit in IA-32e mode with a 64-bit code segment, else 32-bit; real mode and 16-bit code give an error. The range is as for u: L instructions (8 by default), or an end address or byte length, listing every instruction that starts before the range ends. The listing stops at the first unreadable page and says where. ntoseye has no symbols for the guest, so branch targets and RIP-relative operands show as addresses. The numbers use the current radix. Needs the VM’s hv-evmcs enlightenment.

!hvvmcs

Show the eVMCS of a VTL of a Windows hypervisor VP, or the MSRs and I/O ports it intercepts.

!hvvmcs [-msr|-io] [partition-id [vp-index [vtl]]]

Reads the Enlightened VMCS of a VTL, by default the VP the current vCPU’s processor runs (a guest partition’s first, else the root partition’s) and the VTL it runs in, and shows each field with its offset and value. With -msr, it shows the MSRs whose reads and writes the VTL’s MSR bitmap intercepts, with the architectural MSRs each range holds, and then the architectural MSRs the VTL reads and writes without an exit; MSRs outside the bitmap’s 0x0-0x1fff and 0xc0000000-0xc0001fff always exit. With -io the I/O ports its I/O bitmaps intercept, or that every access is intercepted when the VM-execution controls do not use the bitmaps. The layout is the Hyper-V TLFS’s, so this does not depend on the hypervisor build. The IDs use the current radix. Needs the VM’s hv-evmcs enlightenment.

!hvvps

List the virtual processors of a Windows hypervisor partition (the root by default).

!hvvps [partition-id]

Each row is one VTL enabled on a VP, and * marks the VTL the VP runs or last ran in. CPU is the processor whose current VP it is (the one that runs it, or ran it last), by number, or by processor block on builds before 10.0.19041, which keep no number there. The row shows the hypervisor’s context object for the VTL and, when found, the physical address of the VTL’s eVMCS with the EPT pointer (the root of the VTL’s second-level address translation), the guest RIP where the VTL left off, and why it last left for the hypervisor. The eVMCS columns need the VM’s hv-evmcs enlightenment. The partition ID uses the current radix. See !hvpartitions for where the objects come from.

!job

Show a job object and the processes in it.

!job [address [flags]]

Also: job

Shows a job object. The address can be a job, or a process or thread, for which the command shows its job. If you do not give an address, or give 0, the command shows the job of the current process. Flag 1 (the default) shows the accounting, limits, and flags of the job, and also the child jobs, the parent and root job, and the silo state. Flag 2 lists the processes assigned to the job (from ProcessListHead).

!lmi

Show the image identity, debug directory, and symbol state of a loaded module.

!lmi <module|address>

Also: lmi

The argument is a module name as for !dh (nt, ntdll, an image name), or any address inside the module. From the mapped headers, the command shows the base, image name and path, machine, time stamp, size, checksum, and characteristics. It also shows the debug directory with the CodeView PDB name, GUID, and age. Last, it shows whether symbols are loaded, where they come from, and the local PDB file.

!process

List or inspect Windows processes.

!process [eprocess|pid|image-name|0] [flags] [image-name]

!process 0 0 lists all processes. Bit 1 adds process details, bit 2 adds threads, and bit 4 adds the stack of each thread. An image name in place of the process shows every process whose name contains it or matches it as a glob, as the last argument filters: !process explorer.exe shows explorer.exe, and !process svchost 0 lists each svchost.exe. ps keeps its short legacy list format.

!session

List the sessions and the processes in each session.

!session [-s <id>]

Also: session

The command reads session IDs from _EPROCESS.Session and _MM_SESSION_SPACE, or from the primary token if session space is opaque. Use -s -1 for the current session. The process walk stops at 4096 entries.

!sprocess

List the processes in a session.

!sprocess [session [flags [image]]]

Also: sprocess

The session is a signed decimal number. -1 and -2 select the current session, and -4 selects all sessions. If you do not give a session, the command uses the session of the attached process, if it is known. Flags default to 0 (brief output), and any non-zero value selects detailed output. The optional image argument is a glob that ignores case.

!thread

Show a Windows thread and, optionally, its kernel stack.

!thread [ethread|tid] [flags|k|r] [count]

Also: thread

You can also use the legacy thread <tid> k|r [count] forms. A numeric flags value selects the detail and stack output, and count sets the maximum number of stack frames (32 by default, or 16 for k). The command shows - for a field that is not available. If the vCPU of a running thread is halted in the Windows hypervisor (VBS), the command shows the thread from the VTL0 state that the hypervisor saved, and .thread selects the thread in the same way. While the target runs, the command shows the thread but does not select it, and a thread that runs on a processor has no stack.

!trustlets

List the validated VTL1 processes with their NT identities and address-space roots.

!trustlets

Reads SkpsProcessList through the page tables of the secure kernel. Each row shows the secure-kernel process object, the NT PID and image name, the trustlet ID, and the address-space root. If the internal layout is not supported, the command gives an error and does not guess offsets. The command does not change the scope.

!vad

Show the VAD tree of a process.

!vad [pid|eprocess]

Also: vmmap

Select a process by PID or EPROCESS expression. With no argument, the command uses the current process context, which you can select with .process /p <pid>. vmmap [address|filter] keeps the flat region view of the attached process, and shows the kernel modules when no process is attached. The VAD walk has a limit, and it skips the entries that it cannot read and continues the listing.

!zombies

List exited processes and threads that still have references.

!zombies [flags]

Also: zombies

Scans nonpaged pool for process (Proc) and thread (Thre) objects, and finds the header of each object by its decoded type. The command lists the processes that have ExitTime set and the threads in the Terminated state. For each object, it shows the handle count and the pointer count: whatever still holds these references keeps the object in memory. Flag 1 (the default) lists processes, 2 lists threads, and 3 lists both. The scan stops after 4096 processes or 4096 threads, or when you press Ctrl+C.

.context

Set the translation base that inspection uses.

.context <dtb>
.effmach

Show or set the effective code machine.

.effmach [x86|amd64|arm64|auto|.]

With no argument, shows the selected machine. x86, amd64, and arm64 (on an ARM64 target only) make u, ub, uf, and editor disassembly decode all code as that instruction set, and auto or . goes back to selection by context and image. This selection covers the x86 images of a WOW64 program and, on ARM64, also an emulated x64 image and the x64 ranges of an ARM64X/ARM64EC hybrid. x86 also makes ds/dS read 32-bit (WOW64) string descriptors.

.partition

Inspect the Windows guest that a Windows hypervisor partition runs (a Windows Sandbox, a Hyper-V VM) in place of the target, or show which is inspected.

.partition [partition-id]

With a partition ID, the session inspects that partition’s guest instead of the target: its memory is read through the partition’s EPT, its NT kernel is found from the page-table root of a VP in kernel mode, and its symbols are loaded, so lm, !process, dt, db, u, k and the other inspection commands read the guest. Its VPs are the threads (p.<VP index + 1>, so ~Ns selects VP N), with their VTL0 registers, as !hvr 0 shows them: a VP that runs in VTL1 has VTL0’s RIP, RSP, flags, control and segment registers there, without its general-purpose registers. The target stays halted while the view is shown. eb, ed and the other writes go to the guest’s memory through its EPT, and r = to the vCPU that runs the selected VP at the stop; a VP no vCPU runs has its registers in the hypervisor’s memory, and is refused. ba sets a hardware breakpoint of the partition’s, which stops only on its VPs (/c names a VP); bp, bu and bm are refused. g leaves the view and runs the target, and a hit of the partition’s breakpoint shows the view again on the VP that hit it. t, p and gu step the thread of the selected VP, on whichever VP it runs next, and g

runs until any of the partition’s VPs reaches the address: each is a run of the target to a breakpoint of the partition’s. Walks (tc, pa, wt) are refused. bl lists every breakpoint, the target’s too. The root partition’s ID (0x1) returns to the target. Without an ID, it shows which partition is inspected. The ID uses the current radix. Needs the VM’s hv-evmcs enlightenment and a 64-bit Windows guest; breakpoints and steps need the gdb backend.

.process

Select a process address space to inspect.

.process [/i] [/p] [/r] [eprocess|pid|name|0]

The process is named by its EPROCESS address, its PID, or its image name, as for !process; a name several processes share is refused. In this debugger, /i is the same as attach: it also selects a thread of the process, one a processor runs if there is one, else its first, so !teb, !gle, and k show that thread. /p and /r select the same non-invasive context, keeping the current thread, which lasts until the target executes again, as in WinDbg. With no argument, the command prints the current process context.

.vtl

Select NT (VTL0) or secure-kernel (VTL1) memory inspection, or show which one is active.

.vtl [0|1 [pid]]

VTL1 requires AMD64 direct host memory. .vtl 1 changes the scope of reads and symbols, but does not change the VTL of the CPU. With no argument, .vtl shows the current scope. .vtl 0 goes back to the NT kernel. After .vtl 1, it returns to the view the stop selected, which at a stop in the Windows hypervisor is where NT left off; otherwise, at a stop in VTL1 or in the Windows hypervisor, it selects the address space of the vCPU, where you use .vtlcxr or .thread to select NT. .vtl 1 selects the system address space of the secure kernel, and .vtl 1 <pid> selects the address space of a trustlet by its NT PID, which is always decimal. The VTL1 scope is a read-only memory view. Registers, stepping, software breakpoints, writes, and NT-specific extensions need .vtl 0. To stop in VTL1, set a hardware execute breakpoint there (ba e1 securekernel!<function>, GDB backends), and then resume with a plain g, which first goes back to the live context. A vCPU stopped in VTL1 shows its real registers, stack, and memory. With no argument, .vtl also shows whether reads follow such a live stop or the manual view.

attach

Attach to a process by PID.

attach <pid>

The command also selects a thread of the process, as .process /i does: one a processor runs, whose registers are live, else its first, by its stack.

detach

Detach from the current process.

detach
drivers

List the driver objects in the \Driver object directory.

drivers [filter]
ld

Force ntoseye to select the symbol source and index the symbols for one module.

ld <module>
lm

List loaded modules.

lm [m <pattern>|a <address>|<name>] [v] [u|k] [t]

m applies a module-name glob, and v m prints verbose symbol information. a <address> shows only the module that contains the address, a kernel module for a kernel address. u selects user modules, k selects kernel modules, and t adds timestamps.

lmv

Show the detailed symbol status and PDB identity of each module.

lmv [m <pattern>|a <address>|<name>] [v] [u|k] [t]

This command is the same as lm v and uses the same filters, for example lmv m nt.

ps

List running processes.

ps [filter]
threads

List the Windows threads, with an optional filter by process, PID, TID, or ETHREAD.

threads [filter]

With no filter while attached to a process (attach, .process), the command lists that process’s threads, and * lists every thread. The start address is the thread’s own entry (its Win32 start address), else where the kernel started it, named from the symbols loaded: the kernel’s, and the attached process’s.

vcpu

Switch to another processor, by its number.

vcpu <processor>
~

List the processors and where each is.

~

Also: vcpus

~Ns selects processor N, numbered from 0 as Windows numbers them, and t and p then step that processor. Over kd and kdnet on an AMD64 target, the other processors run during the step, as in WinDbg. On an ARM64 target, only the processor that stopped the target can step over kd or kdnet. If a vCPU halted in the Windows hypervisor (VBS), the list also shows where its VTL0 execution stopped, from the saved state of the hypervisor. ~Ns on that vCPU selects the VTL0 context, and .cxr selects the hypervisor registers. A vCPU stopped on the hypervisor’s VM-exit entry from outside shows its saved state as (may be one exit behind) and keeps the hypervisor registers: KVM writes that state when it enters the hypervisor, so it can still describe the previous exit. A breakpoint on the entry fires after that write, so a vCPU stopped on one shows the current exit.