Skip to content

capstone, upx, fq: complete the reverse-engineering workbench - #549

Merged
bryan-minimal merged 4 commits into
mainfrom
add-reveng-tier2
Aug 3, 2026
Merged

capstone, upx, fq: complete the reverse-engineering workbench#549
bryan-minimal merged 4 commits into
mainfrom
add-reveng-tier2

Conversation

@bryan-minimal

@bryan-minimal bryan-minimal commented Jul 31, 2026

Copy link
Copy Markdown
Member

The last three of the reverse-engineering workbench: the disassembler engine, the unpacker, and the binary-format query tool. Independent of #545 (ghidra) and #547 (gef + rizin) — nothing here references them, so this can land in any order.

Each package's interesting decision is in its commit message; the short version:

capstone 5.0.9 — the default is static, and static is invisible

BUILD_SHARED_LIBS defaults to OFF upstream (BUILD_STATIC_LIBS to ON), so a stock build ships libcapstone.a only. Anything linking that absorbs capstone statically, which puts a capstone CVE beyond pkgscan's reach entirely — nothing in the consumer's tree would name capstone at all. Shared on, static off, and a test pins the outcome rather than trusting the flag.

5.0.9 rather than 6.0.0-Alpha10: the 6.0.0 alphas are tagged prerelease=false on GitHub but are alphas by name. rizin (#547) does not consume this — it pins capstone next, the unreleased v6, and vendors it; that's recorded in the rizin package and is unchanged by this PR.

Licence is (BSD-3-Clause AND NCSA): the tree ships LICENSE.TXT (BSD-3-Clause) and LICENSE_LLVM.TXT for the decoders generated from LLVM's tables. That second file is the old LLVM Release License — University of Illinois/NCSA — because capstone 5.x predates LLVM's relicensing. An earlier revision of this PR called it "Apache-2.0 WITH LLVM-exception" and declared BSD-3-Clause alone; both were wrong.

upx 5.2.0 — vendored compressors that cannot be unbundled

The CMake build offers no find_package() or system-lib option at all — unlike rizin, where seventeen use_sys_* flags existed and flipping them was the whole job. It isn't an oversight: upx's output is read back by a decompression stub welded onto the packed executable, so compressor and stub must agree bit-for-bit; a system zlib would produce files this upx's own stubs could not unpack.

What is actually linked in is narrower than vendor/ suggests, and reading the directory listing gets it wrong in both directions:

licence in the binary?
ucl GPL-2.0-or-later yes, own CMake target
zlib Zlib yes, own CMake target
lzma-sdk public domain yes — but invisibly: compress_lzma.cpp #includes its .cpp files directly, so it never appears as its own objects in the ninja log
doctest MIT yes, same trick via src/check/dt_impl.cpp — this release binary carries its test framework
bzip2 noCMakeLists.txt:269 hard-disables it
zstd noCMakeLists.txt:270, "currently not used; maybe in UPX version 6"

So license_spdx is (GPL-2.0-or-later AND Zlib AND MIT) — the LZMA SDK is public domain, adding no conditions and no SPDX identifier.

The consequence is stated rather than left to be discovered: the three libraries that are linked are invisible to pkgscan on this package, permanently. A CVE in the bundled zlib will not appear against it. Not a TODO anyone can close — flagging it since it's the exact blind spot this distro exists to avoid.

Two more things a reviewer would otherwise trip on:

  • The source is not extract = true. Upstream compresses the -src tarball with an xz SHA-256 integrity check (xz --listCheck: SHA-256, not the usual CRC64) and the fetcher's decoder rejects it outright: compression error: Unsupported SHA-256 checksum (not yet implemented). build.sh unpacks with the system tar.
  • runtime_deps needs libstdc++/libgcc. Found by the checker, not guessed — with glibc alone, even upx --version-short died with error while loading shared libraries: libstdc++.so.6.

fq 0.17.0 — jq for binaries

~120 format decoders behind a jq expression language. CGO_ENABLED=0 (upstream's own setting) makes it a pure-Go static binary with no DT_NEEDED, hence empty runtime_deps — same shape as helm.

No -X version stamping: fq keeps its version as a plain const in fq.go, and the Go linker only rewrites vars, so a -ldflags -X aimed at it would be silently ignored. That's the kind of line that looks maintained and does nothing, so it's absent and commented.

Tests target each package's actual silent failure

package what fails quietly what the test asserts
capstone CAPSTONE_ARCHITECTURE_DEFAULT gates every per-arch option at once — cstool still builds, runs, and reports the same version with whole architectures gone real instructions on real bytes across 5 arches incl. two fringe (riscv64, ppc64be), plus a no-such-arch control so the greps can't pass on an error message
upx upx --best will produce a file that is smaller and completely unrunnable if the per-arch stub is wrong pack → execute the packed copy and compare its output to the original → unpack → cmp byte-for-byte
fq the jq engine and the format decoders are separate packages; fq -n '1+1' passes with every decoder broken a gzip's inflated payload equals the input (not just the header field), plus ELF decoding of a second format

All mutation-tested. Broke one load-bearing assertion in each package simultaneously and confirmed min check failed all three, each naming the exact assertion — then restored and re-verified green. Worth doing: StandaloneTestCheck maps a cache miss on any test_dep to Skip, and check only errors on Fail, so a green suite does not by itself prove the tests ran.

Two bugs in my own tests, both caught by running them: cstool ppc64 is CS_MODE_LITTLE_ENDIAN in capstone 5 (ppc64be is the big-endian one) and my vector was the BE encoding; and the fq ELF assertion derives the expected machine from uname rather than hardcoding it, after an earlier package in this loadout pinned an arch literal and only failed once it reached amd64 CI.

Verified

Built and checked entirely through the min-native path — no minimal package anywhere:

min session activate . --loadout pkgbuild --name build
min session attach build --command "min package build --rebuild --verbose capstone upx fq"
min session attach build --command "min check --packages capstone --packages upx --packages fq"
build                       exit 0 (capstone 102 ninja steps, upx 96, fq go build)
min check                   45/45 Pass, 0 Fail  (15 checks × 3 packages)
mutation test               3/3 assertions confirmed load-bearing
reproducibility             byte-identical across a genuine --rebuild:
                              libcapstone.so.5.0.9  d73cab95…
                              cstool                bcc3e710…
                              upx                   6e70030b…
                              fq                    ca2e70ff…
pkgmgr check                all three up_to_date, skipped=0

Reproducibility was measured from inside the build sandbox: a min package build writes to the daemon's store inside the VM and populates neither host cache, so the old "hash the tree under ~/.cache/minimal/built" method no longer applies. Hashes above are of the installed artifacts, before and after a forced rebuild.

Arch note: built and verified on arm64 only — the amd64 path is exercised by CI, and unlike the ghidra PR nothing here compiles per-architecture natives, so the risk is ordinary rather than structural.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Packages

    • Added Capstone 5.0.9 with shared libraries, headers, command-line tools, and development metadata.
    • Added fq 0.17.0, a standalone data query and processing tool.
    • Added UPX 5.2.0, including executable packing tools, documentation, and man pages.
  • Quality Assurance

    • Added tests for multi-architecture disassembly, package discovery, and shared-library outputs.
    • Added smoke tests for compression, decompression, executable integrity, archive restoration, and query evaluation.

The disassembler engine underneath most open reverse-engineering tooling,
packaged as a shared library so consumers can link it rather than vendor it.

BUILD_SHARED_LIBS defaults to OFF upstream (and BUILD_STATIC_LIBS to ON), so a
stock build ships libcapstone.a only. Anything linking that absorbs capstone
statically, which puts a capstone CVE beyond pkgscan's reach entirely — nothing
in the consumer's tree would name capstone at all. Shared on, static off, and
the `ships_shared_lib` test pins it rather than trusting the flag.

Tests disassemble real bytes across five architectures, including two fringe
ones (riscv64, ppc64be), because CAPSTONE_ARCHITECTURE_DEFAULT gates every
per-arch support option at once: turning it off yields a cstool that builds,
runs, and reports the same version while silently having lost whole
architectures. A deliberate no-such-arch case is included as a control, so the
greps cannot pass on an error message.

5.0.9 rather than 6.0.0-Alpha10: the 6.0.0 alphas are tagged prerelease=false
on GitHub but are alphas by name. Note rizin does NOT consume this — it pins
capstone "next" (the unreleased v6) and vendors it, as recorded in that package.
The standard tool for unpacking UPX-compressed binaries, which is most of what
an RE workbench wants it for.

Three things worth a reviewer's attention:

Vendored compressors are STRUCTURAL here, not a config choice. vendor/ carries
ucl, lzma-sdk, zlib, zstd and bzip2, and the CMake build offers no
find_package() or system-lib option at all — unlike rizin, where seventeen
use_sys_* flags existed and flipping them was the whole job. It is not an
oversight either: upx's output is read back by a decompression stub welded onto
the packed executable, so compressor and stub must agree bit-for-bit. The
consequence is stated rather than left to be discovered — those five libraries
are invisible to pkgscan on this package, permanently.

The source is not fetched with extract = true. Upstream compresses the -src
tarball with an xz SHA-256 integrity check (xz --list reports Check: SHA-256,
not the usual CRC64) and the fetcher's decoder rejects it:
  compression error: Unsupported SHA-256 checksum (not yet implemented)
build.sh unpacks it with the system tar instead.

runtime_deps carries libstdc++/libgcc beyond glibc — found by the checker, not
guessed: with glibc alone even `upx --version-short` died with "error while
loading shared libraries: libstdc++.so.6".

The test packs a real binary, EXECUTES the packed copy and compares its output
to the original, then unpacks and cmp's byte-for-byte. Nothing cheaper works:
`upx --version` runs without touching a compressor, and `upx --best` will
happily produce a file that is smaller and completely unrunnable if the
per-architecture stub is wrong.
jq for binaries: ~120 format decoders behind a jq expression language, which
makes "what is in this file, structurally" scriptable across a corpus rather
than a thing you do one file at a time in a hex editor.

Built CGO_ENABLED=0 (upstream's own setting), so the result is a pure-Go static
binary with no DT_NEEDED — hence the empty runtime_deps, same shape as helm. If
CGO ever creeps back in, the missing-runtime_deps checker is what notices.

No -X version stamping: fq keeps its version as a plain const in fq.go, and the
Go linker only rewrites vars — a -ldflags -X aimed at a const is silently
ignored, which is the kind of thing that looks maintained and does nothing.

The test decodes rather than smoke-tests. `fq --version` and even `fq -n '1+1'`
pass on a build whose format decoders are all broken, since the jq engine and
the decoders are separate packages and only the decoders are the reason to ship
this. So it asserts on a gzip's INFLATED payload — proving the DEFLATE decoder
ran and returned the original bytes — not merely on the header field, and adds
a second decoder (ELF) over a real file. The expected ELF machine is derived
from uname rather than hardcoded, after an earlier package in this loadout
pinned an architecture literal and only failed once it reached amd64 CI.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds package specifications and build scripts for Capstone, fq, and UPX. The packages define reproducible builds, outputs, metadata, runtime settings, and validation tests.

Changes

Package additions

Layer / File(s) Summary
Capstone package and validation
packages/capstone/build.ncl, packages/capstone/build.sh
Defines Capstone 5.0.9, builds shared libraries and tools, exposes package outputs, and tests disassembly, library files, and pkg-config discovery.
fq package and validation
packages/fq/build.ncl, packages/fq/build.sh
Defines fq 0.17.0, builds a reproducible static Go binary, and tests version output, gzip decoding, ELF decoding, and jq evaluation.
UPX package and validation
packages/upx/build.ncl, packages/upx/build.sh
Defines UPX 5.2.0, builds the release binary with bundled compressors, and tests version output plus pack-run-unpack behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: twitchyliquid64

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title names all three packages and accurately summarizes their addition to complete the reverse-engineering workbench.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-reveng-tier2

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/capstone/build.ncl`:
- Around line 11-18: Remove the glibc entry from the build_deps list in the
build configuration, leaving its existing runtime_deps declaration unchanged and
avoiding duplication.
- Around line 39-43: Update the license_spdx declaration in the build
configuration from the single BSD-3-Clause identifier to the combined SPDX
expression BSD-3-Clause AND NCSA, reflecting both the project license and
LLVM-derived decoder tables.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 123ecdd1-10bc-475a-a10d-57c30fcf2017

📥 Commits

Reviewing files that changed from the base of the PR and between be740bf and d916168.

📒 Files selected for processing (6)
  • packages/capstone/build.ncl
  • packages/capstone/build.sh
  • packages/fq/build.ncl
  • packages/fq/build.sh
  • packages/upx/build.ncl
  • packages/upx/build.sh

Comment on lines +11 to +18
build_deps = [
{ file = "build.sh" } | Local,
base,
cmake,
ninja,
pkgconf,
toolchain,
glibc,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove glibc from build_deps.

Line 34 already declares glibc in runtime_deps. Do not duplicate runtime dependencies in build_deps.

As per coding guidelines, runtime dependencies do not need to be duplicated in build_deps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/capstone/build.ncl` around lines 11 - 18, Remove the glibc entry
from the build_deps list in the build configuration, leaving its existing
runtime_deps declaration unchanged and avoiding duplication.

Source: Coding guidelines

Comment thread packages/capstone/build.ncl Outdated
Both packages declared a single licence where the shipped artifact carries
more than one. The repo already models this — 54 packages use compound SPDX
expressions — so these were simply wrong, not stylistically different.

capstone: the tree ships LICENSE.TXT (BSD-3-Clause) AND LICENSE_LLVM.TXT for
the decoders generated from LLVM's tables. My comment claimed that second file
was "Apache-2.0 WITH LLVM-exception". It is not: capstone 5.x predates LLVM's
relicensing and the file is the old LLVM Release License, i.e. University of
Illinois/NCSA. Now (BSD-3-Clause AND NCSA), with the comment corrected.

upx: was GPL-2.0-or-later alone, which omits the vendored code compiled into
the binary. Established from the build rather than from the vendor/ listing,
which turns out to overstate it in one direction and understate it in another:

  ucl        GPL-2.0-or-later  linked (own CMake target)
  zlib       Zlib              linked (own CMake target)
  lzma-sdk   public domain     linked INVISIBLY — compress_lzma.cpp #includes
                               its .cpp files directly, so it never appears as
                               its own objects in the ninja log
  doctest    MIT               linked the same way, via src/check/dt_impl.cpp;
                               this release binary carries its test framework
  bzip2      —                 NOT linked; CMakeLists.txt:269 hard-disables it
  zstd       —                 NOT linked; CMakeLists.txt:270 hard-disables it

So (GPL-2.0-or-later AND Zlib AND MIT); the LZMA SDK is public domain and adds
no conditions and no SPDX identifier. The earlier claim that five vendored
libraries ride invisibly in this package was wrong in both directions — it is
three, and two of the five named are not in the binary at all.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
packages/capstone/build.ncl (1)

107-109: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Restrict the pkg-config lookup to the package output.

PKG_CONFIG_PATH adds a directory but does not suppress default directories. Require /usr/lib/pkgconfig/capstone.pc and set PKG_CONFIG_LIBDIR to prevent a false positive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/capstone/build.ncl` around lines 107 - 109, Update the pkg-config
command in the capstone package check to require the exact
/usr/lib/pkgconfig/capstone.pc file and set PKG_CONFIG_LIBDIR to
/usr/lib/pkgconfig, preventing lookup in default directories while preserving
the existing failure behavior.
packages/upx/build.ncl (1)

17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the duplicate glibc build dependency.

glibc is already declared in runtime_deps at Line [46]. Remove the Line [17] entry and keep the runtime declaration.

As per coding guidelines, runtime dependencies do not need to be duplicated in build_deps.

Proposed cleanup
     toolchain,
-    glibc,
     {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/upx/build.ncl` at line 17, Remove the duplicate glibc entry from the
build_deps declaration in build.ncl, while preserving the existing glibc entry
in runtime_deps.

Source: Coding guidelines

packages/upx/build.sh (1)

43-50: 🔒 Security & Privacy | 🔵 Trivial

Track vulnerabilities in bundled components outside pkgscan.

The block at Line [43] through Line [50] states that linked vendor code is invisible to pkgscan. Add a maintained component inventory or CI advisory check for the bundled revisions, especially zlib. Otherwise, a security fix can be missed when UPX is rebuilt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/upx/build.sh` around lines 43 - 50, Extend the build/security
tracking represented by the comments in packages/upx/build.sh with a maintained
inventory or CI advisory check for the bundled linked libraries, explicitly
including zlib and their revisions. Ensure vulnerability checks run
independently of pkgscan so fixes in bundled components are detected when UPX is
rebuilt.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/capstone/build.ncl`:
- Around line 107-109: Update the pkg-config command in the capstone package
check to require the exact /usr/lib/pkgconfig/capstone.pc file and set
PKG_CONFIG_LIBDIR to /usr/lib/pkgconfig, preventing lookup in default
directories while preserving the existing failure behavior.

In `@packages/upx/build.ncl`:
- Line 17: Remove the duplicate glibc entry from the build_deps declaration in
build.ncl, while preserving the existing glibc entry in runtime_deps.

In `@packages/upx/build.sh`:
- Around line 43-50: Extend the build/security tracking represented by the
comments in packages/upx/build.sh with a maintained inventory or CI advisory
check for the bundled linked libraries, explicitly including zlib and their
revisions. Ensure vulnerability checks run independently of pkgscan so fixes in
bundled components are detected when UPX is rebuilt.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68bffac8-3bee-46da-b891-bcf669ca47cf

📥 Commits

Reviewing files that changed from the base of the PR and between d916168 and 0c775c2.

📒 Files selected for processing (3)
  • packages/capstone/build.ncl
  • packages/upx/build.ncl
  • packages/upx/build.sh

@bryan-minimal
bryan-minimal added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit e9fa9a3 Aug 3, 2026
10 checks passed
@bryan-minimal
bryan-minimal deleted the add-reveng-tier2 branch August 3, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants