KfConsole — Command-Line Debugger¶
KfConsole.exe is a console front-end for the KernelFlirt driver, sharing the same hook,
the same relay, and the same IOCTL surface as the WPF UI. It is meant for situations where
GUI is impractical — remote VMs over SSH, scripted debugging sessions, batch analysis,
or simply for users who prefer a WinDbg/x64dbg-style REPL.
The binary lives in bin\Console\KfConsole.exe. It ships with bundled dbghelp.dll and
symsrv.dll so PDB downloading from the Microsoft symbol server works out of the box.
Launching¶
KfConsole.exe :: REPL without auto-connect
KfConsole.exe local :: open \\.\KernelFlirt directly (no relay)
KfConsole.exe 10.100.102.6 :: connect to a relay (default port 31337)
KfConsole.exe 10.100.102.6:31337 :: explicit host:port
The same model as the UI: in the typical setup the VM runs KfLoader.exe load and
KfRelay.exe; the console runs on the host machine.
Prompt¶
kf> not connected
kf*> connected, no target
kf(7236:1876/x64/brk)> PID 7236, TID 1876, x64, stopped
kf(7236:1876/x64/run)> same, but the target is running
kf(8108:2900/x86/brk)> WoW64 / 32-bit target
- PID and TID are shown in cyan.
x64/x86indicates the target's bitness (auto-detected viaPeb32Address).brk(red) andrun(green) reflect whether the target is paused or executing.
Command Reference¶
All numeric arguments are parsed as expressions, not bare hex. Supported tokens:
hex literals (0x..., 1234h, or bare 1234), registers (rip, rsp+8, eax),
symbols (module!Name), pointer dereference ([mem]), and arithmetic
(+ - * /, parentheses, <<, >>, &, |, ^). Numbers default to hex
(same as WinDbg).
Connection¶
| Command | Description |
|---|---|
connect [local \| host[:port]] |
Open the local driver or connect to a relay. |
disconnect |
Tear down the connection. |
q / quit / exit |
Exit the REPL. |
Process & Target¶
| Command | Description |
|---|---|
open <path> |
Launch a process under debug (relay-side only). Patches the entry with EB FE, waits for the thread to reach it, restores the original bytes, and stops. Symbols load automatically for all modules visible at entry. |
attach <pid> |
Take an already-running process under debug. Auto-detects WoW64 via Peb32Address. |
detach |
Remove the hook, reset driver state. |
reset |
Clear all breakpoints in the driver without detaching. |
procs / ps |
List all running processes. |
mods |
List modules of the current target. |
kmods |
List kernel modules (ntoskrnl + drivers) via IOCTL_KF_ENUM_KERNEL_MODULES. |
threads / tt |
List threads of the current target. |
tid <tid> |
Select the active thread (registers/step apply to it). |
Registers¶
| Command | Description |
|---|---|
r |
Print all registers (16-byte hex on x64, 8-byte on x86/WoW64). |
r <name> |
Print a single register. Accepts rax/eax/r10/rip/eflags/cs/dr0/… |
r <name>=<expr> |
Write the register. Driver validates segment selectors (CS ∈ {0x10, 0x33, 0x23}, SS ∈ {0x18, 0x2B, 0x00}) and rejects non-canonical RIP/RSP — invalid values won't reach IRET. |
Memory¶
| Command | Description |
|---|---|
d <addr> [count=64] |
Hex dump (16 bytes per line + ASCII). |
dq <addr> [count=8] |
QWORD dump with symbol resolution for each value. |
dd <addr> [count=16] |
DWORD dump (4 per row). |
dw <addr> [count=32] |
WORD dump (8 per row). |
dp <addr> [count=8] |
Pointer-sized dump (4 bytes on WoW64, 8 on x64) with symbol resolution for each value. |
da <addr> [count=64] |
ASCII string dump (stops at NUL or count chars). |
du <addr> [count=64] |
UTF-16 string dump (stops at NUL or count chars). |
u <addr> [count=16] |
Disassembly (Iced / NASM syntax). Each call/jmp/jcc with a resolvable target gets a ; module!func annotation in green. The current RIP row is prefixed with a red ►. |
e <addr> <hex bytes> |
Write memory. Bytes can be space-separated or solid (e 401570 90 90 c3 or e 401570 9090c3). |
s <addr> <len> <pattern> |
Search memory. Pattern is hex bytes with ?? wildcards (s 401000 1000 48 8b ?? c3), quoted ASCII ("MZ"), or L"unicode". Capped at 4 MB per call and 256 reported hits. |
.alloc <size> [prot=rwx] |
Allocate memory in the target (IOCTL_KF_ALLOC_MEMORY); prints the new base. Protection: mnemonics rwx/rw/rx/r/ro/na/x or a raw hex PAGE_* value. |
.free <addr> |
Free a region (IOCTL_KF_FREE_MEMORY). |
.protect <addr> <size> <prot> |
Change page protection (IOCTL_KF_PROTECT_MEMORY); prints the old protection. |
Breakpoints¶
| Command | Description |
|---|---|
bp <addr> |
Software breakpoint (INT3 via MDL — works on RX .text pages). |
bp <addr> if <expr> |
Conditional BP. After each hit the driver pauses, CLI evaluates <expr>; if the result is 0, the thread is silently released via ContinueDebugEvent(Run) without UI notification. |
ba <e\|r\|w><len> <addr> |
Hardware breakpoint / watchpoint via DR0-3. Mode e = execute, w = write, r = read/write; len ∈ {1,2,4,8} (e.g. ba w4 401000). Routes through IOCTL_KF_SET_BREAKPOINT with Type/Length. |
bm <addr> [size] |
Memory breakpoint (PAGE_GUARD), KF_BP_MEMORY. |
bl |
List active breakpoints. Temp BPs (Step Over/Out, run-to-cursor) are marked [temp]; hardware/memory BPs are tagged <hw-e>/<hw-w>/<hw-rw>/<mem>. |
bc <addr \| handle \| all> |
Remove a BP by address or handle, or all at once. |
Execution¶
| Command | Description |
|---|---|
g / run / go |
Continue execution. For suspend-state (entry-point), this maps to ResumeThread; otherwise to ContinueDebugEvent(Run). On WoW64 the active BPs are converted to EB FE spin-traps and EIP is polled across all threads. |
g <addr> |
Run to cursor — sets a temporary SW BP at <addr>, continues, and auto-removes it on hit (like Step Over/Out temps). Not supported on WoW64 (the KdTrap hook doesn't catch 32-bit exceptions) — use bp <addr> + g instead. |
t / sti |
Step Into (one instruction). On native x64 in suspend-state, sets TF via IOCTL_KF_SINGLE_STEP + ResumeThread. On WoW64, decodes the current instruction with Iced and uses an EB-FE spin-trap on EIP + size. |
p / sto |
Step Over. Decodes the current instruction; targets = [ip + size, branch_target] for jcc, [branch_target] for unconditional jmp, [ip + size] otherwise. Native x64: places temporary SW BPs ([temp]). WoW64: spin-trap on the same set. |
o / out |
Step Out. Reads the return address from the top of the stack and places a temporary BP (or spin-trap on WoW64). |
ss |
Force the TF bit in KTRAP_FRAME of the current thread, without continuing. Useful before manual g. |
wait |
Wait for a single debug event from the driver. Normally not needed — a background listener processes events automatically. |
interrupt |
SuspendThread on the current TID — to force a stop on a running target. |
suspend [tid] |
SuspendThread on the given TID (defaults to the current one). |
resume [tid] |
ResumeThread on the given TID (defaults to the current one). |
Inspection¶
| Command | Description |
|---|---|
k [frames] |
Call stack via frame-pointer (RBP/EBP) unwinding with symbol resolution. Accurate for functions with a classic prologue; approximate under FPO/leaf frames. Default 64 frames. |
!peb / peb |
Parse the target PEB: BeingDebugged (red when set), ImageBaseAddress, Ldr, ProcessParameters, NtGlobalFlag. Uses the 32-bit layout for WoW64 targets, 64-bit otherwise. |
stats |
Inline-hook diagnostics (IOCTL_KF_GET_HOOK_STATS): call / BP-hit / step counters, KiDebugRoutine addr/orig/now, KdpStub and KdTrap addresses, and fast-trace counters. |
Anti-Debug (driver primitives)¶
These rely on existing driver IOCTLs originally added for the UI's anti-debug menu.
| Command | Description |
|---|---|
ad clr_debug_port |
Zero out EPROCESS.DebugPort of the target. |
ad clr_thread_hide |
Clear HideFromDebugger on every thread of the target. |
ad ntqsi on \| off |
Install/remove the NtQuerySystemInformation hook that spoofs class 0x23 (SystemKernelDebuggerInformation). |
ad spoof on \| off |
Toggle KUSER_SHARED_DATA.KdDebuggerEnabled spoofing (usermode reads see FALSE while the kernel flag stays TRUE for KdTrap). |
Misc¶
| Command | Description |
|---|---|
color [on\|off] |
Toggle ANSI coloring. On by default; useful to disable when piping output to a file. |
help / ? |
Show the built-in command list. |
Symbols¶
KfConsole uses the bundled dbghelp.dll and symsrv.dll (copied next to the EXE
by the build). On attach or open, every loaded module is processed:
- The driver reads the target's PE-header via
IOCTL_KF_READ_MEMORY. - The CLI parses the Debug Directory and extracts the RSDS GUID + Age + PDB name.
SymFindFileInPathWlocates or downloads the PDB through the symbol server chain (default:srv*C:\Symbols*https://msdl.microsoft.com/download/symbols).SymLoadModuleExWis called with the resolved PDB path, and a follow-upSymGetModuleInfoW64verifies the load succeeded (SymType == Pdb).
If a module's PDB cannot be found, the CLI prints the reason and skips that
module — no fake module+offset placeholders. The first run downloads
ntdll.pdb/kernel32.pdb/kernelbase.pdb (~30 MB total); subsequent sessions
reuse the local cache.
To use private PDBs for your own builds, drop them next to the executable on
the relay machine or put them anywhere along _NT_SYMBOL_PATH on the host
running KfConsole.exe.
Output Coloring¶
Coloring is on by default. The palette roughly mirrors VS Code Dark+ / x64dbg:
| Color | Used for |
|---|---|
| Cyan | Register names, PIDs, TIDs, field labels in !peb / stats |
| Orange | Hex literals, immediates, register values |
| Blue | Regular x86/x64 mnemonics, BP-kind tags (<hw-w>, <mem>) |
| Magenta | Control-flow mnemonics (jmp, call, ret, jcc), action keywords (step into, running) |
| Yellow | Symbols, paths, architecture tag |
| Green | ASCII strings in dumps, if expression body, success markers |
| Red | Faulting addresses, AV events, breakpoint markers (►, [N]), error markers ✗ |
| Gray | Addresses, separators, dim metadata |
color off disables coloring globally — escape codes will not be emitted, useful
for pipe-friendly output (KfConsole.exe ... > log.txt).
Readline & History¶
In interactive mode the REPL uses ReadLine.Reboot for emacs-style line editing:
↑/↓browse history (persisted across sessions to%APPDATA%\KernelFlirt\history.txt).Ctrl+R— reverse search in history.Tab— completion on the first word against the built-in command list.Ctrl+A/Ctrl+E/Ctrl+K— emacs bindings (line start/end/kill).
When stdin is redirected (type cmds.txt | KfConsole.exe), the REPL falls back to plain
Console.ReadLine so batch input works correctly.
Asynchronous Events¶
A background listener thread continuously calls WAIT_DEBUG_EVENT on the DBG channel.
When a breakpoint hits, the listener prints a banner on top of the current prompt:
*** BP at 00007FFA`14E77344 PID=7236 TID=1876 ntdll!NtCreateFile
RAX=0... RBX=0...
kf(7236:1876/x64/brk)>
This means you can issue read-only commands (r, mods, d, …) on the prompt while
the target is run, and the next break-event will appear without you typing wait
explicitly. Step/run commands (t, p, g) require the target to be stopped first.
Worked Example¶
kf> connect 10.100.102.6:31337
✓ connected (10.100.102.6:31337), driver v0x10000
kf*> open C:\Temp\rc4_strings.exe
• creating process: C:\Temp\rc4_strings.exe
✓ created PID=8424 TID=10136 ImageBase=00007FF7`2EA10000 (x64)
• entry=00007FF7`2EA11570 (orig=48 83, patched 2 byte EB FE)
✓ reached entry after 100 ms
symbols: 3/4 modules loaded
✓ stopped at entry point of C:\Temp\rc4_strings.exe
kf(8424:10136/x64/brk)> bp ntdll!NtCreateFile if rcx!=0
✓ bp [1] 00007FFF`BBF8E030 ntdll!NtCreateFile if rcx!=0
kf(8424:10136/x64/brk)> g
✓ running (PID 8424, TID 10136, resumed from suspend)
*** BP at 00007FFF`BBF8E030 PID=8424 TID=10136 ntdll!NtCreateFile
RAX=0000000000000000 RBX=... RCX=00000010`0007FE00 RDX=...
RIP=00007FFF`BBF8E030 RSP=00000010`0007FD68 RBP=...
kf(8424:10136/x64/brk)> u rip 5
► 00007FFF`BBF8E030 4c 8b d1 mov r10, rcx ntdll!NtCreateFile
00007FFF`BBF8E033 b8 55 00 00 00 mov eax, 0x55
00007FFF`BBF8E038 f6 04 25 08 03 fe 7f 01 test byte ptr [0x7ffe0308], 1
00007FFF`BBF8E040 75 03 jne 0x7fff bbf8e045
00007FFF`BBF8E042 0f 05 syscall
kf(8424:10136/x64/brk)> dq rsp 4
00000010`0007FD68 00007FFFB99E1234 kernelbase!CreateFileW+0x84
00000010`0007FD70 ...
kf(8424:10136/x64/brk)> o
✓ step out: return-address @ [00000010`0007FD68] = 00007FFFB99E1234
*** BP at 00007FFF`B99E1234 PID=8424 TID=10136 kernelbase!CreateFileW+0x84
...
kf(8424:10136/x64/brk)> bc all
✓ cleared 0 BPs
kf(8424:10136/x64/brk)> detach
✓ detached
Known Limitations¶
openrequires the relay. The relay-side IOCTLCREATE_PROCESShandles entry-point patching; the local path falls back to plainCreateProcessWwithout entry patching, so the process resumes immediately. Useattach <pid>after manual launch instead.- Step Out at function prolog may read garbage as the return address (the real one
isn't on the stack yet). Same caveat as in the UI; for tight breakpointing use
bpon a known address inside the function. - WoW64
gblocks the REPL until a BP is hit (synchronous EIP polling). UI does the same work in a background task; the CLI version doesn't yet have cancellation — Ctrl+C will terminate the process. Avoidgon WoW64 if there are no BPs to land on. - No SymFromName for unloaded modules.
bp ntdll!Fooworks only afterattach/openhas finished loading the module list. Set the BP after the target is stopped at entry. kis a frame-pointer walk. It follows the RBP/EBP chain, so it's accurate only for functions built with a classic prologue (push rbp; mov rbp, rsp). FPO-optimized or leaf frames may be skipped or misreported — for an exact stop, set abpinside the function.