---
title: KvcKiller
url: 'http://kvc.pl/repositories/kvckiller'
markdown: 'http://kvc.pl/repositories/kvckiller.md'
date: '2026-08-29'
description: 'KvcKiller — Advanced Process Management Tool for Windows x64: kernel-mode process termination via IOCTL (CVE-2023-52271, wsftprm.sys), IFEO Paralyze via offline hive editing (RegSaveKeyEx/RegRestoreKey bypassing DACL), SCM Restore, 90+ built-in AV/EDR targets (Defender, CrowdStrike, SentinelOne, Kaspersky, ESET, Sophos, Bitdefender, Carbon Black, Cylance, FireEye/Trellix, McAfee, Norton, Avast/AVG, Malwarebytes, Trend Micro), PARALYZED state persistence via IFEO cold-start check, headless CLI (-kill/-restore/-list/-paralyze), dark/light mode with Mica backdrop (Win 11), animated Paralyze label (sine-pulse), DPI-aware Win32 ListView with per-column sorting, worker threads with WorkerGuard RAII, driver binary embedded in icon file (LZX CAB/FDI extraction), TrustedInstaller impersonation, deterministic build timestamp (2030-01-01), C++20, static CRT, no redistributable.'
---

> KvcKiller — Advanced Process Management Tool for Windows x64: kernel-mode process termination via IOCTL (CVE-2023-52271, wsftprm.sys), IFEO Paralyze via offline hive editing (RegSa

 [ Download KvcKiller.7z](http://kvc.pl/repositories/kvckiller/KvcKiller.7z) [ Source on GitHub](https://github.com/wesmar/kvcKiller) 🔐 ARCHIVE PASSWORD: `github.com`

# KvcKiller — Advanced Process Management Tool

**Kernel-mode AV/EDR process termination · IFEO Paralyze · SCM Restore · Headless CLI** *Win32 C++20 application that loads a signed kernel driver on demand, sends IOCTL kill requests from ring 0 — bypassing every user-space self-protection mechanism — then unloads the driver and erases all registry traces.* *Covers 90+ built-in targets across 15 AV/EDR vendors. Paralyze mode injects `Debugger = systray.exe` into Image File Execution Options via offline hive editing — services cannot auto-restart after a kill, even on fully patched Windows 11 with HVCI enabled. A single Restore click reverses both actions.* *The driver binary is embedded inside the application's own icon file as an LZX Cabinet archive — no separate `.sys` file to distribute or detect.* **Requires elevated (Administrator) privileges. For advanced users only.** 

**CLI — terminal demo**

![KvcKiller CLI demo](http://kvc.pl/user/pages/04.repositories/19.kvckiller/images/kvcKiller.gif)

**GUI — main window**

![KvcKiller GUI](http://kvc.pl/user/pages/04.repositories/19.kvckiller/images/kvcKiller.jpg)

---

## 📚 Table of Contents

- [Feature Overview](#feature-overview)
- [Architecture](#architecture)
- [Driver Lifecycle](#driver-lifecycle)
- [Kill Flow](#kill-flow)
- [Paralyze: IFEO Offline Editing](#paralyze-ifeo-offline-editing)
- [Restore Flow](#restore-flow)
- [Built-in Target List](#built-in-target-list)
- [GUI Reference](#gui-reference)
- [CLI Reference](#cli-reference)
- [Registry & Configuration](#registry--configuration)
- [Module Map](#module-map)
- [Build](#build)
- [The Driver — CVE-2023-52271](#the-driver--cve-2023-52271)
- [On Microsoft's Habit of Leaving Doors Open](#on-microsofts-habit-of-leaving-doors-open)
- [Project Context](#project-context)
- [System Requirements](#system-requirements)

---

## Feature Overview

KvcKiller terminates AV/EDR processes that protect themselves against user-space `TerminateProcess` calls by dispatching kills through a kernel driver via `DeviceIoControl`. Self-protection at ring 3 is irrelevant when the termination signal arrives from ring 0.

### Kill Engine

- **Kernel IOCTL termination** — driver handle opened to `\\.\Warsaw_PM`; PID sent via `DeviceIoControl` (IOCTL `0x22201C`); process terminated from ring 0
- **Driver stealth loading** — `NtLoadDriver` / `NtUnloadDriver` via direct NTDLL calls; no `CreateService`, no visible entry in `services.msc` during the session
- **Privilege escalation** — `RtlAdjustPrivilege` for `SeLoadDriverPrivilege` and `SeDebugPrivilege`; TrustedInstaller impersonation for DriverStore writes
- **Driver embedded in icon** — the kernel driver is compressed as an **LZX Cabinet archive** and appended to `KvcKiller.ico`; extracted on demand via the Windows FDI API; no separate `.sys` file to distribute or detect

### Paralyze Mode

- Injects `Debugger = C:\Windows\System32\systray.exe` into **Image File Execution Options** for each target process — Windows intercepts every subsequent launch and runs the dummy debugger instead; no AV code ever executes
- `systray.exe` is chosen deliberately: it is a legitimate system binary, carries a Microsoft signature, and exits silently when invoked with unexpected arguments
- Survives application restart: on next launch, KvcKiller reads IFEO and restores the **PARALYZED** state in the UI without any separate persistence file
- Reversed by Restore — the IFEO entry is removed before the service is restarted
- Temp files created during the offline hive edit (`C:\Windows\Temp\Ifeo.hiv`, `.LOG1`, `.LOG2`, `.blf`, `*.regtrans-ms`) are cleaned up after each operation

### Restore

- Removes the IFEO Debugger entry (unparalyze) before any launch attempt
- Restarts via **SCM StartService** → **ShellExecuteEx (runas)** → direct **CreateProcess**
- Batch restore for all selected targets; path resolved from live `QueryFullProcessImageNameW` or HKCU path cache

### GUI

- Win32 ListView with 5 columns (Process Name, Source, PID, Status, Last Action), click-to-sort
- Dark / light mode auto-detected; **Mica backdrop** on Windows 11; animated **sine-pulse** Paralyze label
- **PARALYZED** row state: orange color, distinct from red KILLED, persists across restarts
- "Show extra processes" — appends running processes not in the config list
- "Hide inactive built-in targets" — collapses not-running entries with no pending action
- Worker threads for kill and restore; atomic shutdown coordination; UI stays responsive

### CLI

Single binary, dual mode — GUI when launched without arguments, headless console tool with arguments. No separate CLI binary.

---

## Architecture

flowchart TD A[WinMain] --> B{argc > 1?} B -->|Yes| C[RunCLI] B -->|No| D[WndProc / GUI] D --> E[ListViewManager] D --> F[ProcessOperations] D --> G[UIHelpers] F --> H[DriverController] F --> I[ProcessKiller] F --> J[ConfigReader] F --> K[IFEO offline edit] H --> L[NtLoadDriver] H --> M[FDI — icon CAB] H --> N[TrustedInstaller] I --> O[DeviceIoControl IOCTL] I --> P[CreateToolhelp32Snapshot] K --> Q[RegSaveKeyEx / RegLoadKey] K --> R[RegRestoreKey] C --> H C --> I C --> J 

```
WinMain
  ├─ argc > 1  →  RunCLI (headless, AttachConsole)
  └─ GUI mode
       ├─ WndProc
       │    ├─ WM_CREATE     →  InitListView, RefreshProcessList, timers
       │    ├─ WM_COMMAND    →  KillOnceAction / RestoreAction (worker threads)
       │    ├─ WM_NOTIFY     →  HandleListCustomDraw, sort on LVN_COLUMNCLICK
       │    ├─ WM_CLOSE      →  set g_shutdownPending; defer DestroyWindow if workers active
       │    └─ WM_PAINT      →  PaintHeaderPanels (custom rounded rects)
       ├─ UIHelpers           (fonts, dark mode, Mica, DPI-aware layout)
       ├─ ListViewManager     (columns, custom draw, row flag bits, IFEO cold-start check)
       ├─ ProcessOperations   (worker threads, IFEO paralyze/unparalyze, driver init)
       ├─ ProcessKiller       (FindProcesses, KillProcess, RestoreProcesses)
       ├─ DriverController    (FDI extract → DriverStore → NtLoad → IOCTL → NtUnload)
       └─ ConfigReader        (HKCU registry: target list, path cache, history ring buffer)
```

### Thread Model

Worker threads (`KillOnceThreadProc`, `RestoreThreadProc`) are created via `CreateThread` from `WM_COMMAND`. `g_activeWorkers` (atomic int) counts live workers; `g_shutdownPending` (atomic bool) signals them to exit cleanly. `WM_CLOSE` defers `DestroyWindow` until `g_activeWorkers == 0` — the window never closes mid-operation. The last worker to exit during shutdown re-posts `WM_CLOSE` to unblock the message loop.

A `WorkerGuard` RAII struct placed at the top of every thread proc guarantees `g_activeWorkers` is decremented on all exit paths, including early returns and exception unwinds.

Cross-thread UI updates use `PostMessage(WMU_STATUS_TEXT)` with a heap-allocated `std::wstring*`; the message handler owns and deletes it. `g_lastActionByName` and `g_prevRunningByName` are guarded by `g_statusLock` (`CRITICAL_SECTION`).

---

## Driver Lifecycle

flowchart LR A[KillOnceAction] --> B[InitializeDriver] B --> C{Already loaded?} C -->|Yes| G C -->|No| D[ExtractDriverFromIcon] D --> E[FDI — icon LZX CAB → DriverStore] E --> F[TrustedInstaller impersonation] F --> G[RegisterDriver — SCM registry key] G --> H[RtlAdjustPrivilege SeLoadDriver] H --> I[NtLoadDriver] I --> J[GetDriverHandle — open Warsaw_PM] J --> K[DeviceIoControl IOCTL kill loop] K --> L[CloseDriverHandle] L --> M[NtUnloadDriver] M --> N[CleanupDriverRegistry] 

**Driver extraction**

The kernel driver binary is stored as an LZX-compressed Cabinet block appended to `KvcKiller.ico` (the application icon, embedded as `MAIN_ICON` in the RC file). On each Kill operation, `ExtractDriverFromIcon`:

1. Reads the icon bytes from the PE resource section via `FindResource` / `LockResource`
2. Scans for the `MSCF` Cabinet signature within the icon binary
3. Sets up a `MemoryReadContext` and calls `FDICopy` with all-in-memory FDI callbacks (`fdi_read`, `fdi_seek`, `fdi_write`, `fdi_open`, `fdi_close`)
4. Writes the extracted `.sys` to the DriverStore path using a HANDLE opened under TrustedInstaller impersonation

`NtLoadDriver` / `NtUnloadDriver` are called directly from `ntdll.lib` — no `CreateService`, no SCM interaction, no visible service entry.

---

## Kill Flow

sequenceDiagram participant UI as WndProc (UI thread) participant W as KillOnceThreadProc participant D as DriverController participant K as ProcessKiller participant P as ProcessOperations UI->>W: CreateThread (IDC_KILL_PROCESS) Note over W: WorkerGuard at entry W->>D: InitializeDriver D-->>W: hDriver W->>K: FindProcesses(selected) K-->>W: ProcessInfo list (PID, name) loop for each found process W->>K: KillProcess(hDriver, pid) K->>K: DeviceIoControl IOCTL 0x22201C K-->>W: success / fail W->>W: collect name in killedNames[] end alt paralyzeEnabled W->>P: ParalyzeProcessesBatch(killedNames) P->>P: IFEO offline edit — write Debugger=systray.exe P-->>W: successCount W->>W: promote KILLED → PARALYZED in g_lastActionByName end W->>UI: PostMessage WMU_REFRESH_LIST W->>UI: PostMessage WMU_STATUS_TEXT Note over W: WorkerGuard destructor → NotifyWorkerDone() 

| Step | Detail |
|---|---|
| **Discovery** | `CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS)` + `Process32FirstW/NextW`; matched by base exe name, case-insensitive |
| **IOCTL** | `DeviceIoControl(hDriver, 0x22201C, buffer, 1036, nullptr, 0, ...)` — PID in first 4 bytes, 1032 bytes zero-padding required by the driver's dispatch handler |
| **Confirmation** | `IsProcessRunning(pid)` via `WaitForSingleObject(hProcess, 0)` — process object is signalled the instant it exits, more reliable than re-snapshotting |
| **State** | `g_lastActionByName[key] = kActionKilled`; promoted to `kActionParalyzed` if IFEO write succeeded |

---

## Paralyze: IFEO Offline Editing

### What IFEO Does

**Image File Execution Options** (`HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options`) is a Windows mechanism originally designed for attaching a debugger to a process at startup. When a `Debugger` value is present under a process's IFEO subkey, the Windows loader (`ntdll!LdrpInitializeProcess`) checks for it before transferring control to the image entry point. If found, it launches the registered debugger binary with the original command line appended — and the original process never runs.

KvcKiller writes `Debugger = C:\Windows\System32\systray.exe`. When the AV service manager issues `StartService`, the Windows loader intercepts the launch and runs `systray.exe` instead. `systray.exe` exits immediately when invoked with unexpected arguments. From SCM's perspective the service "started and exited with code 0", which does not trigger service recovery restart logic. No AV code executed.

`systray.exe` is chosen over a non-existent path or a custom binary because it:

- Carries a valid Microsoft Authenticode signature
- Is present on every Windows installation
- Exits cleanly without error dialogs or event log entries

### Why Direct IFEO Write Fails

Attempting to write `Debugger` directly to a live IFEO subkey with `RegSetValueExW` returns `ERROR_ACCESS_DENIED` (error 5), even from an Administrator process with both `SeRestorePrivilege` and `SeBackupPrivilege` enabled.

The root cause is the DACL on individual IFEO subkeys. Microsoft tightens the ACLs on IFEO entries for Defender components so that only `NT AUTHORITY\SYSTEM` and `NT SERVICE\TrustedInstaller` hold write access. Security vendors (CrowdStrike, SentinelOne, ESET, Kaspersky) extend this by adding explicit deny ACEs as part of their self-protection stack — blocking writes even from a TrustedInstaller-impersonating token.

**The core problem: you cannot reliably write `Debugger` to live IFEO for protected processes regardless of privilege level.** The ACLs are evaluated per-key, not per-privilege.

### The Offline Hive Editing Trick

The solution is to never touch the live IFEO key directly. Instead:

```
Step 1 — RegSaveKeyEx
  Save HKLM\...\Image File Execution Options → C:\Windows\Temp\Ifeo.hiv

  Reads the entire IFEO subtree into a flat hive file on disk.
  Requires SE_BACKUP_NAME — backup privilege bypasses DACL on read.
  The per-subkey ACLs that blocked direct writes are irrelevant here.

Step 2 — RegLoadKey
  Load Ifeo.hiv → HKLM\TempIFEO

  Mounts the saved hive file as a new root key under HKLM.
  The mounted key's effective DACL is inherited from the parent HKLM
  node, NOT from the original IFEO subkey ACLs.
  The calling Administrator process has full KEY_ALL_ACCESS on TempIFEO.

Step 3 — Edit offline
  RegCreateKeyExW / RegSetValueExW / RegDeleteKeyW on HKLM\TempIFEO\...

  All operations succeed — no ACL enforcement from original IFEO DACLs.

Step 4 — RegUnLoadKey
  Unmount HKLM\TempIFEO — flushes all changes to Ifeo.hiv.

Step 5 — RegRestoreKey (REG_FORCE_RESTORE)
  Restore Ifeo.hiv → HKLM\...\Image File Execution Options

  Atomically replaces the entire live IFEO subtree with the modified hive.
  REG_FORCE_RESTORE overrides the target key's DACL.
  Requires SE_RESTORE_NAME.

Step 6 — Cleanup
  Delete C:\Windows\Temp\Ifeo.hiv, Ifeo.hiv.LOG1, Ifeo.hiv.LOG2,
         Ifeo.hiv.blf, *.regtrans-ms
```

This approach works because `RegRestoreKey` with `REG_FORCE_RESTORE` is a legitimate backup/restore API that predates Windows NT 4.0. The `SE_BACKUP_NAME` + `SE_RESTORE_NAME` privilege pair is intentionally granted to Administrator accounts to allow system backup software (Veeam, VSS, Windows Backup) to preserve and restore registry hives without DACL interference. Microsoft cannot restrict these privileges without breaking every backup solution on Windows.

**IFEO ownership tracking:** A value under `HKCU\Software\KvcKiller` records which IFEO entries were created by KvcKiller versus which already existed. On unparalyze, only KvcKiller-created subkeys are deleted entirely; for pre-existing subkeys, only the `Debugger` value is removed.

### The MsMpEng Phenomenon

`MsMpEng.exe` (Windows Defender's antimalware engine) is the canonical demonstration case for why Paralyze is necessary alongside Kill.

After a kernel-mode kill, `WinDefend` detects that `MsMpEng.exe` exited and issues `StartService` within milliseconds. `SecurityHealthService` monitors Defender health independently; SCM service recovery settings specify immediate restart on failure; on Windows 11, Defender processes carry **PPL-Antimalware** flags.

Without Paralyze, `MsMpEng.exe` is back within 1–3 seconds of the kill. With Paralyze:

1. KvcKiller sends the IOCTL kill — `MsMpEng.exe` is terminated at ring 0
2. `WinDefend` calls `StartService` → loader checks IFEO → finds `Debugger = systray.exe` → runs `systray.exe` → exits with code 0
3. SCM records "service started and exited normally" — recovery restart logic does not trigger
4. `SecurityHealthService` sees a clean exit, does not escalate
5. Defender stays dead until Restore explicitly removes the IFEO entry and calls `StartService`

The same mechanism works for CrowdStrike (`CSFalconService`), SentinelOne (`SentinelAgent`), ESET (`ekrn`), Kaspersky (`avp`), and all other AV/EDR engines that restart via SCM — because it targets the **Windows loader**, not the service manager.

---

## Restore Flow

sequenceDiagram participant UI as WndProc (UI thread) participant W as RestoreThreadProc participant P as ProcessOperations participant K as ProcessKiller UI->>W: CreateThread (IDC_RESTORE) Note over W: WorkerGuard at entry W->>W: collect selected names loop for each selected name W->>P: IsProcessParalyzed(name) P->>P: RegOpenKeyEx HKLM IFEO — check Debugger value P-->>W: paralyzed / not end W->>P: UnparalyzeProcessesBatch(paralyzedNames) P->>P: IFEO offline edit — remove Debugger or delete subkey P-->>W: successCount W->>K: RestoreProcesses(allNames) K->>K: SCM StartService → ShellExecuteEx runas → CreateProcess K-->>W: results[] W->>UI: PostMessage WMU_REFRESH_LIST W->>UI: PostMessage WMU_STATUS_TEXT Note over W: WorkerGuard destructor → NotifyWorkerDone() 

`ProcessKiller::RestoreProcesses` tries three methods in order, stopping at the first success:

| Method | When used |
|---|---|
| **SCM StartService** | Process is registered as a Win32 service (covers most AV/EDR) |
| **ShellExecuteEx** with `runas` | GUI apps and self-elevating executables |
| **CreateProcess** | Fallback using the HKCU path cache |

The executable path is resolved from (priority order):

1. Live `QueryFullProcessImageNameW` on the running PID — updated on every refresh cycle
2. `ConfigReader::ReadProcessPath` from the HKCU path cache
3. `QueryServiceConfigW` → `lpBinaryPathName` from the SCM service entry

`IsProcessParalyzed` reads `HKLM\...\Image File Execution Options\<name.exe>\Debugger` directly at restore time, so the correct unparalyze path is taken even when the PARALYZED state was set in a previous session.

---

## Built-in Target List

Compiled into the binary as `IDR_DEFAULT_TARGETS RCDATA "KvcKiller.ini"` and written to HKCU on first run. The resource loader handles three encodings automatically: **UTF-16 LE with BOM**, **UTF-8 with BOM**, and **plain UTF-8 / ANSI**.

90+ targets across 15 vendors:

| Vendor | Processes |
|---|---|
| **Microsoft Defender / Sentinel / Sense** | MsMpEng, MsMpEngCP, MpCmdRun, NisSrv, SecurityHealthService, SecurityHealthHost, SecurityHealthSystray, MsSense, MsSecFw, MsMpSigUpdate, smartscreen, WdNisSvc, WinDefend, Sense, SenseIR, SenseCncProxy, MsColleciton, MpNWMon |
| **Avast / AVG** | AvastSvc, AvastUI, aswEngSrv, aswToolsSvc, avg, avgui, avgsvc, avgidsagent |
| **Bitdefender** | vsserv, bdservicehost, bdagent, bdwtxag, updatesrv, bdredline, bdscan, seccenter |
| **Carbon Black (VMware)** | cb, RepMgr, RepUtils, RepWsc |
| **CrowdStrike** | CSFalconService, CSFalconContainer |
| **Cylance (BlackBerry)** | CylanceSvc, CylanceUI |
| **ESET** | ekrn, egui, ema\_client, ekrnui, elam |
| **FireEye / Trellix** | xagt, FireEye |
| **Kaspersky** | avp, avpui, klnagent, kavfs, kavfsslp, kmon, ksde, kavtray |
| **Malwarebytes** | mbamservice, mbam, mbamtray |
| **McAfee** | McAfeeService, McAPExe, mcshield, mfemms, mfeann, mcagent, mctray |
| **Norton / Symantec** | ccSvcHst, nortonsecurity, symantec, Smc, SNAC, SymCorpUI |
| **SentinelOne** | SentinelAgent, SentinelMonitor, SentinelServiceHost, SentinelHelperService, SentinelBrowserNative |
| **Sophos** | SavService, SophosHealth, SophosCleanService, SophosFileScanner, SophosSafestore64, SophosMcsAgent, SophosEDR |
| **Trend Micro** | TMBMSRV, NTRTScan, TmListen, TmProxy, TmPfw |

Custom targets can be added to `HKCU\Software\KvcKiller\Targets` or by editing `KvcKiller.ini` before first run.

---

## GUI Reference

### Window Layout

| Panel | Content |
|---|---|
| Header left | © WESMAR Marek Wesołowski · Advanced Process Management Tool – Windows 11 25H2 |
| Header right | Warning / status banner |
| **Process Name** · Source · PID · Status · Last Action | MsMpEng · Built-in · 4812 · RUNNING · `-` |
|  | CylanceSvc · Built-in · — · NOT RUNNING · `KILLED` |
|  | MsMpEng · Built-in · — · NOT RUNNING · `PARALYZED` |
| Checkboxes | ☐ Show active processes outside target list |
|  | ☐ Hide inactive built-in targets |
|  | ☐ ~Paralyze self-restart~ `[KILL PROCESS]` `[RESTORE]` |
| Footer | READY · 90 targets · 3 running · 2 extra |

### ListView Columns

| Column | Width | Description |
|---|---|---|
| **Process Name** | Flexible | Base executable name from `CreateToolhelp32Snapshot` |
| **Source** | 60 px | `Built-in` (from config) or `Saved` (extra, added dynamically) |
| **PID** | 68 px | Live PID; `—` when not running |
| **Status** | 118 px | `RUNNING` / `NOT RUNNING`; color-coded via `NM_CUSTOMDRAW` |
| **Last Action** | 120 px | `KILLED` / `PARALYZED` / `RESTORED` / `RESTARTED` / `LIVE` / `RESTORING` / `-` |

### Row Colors (NM\_CUSTOMDRAW)

| State | Dark mode | Light mode |
|---|---|---|
| **RUNNING** | Green text / dark green bg | Green text / light green bg |
| **KILLED** | Red text / dark red bg | Dark red text / light red bg |
| **PARALYZED** | Orange text / dark orange bg | Dark orange text / light orange bg |
| **RESTORED / RESTARTED** | Blue text / dark blue bg | Blue text / light blue bg |
| **NOT RUNNING (no action)** | Gray text | Gray text |

PARALYZED is evaluated before KILLED in the custom draw chain — the more specific state wins.

### Row Flag Bits (LVITEM::lParam)

| Flag | Value | Meaning |
|---|---|---|
| `kRowFlagRunning` | 0x0001 | Process is currently running |
| `kRowFlagExtra` | 0x0002 | Not in config; added by "Show extra" |
| `kRowFlagKilled` | 0x0004 | Was killed this session |
| `kRowFlagRestored` | 0x0008 | Was restored this session |
| `kRowFlagRestarted` | 0x0010 | Restarted on its own after kill |
| `kRowFlagParalyzed` | 0x0020 | Killed and has IFEO Debugger entry blocking restart |

### PARALYZED State Persistence

PARALYZED is the only action state that persists across application restarts without a separate persistence file. IFEO is the source of truth. On startup, `RefreshProcessList` calls `IsProcessParalyzed(name)` for each not-running process with no stored action. If the IFEO `Debugger` value is found, the row is initialised with `kActionParalyzed` and `kRowFlagParalyzed`.

KILLED state is intentionally not persisted across restarts: when KvcKiller exits, the killed process may already be restarting. Showing a stale KILLED label on next launch without knowing actual current state would be misleading.

### Paralyze Label Animation

The Paralyze checkbox label pulses in red using a sine wave tied to `GetTickCount64`. A 40 ms `WM_TIMER` invalidates only the label HWND; `WM_CTLCOLORSTATIC` recalculates the text color each frame:

```
f = 0.5 + 0.5 * sin(t * 2π / period)
R = lerp(bgR, 255, f * 0.9 + 0.1)
G = lerp(bgG,  40, f * 0.9 + 0.1)
B = lerp(bgB,  40, f * 0.9 + 0.1)
```

Pure color interpolation, no alpha blending, no GDI+ — compatible with all Win32 themes and both dark/light mode.

---

## CLI Reference

Because KvcKiller is compiled as a `/SUBSYSTEM:WINDOWS` GUI binary, it cannot simply write to `stdout` when launched from a terminal. On entry to `RunCLI`, `SetupConsole` calls `AttachConsole(ATTACH_PARENT_PROCESS)` — this attaches to the existing console of the parent process without opening a new window. `_setmode(_O_U16TEXT)` is set so `wprintf` outputs correct Unicode on all code pages.

When `RunCLI` finishes, `SendEnterToConsole` injects a synthetic `VK_RETURN` into the console input buffer via `WriteConsoleInputW`, forcing `cmd.exe` and PowerShell to redraw their prompts.

```
KvcKiller.exe -help
KvcKiller.exe -list
KvcKiller.exe -kill <n> [-paralyze]
KvcKiller.exe -restore <n>
```

| Command | Description |
|---|---|
| `-help` | Print usage and exit |
| `-list` | Enumerate config targets; show PID and running status for each |
| `-kill <n>` | Load driver, find process by name, send IOCTL kill |
| `-kill <n> -paralyze` | **Paralyze first** (IFEO injection), then kill — order reversed vs GUI mode |
| `-restore <n>` | Unparalyze if needed, then restart via SCM / ShellExecute / CreateProcess |

> **CLI vs GUI kill+paralyze order:** In CLI mode the IFEO `Debugger` entry is written *before* the kill so the entry is in place before any watchdog can issue a restart. In GUI mode the kill happens first and paralyze follows immediately after — both happen within the same worker thread before any watchdog can react.

---

## Registry & Configuration

All persistent state lives under `HKCU\Software\KvcKiller`.

| Key / Value | Type | Content |
|---|---|---|
| `Targets\List` | REG\_MULTI\_SZ | All target process names (no `.exe`), newline-separated |
| `Paths\<name.exe>` | REG\_SZ | Full executable path; updated on **every** refresh cycle when the process is seen running |
| `History\Entries` | REG\_MULTI\_SZ | **Ring buffer, max 16 entries.** Each entry: `YYYY-MM-DD HH:MM:SS \\| ACTION \\| processName \\| fullPath`. Newest entry at index 0. |

IFEO ownership tracking uses `HKCU\Software\KvcKiller` as well — value name `<ProcessName.exe>`, type `REG_DWORD`, data `1` marks entries created by KvcKiller. On unparalyze: if the marker is present, the entire IFEO subkey is deleted; if absent (pre-existing entry), only the `Debugger` value is removed.

---

## Module Map

| Module | Responsibility |
|---|---|
| **main.cpp** | `WndProc`, `WinMain`; header panel custom drawing; `WM_CLOSE` graceful-shutdown logic; `WM_CTLCOLORSTATIC` sine-pulse Paralyze animation; timer dispatch |
| **DriverController.cpp** | Full driver lifecycle: FDI extraction from icon (LZX CAB), TrustedInstaller impersonation, DriverStore drop, SCM registry key, `NtLoadDriver` / `NtUnloadDriver`, device handle management, `CleanupDriverRegistry` |
| **ProcessKiller.cpp** | `CreateToolhelp32Snapshot` enumeration; `IsProcessRunning` via `WaitForSingleObject`; `SendIOCTL` / `KillProcess`; `RestoreProcess` / `RestoreProcesses` (SCM → ShellExecute → CreateProcess chain) |
| **ProcessOperations.cpp** | `KillOnceThreadProc` / `RestoreThreadProc` worker threads; `WorkerGuard` RAII; IFEO offline hive editing (`ProcessBatchIfeo`); `ParalyzeProcessesBatch` / `UnparalyzeProcessesBatch` / `IsProcessParalyzed`; `KillOnceAction` / `RestoreAction` UI triggers |
| **ConfigReader.cpp** | HKCU registry CRUD: target list (REG\_MULTI\_SZ), path cache (REG\_SZ per process), history ring buffer (max 16); `ReadSeedProcessList` from RCDATA with 3-way BOM detection |
| **ListViewManager.cpp** | `InitListView`, `RefreshProcessList`, `HandleListCustomDraw`, `HandleListRowClick`; sort dispatch; row flag bit management; IFEO cold-start check for PARALYZED state persistence |
| **UIHelpers.cpp** | `CreateUiFont` (DPI-aware), `AppUseDarkMode`, `ApplyModernWindowEffects` (Mica, rounded corners), `CalculateMainLayout`, `LayoutMainWindow`, `UpdateStatusText`, `UpdateProcessCount` |
| **CLIHandler.cpp** | `SetupConsole` (`AttachConsole` + fd re-open + `_setmode`); `SendEnterToConsole` (`WriteConsoleInputW` VK\_RETURN injection); `PrintHelp`; `RunCLI` dispatcher; `InitDriverForCli` thin wrapper |
| **GlobalData.cpp** | Definitions for all globals: window/GDI handles, sort state, `g_statusLock`, `g_lastActionByName`, `g_prevRunningByName`, `g_shutdownPending` (atomic bool), `g_activeWorkers` (atomic int), `WMU_*` message IDs |
| **Utils.cpp** | `LoadStr` (string resource helper), `GetWindowsVersion`, `IsRunningAsAdmin`, `EnsureExeName`, `StripExeExtension` |

---

## Build

```powershell
# Release x64
.\build.ps1

# Clean only (no rebuild)
.\build.ps1 -NoBuild

# Force-clean transient dirs then build
.\build.ps1 -Clean
```

`build.ps1` locates MSBuild via hardcoded VS 2026 paths or `vswhere`, removes transient directories (`.vs`, `obj`, `x64`, `src\obj`, `src\x64`), builds `src\KvcKiller.vcxproj` with `/p:Configuration=Release;Platform=x64 /m`, and sets the output file's `CreationTime` and `LastWriteTime` to **2030-01-01 00:00:00**. This deterministic timestamp prevents compile-time attribution during forensic analysis.

| Setting | Value |
|---|---|
| **Toolset** | Visual Studio 2026 (v145) |
| **Standard** | C++20 |
| **Architecture** | x64 only |
| **CRT** | Static (`/MT`) — no VC++ Redistributable needed |
| **External DLLs** | None |
| **Output** | `bin\KvcKiller.exe` |
| **File timestamps** | Forced to 2030-01-01 00:00:00 (anti-forensic, deterministic builds) |

---

## The Driver — CVE-2023-52271

The kernel driver used is **wsftprm.sys** (product: `wsddprm`, version 2.0.0.0), the kernel component of **Topaz Warsaw** — banking antifraud software developed by the Brazilian company Topaz OFD. The driver carries a valid Authenticode signature from a trusted CA, which is the only reason `NtLoadDriver` accepts it without a DSE bypass on a stock Windows 10/11 installation.

**CVE-2023-52271** describes a vulnerability in wsftprm.sys v2.0.0.0: the driver exposes device `\\.\Warsaw_PM` to any process on the system and implements an IOCTL handler at code `0x22201C` that calls `ZwTerminateProcess` in kernel context without any privilege validation. Any process that can open the device can terminate any other process — including Protected Process Light (PPL) processes such as Windows Defender on a fully patched, HVCI-enabled, Secure Boot system.

CVSS v3.1 score: **6.5** (Medium) — the scoring reflects a low-privilege attacker; from an Administrator context the impact is unconditional.

**BYOVD deployment:** KvcKiller extracts the kernel driver into `System32\DriverStore\FileRepository`, masquerading as a legitimate network adapter component (`netadapter.sys`). It dynamically scans for the correct version-specific subfolder (`netadapter.inf_amd64_*`) to blend into the OS structure. The driver is loaded via `NtLoadDriver`, performs the kill operation via IOCTL `0x22201C`, and is immediately unloaded with full cleanup of file and registry artifacts.

### IOCTL Details

Input buffer: 1036 bytes — target PID as `DWORD` in the first 4 bytes, 1032 bytes zero-padding required by the driver's dispatch routine. No output buffer. The driver invokes `ZwTerminateProcess` from ring 0, bypassing all user-space self-protection callbacks and `PROCESS_TERMINATE` handle restrictions enforced by PPL.

### Privilege Requirements

| Operation | Required privilege |
|---|---|
| Load / unload kernel driver | `SeLoadDriverPrivilege` |
| Write driver to DriverStore | TrustedInstaller impersonation |
| Save IFEO hive (`RegSaveKeyEx`) | `SE_BACKUP_NAME` |
| Restore IFEO hive (`RegRestoreKey`) | `SE_RESTORE_NAME` |
| Query process image path | `PROCESS_QUERY_LIMITED_INFORMATION` |

All privileges are available to a standard Administrator-elevated process on Windows 10/11.

### What KvcKiller Does Not Do

- Does not disable DSE or patch kernel callbacks
- Does not write to memory of other processes
- Does not install persistent services, scheduled tasks, or startup entries
- Does not make network connections
- Does not exfiltrate or store credentials
- The kernel driver is unloaded and its registry key cleaned up after every Kill operation

---

## On Microsoft's Habit of Leaving Doors Open

There is a recurring pattern in Windows security architecture: a team ships a powerful API to solve a legitimate infrastructure problem, another team later builds a security boundary that the API quietly walks straight through, and nobody closes the gap because closing it would break the legitimate use case. KvcKiller is built almost entirely from this pattern.

### The IFEO Problem That Cannot Be Fixed

`HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options` is the mechanism that lets Windows redirect any process launch to a debugger before the original binary gets control. Microsoft tightened the DACLs on individual IFEO subkeys so that security software entries — Defender, CrowdStrike, SentinelOne — cannot be modified by Administrator. A direct `RegSetValueExW` returns `ERROR_ACCESS_DENIED`.

The answer to this hardening is the Windows registry backup API, which predates the hardening by about twenty years.

`RegSaveKeyEx` reads any registry subtree into a flat hive file, bypassing DACL checks entirely because `SE_BACKUP_NAME` is explicitly designed to override object security for backup purposes. `RegLoadKey` mounts the file under a new root where the calling process has full control. `RegRestoreKey` with `REG_FORCE_RESTORE` writes the modified hive back over the live key, again bypassing the target DACL because `SE_RESTORE_NAME` is explicitly designed to override object security for restore purposes.

Both privileges are granted to Administrators by default. Microsoft cannot revoke them without breaking every backup product on the platform — Veeam, Acronis, Windows Server Backup, VSS. The security team hardened IFEO. The infrastructure team shipped the escape hatch decades earlier and it is load-bearing. The result: a completely legitimate sequence of documented API calls, requiring no shellcode, no kernel patch, no signature bypass — writes to a key that Microsoft explicitly marked as protected against Administrator writes.

This is not a bug in the backup API. It works exactly as designed. That is the point.

### The Broader Catalogue

**Accessibility features as debugger proxies.** IFEO's `Debugger` mechanism was widely abused by replacing `sethc.exe` (Sticky Keys), `Magnify.exe`, or `Narrator.exe` with `cmd.exe` — accessible from the Windows lock screen before any authentication. The technique has been documented since at least 2008. Microsoft's fix was to add image file hash validation for specific accessibility binaries. The IFEO mechanism itself was not changed.

**UI Automation as a Tamper Protection bypass.** The Windows Security application that controls Defender's Tamper Protection toggle is an ordinary UWP window. Its toggle controls are fully accessible via `IUIAutomationTogglePattern`. An Administrator process can open the Windows Security window invisibly (opacity 0, DWM cloaking, off-screen positioning), find the Tamper Protection toggle by automation ID, invoke it, and close the window — no registry write, no service call, no kernel interaction. Tamper Protection is now off. The entire operation uses Microsoft accessibility infrastructure that cannot be removed without breaking screen readers.

This exact technique is implemented in **[WinDefCtl](https://github.com/wesmar/WinDefCtl)** — a companion utility that disables Defender's Real-Time Protection and Tamper Protection through UI Automation alone. No privileges beyond Administrator. No kernel driver. No registry manipulation.

**SMSS, BootExecute, and the pre-security boot window.** `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\BootExecute` is a `REG_MULTI_SZ` value that SMSS reads and executes during the native phase of Windows boot — before the Win32 subsystem initialises, before the SCM starts, before any security service or EDR agent has loaded. Entries run as native subsystem applications with SYSTEM privileges in an environment where the entire security stack simply does not exist yet.

**[KernelResearchKit](https://github.com/wesmar/KernelResearchKit)** explores this space. `BootBypass(FastReboot)` is a native subsystem application deployed via `BootExecute` that patches Driver Signature Enforcement by manipulating kernel code integrity callbacks — before Windows Defender, before any EDR agent, before the Win32 subsystem itself.

The offset of the DSE control flag (`ci.dll!g_CiOptions`) in any given Windows build is resolved dynamically from Microsoft's own symbol server (`https://msdl.microsoft.com/download/symbols`). Microsoft publishes full PDB files for every Windows component to support crash dump analysis, WinDbg, and Visual Studio debugging. Those PDB files contain the precise byte offsets of every internal structure and global variable in the kernel, for every build ever shipped. The symbol server is effectively a changelog of exploitable offsets, maintained and hosted by Microsoft.

KvcKiller is a demonstration that the surface exists and is accessible to any sufficiently motivated Administrator-level process. The techniques are not novel individually — they are documented in CVEs, security research, and Microsoft's own API documentation. The combination assembled here is the contribution.

---

## Project Context

KvcKiller is part of a broader set of tools exploring the gap between Windows security guarantees and Windows API capabilities:

- **[KVC](https://github.com/wesmar/kvc)** — the flagship framework. KvcKiller will be integrated as a module into KVC, providing kernel-mode process termination as one component of a larger capability set.
- **[WinDefCtl](https://github.com/wesmar/WinDefCtl)** — Defender Real-Time Protection and Tamper Protection control via UI Automation API. No kernel driver, no registry writes — Microsoft's own accessibility infrastructure used to click the toggle that disables the security software.

---

## System Requirements

| Component | Requirement |
|---|---|
| **OS** | Windows 10 or later (Windows 11 recommended — Mica backdrop) |
| **Privileges** | Administrator (elevated token) |
| **Architecture** | x64 |
| **Dependencies** | None — static CRT, no VC++ Redistributable |
| **Compiler** | Visual Studio 2026 (v145), C++20 (build only) |

---

**KvcKiller** *Kernel-mode process termination — IFEO paralyze — no user-space self-protection survives* 🌐 [kvc.pl](https://kvc.pl) | 📧 [marek@kvc.pl](mailto:marek@kvc.pl) *© 2026 WESMAR Marek Wesołowski* 

### Add a comment

---

## Navigation

- Parent: [Repositories](http://kvc.pl/repositories.md)
- Previous: [UnderVolter](http://kvc.pl/repositories/undervolter.md)
- Next: [BootBypass](http://kvc.pl/repositories/bootbypass.md)
