A static inspector that reads ELF, PE/COFF, and Mach-O files you compiled and reports their layout, names, links, and function starts.
You give it an ELF64 object, a COFF object, a PE32+ image, or a Mach-O 64 file. It reports the format, the architecture, and the sections and segments with permissions and entropy. It also reports symbols with demangled C++ names, imports, exports, relocations, Mach-O chained fixups, strings with their section, and function starts. With the unstripped build of the same source, it scores each function start method with precision and recall. It writes a JSON report, a standalone HTML report with a byte map, and a diff between two builds.
Python 3.10 or later and the standard library only. The grader also uses Python. The fixtures are already built, so you need clang only to rebuild them.
Scope and rules
Every input in this project is a program you compiled from source in the fixtures. Use the inspector on your own builds. It reads files and never runs them. It does not remove protections, unpack, or analyze third-party software. Lesson 32 states the authorization rule that applies to every target in this track.
Build and run your version
From the repository root, initialize once. A fresh starter fails stage 1 on purpose.
python3 scripts/project_test.py binary-inspector --init learning-artifacts/binary-inspector
python3 scripts/project_test.py binary-inspector --stage 1 --path learning-artifacts/binary-inspector --strictThe workspace has one stub module per stage, the CLI, the JSON Schema of the report, and the fixtures. Implement each stage, then run the cumulative grader and the CLI on any file:
python3 scripts/project_test.py binary-inspector --all --path learning-artifacts/binary-inspector --strict
cd learning-artifacts/binary-inspector
python3 cli.py inspect fixtures/tally-arm64-O2.stripped --truth fixtures/tally-arm64-O2 --json report.json --html report.html
python3 cli.py diff fixtures/tally-arm64-O0 fixtures/tally-arm64-O2 --json diff.json
python3 cli.py inspect path/to/your-own-build.oThe CLI and the fixtures are supplied scaffolding. Its imports resolve to your modules. The API contract lists every public function and its return shape.
Inspect the reference separately
python3 scripts/project_test.py binary-inspector --all --solution --strict
cd projects/binary-inspector/solution
python3 cli.py inspect fixtures/tally-arm64-O2.stripped --truth fixtures/tally-arm64-O2 --json report.json --html report.htmlObserve the result
The demo command reads the stripped arm64 executable. Strip removed every defined symbol, so the symbols method finds 0 of 6 function starts. LC_FUNCTION_STARTS still lists all 6. Direct bl targets find 5, because nothing calls main. The prologue scan finds 1, because the five tally functions at -O2 have no stack frame. The four imports from libSystem come from the chained fixups, with the address of each slot that dyld fills.
Fixtures
The fixture folder holds two C files, one C++ file, the build script, and the outputs of Apple clang 17:
| Target | Files |
|---|---|
| x86-64 Linux ELF objects | tally at -O0 and -O2, main at -O2, names.cpp at -O2 |
| x86-64 Windows MSVC COFF objects | tally at -O0 and -O2, main at -O2 |
| arm64 macOS Mach-O | tally-arm64-O2.o, executables at -O0 and -O2, a stripped copy of the -O2 executable, a dylib and its stripped copy |
| PE32+ image | a small image that a Python script writes field by field, because the toolchain on the authoring machine has no Windows linker |
Rebuild with the build script in the fixture folder. The script writes a new list of SHA-256 sums. The committed list records the build that the tests describe. A rebuild of the -O0 executable can differ in its debug map entries, which hold object file paths. The tests read the committed files.
A second program, meter, is the held-out case. Its sources, build script, and binaries live outside the workspace, and only the tests read them.
Limits
- ELF64 and Mach-O 64 little-endian, COFF objects for x86-64 and arm64 machines, PE32+ images. ELF32, PE32, big-endian files, and universal Mach-O files fail with a message that names the reason.
- Chained fixups with pointer formats 2 and 6. The arm64e formats fail with a message.
- The demangler covers the documented Itanium subset and returns
Nonefor the rest, including MSVC names. - Prologue and call target scans cover x86-64 and arm64.
Stages
- Detect the format and parse the header
- Sections, segments, permissions, and entropy
- Symbols, string tables, and demangled names
- Imports, exports, relocations, and chained fixups
- Strings with sections and recovered function starts
- JSON report, HTML byte map, and build diff
Lessons this project applies
- 32, Program Anatomy: an ELF reader from scratch
- 40, Linking, Loading, and Stripped Binaries
- 41, Recovering Data and Types