haunt 0.1.1

Command-line client for the haunt agent's HTTP protocol.
# 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.

```sh
# macOS
brew install mingw-w64

# build everything for Windows x86_64
./build.sh           # haunt.dll only
cargo build --release   # all artifacts
```

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` — loader
- `haunt.exe` — CLI client

## Quick start

On the Windows target, with all three binaries colocated:

```sh
haunt-inject --pid 1234 .\haunt.dll

# same box, talk to the agent
set HAUNT_URL=http://127.0.0.1:7878
haunt ping
haunt modules
haunt exports kernel32.dll
haunt bp set 0x7FF601234000 --kind sw
haunt wait          # long-polls until a breakpoint halts a thread
haunt regs 1        # hit_id
haunt resume 1 --step
```

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

```sh
haunt bp set kernel32.dll!CreateFileW --kind sw
haunt wait                              # blocks until a thread halts
haunt regs 3                            # hit_id from wait output; args in rcx/rdx/r8/r9
haunt stack 3                           # backtrace with module!offset resolution
haunt read 0x00000014FE3C0000 64        # dereference a pointer arg
haunt resume 3
```

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

```sh
haunt bp set 0x7FF601234ABC --kind sw --one-shot
haunt wait
printf 'rax=0\n' | haunt setregs 7
haunt resume 7
```

**Watch a field for writes.** Hardware breakpoint with `--access w`
catches any thread modifying the address; breakpoints auto-propagate to
new threads.

```sh
haunt bp set 0x00007FF6DEADBEEF --kind hw --access w --size 4
haunt wait --timeout 60000
haunt regs 12                           # rip points at the writing instruction
haunt resume 12
```

**Non-halting tripwire.** `--no-halt` records hits without parking the
thread — useful for sampling hot paths.

```sh
haunt bp set 0x7FF601234000 --kind sw --no-halt
# ...let it run...
haunt bp list                           # hit counts per breakpoint
```

## 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:

```sh
ssh -L 7878:127.0.0.1:7878 <target-host>
HAUNT_URL=http://127.0.0.1:7878 haunt ping
```

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=N`
- `GET  /bp/list`

Halts (parked threads):
- `GET  /halts`
- `GET  /halts/wait?timeout=<ms>`
- `GET  /halts/<hit_id>`                       — register dump
- `GET  /halts/<hit_id>/stack[?depth=N]`       — rbp-chain backtrace with `module+offset`
- `POST /halts/<hit_id>/regs`                  — modify registers
- `POST /halts/<hit_id>/resume?mode=continue|step|ret`

Introspection:
- `GET  /modules`
- `GET  /modules/<name>/exports`
- `GET  /memory/regions`
- `GET  /symbols/resolve?name=module!symbol`
- `GET  /ping`  `GET  /version`  `POST /shutdown`

## Status

- x86_64 Windows only for now. The `Process` trait 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`
  (uses `SetThreadContext` on self — documented-fragile but empirically
  reliable on modern Windows).
- `panic = "abort"`; the agent is audited for unwrap/expect. Memory
  read/write uses `ReadProcessMemory`/`WriteProcessMemory` so 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 proper
  `RtlVirtualUnwind`-based walker is on the roadmap.

## License

AGPL-3.0. See [LICENSE](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.