diff --git a/README.md b/README.md index faf55a5..d75e649 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ the module: imports = [ seclab-pkgs.flakeModules.default ]; # All optional. `nixpkgs` defaults to ours, `shellName` to "re", and - # every toolset to true. + # every toolset to true except `cross` and `cross-extra`. seclab.re = { nixpkgs = inputs.nixpkgs; toolsets.hardware = false; @@ -169,10 +169,22 @@ devShells.default = pkgs.mkShell { }; ``` -The groups are `binary-analysis`, `crypto`, `debugging`, `dev-tools`, -`firmware`, `forensics`, `hardware`, `python` and `reversing`; -`lib.mkToolsets { inherit pkgs; }` returns all of them as -`{ packagesByGroup, allPackages }`. +The groups are `binary-analysis`, `crypto`, `cross`, `cross-extra`, +`debugging`, `dev-tools`, `firmware`, `forensics`, `hardware`, `python`, +`reversing` and `static-analysis`; `lib.mkToolsets { inherit pkgs; }` returns +all of them as `{ packagesByGroup, allPackages }`, with `cross` and +`cross-extra` left out of `allPackages` unless `enabled` turns them on. + +`cross` gives a gcc and binutils per foreign target (`aarch64`, `arm`, `i386`, +`mips64el`, `mipsel`, `ppc64` (ELFv1), `ppc64le`, `riscv64`), plus a +`qemu--sysroot` command that runs a dynamically linked foreign binary +against that target's glibc, libgcc_s and libstdc++. `cross-extra` adds `mips`, +`mips64` and `ppc`, whose toolchains are not in cache.nixos.org and build +locally. Neither helps with uClibc or musl firmware: point `qemu- -L` at +the extracted rootfs for those. + +On x86_64, `afl-qemu-arch afl-fuzz -Q ...` runs AFL++'s QEMU mode +against a foreign binary; plain `afl-fuzz -Q` handles x86_64 only. > A toolset needs `pkgs` built with **both** `overlays.default` and > `config.allowUnfree = true` — it contains this repo's own packages, and diff --git a/modules/re-environment/default.nix b/modules/re-environment/default.nix index 63a4239..ce213fb 100644 --- a/modules/re-environment/default.nix +++ b/modules/re-environment/default.nix @@ -50,14 +50,14 @@ in description = "Extra packages for the shell, on top of the toolsets."; }; - toolsets = lib.genAttrs groups ( - group: + toolsets = lib.mapAttrs ( + group: enabledByDefault: lib.mkOption { type = lib.types.bool; - default = true; + default = enabledByDefault; description = "Include the ${group} toolset."; } - ); + ) groups; }; config.perSystem = diff --git a/nix/exclusions.nix b/nix/exclusions.nix index 1f1469f..b6ea415 100644 --- a/nix/exclusions.nix +++ b/nix/exclusions.nix @@ -11,6 +11,8 @@ "bindiff" # Half-hour from-source build behind a submodule fetch. "dynamorio" + # Wraps uniflash, a requireFile CI cannot satisfy. + "f28335-tools" # Unfree CUDA 13 closure, and the derivation is only the FHS sandbox. "freetoken" # A Gradle build against the whole Ghidra distribution. @@ -28,7 +30,7 @@ "pwndbg-lldb" # requireFile behind an ST login. "stm32cubeprogrammer" - # Large vendor installer from TI. + # requireFile; the installer is staged by hand. "uniflash" # keep-sorted end ]; diff --git a/nix/lib.nix b/nix/lib.nix index a5d9b11..076eaa5 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -11,6 +11,8 @@ _: { # keep-sorted start binary-analysis = import ../toolsets/binary-analysis.nix; crypto = import ../toolsets/crypto.nix; + cross = import ../toolsets/cross.nix; + cross-extra = import ../toolsets/cross-extra.nix; debugging = import ../toolsets/debugging.nix; dev-tools = import ../toolsets/dev-tools.nix; firmware = import ../toolsets/firmware.nix; @@ -18,6 +20,7 @@ _: { hardware = import ../toolsets/hardware.nix; python = import ../toolsets/python.nix; reversing = import ../toolsets/reversing.nix; + static-analysis = import ../toolsets/static-analysis.nix; # keep-sorted end }; diff --git a/overlays/angr-suite.nix b/overlays/angr-suite.nix index 90ffdca..4a1fe3f 100644 --- a/overlays/angr-suite.nix +++ b/overlays/angr-suite.nix @@ -76,7 +76,11 @@ let inherit (wheel) hash; }; nativeBuildInputs = final.lib.optional final.stdenv.hostPlatform.isLinux final.autoPatchelfHook; - buildInputs = final.lib.optional final.stdenv.hostPlatform.isLinux final.stdenv.cc.cc.lib; + # zlib: the aarch64 wheel's bundled blink links libz.so.1. + buildInputs = final.lib.optionals final.stdenv.hostPlatform.isLinux [ + final.stdenv.cc.cc.lib + final.zlib + ]; pythonImportsCheck = [ "pyxdia" ]; meta.description = "Extract program information from PDB files (prebuilt wheel)"; }; diff --git a/packages/misc/stm32cubeprogrammer/default.nix b/packages/misc/stm32cubeprogrammer/default.nix index 6b7572c..cb9823c 100644 --- a/packages/misc/stm32cubeprogrammer/default.nix +++ b/packages/misc/stm32cubeprogrammer/default.nix @@ -12,11 +12,11 @@ makeDesktopItem, writeShellScript, symlinkJoin, + version ? "2.23.0", + hash ? "sha256-ap5gpaBIxF6zJB+btmvcLmy9ARn7LkJWjcBZ/GFnRCo=", }: let - version = "2.22.0"; - desktopItem = makeDesktopItem { name = "STM32CubeProgrammer"; # Must match the FHS wrapper binary name (pname of the GUI env below) — @@ -38,7 +38,7 @@ let src = requireFile { name = "SetupSTM32CubeProgrammer_linux_64.zip"; url = "https://www.st.com/en/development-tools/stm32cubeprog.html"; - hash = "sha256-//oBertNoUWC4Smqmh5Ph+bQcZo8uVDAGE9MtIq2Cqc="; + inherit hash; }; nativeBuildInputs = [ @@ -181,10 +181,12 @@ let at-spi2-atk dbus + brotli openssl krb5 zlib + zstd cups nspr nss diff --git a/packages/misc/uniflash/default.nix b/packages/misc/uniflash/default.nix index abd235e..f389171 100644 --- a/packages/misc/uniflash/default.nix +++ b/packages/misc/uniflash/default.nix @@ -3,24 +3,26 @@ { stdenvNoCC, lib, - fetchurl, + requireFile, buildFHSEnv, writeShellScript, symlinkJoin, glibc, + version ? "9.6.0", + buildId ? "5764", + hash ? "sha256-ZueL+gg0kpmcUkpuqXwWtLkL5U1lvq53ASEPtkBLDZw=", }: let - version = "9.5.0"; - buildId = "5651"; - package = stdenvNoCC.mkDerivation { pname = "uniflash-unwrapped"; inherit version; - src = fetchurl { - url = "https://dr-download.ti.com/software-development/software-programming-tool/MD-QeJBJLj8gq/${version}/uniflash_sl.${version}.${buildId}.run"; - hash = "sha256-/2gUq90WcEIGmnmpjaNGXxaSEXQn12B7AaHTgUNjn3M="; + # Register with: nix-store --add-fixed sha256 + src = requireFile { + name = "uniflash_sl.${version}.${buildId}.run"; + url = "https://www.ti.com/tool/UNIFLASH"; + inherit hash; }; dontUnpack = true; diff --git a/requiredFiles/README.md b/requiredFiles/README.md index 0fdfecc..42b33f4 100644 --- a/requiredFiles/README.md +++ b/requiredFiles/README.md @@ -56,6 +56,7 @@ no-op. | --------------------------------------- | ----------------------------------------- | --------------------------- | | `binaryninja_linux_dev_ultimate.zip` | Binary Ninja (`nixosModules.binaryninja`) | base32, set by the consumer | | `SetupSTM32CubeProgrammer_linux_64.zip` | `stm32cubeprogrammer` | SRI, pinned in-package | +| `uniflash_sl.9.6.0.5764.run` | `uniflash` | SRI, pinned in-package | The two formats are not interchangeable, which is why the helper prints both. diff --git a/toolsets/binary-analysis.nix b/toolsets/binary-analysis.nix index ccc8295..fdbd0f3 100644 --- a/toolsets/binary-analysis.nix +++ b/toolsets/binary-analysis.nix @@ -2,31 +2,89 @@ # SPDX-License-Identifier: Apache-2.0 { pkgs }: let + inherit (pkgs) lib; seclab = import ./lib.nix { inherit pkgs; }; + + # capa looks for rules/ and sigs/ beside its package, which nixpkgs does not + # install; without them it exits 10 on every sample. The source carries both. + capa = pkgs.python3Packages.toPythonApplication ( + pkgs.python3Packages.capa.overridePythonAttrs (old: { + postInstall = (old.postInstall or "") + '' + ln -s ${old.src}/rules ${old.src}/sigs $out/${pkgs.python3.sitePackages}/ + ''; + }) + ); + + # nixpkgs builds afl-qemu-trace for the host arch only. ppc64 and ppc64le + # are absent: qemuafl's target/ppc does not compile with current gcc. + aflQemuTargets = [ + "aarch64" + "arm" + "i386" + "mips" + "mips64" + "mips64el" + "mipsel" + "ppc" + "riscv64" + "x86_64" + ]; + aflQemu = pkgs.aflplusplus.qemu.overrideAttrs (old: { + configureFlags = [ + "--target-list=${lib.concatMapStringsSep "," (t: "${t}-linux-user") aflQemuTargets}" + ] + ++ lib.filter (f: !lib.hasPrefix "--target-list=" f) old.configureFlags; + }); + # afl-fuzz, afl-showmap and afl-tmin all find afl-qemu-trace through AFL_PATH. + afl-qemu-arch = pkgs.runCommand "afl-qemu-arch" { } '' + ${lib.concatMapStrings (t: '' + mkdir -p $out/share/afl-qemu/${t} + ln -s ${aflQemu}/bin/qemu-${t} $out/share/afl-qemu/${t}/afl-qemu-trace + '') aflQemuTargets} + mkdir -p $out/bin + cat > $out/bin/afl-qemu-arch < } + [ -d $out/share/afl-qemu/\$arch ] || { echo "no afl-qemu-trace for \$arch; have: ${toString aflQemuTargets}" >&2; exit 1; } + shift + AFL_PATH=$out/share/afl-qemu/\$arch exec "\$@" + EOF + chmod +x $out/bin/afl-qemu-arch + ''; in seclab.requireSeclabPkgs ( with pkgs; [ # keep-sorted start bloaty + capa capstone checksec # Minimal, not full: the full build drags ghc, ocaml, mono and four JDKs. diffoscopeMinimal + dwarfdump elfutils + flare-floss + honggfuzz keystone # libtriton, not triton: the module it installs is named triton, which # would collide with python3Packages.triton. libtriton lief + one_gadget + osslsigncode pahole pax-utils pev + radamsa + ropgadget unicorn + upx yara-x # keep-sorted end ] ++ lib.optionals stdenv.hostPlatform.isx86_64 [ + afl-qemu-arch aflplusplus bindiff # meta.platforms is x86_64-linux; upstream treats aarch64 as a cross target. diff --git a/toolsets/cross-extra.nix b/toolsets/cross-extra.nix new file mode 100644 index 0000000..e40f222 --- /dev/null +++ b/toolsets/cross-extra.nix @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2025-2026 Technology Innovation Institute (TII) +# SPDX-License-Identifier: Apache-2.0 +# +# Not in cache.nixos.org: enabling this builds a cross gcc and glibc per target. +{ pkgs }: +let + seclab = import ./lib.nix { inherit pkgs; }; +in +seclab.mkCross "cross-extra" ( + with pkgs.pkgsCross; + { + # keep-sorted start + mips = mips-linux-gnu; + mips64 = mips64-linux-gnuabi64; + ppc = ppc32; + # keep-sorted end + } +) diff --git a/toolsets/cross.nix b/toolsets/cross.nix new file mode 100644 index 0000000..15557f2 --- /dev/null +++ b/toolsets/cross.nix @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2025-2026 Technology Innovation Institute (TII) +# SPDX-License-Identifier: Apache-2.0 +# +# Only targets whose gcc and glibc cache.nixos.org serves for x86_64-linux; +# cross-extra.nix holds the ones that build locally. +{ pkgs }: +let + seclab = import ./lib.nix { inherit pkgs; }; +in +seclab.mkCross "cross" ( + with pkgs.pkgsCross; + { + # keep-sorted start + aarch64 = aarch64-multiplatform; + arm = armv7l-hf-multiplatform; + i386 = gnu32; + mips64el = mips64el-linux-gnuabi64; + mipsel = mipsel-linux-gnu; + # ELFv1, not nixpkgs' default ppc64: big-endian binaries in the wild are + # ELFv1 and ask for ld64.so.1, which an ELFv2 glibc does not provide. + ppc64 = ppc64-elfv1; + ppc64le = powernv; + inherit riscv64; + # keep-sorted end + } +) diff --git a/toolsets/default.nix b/toolsets/default.nix index 1f3b05b..26aa4dc 100644 --- a/toolsets/default.nix +++ b/toolsets/default.nix @@ -10,12 +10,16 @@ let inherit (pkgs) lib; + groups = import ./groups.nix; + # Every group, whatever `enabled` says: a consumer who turned a group off in # the shell can still reach it by name. packagesByGroup = { # keep-sorted start binary-analysis = import ./binary-analysis.nix { inherit pkgs; }; crypto = import ./crypto.nix { inherit pkgs; }; + cross = import ./cross.nix { inherit pkgs; }; + cross-extra = import ./cross-extra.nix { inherit pkgs; }; debugging = import ./debugging.nix { inherit pkgs; }; dev-tools = import ./dev-tools.nix { inherit pkgs; }; firmware = import ./firmware.nix { inherit pkgs; }; @@ -23,14 +27,15 @@ let hardware = import ./hardware.nix { inherit pkgs; }; python = import ./python.nix { inherit pkgs; }; reversing = import ./reversing.nix { inherit pkgs; }; + static-analysis = import ./static-analysis.nix { inherit pkgs; }; # keep-sorted end }; in -# groups.nix repeats these names for the re-environment module, which declares -# one option per group and has no pkgs to ask. -assert lib.assertMsg (lib.attrNames packagesByGroup == import ./groups.nix) '' +# groups.nix repeats these names, with their defaults, for the re-environment +# module, which declares one option per group and has no pkgs to ask. +assert lib.assertMsg (lib.attrNames packagesByGroup == lib.attrNames groups) '' toolsets/groups.nix and toolsets/default.nix disagree: - groups.nix: ${lib.concatStringsSep " " (import ./groups.nix)} + groups.nix: ${lib.concatStringsSep " " (lib.attrNames groups)} default.nix: ${lib.concatStringsSep " " (lib.attrNames packagesByGroup)} ''; { @@ -38,7 +43,7 @@ assert lib.assertMsg (lib.attrNames packagesByGroup == import ./groups.nix) '' allPackages = lib.concatLists ( lib.mapAttrsToList ( - group: packages: lib.optionals (enabled.${group} or true) packages + group: packages: lib.optionals (enabled.${group} or groups.${group}) packages ) packagesByGroup ); } diff --git a/toolsets/dev-tools.nix b/toolsets/dev-tools.nix index 57a2cab..7de7821 100644 --- a/toolsets/dev-tools.nix +++ b/toolsets/dev-tools.nix @@ -17,10 +17,14 @@ with pkgs; hexyl imhex jq + kaitai-struct-compiler + nasm ninja + parallel patchelf pkg-config rehex ripgrep + wireshark-cli # keep-sorted end ] diff --git a/toolsets/groups.nix b/toolsets/groups.nix index 03c921f..5275e78 100644 --- a/toolsets/groups.nix +++ b/toolsets/groups.nix @@ -1,18 +1,24 @@ # SPDX-FileCopyrightText: 2025-2026 Technology Innovation Institute (TII) # SPDX-License-Identifier: Apache-2.0 # -# The group names, without a `pkgs` in hand: the re-environment module declares -# one option per group and cannot instantiate nixpkgs to ask. -[ +# The group names and whether each is on by default, without a `pkgs` in hand: +# the re-environment module declares one option per group and cannot +# instantiate nixpkgs to ask. +{ # keep-sorted start - "binary-analysis" - "crypto" - "debugging" - "dev-tools" - "firmware" - "forensics" - "hardware" - "python" - "reversing" + binary-analysis = true; + crypto = true; + # Off: a cross gcc and glibc per target is a large closure few users need, + # and cross-extra's targets are not in cache.nixos.org at all. + cross = false; + cross-extra = false; + debugging = true; + dev-tools = true; + firmware = true; + forensics = true; + hardware = true; + python = true; + reversing = true; + static-analysis = true; # keep-sorted end -] +} diff --git a/toolsets/hardware.nix b/toolsets/hardware.nix index 9e9f7fb..79bf267 100644 --- a/toolsets/hardware.nix +++ b/toolsets/hardware.nix @@ -3,6 +3,13 @@ { pkgs }: let seclab = import ./lib.nix { inherit pkgs; }; + + # write_chip_bad_status_test fails on aarch64 in this pin; Hydra has no build. + flashrom = + if pkgs.stdenv.hostPlatform.isAarch64 then + pkgs.flashrom.overrideAttrs { doCheck = false; } + else + pkgs.flashrom; in seclab.requireSeclabPkgs ( with pkgs; @@ -35,8 +42,8 @@ seclab.requireSeclabPkgs ( # has not staged the ST zip cannot even evaluate this list. ] ++ lib.optionals stdenv.hostPlatform.isx86_64 [ - f28335-tools saleae-logic-2 - uniflash + # uniflash and f28335-tools, which wraps it, are missing for the same + # reason: the TI installer is a requireFile. ] ) diff --git a/toolsets/lib.nix b/toolsets/lib.nix index 63feaf2..8255437 100644 --- a/toolsets/lib.nix +++ b/toolsets/lib.nix @@ -23,4 +23,44 @@ `Refusing to evaluate package 'dumpifs-...'` otherwise. ''; value; + + # `targets` maps a qemu-user arch name to a pkgsCross set. + mkCross = + name: targets: + let + inherit (pkgs) lib; + # A set targeting the host itself would put a bare `gcc` on PATH. + foreign = lib.filterAttrs ( + _: cross: cross.stdenv.hostPlatform.config != pkgs.stdenv.hostPlatform.config + ) targets; + in + [ + (pkgs.runCommand "seclab-${name}" { } ( + '' + mkdir -p $out/bin + '' + + lib.concatStrings ( + lib.mapAttrsToList ( + arch: cross: + let + inherit (cross.stdenv) cc; + sysroot = "$out/share/sysroots/${arch}"; + in + # bin/ only: the wrappers' setup hooks would retarget mkShell's stdenv. + # C++ needs libstdc++ beside glibc, and LD_LIBRARY_PATH: our ld.so skips /lib. + '' + ln -s -t $out/bin ${cc}/bin/* + mkdir -p ${sysroot}/lib + ln -s -t ${sysroot}/lib ${cross.glibc}/lib/* ${cc.cc.lib}/${cross.stdenv.hostPlatform.config}/lib/* + ln -s lib ${sysroot}/lib64 + cat > $out/bin/qemu-${arch}-sysroot <