NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

python.nix (5254B)


      1 # modules/features/pentest/python.nix — one interpreter with every offensive
      2 # library importable, plus impacket's 70 example scripts reachable by name.
      3 { lib, ... }:
      4 (import ./_sets.nix { inherit lib; }) {
      5   name = "python";
      6   description = "offensive python: impacket, certipy, pypykatz and friends";
      7 
      8   packages = pkgs:
      9     let
     10       inherit (import ./_impacket.nix { inherit lib pkgs; }) impacket aliases;
     11 
     12       # Libraries, for importing. Applications are NOT installed from this env:
     13       # python3.withPackages links every package's console scripts into the
     14       # env's bin/, so an app that is ALSO installed standalone (certipy in
     15       # ad.nix, bloodhound-py, pypykatz…) gives two store paths owning one
     16       # name, which is a profile collision that stops the system building.
     17       # checks.pentest-collisions found exactly that, twice.
     18       env = pkgs.python3.withPackages (ps: with ps; [
     19         impacket
     20         certipy
     21         dploot
     22         masky
     23         ldapdomaindump
     24         pypykatz
     25         bloodyad
     26         lsassy
     27         minikerberos
     28         aiowinreg
     29         dnspython
     30         scapy
     31         pwntools
     32         pycryptodomex
     33         requests
     34         rich
     35       ]);
     36 
     37       # Spec C2's discovery command: `impacket` alone lists the 70 scripts
     38       # (through fzf when there is a terminal), `impacket <name>` runs one.
     39       # Tab-completing `impacket-` does most of this already, but this is the
     40       # entry point when you cannot remember whether it is GetUserSPNs or
     41       # getuserspns.
     42       impacket-cmd = pkgs.writeShellScriptBin "impacket" ''
     43         set -uo pipefail
     44         names() {
     45           ${pkgs.coreutils}/bin/ls -1 ${aliases}/bin \
     46             | ${pkgs.gnugrep}/bin/grep '^impacket-' \
     47             | ${pkgs.gnused}/bin/sed 's/^impacket-//' \
     48             | ${pkgs.coreutils}/bin/sort
     49         }
     50         case "''${1:-}" in
     51           -l|--list) names; exit 0 ;;
     52           -h|--help)
     53             echo "usage: impacket [<script>] [args…]   (no script: pick one)"
     54             echo "       impacket --list"
     55             echo "       every script is also a command: impacket-<script>"
     56             exit 0 ;;
     57         esac
     58         if [ "$#" -eq 0 ]; then
     59           if [ -t 0 ] && [ -t 1 ]; then
     60             sel=$(names | ${pkgs.fzf}/bin/fzf --prompt='impacket › ' \
     61                     --preview='${aliases}/bin/impacket-{} --help 2>&1 | head -40' \
     62                     --preview-window='right,65%,border-left,wrap') || exit 0
     63             [ -n "''${sel:-}" ] || exit 0
     64             exec ${aliases}/bin/impacket-"$sel"
     65           fi
     66           names
     67           exit 0
     68         fi
     69         script="$1"; shift
     70         if [ ! -x "${aliases}/bin/impacket-$script" ]; then
     71           echo "impacket: no script '$script'" >&2
     72           echo "try: impacket --list" >&2
     73           exit 2
     74         fi
     75         exec ${aliases}/bin/impacket-"$script" "$@"
     76       '';
     77 
     78       # So only ONE name is exported from the env: the interpreter. Any offensive script you
     79       # download runs with `pentest-python foo.py` and its imports resolve, with
     80       # no venv and no collisions.
     81       pentest-python = pkgs.runCommand "pentest-python"
     82         {
     83           meta = {
     84             description = "Python with the offensive library set importable";
     85             mainProgram = "pentest-python";
     86           };
     87         }
     88         ''
     89           mkdir -p $out/bin
     90           ln -s ${env}/bin/python3 $out/bin/pentest-python
     91         '';
     92     in
     93     [
     94       pentest-python
     95       impacket-cmd # `impacket` / `impacket --list`
     96       impacket # the 70 example scripts, as `secretsdump.py` etc.
     97       aliases # ...and as `secretsdump`, collision-guarded
     98     ];
     99 
    100   expectedBins = [
    101     "pentest-python"
    102     "secretsdump.py" # impacket's own name
    103     "secretsdump" # the suffix-free alias
    104     "GetUserSPNs"
    105     "impacket" # the discovery command
    106     "impacket-split" # held back from the bare name, reachable prefixed
    107   ];
    108 
    109   # Review Focus #1. environment.systemPackages merges every package into ONE
    110   # profile with buildEnv, so two packages owning bin/split is a collision, not
    111   # a PATH-order question. The probe below is that same merge: it fails to build
    112   # if an alias claims a name a real tool already owns, and once it builds we
    113   # assert the real tool is what the name resolves to.
    114   checkScript = { pkgs, lib }:
    115     let
    116       probe = pkgs.buildEnv {
    117         name = "pentest-python-profile-probe";
    118         paths = [
    119           (import ./_impacket.nix { inherit lib pkgs; }).aliases
    120           pkgs.coreutils
    121           pkgs.iputils
    122           pkgs.samba
    123         ];
    124       };
    125     in
    126     ''
    127       # The bare names must still be the real tools.
    128       for n in split ping net smbclient; do
    129         t=$(readlink -f ${probe}/bin/$n)
    130         case "$t" in
    131           *impacket*)
    132             echo "pentest-python: '$n' resolves to impacket ($t)" >&2
    133             echo "  a bare '$n' must stay the real tool; impacket's is impacket-$n" >&2
    134             exit 1 ;;
    135         esac
    136       done
    137       # ...and impacket's versions must be reachable under the prefix.
    138       for n in split ping net smbclient mimikatz; do
    139         if [ ! -e ${probe}/bin/impacket-$n ]; then
    140           echo "pentest-python: impacket-$n is missing" >&2
    141           exit 1
    142         fi
    143       done
    144     '';
    145 }