Back to Technology

x86 Assembly Series Part 18: Memory Protection & Security

February 6, 2026 Wasil Zafar 35 min read

Understand memory vulnerabilities and protections: buffer overflows, stack canaries, ASLR, DEP/NX, return-oriented programming (ROP), and secure coding practices in assembly language.

Table of Contents

  1. Memory Vulnerabilities
  2. Protection Mechanisms
  3. Return-Oriented Programming
  4. Secure Assembly Practices

Memory Vulnerabilities

Buffer Overflow

Classic Vulnerability: Writing beyond buffer bounds can overwrite adjacent memory, including return addresses, allowing attackers to hijack control flow.

x86 Assembly Mastery

Your 25-step learning path • Currently on Step 19
Development Environment, Tooling & Workflow
IDEs, debuggers, build tools, workflow setup
Assembly Language Fundamentals & Toolchain Setup
Syntax basics, assemblers, linkers, object files
x86 CPU Architecture Overview
Instruction pipeline, execution units, microarchitecture
Registers – Complete Deep Dive
GPRs, segment, control, flags, MSRs
Instruction Encoding & Binary Layout
Opcode bytes, ModR/M, SIB, prefixes, encoding schemes
NASM Syntax, Directives & Macros
Sections, labels, EQU, %macro, conditional assembly
Complete Assembler Comparison
NASM vs MASM vs GAS vs FASM, syntax differences
Memory Addressing Modes
Direct, indirect, indexed, base+displacement, RIP-relative
Stack Internals & Calling Conventions
Push/pop, stack frames, cdecl, System V ABI, fastcall
Control Flow & Procedures
Jumps, loops, conditionals, CALL/RET, function design
Integer, Bitwise & Arithmetic Operations
ADD, SUB, MUL, DIV, AND, OR, XOR, shifts, rotates
Floating Point & SIMD Foundations
x87 FPU, IEEE 754, SSE scalar, precision control
SIMD, Vectorization & Performance
SSE, AVX, AVX-512, data-parallel processing
System Calls, Interrupts & Privilege Transitions
INT, SYSCALL, IDT, ring transitions, exception handling
Debugging & Reverse Engineering
GDB, breakpoints, disassembly, binary analysis, IDA
Linking, Relocation & Loader Behavior
ELF/PE formats, symbol resolution, dynamic linking, GOT/PLT
x86-64 Long Mode & Advanced Features
64-bit extensions, RIP addressing, canonical addresses
Assembly + C/C++ Interoperability
Inline assembly, calling C from ASM, ABI compliance
19
Memory Protection & Security Concepts
DEP, ASLR, stack canaries, ROP, mitigations
You Are Here
20
Bootloaders & Bare-Metal Programming
BIOS/UEFI, MBR, real mode, protected mode transition
21
Kernel-Level Assembly
Context switching, interrupt handlers, TSS, GDT/LDT
22
Complete Emulator & Simulator Guide
QEMU, Bochs, instruction-level simulation, debugging VMs
23
Advanced Optimization & CPU Internals
Pipeline hazards, branch prediction, cache optimization, ILP
24
Real-World Assembly Projects
Shellcode, drivers, cryptography, signal processing
25
Assembly Mastery Capstone
Final project, comprehensive review, advanced techniques
; Vulnerable code pattern
section .bss
    buffer resb 64          ; 64-byte buffer

section .text
vulnerable_func:
    ; No bounds checking!
    lea rdi, [buffer]
    call gets               ; NEVER use gets()
    ret                     ; May jump to attacker-controlled address

Stack Smashing Attack

A stack smashing attack overwrites the return address to hijack control flow. Let's visualize exactly what happens:

Stack smashing attack diagram showing buffer overflow overwriting return address
Stack smashing attack: buffer overflow overwrites saved RBP and return address to hijack control flow
Stack Layout During Function Call:
                         High Memory
┌───────────────────┐
│ Return Address   │ ← Attacker's target! 
├───────────────────┤    Overwrite this to redirect execution
│ Saved RBP        │
├───────────────────┤
│ Local Buffer[63] │
│ ...              │  Buffer overflow direction ↑
│ Local Buffer[0]  │ ← Input starts here, overflows upward
└───────────────────┘
                         Low Memory (RSP)

Overflow Attack:
1. Attacker inputs 64+ bytes via gets()
2. Input overflows buffer, overwrites saved RBP
3. Keeps writing, overwrites return address
4. When function returns, RIP = attacker-controlled!
; Exploitable function
vulnerable:
    push rbp
    mov rbp, rsp
    sub rsp, 64             ; 64-byte buffer
    
    lea rdi, [rbp-64]       ; Buffer address
    call gets               ; Reads unlimited input!
    
    leave                   ; mov rsp, rbp; pop rbp
    ret                     ; pop rip - DANGER: may be overwritten!

; Attack payload (conceptual)
; [64 bytes padding][8 bytes fake RBP][8 bytes shellcode_addr]
Classic Attack Flow: Attacker sends 80 bytes: 64 bytes trash + 8 bytes fake RBP + 8 bytes pointing to shellcode. When ret executes, the CPU jumps to shellcode instead of the real caller.

Protection Mechanisms

Stack Canaries

; GCC -fstack-protector inserts canary
function_with_canary:
    push rbp
    mov rbp, rsp
    sub rsp, 80
    
    ; Canary placed between buffer and saved RBP
    mov rax, fs:[0x28]      ; Load canary from TLS
    mov [rbp-8], rax        ; Store on stack
    
    ; ... function body ...
    
    ; Check canary before return
    mov rax, [rbp-8]
    xor rax, fs:[0x28]
    jnz .stack_smash_detect ; Abort if modified
    
    leave
    ret
.stack_smash_detect:
    call __stack_chk_fail

Address Space Layout Randomization (ASLR)

ASLR randomizes where code and data are loaded in memory each time a program runs. This breaks exploits that hardcode addresses.

ASLR memory layout randomization showing different base addresses across program executions
ASLR in action: stack, heap, and library base addresses change with each program execution
# Observe ASLR in action
$ cat /proc/sys/kernel/randomize_va_space
2   # 0=off, 1=stack/mmap, 2=full (heap too)

# Run program twice, check stack address
$ ./test_aslr
Stack address: 0x7fff2a3b4c80
$ ./test_aslr
Stack address: 0x7fffde123a50    # Different each time!

# Check library base addresses
$ ldd /bin/ls | head -2
    linux-vdso.so.1 => (0x00007ffd12345000)    # Randomized
    libc.so.6 => /lib/x86_64.../libc.so.6 (0x00007f8abc123000)
ASLR LevelRandomized RegionsEntropy (bits)
Level 1Stack, shared libraries, mmap~28 bits
Level 2+ Heap (brk)~28 bits
PIE Binary+ Main executable~28 bits
Bypassing ASLR: Attackers use information leaks (format string bugs, buffer over-reads) to discover actual addresses at runtime, then calculate offsets to known functions.

Data Execution Prevention (DEP/NX)

DEP (Windows) / NX bit (Linux) marks memory pages as non-executable. Even if an attacker gets shellcode into memory, the CPU refuses to execute it.

DEP and NX bit page protection showing W^X policy with executable vs writable memory regions
DEP/NX bit enforcement: W^X policy ensures memory pages are either writable or executable, never both
Page Table Entry NX Bit:
┌─────────────────────────────────────────────┐
Bit 63: NX (No eXecute)
  0 = Page is executable (code)
  1 = Page is NOT executable (data/stack)
└─────────────────────────────────────────────┘

W^X (Write XOR Execute) Policy:
  - A page can be Writable OR Executable, never both
  - Stack: RW- (read, write, no execute)
  - Code:  R-X (read, execute, no write)
  - Heap:  RW- (read, write, no execute)
# Check memory protections on a process
$ cat /proc/$(pidof victim)/maps
00400000-00401000 r-xp  /victim        # Code: executable
00600000-00601000 rw-p  /victim        # Data: writable, not exec
7ffffffde000-7ffffffff000 rw-p [stack] # Stack: not executable

# Attempt to execute shellcode on NX stack
$ ./shellcode_test
Segmentation fault (core dumped)       # CPU refused!
Legacy Note: The NX bit requires CPU support (most CPUs since ~2004) and 64-bit mode (or PAE in 32-bit). Check with grep nx /proc/cpuinfo.

Return-Oriented Programming (ROP)

ROP bypasses NX by reusing existing executable code snippets called "gadgets." Instead of injecting shellcode, attackers chain together instruction sequences that end in ret.

ROP chain construction showing gadgets chained on the stack to build arbitrary computation
ROP chain: attacker-controlled stack chains gadgets (pop/ret sequences) to execute arbitrary syscalls

What is a Gadget?

A gadget is any sequence of instructions ending in RET:

; Example gadgets found in libc:
pop rdi; ret          ← Set RDI (first argument)
pop rsi; ret          ← Set RSI (second argument)
pop rdx; ret          ← Set RDX (third argument)
mov [rdi], rax; ret   ← Write memory
xchg eax, esp; ret    ← Stack pivot

Building a ROP Chain

Goal: Call execve("/bin/sh", NULL, NULL)

Stack Layout (attacker-controlled):
┌───────────────────────┐
│ pop rdi; ret         │  RSP → First gadget
├───────────────────────┤
│ addr of "/bin/sh"    │  ← Popped into RDI
├───────────────────────┤
│ pop rsi; ret         │  Next gadget
├───────────────────────┤
│ 0 (NULL)             │  ← Popped into RSI (argv)
├───────────────────────┤
│ pop rdx; ret         │  Next gadget
├───────────────────────┤
│ 0 (NULL)             │  ← Popped into RDX (envp)
├───────────────────────┤
│ syscall; ret         │  Execute syscall 59 (execve)
└───────────────────────┘

Execution Flow:
1. ret pops "pop rdi; ret" address into RIP
2. pop rdi loads "/bin/sh" address into RDI
3. ret pops next gadget address...
4. Eventually syscall executes execve("/bin/sh", NULL, NULL)
5. Attacker gets shell!

Finding Gadgets

# Use ROPgadget tool
$ ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 | grep "pop rdi"
0x000000000002155f : pop rdi ; ret
0x00000000000215a0 : pop rdi ; pop rbp ; ret

# Or use ropper
$ ropper -f /lib/x86_64-linux-gnu/libc.so.6 --search "pop rdi"
[INFO] Searching for gadgets: pop rdi
0x000000000002155f: pop rdi; ret;
Defense: Control Flow Integrity (CFI) and Shadow Stacks detect and prevent ROP by validating return addresses match legitimate call sites.

Secure Assembly Practices

Writing secure assembly requires constant vigilance. Here are essential practices to protect your code:

Secure assembly practices checklist with bounds checking, zeroing, constant-time comparison, and canaries
Secure assembly practices: bounds checking, sensitive data zeroing, constant-time comparisons, and stack canaries

1. Always Bounds Check

; SAFE: Bounds-checked copy
safe_copy:
    ; RDI = dest, RSI = src, RDX = max_len, RCX = dest_size
    cmp rdx, rcx
    jbe .size_ok
    mov rdx, rcx            ; Clamp to buffer size
.size_ok:
    ; Now safe to copy RDX bytes
    rep movsb
    ret

2. Zero Sensitive Data

; Clear password buffer before freeing
zero_sensitive:
    mov rdi, password_buffer
    mov rcx, PASSWORD_LEN
    xor eax, eax
    rep stosb               ; Fill with zeros
    
    ; Use memory barrier to prevent optimization away
    mfence
    ret

3. Constant-Time Comparisons

; Timing-safe comparison (for passwords, tokens)
; Prevents timing attacks that measure comparison duration
constant_time_compare:
    ; RDI = buf1, RSI = buf2, RDX = length
    ; Returns: RAX = 0 if equal, non-zero if different
    xor eax, eax            ; Result accumulator
    test rdx, rdx
    jz .done
.loop:
    mov cl, [rdi]
    xor cl, [rsi]           ; XOR bytes (0 if equal)
    or al, cl               ; Accumulate differences
    inc rdi
    inc rsi
    dec rdx
    jnz .loop
.done:
    ret                     ; AL=0 only if all bytes matched

4. Stack Canary Manual Implementation

; Manual canary for functions with buffers
section .data
    canary dq 0xDEADBEEFCAFEBABE  ; Better: random at startup

section .text
protected_func:
    push rbp
    mov rbp, rsp
    sub rsp, 80
    
    ; Place canary
    mov rax, [rel canary]
    mov [rbp-8], rax
    
    ; ... function body with buffer at [rbp-72] ...
    
    ; Verify canary
    mov rax, [rbp-8]
    cmp rax, [rel canary]
    jne .abort              ; Corruption detected!
    
    leave
    ret
    
.abort:
    mov rdi, 1
    mov rax, 60             ; exit(1)
    syscall

Security Checklist

PracticePreventsImplementation
Bounds checkingBuffer overflowCompare index/length before access
Zero after useMemory disclosurerep stosb + memory barrier
Constant-time opsTiming attacksXOR + OR accumulator pattern
Validate pointersNULL deref, UAFCheck against NULL, valid ranges
Minimize privilegesEscalationDrop caps after initialization
Stack canariesStack smashingCompile with -fstack-protector-strong
Exercise

Security Audit Challenge

Find the vulnerabilities in this code:

get_user_input:
    sub rsp, 64
    mov rdi, rsp
    call gets           ; Vulnerability 1: ?
    mov rax, rsp
    add rsp, 64
    ret                 ; Vulnerability 2: ?

Answers: 1) Unbounded input via gets(), 2) Returns pointer to stack memory (use-after-free when caller accesses it)