Before
- Read instructions line by line
- Trust decompiler variable names
- Treat missing functions as suspicious
Module 5 of 17
Read compiler output as evidence and recover a harmless program's control flow without guessing from syntax alone.
Start here
Before
After
Reverse engineering becomes manageable when you stop treating assembly as a wall of instructions. A processor changes state: registers, memory, flags, and instruction position. Functions receive values according to a platform calling convention, perform calculations and memory access, call other functions, branch on conditions, and return a value.
Start with the few questions that support analysis. Where do arguments arrive? Which value is returned? Which registers must a function preserve? Which memory address comes from a local variable, a global, or a caller-provided pointer? Which comparison controls a branch? You do not need to memorize the entire instruction set before answering these questions.
The course uses only a transparent harmless utility that reads instructor-authored text from its fixed course directory and writes a summary there. It has no network feature, arbitrary target path, plugin system, privilege request, persistence, credential access, destructive behavior, or hidden capability.
Data flow tracks where a value comes from, how it changes, and where it is used. Control flow tracks which instruction or basic block may execute next. Mark function boundaries, calls, comparisons, conditional branches, loops, and returns. Then annotate important values at those points.
A basic block is a straight-line sequence with one entry and a transfer at the end. A control-flow graph connects these blocks. For a harmless parser, one branch might represent “input line is empty” and another “record is accepted.” Name a block after evidence supports its role, not after the first guess produced by a decompiler.
Pay special attention to bounds, lengths, signedness, and error paths because these often differ between source intent and machine behavior. In this course, the goal is understanding and secure review, not discovering or exploiting a vulnerability.
A calling convention defines how functions exchange arguments and return values and which registers a callee must preserve. It depends on architecture, operating system, application binary interface, compiler, and sometimes function attributes. Identify the correct convention before assigning meaning to a register.
Observe the call site and the called function together. Track argument preparation, stack alignment, indirect calls, return-value checks, and cleanup. Library calls can reveal broad intent, but names may be missing or wrapped. A familiar function name remains only one observation; confirm how its inputs and outputs are used.
Compilers transform source. Optimization can inline functions, remove variables, fold constants, reorder independent operations, reuse registers, replace loops with equivalent patterns, or remove branches that can never run. Runtime startup and safety features can add code that the developer did not write directly.
Compile the same harmless source with debug and release settings and compare the output. The difference teaches an important limit: a decompiler reconstructs a plausible high-level representation, not the original source. Variable names, comments, types, and exact control structures may be gone.
Build metadata also matters. Record compiler family and version, target architecture, optimization level, debug-symbol policy, and linked runtime. Without this context, a normal toolchain pattern may look suspicious.
Use multiple views: source, compiler-generated assembly for the harmless utility, disassembly, symbols, control-flow graph, and observed synthetic test output. When a decompiler suggests a type or condition, confirm it against argument use, instruction width, branches, and known inputs.
Keep an annotation table with address or block ID, observation, interpretation, evidence, confidence, and open question. This prevents a guessed function name from becoming an unchallenged fact later in the report. Stop if the compiled hash or observed scope differs from the lab manifest.
The published duration includes active practice, not video playback alone. Complete each block with the course-owned evidence and retain the stated deliverable so another reviewer can reproduce your reasoning.
| Study block | Time | Required evidence |
|---|---|---|
| Guided lesson and primary-source review | 1h 30m | Annotated notes that separate observations, hypotheses, limits, and version-sensitive facts. |
| Worked evidence walkthroughs | 1h 15m | Reproduce the lesson's tables or decision flow and challenge at least two assumptions. |
| Independent practice rounds | 1h 15m | Apply the method to two alternate records in the sanitized evidence pack and compare the conclusions. |
| Required lab | 3 hours | A reproducible build record, debug-versus-release comparison, annotated control-flow graph, two data-flow traces, confidence notes, and a completed teardown record. |
| Knowledge check and review | 1 hour | Answer the evidence check, review the rubric, and record one production follow-up. |
Question: The release build has no function matching a small helper visible in the source and debug build. Is this evidence that the release binary was altered?
Not by itself. The optimizer may have inlined the helper, removed it, or transformed its control flow. Compare the compiler settings, call sites, resulting behavior, build provenance, and trusted-build hash before concluding that the artifact differs unexpectedly.
Use these primary sources for the current standard or tool behavior. The course records framework versions so mappings can be reviewed when upstream guidance changes.
Real world
Production notes
Common mistakes
Security risks
Tradeoffs
Pros
Cons
Pros
Cons
Think like an engineer
Key terms
An architecture-defined operation that changes processor or memory state.
Rules for passing arguments, returning values, preserving registers, and maintaining stack layout across a function call.
A straight-line instruction sequence with one entry and a control transfer at its end.
A graph whose nodes are basic blocks and whose edges represent possible execution paths.
A compiler optimization that replaces a function call with an adapted copy of the called function's body.
Exercises
Compile a transparent course utility inside an isolated VM and connect selected source constructs to debug and optimized machine code.
Expected evidence
A reproducible build record, debug-versus-release comparison, annotated control-flow graph, two data-flow traces, confidence notes, and a completed teardown record.
Assessment criteria
Course-owned resources
Teardown
Recap
Related resources