2026-05-24 — Build 54 784 B, 0 CRT imports

VaultGuard (vg.exe) is a complete rewrite of a 12-year-old Qt/C++ folder-protection suite in pure x64 MASM — zero CRT, native WinAPI only, Windows 11 Dark Mode + Mica. The same binary acts as a full Win32 GUI application or a scriptable CLI tool depending on its arguments. It communicates with vg.sys, a kernel-mode FSFilter Content Screener minifilter signed by PROMOSOFT CORPORATION (2014), which loads on Windows 11 26H1 via Microsoft's backward-compatibility mechanism for cross-signed drivers predating 29 July 2015.

VaultGuard — Kernel-Backed Folder Protection for Windows

VaultGuard


**Pure x64 MASM application — zero CRT, native WinAPI, Windows 11 Dark Mode + Mica** *Communicates with a kernel minifilter driver via reverse-engineered IOCTL codes* *54 KB binary — GUI tree view with flag checkboxes, or fully scriptable CLI*

Table of Contents


Overview

VaultGuard is an access-control tool for Windows directories and files, backed by a kernel-mode driver of type Minifilter (FSFilter Content Screener). The project is a ground-up rewrite of the original 12-year-old VaultGuard (Qt/C++, split GUI + CLI) into pure x64 MASM assembly. The primary design goals are:

Goal Implementation
Zero CRT No msvcrt, ucrtbase, or vcruntime. All memory, string, and file operations go through native Win32 API directly
Minimal footprint Compiled vg.exe is 54 784 bytes — roughly the size of a 1990s boot sector
Modern UX (Windows 11) Dark Mode, Mica material (DWM API), PerMonitorV2 DPI scaling, flicker-free rendering
Dual-head binary Same binary: no arguments → rich GUI; any recognized argument → scriptable CLI
Driver backward compatibility Original vg.sys signed by PROMOSOFT CORPORATION (2014 certificate) loads correctly on Windows 11 26H1 via Microsoft's cross-signed driver legacy mechanism

Architecture

flowchart TD A[vg.exe launched] --> B{argc >= 2?} B -->|Yes| C[CliDispatch argv] B -->|No| D[CreateMainWindow GUI] C --> E{Known switch?} E -->|Yes| F[CLI command handler] E -->|No| D D --> G[WM_CREATE: _OnCreate layout] G --> H[SetTimer 2000ms] H --> I[Message Loop] I --> J{WM_MESSAGE} J -->|WM_COMMAND| K[_OnCommand handlers.asm] J -->|WM_NOTIFY| L[_OnNotify flag checkboxes] J -->|WM_DROPFILES| M[_OnDropFiles drop.asm] J -->|WM_TIMER| N[UpdateStatusBar + RefreshLists] J -->|WM_SETTINGCHANGE| O[_ReadDarkMode + ApplyDarkMode] K --> P[EnsureDriverReady] F --> P P --> Q{Device open?} Q -->|Yes| R[IOCTL DeviceIoControl] Q -->|No| S[InstallDriver: FDI extract + CreateServiceW] S --> T[StartDriver: SCM StartServiceW] T --> R R --> U[ConfigSave/Load: HKCU registry]

GUI Reference

Launching vg.exe without arguments (or with an unrecognized argument) starts the graphical interface. Because the application installs and communicates with a kernel driver, it requires Administrator privileges (enforced via requireAdministrator in the manifest).

Main Window

The main window (class VGMainWnd) has a fixed size of 680 × 450 pixels and uses a native Win32 window with a Mica backdrop and a dark title bar when the system is in Dark Mode.

Instead of a traditional status bar at the bottom, driver and protection state is embedded dynamically in the window title:

Vault Guard | Driver: STOPPED   | Protection: OFF
Vault Guard | Driver: TRANSIENT | Protection: ON

This state refreshes every 2 seconds via WM_TIMER and after every user action.

Protected Folders Panel

The upper section contains a ListView of protected paths.

Control Behavior
[Add path...] button Opens SHBrowseForFolderW native folder browser
[Remove selected] button Removes all selected entries (multi-select supported)
Flag columns (H / L / R / X) Clicking any flag cell immediately toggles the checkbox and sends an IOCTL update to the driver
Drag & Drop Accepts folders (and files) dragged from Windows Explorer; .lnk shortcuts are resolved automatically via COM IShellLink

Columns:

Column Flag Hex
Path
Hidden (H) VG_FLAG_HIDDEN 0x01
Locked (L) VG_FLAG_LOCKED 0x02
Read-only (R) VG_FLAG_READONLY 0x04
No run (X) VG_FLAG_NOEXEC 0x08

Trusted Processes Panel

The lower section defines processes that bypass all driver protections (Locked / Read-only / Hidden rules do not apply to them).

Control Behavior
Edit box Enter the process executable name (e.g. totalcmd64.exe)
[Add] button Normalizes to lowercase and calls IoctlAddTrusted + ConfigSaveTrusted
[Remove] button Clears the entire driver trusted list, removes the registry entry, then reloads remaining entries via ConfigLoad

Important: Removing a trusted process from the GUI sends an empty IoctlRemoveTrusted call that wipes the entire active trusted list in the driver. ConfigLoad is then called immediately to reload all remaining entries from the registry. This is by design — the driver provides no per-item removal IOCTL.


CLI Reference

CLI mode is activated when vg.exe is launched with at least one recognized argument. It is designed for automation, PowerShell/Batch scripting, and CI/CD integration.

The binary uses mainCRTStartup as its entry point. When launched with arguments it calls AttachConsole(ATTACH_PARENT_PROCESS) or AllocConsole() as needed, then routes through CliDispatch().

vg.exe /?
vg.exe /enumitems    <outfile.csv>
vg.exe /enumtrusted  <outfile.csv>
vg.exe /protection   on | off
vg.exe /setitem      <path>  Hidden|Locked|Read-only|No-execution|Disabled
vg.exe /settrusted   <name.exe>  Enabled|Disabled
/p <password>  — parsed and silently ignored (driver has no password layer)

Command Summary

Command Description Example
/?, -h, --help Print help to stdout vg.exe /?
/protection on\|off Enable or disable global protection vg.exe /protection on
/setitem <path> <mode> Set protection flags for a directory vg.exe /setitem "C:\Data" Locked
/settrusted <name> <state> Add or remove a trusted process vg.exe /settrusted cmd.exe Enabled
/enumitems <file.csv> Export protected paths list as UTF-16LE CSV vg.exe /enumitems out.csv
/enumtrusted <file.csv> Export trusted processes list as UTF-16LE CSV vg.exe /enumtrusted trust.csv

Protection Modes for /setitem

Mode Effect
Hidden Directory becomes invisible in Explorer and dir listings
Locked All access attempts return ACCESS_DENIED
Read-only FILE_WRITE_DATA and DELETE bits stripped from DesiredAccess
No-execution Execute bits stripped from DesiredAccess
Disabled Path remains in registry with flags=0; appears in /enumitems with all flags = 0

Modes may not be combined on the CLI — pass one mode per /setitem call.

CSV Output Formats

/enumitems output:

<BOM>Path,Hidden,Locked,ReadOnly,NoExec\r\n
C:\temp\aaa,1,0,0,0\r\n

/enumtrusted output:

<BOM>Application\r\n
totalcmd64.exe\r\n

Both files are written in UTF-16LE with BOM. The /enumitems command reads from the registry (HKCU\Software\VG\Paths), not from the IOCTL buffer.

Exit Codes

Code Meaning
0 Success
1 Unknown switch, bad argument, or driver error

Every CLI exit path goes through _CliFinish(code), which calls ConsoleSendEnter() — injecting a VK_RETURN keystroke via WriteConsoleInputW — so the CMD prompt reappears without waiting for the user to press Enter.


Use Cases

Scenario 1: Lock a Folder and Allow a Specific Application

Most common workflow: C:\Private is inaccessible to everything, but Total Commander can open it normally.

1. Enable protection and lock the folder:

vg.exe /protection on
vg.exe /setitem "C:\Private" Locked

2. Add a trusted process:

vg.exe /settrusted totalcmd64.exe Enabled

Total Commander now bypasses the driver filter. Every other process receives ACCESS_DENIED.

3. Verify the configuration:

vg.exe /enumitems   items.csv
vg.exe /enumtrusted trust.csv

items.csv (UTF-16LE with BOM):

Path,Hidden,Locked,ReadOnly,NoExec
C:\Private,0,1,0,0

trust.csv:

Application
totalcmd64.exe

4. Revoke access when done:

vg.exe /settrusted totalcmd64.exe Disabled

The driver drops totalcmd64.exe immediately from its active list. No reboot required.


Scenario 2: Hide a Folder from Explorer

vg.exe /setitem "C:\Secret" Hidden

The folder disappears from Explorer, dir, and all directory enumeration APIs. Processes that know the full path can still address it unless Locked is also set. To combine flags (e.g. Hidden + Locked), use the GUI checkboxes — the CLI supports one flag per /setitem call.


Scenario 3: Read-Only Archive

vg.exe /setitem "C:\Backups" Read-only

The driver strips FILE_WRITE_DATA and DELETE bits from DesiredAccess at the kernel level. Applications added to the trusted list can still write normally.


Scenario 4: Scripted Status Check

vg.exe /enumitems C:\temp\items.csv
$rows = Import-Csv C:\temp\items.csv -Encoding Unicode
$locked = $rows | Where-Object { $_.Locked -eq '1' }
Write-Host "Locked paths: $($locked.Count)"

Import-Csv -Encoding Unicode reads UTF-16LE correctly on both Windows PowerShell 5.1 and PowerShell 7.


Module Analysis

The source tree is organized into 13 MASM source files plus includes. Each file has a single clearly defined responsibility.

main.asm — Entry Point & Globals

Entry point: mainCRTStartup

Startup sequence:

  1. GetStdHandle(STD_OUTPUT_HANDLE) + GetFileTypeAttachConsole(-1) if no TTY
  2. GetCommandLineWCommandLineToArgvWlea rdx, [rsp+20h] as &argc
  3. argc >= 2CliDispatch(argv[1], argv, argc); if it returns 0 (unknown switch) or argc < 2 → start GUI

Public globals (declared PUBLIC from main.asm):

Symbol Type Description
g_hInstance dq Process HINSTANCE
g_hwndMain dq Main window handle
g_hwndLvPaths dq ListView "Protected Paths"
g_hwndLvTrusted dq ListView "Trusted Processes"
g_hwndBtnToggle dq Toggle button [●] ACTIVE / [○] INACTIVE
g_hwndDrvStatus, g_hwndProtStatus dq Status label handles
g_hDevice dq Handle to \\.\BE79F7D853E643089D51EDCDA79805C4
g_hFontMain, g_hFontSmall dq GDI font handles
g_hBrushBg dq Background brush (COLORREF_DARK_BG = 0x202020)
g_isDarkMode dd 1 = dark mode active
g_driverInstalled, g_driverRunning, g_protActive dd Driver state flags
g_cliMode dd 1 = launched with CLI arguments
g_ioBuf 65536 B IOCTL enumeration buffer (64 KB)
g_pathBuf, g_tempBuf, g_statusBuf 520 W Wide-character scratch buffers

window.asm — Window Skeleton

Contains exclusively MainWndProc and CreateMainWindow. All layout logic lives in layout.asm, theming in theme.asm, command handlers in handlers.asm, and drag-and-drop in drop.asm.

Window properties: 680 × 450 px, fixed size, class VGMainWnd.

Handled WM messages:

Message Action
WM_CREATE _OnCreate (layout.asm)
WM_DESTROY KillTimer, DeleteObject (fonts + brush), PostQuitMessage(0)
WM_CLOSE DestroyWindow
WM_DROPFILES _OnDropFiles (drop.asm)
WM_NOTIFY _OnNotify (handlers.asm) — flag checkboxes in the ListView
WM_COMMAND _OnCommand (handlers.asm) — button clicks
WM_TIMER UpdateStatusBar + RefreshLists every 2 seconds
WM_SETTINGCHANGE _ReadDarkMode + ApplyDarkMode + _ApplyThemeColors + InvalidateRect
WM_ERASEBKGND GetClientRect to [rsp+20h] (local, above shadow space) + FillRect(g_hBrushBg)
WM_CTLCOLORSTATIC Dark mode: SetBkMode(OPAQUE) + colors + returns g_hBrushBg

layout.asm — Control Creation

_OnCreate(rcx=hwnd) creates all widgets in a single pass:

[y=  8] Driver status label  +  Protection status label  +  [●] ACTIVE toggle button
[y= 40] "Protected Paths" header  +  [+] button  +  [–] button
[y= 60] Edit box (hint: totalcmd64.exe, invisible placeholder)
[y= 65] ListView Paths (h=270): columns Path/H/L/R/X + LVS_EX_CHECKBOXES
         style: LVS_REPORT|LVS_SHOWSELALWAYS — no LVS_SINGLESEL (multi-select enabled)
[y=345] "Trusted Processes" header  +  [+] button  +  [–] button
[y=365] Trusted edit box
[y=370] ListView Trusted (h=80, ~3 rows): 1 column: Application

Key initialization calls:

  • InitCommonControlsEx(ICC_LISTVIEW_CLASSES) — called before any ListView creation
  • SetWindowTheme(L"DarkMode_Explorer") — applied to both ListViews
  • ChangeWindowMessageFilterEx(WM_DROPFILES, MSGFLT_ALLOW) + DragAcceptFiles(TRUE) — UIPI bypass for drag-and-drop
  • SetTimer(TIMER_STATUS_ID, 2000) — triggers periodic refresh

theme.asm — Dark Mode & Colors

Procedure Description
_ReadDarkMode Reads HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme; sets g_isDarkMode = (val == 0)
ApplyDarkMode(rcx=hwnd) DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE, 1) + DwmSetWindowAttribute(DWMWA_SYSTEMBACKDROP_TYPE, DWMSBT_MAINWINDOW) (Mica backdrop)
_SetLvColors(rcx=hwndLv, rdx=isDark) SetWindowTheme("DarkMode_Explorer") + LVM_SETTEXTCOLOR + LVM_SETTEXTBKCOLOR + LVM_SETBKCOLOR; in light mode: CLR_DEFAULT
_ApplyThemeColors Recreates g_hBrushBg; calls _SetLvColors for both g_hwndLvPaths and g_hwndLvTrusted

handlers.asm — Commands & Status

_OnCommand(rcx=hwnd, rdx=wParam) — dispatch by control ID:

IDC Action
IDC_BTN_TOGGLE UpdateStatusBar → if driver ready: IoctlSetActive(!g_protActive) + ConfigLoad when turning on
IDC_BTN_ADD_PATH SHBrowseForFolderW or text from edit box → IoctlAddPath(flags) + ConfigSavePath
IDC_BTN_REM_PATH Multi-select loop: LVM_GETNEXTITEM(-1, LVNI_SELECTED)IoctlAddPath(flags=0) + ConfigRemovePath + LVM_DELETEITEM; repeat until selection empty
IDC_BTN_ADD_TRUSTED Text from edit box → lowercase → IoctlAddTrusted + ConfigSaveTrusted
IDC_BTN_REM_TRUSTED _LvGetItemTextConfigRemoveTrusted + wcs_ascii_lower_inplaceIoctlRemoveTrusted + CloseDeviceConfigLoad (reloads remaining entries into driver) → RefreshLists

Critical implementation detail: IoctlRemoveTrusted sends an empty input buffer — the driver clears the entire active trusted list. Therefore ConfigLoad is mandatory immediately after, to reload remaining registry entries back into the driver.

UpdateStatusBarEnsureDriverReadyIoctlGetStatus → updates g_driverInstalled/Running/protActive, label text, and toggle button text.

RefreshListsIoctlEnumPaths + IoctlEnumTrustedLVM_DELETEALLITEMS_LvInsertItem for each entry.


drop.asm — Drag & Drop

_OnDropFiles(rcx=HDROP) — handles WM_DROPFILES:

  1. DragQueryFileW(0) → first dropped path into g_pathBuf
  2. If the last 4 characters are .lnk (checked via wcscmp_ci) → call ResolveLnkPath
  3. Result is stored in g_pendingPath
  4. RefreshListsDragFinish

ResolveLnkPath(rcx=.lnk path, rdx=out buf) — full COM chain:

CoInitializeCoCreateInstance(CLSID_ShellLink)QueryInterface(IID_IPersistFile)IPersistFile::LoadIShellLinkW::GetPath → full release chain → CoUninitialize

COM is initialized and uninitialized on every ResolveLnkPath call. If the GUI main thread uses COM (e.g. via SHBrowseForFolderW), consider moving COM initialization to OleInitialize at startup to avoid repeated apartment transitions.


driver.asm — SCM & IOCTL Layer

Service Manager (SCM)

Property Value
Service name clrcd
Display name Vault Guard Driver
Type SERVICE_KERNEL_DRIVER
Start SERVICE_DEMAND_START
Dependency FltMgr\0\0 (minifilter requires Filter Manager)
Device path \\.\BE79F7D853E643089D51EDCDA79805C4
Procedure Description
OpenDevice CreateFileW("\\.\BE79...", GENERIC_RW, 0, NULL, OPEN_EXISTING, 0, NULL)
IsDriverInstalled OpenSCManagerWOpenServiceW("clrcd") → returns 1/0
InstallDriver ExtractDriver()CreateServiceW(13 args, lpDependencies="FltMgr\0\0")
StartDriver StartServiceW; treats ERROR_SERVICE_ALREADY_RUNNING (1056) and ERROR_ALREADY_EXISTS (183) as success
EnsureDriverReady OpenDevice → fail → InstallDriver (if absent) → StartDriverOpenDevice; after StartDriver with code 183, retries OpenDevice
StopDriver / UninstallDriver ControlService(STOP) / StopDriver + DeleteService

SCM code 183 behavior: The original vg.sys reports ERROR_ALREADY_EXISTS to the SCM on restart because IoCreateDevice / IoCreateSymbolicLink returns STATUS_OBJECT_NAME_COLLISION — the device and symlink already exist from the previous session. SCM shows the service as STOPPED, but the device \\.\BE79... is accessible and responds to IOCTL. EnsureDriverReady handles this case by treating code 183 identically to 1056.

IOCTL Codes (reverse-engineered from vg.sys)

Constant Value Description
IOCTL_VG_ADD_PATH 0x9C402400 Add/update path (flags=0 → Disabled)
IOCTL_VG_REMOVE_PATH 0x9C402400 Alias of ADD_PATH; Disabled = flags=0
IOCTL_VG_ENUM_PATHS 0x9C402404 Retrieve list of protected paths
IOCTL_VG_ADD_TRUSTED 0x9C402408 Add trusted process; also used for REMOVE
IOCTL_VG_REMOVE_TRUSTED 0x9C402408 Alias of ADD_TRUSTED; empty input (size=0) clears entire list
IOCTL_VG_ENUM_TRUSTED 0x9C40240C Retrieve list of trusted processes
IOCTL_VG_SET_ACTIVE 0x9C40241C Enable/disable protection (DWORD, 4 bytes)
IOCTL_VG_GET_STATUS 0x9C402420 Retrieve VG_STATUS (16 bytes)
IOCTL_VG_CLEAR_ALL 0x9C402424 Full reset / sync

Correction note: Earlier documentation (pre-2026-05-24) contained incorrect IOCTL codes for trusted operations (0x9C41A00C, 0x9C416014, etc.) — these values do not appear in the vg.sys binary. The values in the table above have been confirmed by binary analysis of the driver.


config.asm — Registry Persistence

Registry keys:

Key Format Content
HKCU\Software\VG\Paths\ Key names = DOS paths; values = REG_DWORD flags Protected paths (0x0..0xF)
HKCU\Software\VG\Trusted\ Key names = lowercase exe names; values = REG_DWORD 1 Trusted processes
Procedure Description
ConfigLoad Enumerates PathsIoctlAddPath for each; enumerates TrustedIoctlAddTrusted for each; calls IoctlSetActive(1) at the end
ConfigSavePath(rcx=path, rdx=flags) RegCreateKeyExW(HKCU, "Software\VG\Paths")RegSetValueExW(path, flags)
ConfigRemovePath(rcx=path) RegOpenKeyExWRegDeleteValueW(path)
ConfigSaveTrusted(rcx=name_lowercase) RegCreateKeyExW(HKCU, "Software\VG\Trusted")RegSetValueExW(name, 1)
ConfigRemoveTrusted(rcx=name) RegOpenKeyExWRegDeleteValueW(name)

cli.asm — Command-Line Interface

Interface (fully compatible with the original VaultGuard.exe):

Switch comparison uses wcscmp_ci — a fast ASCII case-insensitive wide-string comparator that avoids CharLowerW. All switches work regardless of capitalization.

Implementation details:

  • /setitem with any mode other than Disabled → IoctlAddPath(flags) + IoctlSetActive(1) + ConfigSavePath
  • /setitem DisabledIoctlAddPath(flags=0) + ConfigSavePath(flags=0); path remains in registry with value 0 and appears in /enumitems with all flags = 0
  • /settrusted Enabledwcs_ascii_lower_inplaceIoctlAddTrusted + ConfigSaveTrusted
  • /settrusted DisabledConfigRemoveTrusted (original casing) + lowercase + IoctlRemoveTrusted + ConfigRemoveTrusted (lowercase); entry removed from registry
  • /enumitems → reads from registry HKCU\Software\VG\Paths, not from the IOCTL buffer; exports UTF-16LE CSV with BOM
  • /enumtrusted → attempts EnsureDriverReady + IoctlEnumTrusted; if no driver → @cet_no_driver; reads from registry HKCU\Software\VG\Trusted
  • /protection onConfigLoad + IoctlSetActive(1)
  • Every ExitProcess goes through _CliFinish(code)ConsoleSendEnter() (injects VK_RETURN via WriteConsoleInputW) so the CMD prompt appears without waiting for Enter

res.asm — Driver Extraction (FDI)

The driver binary vg.sys is embedded inside vg.exe as a resource, compressed in a CAB archive appended to the application icon:

  1. FindResourceW(NULL, IDR_DRIVER=102, RT_RCDATA=10) → pointer to resource data
  2. LockResource → raw bytes of ICON/vg.ico; CAB starts at offset 1 662 bytes
  3. g_fdiCabPtr = resourcePtr + 1662, g_fdiCabEnd = totalSize - 1662
  4. Allocates 1 MB heap as output buffer
  5. FDICreate(alloc, free, open, read, write, close, seek, CPUID=0, &erf) — all callbacks operate on memory, not on disk files
  6. FDICopy(hfdi, "memory.cab", "")fdi_notify accepts the file, fdi_write accumulates bytes into heap
  7. FDIDestroy
  8. GetSystemWindowsDirectoryW + \system32\drivers\vg.sysCreateFileW(CREATE_ALWAYS)WriteFile

The CAB is packed at ~42% of original size via LZX compression (makecab).


strutil.asm — String Utilities

Procedure Signature Description
wcslen_p rcx=s → rax=count Wide strlen (characters, not bytes)
wcscpy_p rcx=dst, rdx=src → rax=dst Wide strcpy
wcscat_p rcx=dst, rdx=src → rax=dst Wide strcat
wcscmp_ci rcx=a, rdx=b → rax=0/nonzero Case-insensitive wide compare (ASCII A-Z only, no CharLowerW)
wcs_ascii_lower_inplace rcx=s Lowercases A-Z in place (used before IOCTL trusted operations)
IntToDecW rcx=val, rdx=buf → rax=ptr DWORD → wide decimal string
IntToHexW rcx=val, rdx=buf → rax=ptr DWORD → 8-character wide hex string
WideWriteConsole rcx=handle, rdx=str WriteConsoleW; falls back to ANSI WriteFile if handle is not a console
WideWriteLn rcx=str WideWriteConsole(stdout, str) + CRLF
ConsoleSendEnter Injects VK_RETURN via WriteConsoleInputW

WideWriteConsole first tries WriteConsoleW — if that fails (e.g. redirect/pipe), it converts to ANSI and uses WriteFile. This makes CLI output work correctly both in CMD and when captured via Start-Process -RedirectStandardOutput in PowerShell.


listview.asm — ListView Wrappers

Procedure Signature Description
_LvAddColumn rcx=hwnd, rdx=idx, r8=width, r9=text LVM_INSERTCOLUMNW
_LvInsertItem rcx=hwnd, rdx=row, r8=col, r9=text LVM_INSERTITEMW / LVM_SETITEMW
_LvGetItemText rcx=hwnd, rdx=row, r8=col, r9=buf LVM_GETITEMTEXTW

Driver Communication Protocol

flowchart TD UA[User action: GUI or CLI] --> EDR[EnsureDriverReady] EDR --> OD[OpenDevice: \\.\BE79F7D8...] OD -->|success| IOCTL OD -->|fail| ID[InstallDriver: FDI extract + CreateServiceW] ID --> SD[StartDriver: tolerates codes 1056 and 183] SD --> OD2[OpenDevice retry] OD2 --> IOCTL IOCTL --> IAP[IoctlAddPath flags + NT path] IAP --> QDD[QueryDosDeviceW C: → Device/HarddiskVolumeN] QDD --> DIO[DeviceIoControl 0x9C402400, buf 0x6414 bytes] DIO --> CSP[ConfigSavePath: HKCU/Software/VG/Paths/path = REG_DWORD flags] CSP --> HIDE[Folder disappears from Explorer / returns ACCESS_DENIED] RT[Remove trusted: GUI or CLI] --> IRT[IoctlRemoveTrusted empty input clears ALL] IRT --> CRT[ConfigRemoveTrusted: delete registry entry] CRT --> CL[ConfigLoad: reload remaining entries from registry into driver]

IOCTL Buffer Formats

IoctlAddPath(rcx=flags DWORD, rdx=DOS path):

  • QueryDosDeviceW("C:") → NT prefix \Device\HarddiskVolume3
  • Buffer: [0..3] = DWORD flags, [4..] = NT path as WCHAR
  • nInBufSize = 0x6414 (fixed — the original VaultGuard.exe always used this size; a smaller buffer causes the driver to accept the IOCTL but not apply the rule)

IoctlAddTrusted(rcx=process name lowercase):

  • Buffer g_ioBuf: [0..3] = 0, [4..] = WCHAR process name
  • nInBufSize = 0xD94 (fixed record, padded with zeros beyond the name)

IoctlRemoveTrusted:

  • nInBufSize = 0 → driver clears the entire active trusted process list
  • Caller is required to call ConfigLoad afterward to reload remaining entries from the registry

IoctlSetActive(rcx=0|1):

  • Buffer: DWORD (4 bytes) — not a BYTE

IoctlGetStatus → output buffer VG_STATUS (16 bytes):

Offset Type Description
0 BYTE IsActive (protection enabled)
4 DWORD PathCount
8 DWORD TrustedCount
12 DWORD Version

Protection Flags

Protection flags are stored as a REG_DWORD bitmask in the registry and sent as a DWORD in the IOCTL buffer.

Flag Hex Driver behavior
VG_FLAG_HIDDEN 0x01 PreCreateSTATUS_OBJECT_NAME_NOT_FOUND; PostDirControl → unlinks entry from directory listing buffer
VG_FLAG_LOCKED 0x02 PreCreateSTATUS_ACCESS_DENIED
VG_FLAG_READONLY 0x04 Strips FILE_WRITE_DATA and DELETE bits from DesiredAccess
VG_FLAG_NOEXEC 0x08 Strips execute bits from DesiredAccess

Flags may be combined as a bitmask: Hidden + Locked = 0x03, Hidden + Locked + Read-only = 0x07, and so on. Disabled = 0x00 means the path is stored in the registry but inactive in the driver.


Registry Layout

All state is stored under the current user's hive (HKCU). No machine-wide keys are written.

HKEY_CURRENT_USER\
└── Software\
    └── VG\
        ├── Paths\
        │     "C:\Private\Data"   REG_DWORD  0x00000003   (Hidden + Locked)
        │     "C:\Projects\Work"  REG_DWORD  0x00000004   (Read-only)
        │     "C:\Temp\Archive"   REG_DWORD  0x00000000   (Disabled)
        └── Trusted\
              "totalcmd64.exe"    REG_DWORD  0x00000001
              "explorer.exe"      REG_DWORD  0x00000001

ConfigLoad enumerates both subkeys on every protection-on event and reloads the full configuration into the driver. This is the only mechanism for driver state reconstruction — the driver itself holds no persistent state across reboots.


Build System

Property Value
Toolset MSVC (auto-detected via vswhere.exe, latest installed)
Assembler ml64.exe — MASM x64
Standard x64 MASM, zero CRT
Platform x64
Configuration Release
Output bin\vg.exe (54 784 B)
Subsystem Windows GUI (WinMain/mainCRTStartup) — CLI attaches/allocates console at runtime
Build script build.ps1 — auto-detects VS + SDK via vswhere.exe, 4 steps, verifies imports

Build Steps

.\build.ps1

The script performs four ordered steps:

[0] Packaging vg.sys → ICON/vg.ico
    makecab vg.sys → vg.cab (LZX compression, ~42% of original size)
    vg.ico = IcoBuilder\vg.ico[0..1661] + vg.cab

[1] Resource compilation
    rc.exe /c65001 vg.rc → vg.res

[2] Assembly (ml64.exe /c /Cp /Cx /Zi)
    Order: strutil res driver config cli theme listview handlers drop layout window main

[3] Linking (link.exe /SUBSYSTEM:WINDOWS /NODEFAULTLIB /MANIFEST:EMBED)
    Libs: kernel32 user32 advapi32 shell32 ole32 dwmapi gdi32 comctl32 uxtheme cabinet
    UAC: requireAdministrator (MANIFESTUAC flag)

[4] Import verification (dumpbin)
    Blocked: msvcr*, vcruntime*, ucrtbase*, rstrtmgr*
    Allowed DLLs: ADVAPI32 CABINET COMCTL32 DWMAPI GDI32 KERNEL32 OLE32 SHELL32 USER32 UXTHEME

Parameters:

  • -SkipRC — skips steps 0 and 1; requires a pre-existing vg.res

The script always performs a full build. Intermediate files (x64\*.obj, *.res, x64\*.pdb) are removed on completion.

Dependencies

All dependencies are self-contained. No package manager or network access is required at build time.

Dependency Location Purpose
Win32 SDK System All WinAPI calls, no abstractions
cabinet.lib System FDI decompression for driver extraction
comctl32.lib System ListView, InitCommonControlsEx
dwmapi.lib System Mica, dark mode, DWM attributes
uxtheme.lib System SetWindowTheme (DarkMode_Explorer)
MASM 64-bit (ml64.exe) VS toolset Assembler

Project Structure

VaultGuard\
│
├── x64/
│   ├── consts.inc       EQU constants: IOCTL codes, flags, struct offsets, control IDs
│   ├── globals.inc      EXTRN declarations for globals from main.asm
│   ├── main.asm         Entry point, global data, message loop
│   ├── window.asm       MainWndProc + CreateMainWindow (~165 lines)
│   ├── layout.asm       _OnCreate: all controls creation + layout data
│   ├── theme.asm        Dark mode, Mica, ListView colors (_ApplyThemeColors)
│   ├── handlers.asm     _OnCommand, _OnNotify, UpdateStatusBar, RefreshLists
│   ├── drop.asm         WM_DROPFILES + ResolveLnkPath (IShellLink COM)
│   ├── listview.asm     _LvAddColumn, _LvInsertItem, _LvGetItemText
│   ├── driver.asm       SCM + full IOCTL layer + EnsureDriverReady
│   ├── config.asm       ConfigLoad/Save/Remove for Paths and Trusted (registry)
│   ├── cli.asm          CliDispatch — CLI interface
│   ├── strutil.asm      String utilities + WideWriteLn/WideWriteConsole
│   └── res.asm          ExtractDriver — FDI decompression of CAB from icon
│
├── tests/
│   └── cli_test.ps1     33 regression tests (registry + CSV, no GUI required)
│
├── ICON/
│   └── vg.ico           [generated] 1662 B ICO header + CAB containing vg.sys
│
├── bin/
│   └── vg.exe           Output binary (54 784 B)
│
├── build.ps1            Auto-detects VS + SDK, 4 steps, verifies imports
├── vg.manifest          requireAdministrator, Win11 GUID, perMonitorV2 DPI
├── vg.rc                ICON 101 + RCDATA 102 (both = ICON/vg.ico)
└── IcoBuilder/
    ├── vg.sys           Original driver (source for packaging)
    └── vg.ico           Base icon (ICO header)

Regression Tests

tests/cli_test.ps1 contains 33 regression tests that validate the CLI interface without any GUI. Tests require bin\vg.exe, a loaded driver, and an Administrator context.

powershell -ExecutionPolicy Bypass -File tests\cli_test.ps1
# -KeepOutput   preserves CSV output files in tests\out\

Test Groups

Group Tests What is verified
Help 1 /? output on stdout via Start-Process -RedirectStandardOutput
setitem flags 4 Each flag individually → registry value correctness
setitem Disabled 1 Path remains in registry with value 0
enumitems CSV 3 File content, 2 rows, correct flag bits
settrusted + enumtrusted 4 Both entries in registry and CSV
settrusted Disabled 2 Entry removed; second entry unaffected
protection on/off 2 Exit code 0; bad argument → exit code 1
error cases 3 Missing args, bad mode → exit code 1
registry consistency 13 Remove-one-trusted: remaining entry survives in registry and CSV

All tests capture output via Start-Process -RedirectStandardOutput, which exercises the WriteConsoleW → WriteFile ANSI fallback path in WideWriteConsole.


Known Limitations

Item Status
wcscmp_ci ASCII only (A-Z). Paths with non-ASCII characters (accented letters, CJK, etc.) use case-sensitive comparison; for NT paths and CLI switches this is sufficient
COM apartment CoInitialize/CoUninitialize at every .lnk resolution; safe for GUI usage but non-standard — consider moving to OleInitialize at startup
Password mode /p is parsed and silently ignored; the driver has no password enforcement layer
Light mode GUI functions in light mode, but ListView colors fall back to system defaults (no full light-theme color tweaking implemented)
Flag dialog Dead code _ShowFlagsDialog/_FlagsDlgProc in handlers.asm — unused but not removed
Trusted list removal No per-item IOCTL for removing a single trusted process; the driver only supports clearing the entire list, requiring a full reload cycle
Re-entry on D&D Only the first dropped file/folder is processed per WM_DROPFILES event; multi-file drops discard all but the first

License

Apache License 2.0

Full text available in the project repository (LICENSE file).

Disclaimer

WARNING: This tool installs a kernel-mode driver (vg.sys) and communicates with it via IOCTL. It requires Administrator privileges. Incorrect use — especially manually editing registry keys under HKCU\Software\VG\ while the driver is running — may result in inaccessible folders until the driver is stopped. Use at your own risk.

The original vg.sys driver is the property of PROMOSOFT CORPORATION. All trademarks and brand names are the property of their respective owners. Intel, Microsoft, Windows, and related marks are trademarks of their respective owners.


Last updated: 2026-05-25