2026-10-08-pentest-toolkit-design.md (18796B)
1 # Pentest toolkit for NixDaemon — design 2 3 **Date:** 2026-10-08 4 **Status:** awaiting review 5 **Author:** daemon-sec, with Claude 6 7 ## Intent 8 9 Turn this laptop into a Kali-equivalent offensive workstation, declaratively, 10 for **HackTheBox CPTS prep**. Everything comes from the flake: no `curl | bash`, 11 no tools living outside the store except the ones that must be mutable 12 (VPN profiles, loot). 13 14 Success criteria: 15 16 1. `nh os switch` is the only install step. A fresh machine reaches the same kit. 17 2. Tools are grouped into independently toggleable modules (`daemon.pentest.*`), 18 so a category can be dropped in one line. 19 3. Tools are reachable with no ceremony: `nxc`, `ffuf`, `secretsdump` work in any 20 terminal, including under `sudo`. 21 4. Windows/Linux/macOS agent binaries and offensive payloads are staged in one 22 predictable tree, servable to a target in one command. 23 5. The HTB loop — connect VPN, set target, fix clock skew, enumerate — is 24 driven by a handful of commands that share state across terminals. 25 26 Non-goals: C2 infrastructure beyond what ships in nixpkgs; anything targeting 27 hosts outside HTB/authorised labs; Kali's full 600-package catalogue (curated 28 instead); a second non-NixOS VM. 29 30 ## Decisions 31 32 These were delegated to me with "whatever is best for NixOS". Recorded with 33 reasoning so they can be revisited. 34 35 ### D1 — Tools install to `environment.systemPackages`, not `home.packages` 36 37 **The single most important NixOS-specific decision.** Half this toolkit needs 38 root: `nmap -sS` (raw sockets), `responder`, `mitm6`, `bettercap`, `tcpdump`, 39 `masscan`. home-manager packages are on the *user's* PATH only, so 40 `sudo nmap -sS` would fail with `command not found` — the classic 41 Kali-refugee-on-NixOS trap. System packages behave like Kali's `/usr/bin`. 42 43 Consequence: modules live in `modules/features/pentest/` as 44 `flake.nixosModules.pentest-*`. Only genuinely user-scoped things (the `htb*` 45 commands, the cheat card, shell integration) are home modules. 46 47 ### D2 — Everything on PATH, per-category toggles, devshells as a bonus 48 49 Friction matters more than PATH tidiness during CPTS. 1.8TB free, and the kit 50 comes from the binary cache rather than compiling. Toggles are the escape hatch. 51 52 `perSystem.devShells.pentest` (plus one per category) is declared anyway — ~10 53 lines, and it makes the kit portable: `nix develop github:…#pentest` on any 54 machine with Nix. 55 56 ### D3 — Missing tools become pinned Nix derivations; a script re-pins them 57 58 ~20 tools aren't in nixpkgs. Each gets a derivation in `_pkgs/` with a pinned 59 `rev`/`hash`, so the toolkit rolls back with the system generation. 60 `pentest-update` re-pins to latest on demand. 61 62 Where a tool declares Windows support in nixpkgs, build it **from source** with 63 `pkgsCross.mingwW64` rather than downloading a release blob. Verified available 64 for `mimikatz`, `ligolo-ng`, `netexec`. Genuine provenance, no trusting strangers. 65 66 Prebuilt .NET binaries (SharpCollection, Ghostpack, potato family) are the 67 exception: building .NET offline in Nix is brittle, so those are pinned release 68 assets with recorded hashes. 69 70 ### D4 — Defaults: CPTS core + heavy GUI on; the rest written but off 71 72 On: `recon ad bloodhound web pivot crack shells wordlists payloads python gui`. 73 Off: `dfir reversing wireless cloud osint mobile`. 74 Keeps the first build finishable in one sitting; each is one line to enable. 75 76 ### D5 — Native NixOS options over bare packages, wherever they exist 77 78 Not cosmetic — on NixOS the package alone is often *unusable*: 79 80 | Option | Why the package alone is not enough | 81 |---|---| 82 | `programs.wireshark.enable` | Creates the `wireshark` group and the `dumpcap` capability wrapper. Without it you cannot capture without full root. | 83 | `programs.proxychains.enable` | Manages `/etc/proxychains.conf`. `/etc` is read-only on NixOS, so the Kali habit of editing that file by hand simply fails. This module is what makes `proxychains` work at all. | 84 | `security.wrappers` | Capabilities (`cap_net_raw`) instead of setuid, for raw-socket tools. | 85 | `services.neo4j` + `services.postgresql` | BloodHound CE needs both; there is no `services.bloodhound` in nixpkgs, so this is wired by hand. | 86 87 `rockyou.txt` ships gzipped in the store, so `wordlists.nix` produces a 88 decompressed derivation and exports `$WORDLISTS`. 89 90 ## Architecture 91 92 Dendritic, matching the existing repo: every file under `modules/` is a 93 flake-parts module declaring the outputs it owns, cross-referenced by **name** 94 through `self`, never by path. Switches live in one `options.nix`; each category 95 is gated with `lib.mkIf`. 96 97 ```text 98 modules/features/pentest/ 99 ├── options.nix daemon.pentest.{enable,<cat>.enable} + assertions 100 ├── core.nix always-on base + $PAYLOADS/$WORDLISTS env + /etc/hosts helper 101 ├── python.nix the combined offensive Python env + suffix-free aliases 102 ├── recon.nix ad.nix bloodhound.nix web.nix pivot.nix crack.nix 103 ├── shells.nix wordlists.nix payloads.nix gui.nix 104 ├── dfir.nix reversing.nix wireless.nix cloud.nix osint.nix mobile.nix 105 ├── vpn.nix htbvpn@.service template + scoped sudo rule 106 ├── time.nix htb-time privileged helper + scoped sudo rule 107 ├── devshells.nix perSystem.devShells.pentest and per-category 108 └── _pkgs/ ~20 pinned derivations (paths with /_ are skipped by import-tree) 109 110 modules/home/ 111 ├── htb.nix htbtarget htbvpn htbtime htbip htb new — the user-facing CLI 112 ├── htb-shell.nix zsh precmd integration: $TARGET/$BOX live in every terminal 113 └── cheats/pentest.md `pentest-cheat`, in the existing cheat-card style 114 ``` 115 116 ### Option tree 117 118 ```nix 119 daemon.pentest = { 120 enable = mkEnableOption "the offensive toolkit"; # master switch, default false 121 <category>.enable = ... ; # per D4 defaults 122 payloads.windowsArches = [ "amd64" "x86" ]; # which .exe variants to build 123 htb.vpnDir = "~/.config/htb/vpn"; # where .ovpn profiles live 124 htb.promptTarget = true; # show $TARGET in the prompt 125 }; 126 ``` 127 128 Every category also implies `core`, so `$PAYLOADS`/`$WORDLISTS` always exist. 129 An assertion fires if a category is enabled while `daemon.pentest.enable` is false. 130 131 ## Components 132 133 ### C1 — Tool modules 134 135 Curated from what was verified present in the pinned nixpkgs (2026-10-08). 136 137 - **recon** — nmap masscan rustscan naabu fscan nbtscan enum4linux-ng snmpcheck 138 onesixtyone thc-ipv6 dnsrecon subfinder amass httpx dnsx katana gowitness 139 eyewitness whatweb 140 - **ad** — netexec impacket certipy bloodhound-py kerbrute responder mitm6 141 coercer donpapi adidnsdump ldapdomaindump smbmap evil-winrm pypykatz bloodyad 142 lsassy dploot masky pywerview krb5 openldap samba(smbclient) sshpass 143 · custom: krbrelayx sprayhound 144 - **bloodhound** — bloodhound-ce with neo4j + postgresql wired up 145 - **web** — ffuf gobuster feroxbuster dirb nikto sqlmap commix wfuzz nuclei 146 nuclei-templates wpscan joomscan dalfox arjun jwt-cli jwt-hack mitmproxy 147 burpsuite zap 148 - **pivot** — ligolo-ng chisel socat proxychains-ng(native opt) sshuttle gost 149 frp iodine pingtunnel stunnel wireguard-tools openvpn 150 - **crack** — hashcat hashcat-utils john hydra medusa crowbar crunch cewl hashid 151 name-that-hash 152 - **shells** — metasploit msfpc powershell pwncat rlwrap updog miniserve 153 freerdp remmina rdesktop upx 154 - **wordlists** — seclists wordlists rockyou(decompressed) exploitdb 155 - **dfir** — volatility3 sleuthkit autopsy yara capa chainsaw hayabusa exiftool 156 foremost testdisk chntpw binwalk · custom: velociraptor 157 - **reversing** — ghidra radare2 rizin cutter gef pwntools one_gadget pwninit 158 checksec flare-floss retdec patchelf 159 - **wireless** — aircrack-ng hcxtools bettercap wireshark(native opt) termshark tcpdump 160 - **cloud** — awscli2 azure-cli google-cloud-sdk kubectl trivy kube-hunter pacu 161 - **osint** — theharvester recon-ng sherlock maigret holehe 162 - **mobile** — apktool jadx dex2jar frida-tools objection scrcpy 163 164 ### C2 — The Python toolkit (`python.nix`) 165 166 Answers "a script built into the nixos config that can set up all the python tools". 167 168 Three parts: 169 170 1. **One combined interpreter.** `pentest-python` = `python3.withPackages` with 171 every offensive library: impacket certipy pywerview dploot masky bloodhound 172 ldapdomaindump pypykatz bloodyad lsassy minikerberos aiowinreg ldap3 dnspython 173 scapy pwntools pycryptodomex requests rich. Any offensive script you download 174 runs with `pentest-python foo.py` and its imports already resolve — no venv, 175 no `pip install`. 176 177 *Risk:* a version conflict inside one env (impacket is built against 178 python3.14 here). Mitigation: if `withPackages` fails to resolve, fall back to 179 per-tool standalone packages and a slimmer shared env. Decided at build time, 180 not guessed. 181 182 2. **Suffix-free aliases.** impacket installs 70 scripts as `secretsdump.py`, 183 `psexec.py`, `GetUserSPNs.py`. A `mkSuffixFreeAliases` helper globs a package's 184 `bin/*.py` and symlinks `secretsdump` → `secretsdump.py`, so both spellings 185 work. Applied to impacket and any other `.py`-suffixed toolkit. 186 187 3. **A discovery command.** `impacket` with no arguments lists all 70 scripts 188 with fzf selection and `--help` preview; `impacket <name>` runs one. Same for 189 `pentest-python --list`. 190 191 ### C3 — Payload tree (`payloads.nix`) 192 193 A derivation of symlinks at a stable path, exported as `$PAYLOADS`: 194 195 ```text 196 $PAYLOADS/ 197 ├── windows/{amd64,x86}/ 198 │ ├── privesc/potato/ JuicyPotato JuicyPotatoNG RoguePotato SweetPotato 199 │ │ GodPotato PrintSpoofer SharpEfsPotato EfsPotato 200 │ │ LocalPotato DeadPotato 201 │ ├── privesc/printer/ SpoolSample SharpPrintNightmare PrintSpoofer 202 │ ├── privesc/ winPEASx64 winPEASx86 Seatbelt SharpUp 203 │ ├── ad/ Rubeus SharpHound Certify Whisker SharpView 204 │ │ ADCSPwn StandIn 205 │ ├── creds/ mimikatz-2.2.0 mimikatz-2.1.1 LaZagne SharpDPAPI 206 │ ├── agents/ ligolo-agent chisel nc 207 │ └── misc/ SharpCollection (the full upstream set) 208 ├── linux/{amd64,arm64}/ ligolo-agent chisel linpeas pspy socat nc 209 ├── macos/{amd64,arm64}/ ligolo-agent chisel 210 └── scripts/ 211 ├── ad/ PowerView PowerUp SharpView.ps1 Invoke-Mimikatz 212 │ PowerSploit nishang Invoke-Kerberoast 213 ├── printer/ printerbug.py CVE-2021-1675.py Invoke-Nightmare.ps1 214 └── privesc/ linux-smart-enumeration linux-exploit-suggester 215 PowerUp.ps1 Sherlock.ps1 216 ``` 217 218 Multi-arch comes from `pkgsCross.mingwW64` (Windows) and `pkgsCross.aarch64-multiplatform` 219 (Linux arm64) over the same source. Versioned duplicates live here as distinct 220 filenames (`mimikatz-2.2.0.exe`, `mimikatz-2.1.1.exe`) rather than contending 221 for one PATH name — the reason payloads are files, not commands. 222 223 `payload-serve [port]` HTTP-serves the tree bound to `tun0`; 224 `payload-serve --smb` runs the impacket `smbserver` equivalent; 225 `payload-serve --list` prints the tree. 226 227 ### C4 — Shared target state (`htbtarget`) 228 229 The requirement is a target IP visible **across terminals**. Environment 230 variables cannot be pushed into already-running processes, so: 231 232 - **State** lives in `$XDG_STATE_HOME/htb/` (`target`, `name`, `vpn`), surviving 233 reboots so a box can be resumed next day. 234 - **Propagation** is a zsh `precmd` hook that re-reads the state file and exports 235 `$TARGET`, `$RHOST`, `$IP`, `$BOX`. Every terminal picks up a change made in 236 any other terminal **at its next prompt**. 237 238 ```sh 239 htbtarget 10.10.11.5 vintage.htb # set IP (+ optional FQDN) 240 htbtarget # print current 241 htbtarget clear 242 ``` 243 244 With a FQDN, it also manages a marked block in `/etc/hosts` through a privileged 245 helper — necessary because Kerberos in CPTS AD boxes needs working name 246 resolution, and `/etc` is read-only on NixOS. 247 248 **Honest limitation, to be documented in the cheat card:** a shell that is 249 mid-command when the target changes keeps the old value until its next prompt. 250 Interactive use is unaffected. 251 252 ### C5 — VPN control (`htbvpn`) 253 254 Implemented as a **systemd service template** `htbvpn@.service`, not a 255 backgrounded process — it survives closing the terminal, logs to the journal, 256 and `down` reliably kills it. 257 258 ```sh 259 htbvpn list # profiles in ~/.config/htb/vpn, active one marked 260 htbvpn up lab_eu_free # sudo systemctl start htbvpn@lab_eu_free 261 htbvpn down # stop whatever is up 262 htbvpn status # unit state + tun0 address 263 htbip # just the tun0 IP, for pasting into payloads 264 ``` 265 266 Profiles are **not** Nix-managed: they are per-account files that rotate, and 267 `~/.config/htb/vpn/` is created for them. A sops-encrypted variant is available 268 later if wanted. A scoped `NOPASSWD` sudo rule covers only 269 `systemctl {start,stop,restart} htbvpn@*`. 270 271 ### C6 — Clock skew control (`htbtime`) 272 273 The Kerberos-skew problem: AD authentication rejects a clock more than ~5 274 minutes off the DC, and on NixOS `systemd-timesyncd` is enabled by default and 275 will **undo** any manual adjustment. 276 277 `htbtime $TARGET`: 278 1. Record prior state (is `timesyncd` active? what does `timedatectl` report for 279 NTP? are `chrony`/`ntpd` running?) into `/var/lib/htb-time/state`. 280 2. Disable the conflicts: `timedatectl set-ntp false`, stop `chrony`/`ntpd` if up. 281 3. Sync to the target: `ntpdate -u <target>` (from `pkgs.ntp`, confirmed to ship 282 `ntpdate` and `sntp`), falling back to `chronyd -q` if the DC refuses NTP. 283 4. Report the resulting offset. 284 285 `htbtime off` reads the state file and restores **exactly** what was there, then 286 re-syncs to real time. `htbtime status` shows the current offset and mode. 287 288 Two safeguards: 289 290 - **A skew warning.** A large offset breaks TLS certificate validation, so `nix`, 291 `git` and HTTPS may fail while skewed. `htbtime` says so explicitly rather than 292 letting it become a confusing debugging session. 293 - **Self-healing.** State lives in `/var/lib`, but a reboot re-applies the NixOS 294 default anyway, so a forgotten `htbtime off` cannot permanently break the clock. 295 296 Root side is `htb-time`, a fixed store script with a scoped `NOPASSWD` rule for 297 `wheel` — mirroring the existing `fan-ec` / `fan` split in 298 `modules/hosts/laptop/fan-cli.nix` and `modules/home/fan.nix`. 299 300 ### C7 — Engagement scaffolding and cheat card 301 302 ```sh 303 htb new escape # ~/htb/escape/{nmap,loot,creds,www,exploit}/ + notes.md template, 304 # sets the target if given, cds in 305 htb ls # boxes worked, newest first 306 pentest-cheat # recon | ad | pivot | transfer | crack | web | dfir | htb 307 ``` 308 309 `pentest-cheat` follows the existing card mechanism exactly 310 (`modules/home/cheats/*.md` + `cheat-render`), so it joins `nix-cheat`, 311 `gpg-cheat` and `niri-cheat` with no new machinery. 312 313 ### C8 — `pentest-update` 314 315 Re-pins every `_pkgs/` derivation to its latest upstream `rev`/`hash` using 316 `nix-prefetch-url`/`nurl`, rewrites the hashes in place, and prints a diff for 317 review. Never auto-commits and never runs during a rebuild — pinning stays 318 deliberate. 319 320 ## Error handling 321 322 - **Missing VPN profile** — `htbvpn up` lists what is available and exits 2. 323 - **No target set** — any command needing `$TARGET` says which command sets it. 324 - **`htbtime` without a reachable DC** — reports the failure and leaves NTP 325 *disabled* with a clear warning plus the `htbtime off` hint, rather than 326 silently half-applying. 327 - **Unbuildable `_pkgs` entry** (upstream retagged, asset renamed) — that one 328 derivation fails with the URL that broke; it must not take the rebuild down, so 329 payload entries are assembled so a single failure is diagnosable in isolation. 330 - **Python env conflict** — falls back to standalone packages (C2). 331 332 ## Testing 333 334 No unit-test framework applies to a package set, so verification is build- and 335 behaviour-based, **per module as it lands**, not in one pass at the end: 336 337 1. `nix flake check` and `nix build .#nixosConfigurations.nixos.config.system.build.toplevel` 338 after each module — eval errors and missing attributes surface immediately. 339 2. **Smoke test per category**: one `--version`/`--help` per tool, scripted, so a 340 package that installs but cannot run is caught. 341 3. **Payload tree**: assert every declared path exists and Windows artefacts 342 report as PE binaries (`file`), Linux ELF ones as ELF. 343 4. **`htbtarget` cross-terminal**: set in terminal A, confirm `$TARGET` appears in 344 terminal B at its next prompt. 345 5. **`htbvpn`**: unit starts, `tun0` gains an address, `down` removes it. 346 6. **`htbtime`**: capture `timedatectl` before, run `htbtime` against a test NTP 347 source, confirm NTP disabled and offset applied, run `htbtime off`, confirm 348 `timedatectl` matches the original state byte for byte. This round-trip is the 349 one that must not be hand-waved. 350 7. `sudo nmap -sS` and `sudo responder -h` — the D1 guarantee, explicitly tested. 351 352 ## Risks 353 354 | Risk | Mitigation | 355 |---|---| 356 | First build is 40–60GB / a couple of hours | Mostly cache downloads. Phased: core + AD first, GUI last. | 357 | Upstream release assets move, breaking `_pkgs` | Hashes pinned; `pentest-update` re-pins; failures isolated per derivation. | 358 | `pkgsCross.mingwW64` fails for a given tool | Verified for mimikatz/ligolo-ng/netexec. Anything else falls back to a pinned release asset. | 359 | Combined Python env conflicts | C2 fallback to standalone packages. | 360 | Clock skew breaks TLS mid-engagement | Explicit warning + reboot self-heal (C6). | 361 | 200 new names shadow an existing command | Smoke test diffs `PATH` collisions against the pre-change profile and reports them. | 362 363 ## Phasing 364 365 Each phase ends with a working, committed system. 366 367 1. **Foundation** — `options.nix`, `core.nix`, `wordlists.nix`, `python.nix`. Proves the pattern. 368 2. **CPTS core** — `recon`, `ad`, `web`, `pivot`, `crack`, `shells`. 369 3. **HTB workflow** — `vpn.nix`, `time.nix`, `htb.nix`, `htb-shell.nix`. The commands. 370 4. **Payloads** — `_pkgs/` derivations, cross-builds, the staging tree, `payload-serve`. 371 5. **Heavy and optional** — `bloodhound`, `gui`, then `dfir`/`reversing`/`wireless`/`cloud`/`osint`/`mobile` (off by default). 372 6. **Polish** — `pentest-cheat`, `pentest-update`, `devshells.nix`, README. 373 374 ## Scope note 375 376 Written for authorised lab use: HackTheBox CPTS preparation. The toolkit is 377 ordinary defensive-security coursework equipment; it carries no targeting 378 configuration and nothing here points at infrastructure the operator does not 379 own or have explicit permission to test.