Assembly Crash Course
You do not need to write assembly to exploit a binary, but you have to read it cold.
What you will learn
- -Read the x86-64 instructions that show up in real disassembly
- -Trace a function prologue, call, and return by hand
- -Disassemble a binary with objdump and gdb and find main
The good news: you will never write a compiler. The bad news: when gdb drops you at a crash, it shows you , and you have to know what you are looking at. You need maybe twenty instructions and a feel for the . That is the whole ask.
x86-64 has sixteen general-purpose 64-bit registers. The ones you will stare at constantly are rax (return values and syscall numbers), rdi, rsi, rdx, rcx, r8, r9 (the first six function arguments, in that order), plus rsp, rbp, and rip from the last lesson. The lower halves have names too: eax is the bottom 32 bits of rax, al is the bottom 8. Same register, different slice.
The instructions that actually show up
Most disassembly is boring on purpose. mov copies data. lea computes an address without dereferencing it. add and sub do the obvious. push and pop move values on and off the . cmp subtracts two values just to set flags, then a jmp, je, or jne branches on the result. call jumps to a function and pushes the return address; ret pops that address back into rip.
Read this fragment out loud and you already understand it:
push rbp ; save caller's base pointer
mov rbp, rsp ; set up this function's frame
sub rsp, 0x40 ; reserve 64 bytes for locals
lea rdi, [rbp-0x40] ; rdi = address of local buffer
call gets ; gets(buf), no bounds check, game over
Those first three lines are the function prologue, the same setup nearly every function does. That call gets with a stack buffer as the only argument is a textbook overflow waiting to happen.
AT&T versus Intel, pick one and commit
You will see the same code two ways. objdump defaults to AT&T syntax, where the destination is on the right and registers wear a %: mov %rsp, %rbp. Intel syntax puts the destination on the left and reads cleaner: mov rbp, rsp. Most writeups use Intel. Set it once and stop fighting it.
$objdump -d -M intel ./vuln | less$gdb -q ./vuln -ex 'set disassembly-flavor intel' -ex 'disassemble main'Break at a function and step one instruction at a time in gdb with si (step into) and ni (step over). Watch the registers change with info registers after each step. Doing this by hand on a small function for ten minutes teaches you more than any diagram.
Disassembly and debugging are . Do it to code you wrote, open-source binaries, or CTF challenges you are cleared to solve. Reversing shipped commercial software can violate its license, and touching another party's process needs written .
Calling convention is the cheat sheet
When you set up an exploit that calls a function, you are just filling registers the way the compiler would. Want to call system("/bin/sh") on x86-64 Linux? Put the address of the string in rdi, then jump to system. First argument goes in rdi, second in rsi, third in rdx, and so on down the list. Memorize rdi, rsi, rdx, rcx, r8, r9. You will use it in every ROP chain you ever build.
On x86-64 Linux (System V ABI), which register holds the first integer or pointer argument to a function call?
Return values come back in rax. Arguments go in a fixed order starting with the register you want here, then rsi, rdx, rcx, r8, r9.