OSED cheat sheet
OSED · Keep the key ideas within reach.
Reverse Engineering Binary Applications to Locate Vulnerabilities
Static analysis workflow: identify file type (file), check mitigations (checksec), list symbols/imports (nm, objdump -T, readelf -s), then disassemble/decompile (objdump -d, Ghidra, IDA).
Dangerous C functions to grep for in disassembly/strings: strcpy, strcat, sprintf, gets, scanf with %s, memcpy with user-controlled length, and any loop copying bytes without bounds check.
Stack frame layout rule: local buffers are at negative offsets from EBP/RBP; saved return address is at [EBP+4] (x86) or [RBP+8] (x64). Overflow distance = buffer offset to saved EIP/RIP.
Pattern to spot format string bugs: a printf-family call where the format argument is a register/stack value derived from user input rather than a .rodata string pointer.
Use cross-references (xrefs) in the decompiler to trace user input from recv/read/fgets to the vulnerable copy; note the exact byte count and destination size.
For indirect calls and virtual dispatch, resolve the vtable/function pointer table in .data.rel.ro or .rdata to map attacker-influenced control flow.
Dynamic confirmation: run under gdb with a cyclic pattern (e.g., pattern_create) and inspect EIP/RIP after crash to compute exact offset; verify with pattern_offset.
Compiler artifacts: stack canary is loaded from fs:0x28 (x64) or gs:0x14 (x86) at function prologue; its presence indicates __stack_chk_fail and a canary check before return.
Stack Buffer Overflow Exploitation
Offset formula: distance from start of overflowed buffer to saved return address = (saved EBP/RBP - buffer address) + pointer size (4 on x86, 8 on x64).
Classic x86 payload: [padding to EIP][address of shellcode][NOP sled][shellcode]; place shellcode after the return address to avoid clobbering it.
x64 calling convention: first six integer args in RDI, RSI, RDX, RCX, R8, R9; a ret2libc/ret2win chain must set these via gadgets before the call.
Stack alignment: x64 System V ABI requires RSP % 16 == 0 at the call instruction; add a single ret gadget before a function call if a movaps crash occurs.
ret2win: overwrite saved RIP with the address of an existing function (e.g., win/flag printer); no shellcode needed, bypasses NX.
ret2libc: chain = [pop rdi; ret][binsh_addr][system_addr] on x64; on x86 = [system_addr][fake_ret][binsh_addr]. Resolve libc base via leaked address minus known symbol offset.
Off-by-one/null-byte overflows can corrupt saved RBP low byte, enabling stack pivot via leave; ret when the saved frame pointer is attacker-controlled.
When the buffer is not adjacent to the return address (e.g., function pointers or adjacent structs), target the nearest control-data (function pointer, vtable) instead of EIP.
Custom Shellcode Development
Position-independent shellcode uses call/pop to obtain its own address: call $+5; pop esi (x86) or pop rsi (x64) to reference embedded strings.
Avoid null bytes: use xor eax,eax then mov al,imm; use push/pop sequences; test with msfvenom -b '\x00' or a custom encoder.
Linux x86 execve('/bin/sh',0,0): xor eax,eax; push eax; push 0x68732f2f; push 0x6e69622f; mov ebx,esp; xor ecx,ecx; xor edx,edx; mov al,11; int 0x80.
Linux x64 execve: xor rsi,rsi; push rsi; mov rbx,0x68732f2f6e69622f; push rbx; mov rdi,rsp; xor rdx,rdx; push 59; pop rax; syscall.
Windows x86 shellcode resolves API addresses via PEB->Ldr->InMemoryOrderModuleList, then walks the export table (IMAGE_EXPORT_DIRECTORY) to find functions by hash or name.
Staged shellcode: a small first stage reads a larger second stage into RWX memory (VirtualAlloc/mmap) and jumps to it; keeps initial payload under size limits.
Alphanumeric/ASCII-only encoders restrict bytes to [0-9A-Za-z]; use them when input filtering rejects non-alphanumeric bytes.
Test shellcode in isolation with a C harness that mmaps RWX memory, copies the bytes, and calls the buffer; verify no crashes before embedding in exploit.
Bypassing Modern Mitigations (DEP and ASLR)
DEP/NX marks stack and heap non-executable; bypass by ROP/JOP (reuse existing code), ret2libc, or mprotect/VirtualProtect to re-enable execute permission on a buffer.
ASLR randomizes base addresses of stack, heap, libraries, and PIE binaries; non-PIE main executable base is fixed, so its gadgets are usable without a leak.
Information leak primitives (format string %p/%s, uninitialized memory, out-of-bounds read) defeat ASLR by disclosing a libc or stack pointer; subtract known offset to get base.
Partial overwrite: overwrite only the low 1-2 bytes of a saved return address to redirect within the same 4KB/64KB region, bypassing ASLR without a leak.
ret2plt/ret2dlresolve: call functions via PLT stubs or resolve symbols at runtime, avoiding the need for a libc base leak.
Stack canary bypass: leak the canary via format string or OOB read, then include it in the payload; or overwrite a function pointer/return address not protected by the canary.
PIE binaries require a code leak to compute gadget addresses; if PIE is enabled but no leak exists, target non-PIE shared libraries or use partial overwrites.
Full RELRO makes GOT read-only; partial RELRO leaves .got.plt writable, enabling GOT overwrite as a control-flow hijack without stack overflow.
Format String Vulnerability Exploitation and Read Primitive Development
Format specifier %n writes the number of bytes printed so far to the address pointed to by the corresponding argument; %hn writes 2 bytes, %hhn writes 1 byte.
On x86, printf arguments beyond the format string are read from the stack; the Nth %p prints the Nth 4-byte stack slot after the format pointer.
Direct parameter access %N$p reads the Nth argument; %N$s dereferences it as a char* and prints the string at that address - a read primitive.
To place an address on the stack for %n, embed it in the format string itself; if the buffer is on the stack, the address occupies stack slots and can be referenced by index.
Write primitive: split target value into two 2-byte writes with %hn to avoid huge output; order writes by increasing value and account for bytes already printed.
Read primitive development: use %s with a pointer to a known address (e.g., GOT entry) to leak a libc address; compute libc base = leaked - symbol offset.
Precision: use %c or %d to control bytes printed before %n; for large widths, use %hn to limit output to 65535 bytes per write.
Finding your offset: send a pattern like AAAA%p.%p.%p... and locate 0x41414141 in the output; the index at which it appears is the stack argument position of your buffer.