ghidra-cli/flake.nix
2026-07-28 22:20:39 +00:00

306 lines
9.9 KiB
Nix

{
description = "ghidr 0.1: pinned read-only Ghidra analysis CLI";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/62e0f05ede1da0d54515d4ea8ce9c733f12d9f08";
outputs =
{ self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
inherit (pkgs) lib;
jdk = pkgs.jdk21_headless;
ghidra = pkgs.stdenvNoCC.mkDerivation {
pname = "ghidra";
version = "12.1.2";
src = pkgs.fetchurl {
url = "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_12.1.2_build/ghidra_12.1.2_PUBLIC_20260605.zip";
hash = "sha256-ti6BoDkGGEZsAZxg2ML3ls7SUJxMGupKN2RKdycs+Z0=";
};
nativeBuildInputs = [
pkgs.autoPatchelfHook
pkgs.makeWrapper
pkgs.unzip
];
buildInputs = [ pkgs.stdenv.cc.cc.lib ];
dontConfigure = true;
dontBuild = true;
unpackPhase = ''
runHook preUnpack
unzip -q "$src"
sourceRoot=ghidra_12.1.2_PUBLIC
runHook postUnpack
'';
installPhase = ''
runHook preInstall
mkdir -p "$out/lib/ghidra" "$out/bin"
cp -a . "$out/lib/ghidra/"
patchShebangs "$out/lib/ghidra/support"
makeWrapper "$out/lib/ghidra/support/analyzeHeadless" "$out/bin/ghidra-analyzeHeadless" \
--set JAVA_HOME "${jdk}" \
--prefix PATH : "${
lib.makeBinPath [
jdk
pkgs.bash
pkgs.coreutils
pkgs.findutils
pkgs.gnugrep
pkgs.gnused
]
}"
runHook postInstall
'';
passthru = { inherit jdk; };
meta = {
description = "Ghidra 12.1.2 headless analysis distribution";
homepage = "https://github.com/NationalSecurityAgency/ghidra";
license = lib.licenses.asl20;
platforms = [ system ];
sourceProvenance = [ lib.sourceTypes.binaryBytecode ];
};
};
adapter = pkgs.stdenvNoCC.mkDerivation {
pname = "ghidr-java-adapter";
version = "0.1.0";
src = ./java;
nativeBuildInputs = [
jdk
pkgs.clang-tools
];
dontConfigure = true;
buildPhase = ''
runHook preBuild
clang-format --dry-run --Werror --style=Google GhidrAdapter.java
classpath="$(find ${ghidra}/lib/ghidra -type f -name '*.jar' -print | LC_ALL=C sort | paste -sd: -)"
# Ghidra's release JAR manifests reference optional JARs that are not
# shipped in the distribution. Keep adapter source warnings fatal,
# while excluding those upstream class-path warnings and preventing
# processors discovered in Ghidra's dependency closure from running.
javac -encoding UTF-8 -source 21 -target 21 -proc:none \
-Xlint:all,-path -Werror -cp "$classpath" GhidrAdapter.java
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p "$out/share/ghidr/java"
install -m 0444 GhidrAdapter.java dispatch.txt "$out/share/ghidr/java/"
install -m 0444 ./*.class "$out/share/ghidr/java/"
runHook postInstall
'';
};
elfFixture = pkgs.stdenv.mkDerivation {
pname = "ghidr-fixture-elf-x86-64";
version = "0.1.0";
src = ./fixtures/src/known.c;
dontUnpack = true;
SOURCE_DATE_EPOCH = "1";
buildPhase = ''
runHook preBuild
$CC -x c "$src" -Os -ffreestanding -fno-ident -fno-stack-protector \
-fno-asynchronous-unwind-tables -fno-unwind-tables -nostdlib -static \
-no-pie -Wl,--build-id=none,-e,_start -o known.elf
runHook postBuild
'';
installPhase = ''
mkdir -p "$out/elf-x86_64"
install -m 0555 known.elf "$out/elf-x86_64/known.elf"
'';
};
peFixture = pkgs.pkgsCross.mingwW64.stdenv.mkDerivation {
pname = "ghidr-fixture-pe32plus-x86-64";
version = "0.1.0";
src = ./fixtures/src/known.c;
dontUnpack = true;
SOURCE_DATE_EPOCH = "1";
buildPhase = ''
runHook preBuild
$CC -x c "$src" -Os -ffreestanding -fno-ident -fno-stack-protector \
-nostdlib -Wl,--no-insert-timestamp,--entry,mainCRTStartup,--subsystem,console \
-o known.exe
runHook postBuild
'';
installPhase = ''
mkdir -p "$out/pe32plus-x86_64"
install -m 0555 known.exe "$out/pe32plus-x86_64/known.exe"
'';
};
fixtures = pkgs.symlinkJoin {
name = "ghidr-fixtures-0.1.0";
paths = [
elfFixture
peFixture
];
};
rustPackage = pkgs.rustPlatform.buildRustPackage {
pname = "ghidra-cli";
version = "0.1.0";
src = lib.cleanSource self;
cargoLock.lockFile = ./Cargo.lock;
doCheck = true;
cargoTestFlags = [ "--features=test-support" ];
nativeBuildInputs = [ pkgs.installShellFiles ];
};
ghidr =
pkgs.runCommand "ghidr-0.1.0"
{
nativeBuildInputs = [ pkgs.makeWrapper ];
meta.mainProgram = "ghidr";
}
''
mkdir -p "$out/bin" "$out/share/ghidr"
ln -s ${adapter}/share/ghidr/java "$out/share/ghidr/java"
makeWrapper ${rustPackage}/bin/ghidr "$out/bin/ghidr" \
--set GHIDR_GHIDRA_VERSION 12.1.2 \
--set GHIDR_JAVA_HOME ${jdk} \
--set GHIDR_ANALYZE_HEADLESS ${ghidra}/bin/ghidra-analyzeHeadless \
--set GHIDR_ADAPTER_PATH "$out/share/ghidr/java" \
--set GHIDR_BUBBLEWRAP ${pkgs.bubblewrap}/bin/bwrap \
--prefix PATH : ${
lib.makeBinPath [
ghidra
jdk
pkgs.bubblewrap
]
}
'';
realGhidraE2e =
pkgs.runCommand "ghidr-real-ghidra-e2e"
{
nativeBuildInputs = [
ghidr
pkgs.jq
];
}
''
export HOME="$TMPDIR/home"
mkdir -p "$HOME" "$TMPDIR/store"
${ghidr}/bin/ghidr --sandbox off --store "$TMPDIR/store" inspect \
${fixtures}/elf-x86_64/known.elf > elf.json
jq -e '
.kind == "inspection"
and .provenance.ghidr_version == "0.1.0"
and .provenance.ghidra_version == "12.1.2"
' elf.json
${ghidr}/bin/ghidr --sandbox off --store "$TMPDIR/store" functions \
${fixtures}/pe32plus-x86_64/known.exe --all > pe.json
jq -e '.kind == "functions" and (.data.items | length) > 0' pe.json
${ghidr}/bin/ghidr --sandbox off --store "$TMPDIR/store" decompile \
${fixtures}/elf-x86_64/known.elf --name fixture_add > decompile.json
jq -e '.data.function.name == "fixture_add"' decompile.json
touch "$out"
'';
adapterE2e =
pkgs.runCommand "ghidr-java-adapter-e2e"
{
nativeBuildInputs = [
ghidra
jdk
pkgs.bash
pkgs.jq
];
}
''
export HOME="$TMPDIR/home"
export GHIDR_ANALYZE_HEADLESS=${ghidra}/bin/ghidra-analyzeHeadless
export GHIDR_ADAPTER_PATH=${adapter}/share/ghidr/java
export GHIDR_FIXTURES=${fixtures}
mkdir -p "$HOME"
cp -R ${./tests} tests
chmod +x tests/real_ghidra_adapter.sh
patchShebangs tests/real_ghidra_adapter.sh
./tests/real_ghidra_adapter.sh
touch "$out"
'';
in
{
packages.${system} = {
default = ghidr;
inherit
ghidr
ghidra
adapter
fixtures
;
real-ghidra-e2e = realGhidraE2e;
};
apps.${system}.default = {
type = "app";
program = "${ghidr}/bin/ghidr";
meta.description = "Run ghidr with its pinned Ghidra runtime";
};
checks.${system} = {
rust = rustPackage;
java-adapter = adapter;
java-adapter-e2e = adapterE2e;
inherit fixtures;
real-ghidra-e2e = realGhidraE2e;
cargo-deny = pkgs.stdenvNoCC.mkDerivation {
pname = "ghidr-cargo-deny";
version = "0.1.0";
src = lib.cleanSource self;
inherit (rustPackage) cargoDeps;
nativeBuildInputs = [
pkgs.cargo
pkgs.cargo-deny
pkgs.rustPlatform.cargoSetupHook
];
dontConfigure = true;
CARGO_NET_OFFLINE = "true";
buildPhase = ''
runHook preBuild
cargo deny check bans licenses sources
runHook postBuild
'';
installPhase = ''
touch "$out"
'';
};
nix-style =
pkgs.runCommand "ghidr-nix-style"
{
nativeBuildInputs = [
pkgs.nixfmt-rfc-style
pkgs.statix
];
}
''
cp ${./flake.nix} flake.nix
nixfmt --check flake.nix
statix check flake.nix
touch "$out"
'';
};
devShells.${system}.default = pkgs.mkShell {
packages = [
pkgs.rustc
pkgs.cargo
pkgs.clippy
pkgs.rustfmt
pkgs.cargo-deny
pkgs.bubblewrap
pkgs.clang-tools
pkgs.nixfmt-rfc-style
pkgs.statix
pkgs.jq
jdk
ghidra
];
GHIDR_GHIDRA_VERSION = "12.1.2";
GHIDR_JAVA_HOME = "${jdk}";
GHIDR_ANALYZE_HEADLESS = "${ghidra}/bin/ghidra-analyzeHeadless";
GHIDR_ADAPTER_PATH = "${adapter}/share/ghidr/java";
};
};
}