Disassembly your first program like a pro!
Published: September 19, 2025 | Tags: #c #assembly #reverse engineering
It is indeed really simple, at least on the surface, to imagine what would happen with a C program after compilation. In fact, if you cannot imagine the outcome, you either need to study more, or stop vibe-coding. If we use printf("Hello, World!\n");
, it is logic to guess that the string literal will be printed in the terminal.
But, is it that simple the other way around? Could we extract the code, the instructions of the program, and remake it out of a binary? Completely. After all, even if more complex, computers follow logic - what seems like chaotic behavior is simply a really intricate logic.
Today we are going to explore how to extract all this from the machine code.
But before that, some notes.
I am assuming that you understand the process of compilation. Also, what we are going to do is called disassembly and not decompilation, even if we start with a C program. Why? Because our end-point will not be recreating the C program, but rebuilding from the assembly instructions. A decompilation is possible, but not what we are going to focus on today.
Also, if you have worked with assembly, you know that, if you try this yourself in your computer, the process and result can be different. This happens because “assembly” is not a unique language, but there are several designs. This is known as ISA (Instruction Set Architecture). Common computer ISAs are: x86-64, ARM, MIPS, RISC-V, … Your computer usually supports one, or more, but these are usually related (a x86 CPU can run both 32-bit x86 and 64-bit x86-64 instructions natively).
In our current example we will work with x86-64. Your computer might use something different (surely ARM if so).
For this example, we will not need specific knowledge about the ISA, that’s not the objective, but it is recommended to have a general idea of what assembly actually is.
Our Example.
Maybe the simplest practical program. The 101 of CS.
If you ask yourself why we use puts()
instead of printf()
, it’s because of simplicity. Actually, this would not be necessary, as your compiler surely would call puts()
by default if the parameter is a string literal (with no formatting).
Even with that assumption, I prefer to express it explicitly.
#include <stdio.h>
int main(void)
{
puts("Hello, World!");
return 0;
}
The tools!
For compilation we will use gcc
. This does not require an introduction (I hope).
gcc -O0 -o hello hello.c
For disassembly we will use objdump
, which is a command-line program on Linux systems that will display information from binary files.
objdump -d hello
# Do not forget to check your man pages if needed.
man objdump
Note: This will give you the code in AT&T syntax, use
objdump -d -M intel hello
for Intel syntax.
What does the output even mean?
Okay, you have surely been welcomed with tons of lines. You can use less or redirect the output to a file for simplicity.
But, what does each part even mean?
We have to focus on the main event:
0000000000001139 <main>:
1139: 55 push %rbp
113a: 48 89 e5 mov %rsp,%rbp
113d: 48 8d 05 c0 0e 00 00 lea 0xec0(%rip),%rax # 2004 <_IO_stdin_used+0x4>
1144: 48 89 c7 mov %rax,%rdi
1147: e8 e4 fe ff ff call 1030 <puts@plt>
114c: b8 00 00 00 00 mov $0x0,%eax
1151: 5d pop %rbp
1152: c3 ret
What you are looking at is direct and machine-readable translation of the C code we wrote earlier.
We are not going to explain the specific assembly code. That will be done in a different article, but what matters over here is that out of a simple binary, an actual code. You can even see how it calls puts()
.
We could literally take this code, adding a few more lines for the different sections, and it would compile.
Check it here:
# hello.s (AT&T syntax)
.globl main
.section .rodata
hello_string:
.string "Hello, World!"
.text
main:
pushq %rbp
movq %rsp, %rbp
leaq hello_string(%rip), %rax
movq %rax, %rdi
call puts@PLT
movl $0, %eax
popq %rbp
ret
Now you can test it with:
gcc -no-pie -o hello_asm hello.s
./hello_asm
# Output: Hello, World!
Congratulations! You have rebuilt a program from its binary.
Conclusion
While this example is really simple, it is actually a method that can be used professionally pretty often. A “Hello, World!” program does not require this, but if you wish to rebuild Mario Bros (do not do this, it is property of Nintendo and therefore I do not recommend it for legal reasons, if you do it, it is your fault), you could get the binary and remake it!
Might take time, but things like this can be really rewarding :D.
Also, if you want to learn more about x86 assembly, you could check this x86 Assembly Guide.