Taking a compiled program apart: working out what functions are inside it, turning the machine code back into assembly, and explaining what it does.
Written because the course that assigned this never explained the mechanics. Which command, typed where, and why.
GUIDE.md: the walkthrough, start to finish
Every command, what it does, why it was run, what the output meant. Written while doing the work, not reconstructed afterward. This is the main thing in this repo.
You need Docker, and nothing else. The analysis tools are GNU binutils, which exist on Linux and not on macOS, so everything runs in a pinned container that matches the course environment: Ubuntu 18.04, gcc 7.5.0, binutils 2.30.
git clone https://github.com/jguida941/binary_to_assembly.git
cd binary_to_assembly
make build # builds the analysis image, prints its digest. Takes a few minutes once.
make examples # compiles the five example programs, three ways eachThen take one apart:
make functions FILE=examples/05_functions/build/05_functions
make disassemble FILE=examples/05_functions/build/05_functions FUNCTION=report
make strings FILE=examples/05_functions/build/05_functionsmake help lists everything, and every target prints the raw command it runs before running it,
so you can copy that instead and stop using make whenever you like.
You need none of the above. Codio already has file, readelf, nm, objdump and Bless
installed, and GUIDE.md is written against exactly that. The container is for taking
apart programs you compiled yourself, on your own machine.
docker run --rm --platform linux/amd64 -v "$PWD:/work" -w /work \
-e PYTHONPATH=/work/src binary-assembly-guide:local \
python3.8 -m binary_assembly_guide.cli functions BINARYpython3.8, not python3: Ubuntu 18.04's default is 3.6.9, too old to parse the CLI.
environment/README.md explains why the image is pinned to an old
toolchain anyway, and what was measured to prove it matches Codio.
On the host, not in the container, because they need git and the container has none.
python3 -m pytest -q # everything
python3 -m pytest tests/privacy -q # the guardrail that matters mostFour programs do the work. Each reads a different part of the same file, which is why one is not enough.
| Tool | What it is | What it tells you |
|---|---|---|
file |
identifies file types by content | is this a program, what CPU, are the names still in it |
readelf |
reads ELF structure | the header fields file was summarising: type, entry point |
nm |
lists the symbol table | every name, with a letter saying whether it is code or data |
objdump |
disassembler | machine code decoded back into assembly, and raw section contents |
| Bless | a hex editor | the bytes themselves, and the names sitting in them as plain text |
A hex editor shows you that function names are literally in the file as text. It cannot tell you
which of those names is a function, which is a variable, and which is a source filename. nm reads
the table next door that records exactly that. objdump reads the executable code itself.
Four commands, one per step of the workflow the guide describes.
binary-assembly-guide inspect BINARY
binary-assembly-guide functions BINARY [--compare OTHER ...]
binary-assembly-guide disassemble BINARY FUNCTION [--resolve]
binary-assembly-guide strings BINARY [--section .rodata]
inspect shows what the file is, and whether it can be analyzed at all. Refuses anything that
is not x86-64 ELF and says why, rather than producing confident nonsense.
functions lists every function, with who wrote each one. Given other unrelated binaries via
--compare, it classifies by evidence rather than by a list of remembered names: anything present
in all of them was not written for any of them.
disassemble --resolve is the one worth having. It prints a function's assembly and looks up
what every address the code references actually contains:
400556: lea 0xf7(%rip),%rdi # 400654
400562: callq 4003f0 <printf@plt>
Addresses this function references:
0x400654 "%s: %d"
By hand that is objdump -s -j .rodata, finding the right line, counting bytes in, decoding hex,
and stopping at the first 00. Per address. Skipping it is what turns an explanation into a
description of a mechanism that never says what the program does.
strings shows the string literals in the data sections, split correctly. objdump's own ASCII
column renders every unprintable byte as ., so strings run together and the boundaries are lost.
Shell wrappers in scripts/ call the same commands, so the underlying tool stays
visible rather than hidden behind a program.
Full CLI guide: every command and every flag, with real output for each, including what happens on a stripped binary, a Mach-O file, and a name that does not exist.
Five small C programs, each isolating one construct, each with a written exercise beside it. You own the source, so you can check your reading against something whose answer you already know.
| Example | Construct |
|---|---|
01_simple_return |
prologue, epilogue, and a return value |
02_arithmetic |
where operands live, and which register carries the result |
03_condition |
cmp plus a conditional jump, and why the condition is inverted |
04_loop |
the backward jump, and where the counter is kept |
05_functions |
four functions, argument registers, and a call graph |
Each builds three ways: make for the debug build that resembles the course files, make stripped
to see what happens when the symbol table is gone, and make optimized to see what -O2 does to
code you just learned to read.
docs/ |
five background pages, and troubleshooting where every error is one that actually happened |
images/ |
screenshots used in the guide. Read its README before adding one |
environment/ |
measured toolchain parity between Codio and the pinned container |
dev/PLAN.md |
the architecture, and what building it proved wrong |
Working and finished for what it covers. The guide walks the full workflow, the CLI is tested against every example binary in three build variants, and the five examples compile clean and are documented from captured output.
Scope is deliberately narrow: x86-64 ELF only, non-stripped or it will tell you it cannot name anything. No JSON output and no GUI. Everything in here is something I ran and checked.
No completed coursework and no supplied binaries. inputs/ and outputs/ are gitignored and stay
local, and a test suite enforces it rather than trusting .gitignore. This repo is the method, not
the answers.
If you are taking this course: reading someone's finished worksheet teaches you nothing and your instructor can tell. The examples exist so you can practise on programs whose source you own, then do your own files yourself.