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 withvg.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

Table of Contents
- Overview
- Architecture
- GUI Reference
- CLI Reference
- Use Cases
- Module Analysis
- main.asm — Entry Point & Globals
- window.asm — Window Skeleton
- layout.asm — Control Creation
- theme.asm — Dark Mode & Colors
- handlers.asm — Commands & Status
- drop.asm — Drag & Drop
- driver.asm — SCM & IOCTL Layer
- config.asm — Registry Persistence
- cli.asm — Command-Line Interface
- res.asm — Driver Extraction (FDI)
- strutil.asm — String Utilities
- listview.asm — ListView Wrappers
- Driver Communication Protocol
- Protection Flags
- Registry Layout
- Build System
- Project Structure
- Regression Tests
- Known Limitations
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
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
IoctlRemoveTrustedcall that wipes the entire active trusted list in the driver.ConfigLoadis 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:
GetStdHandle(STD_OUTPUT_HANDLE)+GetFileType→AttachConsole(-1)if no TTYGetCommandLineW→CommandLineToArgvW→lea rdx, [rsp+20h]as&argcargc >= 2→CliDispatch(argv[1], argv, argc); if it returns 0 (unknown switch) orargc < 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 creationSetWindowTheme(L"DarkMode_Explorer")— applied to both ListViewsChangeWindowMessageFilterEx(WM_DROPFILES, MSGFLT_ALLOW)+DragAcceptFiles(TRUE)— UIPI bypass for drag-and-dropSetTimer(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 |
_LvGetItemText → ConfigRemoveTrusted + wcs_ascii_lower_inplace → IoctlRemoveTrusted + CloseDevice → ConfigLoad (reloads remaining entries into driver) → RefreshLists |
Critical implementation detail:
IoctlRemoveTrustedsends an empty input buffer — the driver clears the entire active trusted list. ThereforeConfigLoadis mandatory immediately after, to reload remaining registry entries back into the driver.
UpdateStatusBar — EnsureDriverReady → IoctlGetStatus → updates g_driverInstalled/Running/protActive, label text, and toggle button text.
RefreshLists — IoctlEnumPaths + IoctlEnumTrusted → LVM_DELETEALLITEMS → _LvInsertItem for each entry.
drop.asm — Drag & Drop
_OnDropFiles(rcx=HDROP) — handles WM_DROPFILES:
DragQueryFileW(0)→ first dropped path intog_pathBuf- If the last 4 characters are
.lnk(checked viawcscmp_ci) → callResolveLnkPath - Result is stored in
g_pendingPath RefreshLists→DragFinish
ResolveLnkPath(rcx=.lnk path, rdx=out buf) — full COM chain:
CoInitialize → CoCreateInstance(CLSID_ShellLink) → QueryInterface(IID_IPersistFile) → IPersistFile::Load → IShellLinkW::GetPath → full release chain → CoUninitialize
COM is initialized and uninitialized on every
ResolveLnkPathcall. If the GUI main thread uses COM (e.g. viaSHBrowseForFolderW), consider moving COM initialization toOleInitializeat 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 |
OpenSCManagerW → OpenServiceW("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) → StartDriver → OpenDevice; after StartDriver with code 183, retries OpenDevice |
StopDriver / UninstallDriver |
ControlService(STOP) / StopDriver + DeleteService |
SCM code 183 behavior: The original
vg.sysreportsERROR_ALREADY_EXISTSto the SCM on restart becauseIoCreateDevice/IoCreateSymbolicLinkreturnsSTATUS_OBJECT_NAME_COLLISION— the device and symlink already exist from the previous session. SCM shows the service asSTOPPED, but the device\\.\BE79...is accessible and responds to IOCTL.EnsureDriverReadyhandles 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 thevg.sysbinary. 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 Paths → IoctlAddPath for each; enumerates Trusted → IoctlAddTrusted for each; calls IoctlSetActive(1) at the end |
ConfigSavePath(rcx=path, rdx=flags) |
RegCreateKeyExW(HKCU, "Software\VG\Paths") → RegSetValueExW(path, flags) |
ConfigRemovePath(rcx=path) |
RegOpenKeyExW → RegDeleteValueW(path) |
ConfigSaveTrusted(rcx=name_lowercase) |
RegCreateKeyExW(HKCU, "Software\VG\Trusted") → RegSetValueExW(name, 1) |
ConfigRemoveTrusted(rcx=name) |
RegOpenKeyExW → RegDeleteValueW(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:
/setitemwith any mode other than Disabled →IoctlAddPath(flags)+IoctlSetActive(1)+ConfigSavePath/setitem Disabled→IoctlAddPath(flags=0)+ConfigSavePath(flags=0); path remains in registry with value 0 and appears in/enumitemswith all flags = 0/settrusted Enabled→wcs_ascii_lower_inplace→IoctlAddTrusted+ConfigSaveTrusted/settrusted Disabled→ConfigRemoveTrusted(original casing) + lowercase +IoctlRemoveTrusted+ConfigRemoveTrusted(lowercase); entry removed from registry/enumitems→ reads from registryHKCU\Software\VG\Paths, not from the IOCTL buffer; exports UTF-16LE CSV with BOM/enumtrusted→ attemptsEnsureDriverReady+IoctlEnumTrusted; if no driver →@cet_no_driver; reads from registryHKCU\Software\VG\Trusted/protection on→ConfigLoad+IoctlSetActive(1)- Every
ExitProcessgoes through_CliFinish(code)→ConsoleSendEnter()(injectsVK_RETURNviaWriteConsoleInputW) 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:
FindResourceW(NULL, IDR_DRIVER=102, RT_RCDATA=10)→ pointer to resource dataLockResource→ raw bytes ofICON/vg.ico; CAB starts at offset 1 662 bytesg_fdiCabPtr = resourcePtr + 1662,g_fdiCabEnd = totalSize - 1662- Allocates 1 MB heap as output buffer
FDICreate(alloc, free, open, read, write, close, seek, CPUID=0, &erf)— all callbacks operate on memory, not on disk filesFDICopy(hfdi, "memory.cab", "")→fdi_notifyaccepts the file,fdi_writeaccumulates bytes into heapFDIDestroyGetSystemWindowsDirectoryW+\system32\drivers\vg.sys→CreateFileW(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 |
WideWriteConsolefirst triesWriteConsoleW— if that fails (e.g. redirect/pipe), it converts to ANSI and usesWriteFile. This makes CLI output work correctly both in CMD and when captured viaStart-Process -RedirectStandardOutputin 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
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 originalVaultGuard.exealways 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
ConfigLoadafterward to reload remaining entries from the registry
IoctlSetActive(rcx=0|1):
- Buffer:
DWORD(4 bytes) — not aBYTE
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 |
PreCreate → STATUS_OBJECT_NAME_NOT_FOUND; PostDirControl → unlinks entry from directory listing buffer |
VG_FLAG_LOCKED |
0x02 |
PreCreate → STATUS_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-existingvg.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 underHKCU\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