0x01: The Assembler’s Purpose
The Assembler is a translator. It takes the human-readable text file (.s) and converts it into a binary-encoded Object File (.o).
An Object File is Partial Intel. It contains:
- Binary Instructions: The raw opcodes for the CPU.
- Data Sections: Constants, strings, and global variables.
- Metadata: Information about “Symbols” (function names) that are defined inside the file or needed from the outside.
Think of an Object File as a single LEGO brick. It has the right shape, but it isn’t a “car” until it’s connected to wheels, an engine, and a chassis.
| Section | Content Type | Permissions |
.text | Executable Code | Read + Execute (No Write) |
.data | Initialized Variables | Read + Write |
.rodata | Read-Only Data (Strings) | Read Only |
.bss | Uninitialized Data | Read + Write |
Symbols (.global _start): These are labels that act as “GPS Coordinates.” By marking a symbol as .global, you are telling the Linker: “Hey, other files are allowed to see and jump to this location.”
0x03: The Linker (The Great Unifier)
The Linker (ld) is the final architect. It takes multiple object files and merges them. Its job is to Resolve Symbols.
If your code calls a function like printf, your object file doesn’t actually know where printf is. It just has a note saying: “I need a symbol called printf.” The Linker finds the libc.so library, finds the printf code, and “links” the two together so the CPU knows where to jump.
The C Runtime (CRT): Your code doesn’t start at main(). There is hidden code called the C Runtime (crt1.o, crti.o, crtn.o) that runs first. It cleans up the environment, sets up the stack, and then calls your code. Without the CRT, your program is a “naked” binary that only understands raw System Calls.
0x04: Interactive Lab (The Linking Logic)
In this lab, you must determine which components are missing from a “Broken” link command. If you call a library function, you must include the library; otherwise, the link fails.
print_payload.oMISSING SYMBOL:
undefined reference to 'printf'
MISSION: Complete the linker command. You must link the standard C library (-lc or libc.so) to resolve the printf symbol.
0x05: The “Architect’s” Insight
When you are reverse engineering, you will often find binaries that are Statically Linked vs Dynamically Linked:
- Statically Linked: The Linker copied all the library code (like
printf) directly into the final binary. These files are huge, but they run anywhere. - Dynamically Linked: The binary only contains a “pointer” to the library. When the program runs, the OS finds the library on the disk and loads it. These files are small and professional.
As a Reverse Engineer, you prefer Dynamically Linked files because you can easily spot where your code ends and the system libraries begin.
0x06: Mission Task
Current Objective: Run the following command on any binary on your system:
ldd /bin/ls.Challenge: Look at the list of libraries. This is exactly what the Linker did during the build phase—it created a dependency list. Can you find
libc.so.6in that list? That is the library that provides the functions we discussed today.
0x07: The Anatomy of an Assembly Template
Every assembly program is split into a “Logic” area and a “Data” area. Think of it like a chef (the CPU) and a pantry (the Memory).
1. The Directives (The Setup)
.arch: This tells the assembler exactly which instruction set to use. If you are building for a modern 64-bit phone, you usearmv8-a. For an older IoT device, you might usearmv7-a..text: This is a signal to the OS: “Everything following this line is executable code.” The OS will load this into memory with Read and Execute permissions but will block anyone from Writing to it (to prevent self-modifying malware)..global main/_start: This exports the name to the Linker. Without this, the Linker is “blind”—it wouldn’t know where the first instruction of your program is.
2. The Execution Flow (The Logic)
In the templates print64.s and print32.s, the code follows a standard “Prepare and Call” pattern:
- Loading the Address (
ldr x0, =MYSTRING): We don’t move the actual “Hello World” string into the register (it’s too big). Instead, we move the Memory Address (the pointer) where the string starts. - Branch and Link (
bl printf): This jumps to theprintffunction inside the C library. The “Link” part means the CPU saves its current spot so it knows how to return once the printing is done. - The Exit Protocol: Every program must “die” gracefully. If it doesn’t call
exit, the CPU will keep trying to execute whatever random data is next in memory, causing a Segmentation Fault.
3. The Literal Pool (.section .rodata)
Notice that MYSTRING is not in the .text section. It is in .rodata (Read-Only Data).
.balign 8: This is a “Tactical Alignment.” CPUs are faster when they read data starting at multiples of 8. If your string starts at a weird address like0x401003, the CPU has to work twice as hard..asciz: This adds a “Null Terminator” (\0) to the end of your string. Functions likeprintfkeep reading memory until they hit a00. If you forget this, the program will print “Hello World” followed by random garbage from your RAM.
0x08: Interactive Lab (Section Identifer)
In this mission, you must determine which section a specific piece of “Intel” belongs to based on how it will be used by the system.
"Access Denied: Administrative Privileges Required\n"
MISSION: To which ELF section should this string be assigned to ensure it is protected from being overwritten during execution?
0x09: Mission Task
Current Objective: Compare the 32-bit and 64-bit templates. Note the register naming:
x0(64-bit) vsr0(32-bit).Challenge: Search for the “ARM Calling Convention.” Why does the address of the string go into the first register (
x0/r0) and not a different one? Understanding where arguments go is the key to identifying function calls when you are reverse engineering a stripped binary.
// ---------------------------------------------------------------------------
// OCSALY ACADEMY - TACTICAL ASSEMBLY SERIES
// TOPIC: DIRECT KERNEL INTERFACING (SYSCALLS)
// ---------------------------------------------------------------------------
.arch armv8-a
.section .text
.global _start // The true entry point (no C runtime needed)
_start:
// --- STEP 1: WRITE TO STDOUT ---
// Syscall: write(int fd, const void *buf, size_t count)
mov x0, #1 // Register X0: File Descriptor (1 = Standard Output)
ldr x1, =msg // Register X1: Pointer to our string
mov x2, #19 // Register X2: Length of the string
mov x8, #64 // Register X8: Syscall Number for 'write' in ARM64
svc #0 // Supervisor Call: Jump to Kernel space
// --- STEP 2: GRACEFUL TERMINATION ---
// Syscall: exit(int status)
mov x0, #0 // Register X0: Exit status (0 = Success)
mov x8, #93 // Register X8: Syscall Number for 'exit' in ARM64
svc #0 // Supervisor Call: Kernel takes over and kills process
.section .rodata
.balign 8
msg:
.asciz "OCSALY_SYS_ACTIVE\n"0x0A: Breaking it down for Students
When you share this with your students, explain these three “Laws of Assembly”:
1. The Register Roleplay
In ARM64, registers X0 through X7 are used to pass arguments to functions.
- X0 is always the first argument.
- X8 is special: it’s the “Command Register.” You put the Syscall number here so the Kernel knows what you want to do.
2. The svc #0 (The Handshake)
The svc (Supervisor Call) instruction is the most important moment in a program’s life. It tells the CPU: “Stop running this user code and jump into the Kernel’s brain.” The Kernel looks at X8, sees the number 64, and says: “Okay, I will write data to the screen for you.”
3. No Library Dependencies
Unlike printf, which requires a large library (libc.so), this code is Standalone. If you compile this, the resulting binary will be tiny because it contains nothing but raw instructions. This is how high-level “Shellcode” is written.
0x0B: Interactive Mission (The Register Matcher)
This lab tests if the student can identify which register holds the “Mission Command.”
mov x8, #93
svc #0
MISSION: In the code above, which register is used to tell the Kernel to execute an ‘exit’ syscall?
0x0C: Mission Task for Students
Current Objective: Look at the
write64.scode.Challenge: If you wanted to change the output to print to Standard Error (stderr) instead of Standard Output (stdout), which register would you change, and what value would you give it? (Hint: The file descriptor for stderr is 2).
