Reverse engineering
Intro to Ghidra with a Benign Binary You Compile
Compile a toy C licence check two ways, then use Ghidra to find its string, follow references and compare the decompiler with your source.
Learn what compilation discards, how disassemblers differ from decompilers, and the legal limits to know before you analyze a binary.
Reverse engineering is the work of working out how something was built by examining the finished thing. For software, the finished thing is usually a compiled program: an executable, a shared library or a firmware image. The source code is missing or was never released, so you start from machine code and work back toward what the program does and why.
People do this for many reasons, and most of them are ordinary. A security engineer checks a vendor's binary before trusting it. A developer tries to make a closed file format work with an open tool. A student wants to see what a compiler really produces. This article covers what compilation discards, the difference between static and dynamic analysis, disassemblers versus decompilers, the legitimate reasons people do this work, and the legal limits to know before you start. The next article in the series runs a first real analysis.
A compiler turns human-readable source into instructions a processor can run. The output is correct for the machine and says little about intent. Several things disappear on the way:
for loop becomes a comparison and a jump. An if becomes a conditional jump to another place in the file.Each loss makes the next step less certain. The reverse engineer rebuilds some of these details from clues: the strings the program uses, the functions it imports from the operating system, and the way data moves through registers.
You can see a small version of this with Python 3.12. The example below is a three-line function with a comment. Python bytecode is not native machine code, so treat this as an analogy rather than a model of C output. It still shows the point.
# Teaching constant, not real access control.
def check_pin(user_pin):
training_pin = 2468 # the comment never reaches the bytecode
return user_pin == training_pinCompile it and disassemble it with these commands:
python -c "import py_compile; py_compile.compile('check_pin.py', cfile='check_pin.pyc', doraise=True)"
python -c "import marshal, dis; data = open('check_pin.pyc','rb').read()[16:]; code = marshal.loads(data); fn = [c for c in code.co_consts if hasattr(c,'co_code')][0]; print('name:', fn.co_name, '| constants:', fn.co_consts, '| locals:', fn.co_varnames); dis.dis(fn)"On the machine where this article was checked, the output was:
name: check_pin | constants: (None, 2468) | locals: ('user_pin', 'training_pin')
2 0 RESUME 0
3 2 LOAD_CONST 1 (2468)
4 STORE_FAST 1 (training_pin)
4 6 LOAD_FAST 0 (user_pin)
8 LOAD_FAST 1 (training_pin)
10 COMPARE_OP 40 (==)
14 RETURN_VALUETwo things stand out. The comment is gone: a search of the compiled file for its text finds nothing. The names survived, because Python keeps them in the code object. A compiled C program often keeps neither, so the same exercise on a native binary is harder. Use this as a mental model, not as a promise about every language.
Reverse engineering methods fall into two broad groups.
Static analysis examines the file without running it. You read the instructions, list the imports and strings, map functions and their cross-references, and build a model of the program. The file is never executed, but your tools still parse it, so keep them updated and work on an isolated machine when the sample may be hostile. Static work is slower, but it is repeatable, and most people do it first.
Dynamic analysis runs the program under observation. A debugger pauses it at breakpoints so you can read registers and memory. A tracer logs system calls, file access or network connections. A sandbox records what changes on disk. Dynamic work shows real behavior, including values that exist only at runtime, but it means executing code you may not trust. Do it in a disposable virtual machine with no real accounts, no shared folders and no sensitive data, and ideally with no network access.
Most real work combines the two. A static pass finds the function that checks a password. A debugger run then shows what the input and output really are.
A disassembler converts bytes into assembly instructions for a specific processor architecture. The translation is mechanical: a byte pattern becomes a mnemonic such as mov, cmp or jne, with its operands. The result matches the machine exactly, but it stays low-level. Reading thousands of instructions to understand one feature takes a long time.
A decompiler goes further. It tries to produce higher-level pseudocode, usually C-like, from the same bytes. It recovers function boundaries, control flow such as if and while, and rough data types. Its output is an interpretation, not a copy of the original source. It can be wrong about types, merge things that were separate, or invent a structure that never existed. A good habit is to read the decompiled code next to the disassembly and trust the assembly when the two disagree.
Ghidra is one of the most widely used free tools that does both. It is a software reverse engineering framework created and maintained by the Research Directorate of the U.S. National Security Agency, licensed under Apache-2.0, and it runs on Windows, macOS and Linux. It supports many processor architectures and file formats, and it can be extended with Java or Python. Its README asks for JDK 25 (64-bit) to run an official release. Other tools exist, commercial and open, and the ideas in this article apply to all of them.
Reverse engineering has several legitimate uses.
Interoperability. Your software needs to exchange data with a closed program or format, and the vendor does not publish the details. The European Union addresses this case directly, as the legal section explains.
Vulnerability research. You examine software you are authorized to test, find flaws and report them so the vendor can fix them. This usually follows coordinated disclosure: a private report, a fix window, then public details.
Malware analysis. Defenders take a suspicious file apart to learn what it does, which systems it touches and how to detect it. Much of the public knowledge about threats comes from this work.
Learning. Reading a compiled program is one of the best ways to understand how computers run code: memory layout, calling conventions, and the gap between what you write and what executes.
Checking claims. You may want to verify what a program sends over the network or stores locally, especially when privacy is at stake. This touches the same legal questions as any other analysis.
This is an overview, not legal advice. Laws differ by country, and contracts can add conditions that apply everywhere. Three layers matter most.
1. Licence terms. Most commercial software licences forbid reverse engineering except where the law permits it. Open-source licences work differently and often allow reading and modifying code, but they still have conditions. Reading source you were given is not the same as reverse engineering a binary, and neither is the same as breaking into a service. Before you analyze a product you did not write, read its licence.
2. European Union. Directive 2009/24/EC on the legal protection of computer programs includes an interoperability exception in Article 6. In summary, a lawful user, or someone acting for them, may decompile a program when it is indispensable to obtain the information needed to make an independently created program work with others. Conditions apply. The information must not already be readily available, the work must be limited to the parts of the program needed for interoperability, and the information must not be used for other purposes or shared beyond what interoperability requires. It must not be used to develop a substantially similar program, and the exception cannot unreasonably harm the rights holder's legitimate interests. Article 6 is about interoperability, not general curiosity.
3. United States. Section 1201 of the Digital Millennium Copyright Act prohibits circumventing technological measures that control access to copyrighted works. That prohibition is separate from copyright infringement, so a person can break the rule without copying anything. The statute includes exemptions, among them section 1201(j), which covers certain security testing under stated conditions. The Copyright Office also runs a rulemaking every three years that can add temporary exemptions. Its section 1201 page, linked in the sources, describes the process. At the time of checking, the tenth rulemaking had started, and exemptions renewed in it stay in force for three years, from October 2027 through October 2030. Read the statute and the current regulations before relying on any exemption. The text controls, not a summary.
Other countries have their own rules. Treat the three layers above as examples of how the questions are framed, not as a map of your jurisdiction.
Three practical points follow. Analyze software you own, your own builds, or material you have permission to test. Keep test work inside systems you control or have written authorization for. If a project carries legal risk, ask a lawyer before you start, not after you publish.
The safest way to learn is on your own code. Write a small program, compile it, and analyze the result. You know the source, so you can check every guess against the truth. You also avoid the legal questions that come with third-party software and the risk of running an unknown sample.
Two rules keep the practice clean. Never run unknown binaries on your everyday machine. Never point analysis tools or scans at systems you do not own or have written permission to test, including sites that look harmless. The next article, a first Ghidra analysis with a binary you compile, walks through a first project on a program you build yourself.
Learn the processor you are studying, usually x86-64 or ARM64 on modern desktops, and how the operating system passes arguments to functions. Compile the same small C file twice with a C compiler you trust, once with debug information and once stripped, and compare what remains. Read the Ghidra documentation and the project README. Then continue with the hands-on article that follows.
Reverse engineering
Compile a toy C licence check two ways, then use Ghidra to find its string, follow references and compare the decompiler with your source.