haunt
100% AI-developed.
A minimal debugger-as-an-HTTP-server. Inject haunt.dll into a target
Windows process, drive it from anywhere with curl or the haunt CLI.
Memory read/write, software/hardware/page breakpoints, halt with register snapshot, resume/step/run-to-return — over a flat REST surface with no runtime dependencies on the target.
Layout
crates/
├── core/ haunt-core platform-agnostic HTTP + protocol
├── windows/ haunt-windows cdylib → haunt.dll
├── inject/ haunt-inject CreateRemoteThread(LoadLibraryA)
└── cli/ haunt haunt.exe
Build
Requires Rust (rustup) and MinGW-w64 on macOS / Linux for cross-compile.
# macOS
# build everything for Windows x86_64
Output ends up in target/x86_64-pc-windows-gnu/release/:
haunt.dll— the agent (~400 KB, system DLLs only, no MinGW runtime deps)haunt-inject.exe— loaderhaunt.exe— CLI client
Quick start
On the Windows target, with all three binaries colocated:
# same box, talk to the agent
Workflows
End-to-end recipes that stitch the primitives together. All commands
assume HAUNT_URL is set and the agent is running in the target.
Trace a function call and inspect arguments. Set a breakpoint by name, wait for it to hit, read the register snapshot, then let it continue.
bp set accepts either a hex address or module!symbol; the agent
resolves the name server-side against its export tables. haunt resolve module!symbol returns the resolved address without setting anything.
Patch a return value without touching code. Halt at the ret,
overwrite rax, resume.
|
Watch a field for writes. Hardware breakpoint with --access w
catches any thread modifying the address; breakpoints auto-propagate to
new threads.
Non-halting tripwire. --no-halt records hits without parking the
thread — useful for sampling hot paths.
# ...let it run...
Protocol
All endpoints are plain HTTP/1.1. The agent binds to 127.0.0.1:7878
(loopback only — not currently configurable). Auth is opt-in via
HAUNT_TOKEN: set the variable in the target process's environment
before injection, then pass it from the client via Authorization: Bearer <token>.
To drive the agent from another machine, tunnel over SSH:
HAUNT_URL=http://127.0.0.1:7878
This also encrypts the wire end-to-end — the protocol has no TLS of its own, and the bearer token would otherwise travel in cleartext.
Memory:
GET /memory/read?addr=0x...&len=N[&format=hex|raw]POST /memory/write?addr=0x...(raw body)GET /memory/regions
Breakpoints:
POST /bp/set?{addr=0x...|name=module!symbol}&kind=sw|hw|page[&access=x|w|rw|any][&size=N][&halt=true|false][&one_shot=true][&tid=N]POST /bp/clear?id=NGET /bp/list
Halts (parked threads):
GET /haltsGET /halts/wait?timeout=<ms>GET /halts/<hit_id>— register dumpGET /halts/<hit_id>/stack[?depth=N]— rbp-chain backtrace withmodule+offsetPOST /halts/<hit_id>/regs— modify registersPOST /halts/<hit_id>/resume?mode=continue|step|ret
Introspection:
GET /modulesGET /modules/<name>/exportsGET /memory/regionsGET /symbols/resolve?name=module!symbolGET /pingGET /versionPOST /shutdown
Status
- x86_64 Windows only for now. The
Processtrait is platform-agnostic; a Linux implementation is on the roadmap. - Breakpoint kinds: software (
int3), hardware (DR0–DR3), page (PAGE_GUARD). - Hardware breakpoints propagate to new threads via
DLL_THREAD_ATTACH(usesSetThreadContexton self — documented-fragile but empirically reliable on modern Windows). panic = "abort"; the agent is audited for unwrap/expect. Memory read/write usesReadProcessMemory/WriteProcessMemoryso invalid addresses surface as errors rather than crashing the host.- Stack walking is a plain rbp-chain walk, so it's reliable for code
with frame pointers and truncates early on release-mode Windows
binaries built with
/Oy(which is most of them). A properRtlVirtualUnwind-based walker is on the roadmap.
License
AGPL-3.0. See LICENSE.
If you want to use haunt in a closed-source or SaaS product without publishing your modifications, you'll need a different arrangement — open an issue.