A collection of Nix packages maintained by the TII Secure Systems Research Lab.
flake.nix flake entry point (flake-parts)
default.nix flake-compat shim for non-flake consumers
nix/
flake-module.nix imports the modules below
checks.nix pre-commit hooks + every package exposed as a check
devshell.nix `nix develop` / direnv environment
exclusions.nix packages held out of CI checks / the devshell
lib.nix everything exported under `lib` (single owner)
mcp.nix `packages.mcp-config` + the devshell command
mcp-config.nix the `lib.mkMcpConfig` function itself
re-environment.nix this repo's own `devShells.re`
treefmt.nix formatter configuration (`nix fmt`)
update.nix per-package update policy for `update-packages`
update-packages.sh the runner itself
packages/
flake-module.nix assembles all packages + `overlays.default`
reexports.nix overlay-only attributes surfaced in `packages`
cpp/ C/C++ packages
go/ Go packages
misc/ everything that is not a language package
python/ Python packages
rust/ Rust packages (built with crane)
overlays/ overrides composed into `overlays.default`
modules/
flake-module.nix exports nixosModules / homeModules / flakeModules
re-environment/ the RE shell, as a flake-parts module
toolsets/ curated package groups, exported as `lib.toolsets`
Enter the development shell:
nix develop # or: direnv allowCommon commands:
nix fmt # format the tree with treefmt
nix flake check # run all checks (builds every package)
nix flake show # list outputs
nix build .#<pkg> # build a single package- Create
packages/<language>/<name>/containing the package definition (default.nix). - Register it in
packages/<language>/default.nix.
It is then automatically available as packages.<system>.<name>, as a check,
and through overlays.default.
- Give it an update policy in
nix/update.nix. Evaluation fails until you do — see below.
To keep a package out of CI builds or out of nix develop (vendor blobs, or
anything too heavy to build on every PR), add it to
nix/exclusions.nix. It stays in packages, so
nix build .#<name> and nix flake show keep working.
nix flake update moves flake.lock, and nothing else. Every version pinned
inside packages/ is invisible to it — a fetchPypi version+hash, a
fetchFromGitHub rev, a bare version string interpolated into a script. Those
go stale silently. The two commands are complements, not alternatives:
nix flake update # flake inputs (nixpkgs, crane, ...)
update-packages # version pins inside packages/update-packages is on $PATH in nix develop, or run it as
nix run .#update-packages. It rewrites files in place and prints a summary;
review git diff, then nix fmt && nix flake check. Extra arguments are
forwarded to nix-update (--build and --commit are the useful ones).
Packages it cannot bump automatically — vendor downloads behind a portal, and temporary overrides waiting on nixpkgs — are listed in the summary with their current version rather than skipped silently.
Every package must appear in exactly one bucket of policy in
nix/update.nix (auto, manual, reexported, or
derived).
Adding a package without classifying it fails evaluation — including in
nix flake check — which is what stops the next one from quietly rotting.
Some things need more than a package attribute. Binary Ninja is installed by
overriding an upstream derivation's source and installPhase, and needs a
sha256 that differs per user, so it ships as a module pair rather than an
overlay entry:
| Output | Provides |
|---|---|
nixosModules.binaryninja |
features.development.binaryninja.{enable,sha256} |
homeModules.binaryninja |
the install itself, read from osConfig |
# NixOS
imports = [ inputs.seclab-pkgs.nixosModules.binaryninja ];
features.development.binaryninja = {
enable = true;
sha256 = "<from stage-required-files>";
};
# home-manager
imports = [ inputs.seclab-pkgs.homeModules.binaryninja ];
homeModules.binaryninjaalso requiresoverlays.default. It putspkgs.svd2pyon the Binary Ninja plugin PYTHONPATH, so the overlay must be applied to whateverpkgshome-manager uses. The module asserts this, so a missing overlay fails at evaluation with an explanation rather than at build time withattribute 'svd2py' missing.
The zip itself goes in requiredFiles/ — see its README.
The lab's reverse-engineering tools are grouped into toolsets, and there are three ways to take them.
nix develop .#re gives the whole thing here. In your own flake, either import
the module:
{
inputs.seclab-pkgs.url = "github:Crypto-TII/seclab-pkgs";
outputs = inputs@{ flake-parts, seclab-pkgs, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" ];
imports = [ seclab-pkgs.flakeModules.default ];
# All optional. `nixpkgs` defaults to ours, `shellName` to "re", and
# every toolset to true except `cross` and `cross-extra`.
seclab.re = {
nixpkgs = inputs.nixpkgs;
toolsets.hardware = false;
};
};
}or cherry-pick groups, which are plain { pkgs } -> list functions:
devShells.default = pkgs.mkShell {
packages =
(seclab-pkgs.lib.toolsets.firmware { inherit pkgs; })
++ (seclab-pkgs.lib.toolsets.debugging { inherit pkgs; });
};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-<arch>-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-<arch> -L at
the extracted rootfs for those.
On x86_64, afl-qemu-arch <arch> afl-fuzz -Q ... runs AFL++'s QEMU mode
against a foreign binary; plain afl-fuzz -Q handles x86_64 only.
A toolset needs
pkgsbuilt with bothoverlays.defaultandconfig.allowUnfree = true— it contains this repo's own packages, and ghidra-re and binwalk reach the unfreedumpifsthrough ghidraninja-ghidra-scripts. Both are asserted with an explanation rather than left to fail asattribute 'ghidra-re' missing.
The shell sets GHIDRA_INSTALL_DIR, JAVA_HOME, PYTHONPATH (for
libtriton) and, on x86_64, DynamoRIO_DIR.
python is nixpkgs' default interpreter. angr is the one exception — it needs
the python313 that overlays/angr-suite.nix
patches, because nixpkgs' 3.14 build of it fails — so it comes as a separate
angr-python command rather than moving everything else back an interpreter.
lib.mkMcpConfig builds an MCP client config for the two servers this lab
runs — mcp-reva, pointed at ghidra-re, and Binary Ninja's headless
binaryninja_mcp. Nothing links it for you:
ln -sfn "$(mcp-config)" .mcp.json # in the devshell
ln -sfn "$(nix build --no-link --print-out-paths .#mcp-config)" .mcp.jsonFrom another flake, override the servers:
seclab-pkgs.lib.mkMcpConfig {
inherit pkgs; # needs overlays.default
extraServers.context7 = { type = "http"; url = "https://mcp.context7.com/mcp"; };
}binaryninja_mcp is referenced by name, not by store path: it comes from the
licensed zip that homeModules.binaryninja builds with a per-user sha256, so
it resolves from PATH when that module is active.
{
inputs.seclab-pkgs.url = "github:Crypto-TII/seclab-pkgs";
outputs = { nixpkgs, seclab-pkgs, ... }: {
# either via the overlay
# nixpkgs.overlays = [ seclab-pkgs.overlays.default ];
# or directly
# seclab-pkgs.packages.x86_64-linux.<name>
#
# modules are a separate output kind -- overlays.default contributes
# package attributes, nixosModules/homeModules contribute options.
};
}