# The Stack: Memory's Last-In-First-Out Champion Imagine a stack of papers on your desk - when you add a new document, it goes on top, and when you need to grab one, you take it from the top. You can't pull a page from the middle without moving everything above it. This is precisely how the stack works in computer memory! The stack is one of the most fundamental concepts in program execution, and understanding it is crucial for malware analysis. Each piece of data gets "pushed" onto the top of the stack and can only be "popped" off in reverse order - what programmers call Last-In-First-Out (LIFO). ## What Is The Stack? The stack is a special region of memory that follows a Last-In-First-Out (LIFO) principle. Like our stack of paper analogy - you can only add or remove papers from the top. In memory terms: - PUSH adds data to the top of the stack - POP removes data from the top of the stack >[!note] >Unlike a physical stack of papers, the computer's stack actually grows downward in memory. This means that pushing data onto the stack decreases the stack pointer's address! For example, if RSP is at address 0x7FFFFFFFE000 and you push 8 bytes, RSP will move to 0x7FFFFFFFDFF8. ### Stack Size and Limits The stack isn't infinite - it has a maximum size set by the operating system: - Windows typically allocates 1MB for the stack by default. - Linux often uses 8MB or more. - Stack size can be adjusted at compile time or runtime. >[!warning] >When the stack grows too large (stack overflow) or when it collides with the heap (stack clash), you'll get a crash. Malware sometimes exploits these limitations! We will cover these concepts later in our exploit research section. ## Stack Operations in Detail As covered in [[CPU Registers Building Blocks]], we have different register sizes depending on our architecture. This affects how our stack operations work, too. Let's look at the differences between x86 (32-bit) and x64 (64-bit) stack operations: ### x64 (64-bit) Stack Operations ```nasm ; Basic stack operations (8-byte operations) push rax ; Decrements RSP by 8, then stores RAX push 42 ; Decrements RSP by 8, then stores immediate value 42 pop rbx ; Loads value at RSP into RBX, then increments RSP by 8 ; Manual stack manipulation mov [rsp], rax ; Directly store RAX at stack top mov rbx, [rsp] ; Load value from stack top into RBX add rsp, 8 ; Manually adjust stack pointer (like a pop without saving the value) sub rsp, 8 ; Manually make space on stack (like a push without storing a value) ; Stack alignment (crucial for stability) and rsp, -16 ; Align stack to 16-byte boundary (required by x64 ABI) ``` ### x86 (32-bit) Stack Operations ```nasm ; Basic stack operations (4-byte operations) push eax ; Decrements ESP by 4, then stores EAX push 42 ; Decrements ESP by 4, then stores immediate value 42 pop ebx ; Loads value at ESP into EBX, then increments ESP by 4 ; Manual stack manipulation mov [esp], eax ; Directly store EAX at stack top mov ebx, [esp] ; Load value from stack top into EBX add esp, 4 ; Manually adjust stack pointer sub esp, 4 ; Manually make space on stack ; Stack alignment (less strict in x86) and esp, -8 ; Align stack to 8-byte boundary (common practice) ``` > [!note] Key differences between x86 and x64 > > - Stack operations in x64 work with 8-byte values (64 bits) > - Stack operations in x86 work with 4-byte values (32 bits) > - x64 requires 16-byte stack alignment for stability > - x64 uses RSP as stack pointer, x86 uses ESP > - Memory addresses in x64 are 8 bytes, in x86 they're 4 bytes See [[CPU Registers Building Blocks]] for a detailed explanation of the registers used here (RAX/EAX, RBX/EBX, RSP/ESP). ### Stack Frame Layout in Detail When a function is called, it creates a stack frame. Here's a detailed look at a typical x64 stack frame: <div style="font-family: monospace;"> <strong style="color: #6C83DB;">Higher addresses</strong><br> +------------------------+ ← <strong style="color: #7EA1F8;">Old RSP</strong> (before call)<br> | <strong style="color: #A9C5F2;">Parameter 7</strong> | ← <span style="color: #C8E5C8;">[RBP + 48]</span><br> | <strong style="color: #A9C5F2;">Parameter 6</strong> | ← <span style="color: #C8E5C8;">[RBP + 40]</span><br> | <strong style="color: #A9C5F2;">Parameter 5</strong> | ← <span style="color: #C8E5C8;">[RBP + 32]</span><br> | <strong style="color: #A8B4F3;">Return Address</strong> | ← <span style="color: #F2CF85;">[RBP + 8]</span> <span style="color: #DBB9F8;">What RIP should be when we return</span><br> +------------------------+ ← <strong style="color: #7EA1F8;">New RSP</strong> after call<br> | <strong style="color: #A8B4F3;">Saved RBP</strong> | ← <span style="color: #C8E5C8;">RBP</span><br> | <strong style="color: #F2CF85;">Local Variable 1</strong> | ← <span style="color: #D5A6A6;">[RBP - 8]</span><br> | <strong style="color: #F2CF85;">Local Variable 2</strong> | ← <span style="color: #D5A6A6;">[RBP - 16]</span><br> | <strong style="color: #DBB9F8;">Temporary Storage</strong> | ← <span style="color: #A9C5F2;">[RBP - 24]</span><br> +------------------------+ ← <strong style="color: #7EA1F8;">New RSP</strong> (after local allocation)<br> <strong style="color: #EA7268;">Lower addresses</strong><br> <br> <span style="color: #A8B4F3;">Note:</span> <span style="color: #C8E5C8;">Parameters 1-4 in x64 use registers (RCX, RDX, R8, R9)</span><br> <span style="color: #DBB9F8;">Additional parameters are pushed on the stack</span> </div> Now, let's compare this with the x86 stack frame: <div style="font-family: monospace;"> <strong style="color: #6C83DB;">Higher addresses</strong><br> +------------------------+ ← <strong style="color: #7EA1F8;">Old ESP</strong> (before call)<br> | <strong style="color: #A9C5F2;">Parameter 3</strong> | ← <span style="color: #C8E5C8;">[EBP + 12]</span><br> | <strong style="color: #A9C5F2;">Parameter 2</strong> | ← <span style="color: #C8E5C8;">[EBP + 8]</span><br> | <strong style="color: #A8B4F3;">Return Address</strong> | ← <span style="color: #F2CF85;">[EBP + 4]</span> <span style="color: #DBB9F8;">What EIP should be when we return</span><br> +------------------------+ ← <strong style="color: #7EA1F8;">New ESP</strong> after call<br> | <strong style="color: #A8B4F3;">Saved EBP</strong> | ← <span style="color: #C8E5C8;">EBP</span><br> | <strong style="color: #F2CF85;">Local Variable 1</strong> | ← <span style="color: #D5A6A6;">[EBP - 4]</span><br> | <strong style="color: #F2CF85;">Local Variable 2</strong> | ← <span style="color: #D5A6A6;">[EBP - 8]</span><br> | <strong style="color: #DBB9F8;">Temporary Storage</strong> | ← <span style="color: #A9C5F2;">[EBP - 12]</span><br> +------------------------+ ← <strong style="color: #7EA1F8;">New ESP</strong> (after local allocation)<br> <strong style="color: #EA7268;">Lower addresses</strong> </div> ## Stack Memory Protection Think of your stack like a house - you wouldn't want uninvited guests messing around inside, right? Modern systems feel the same way about their stack memory, so they've got some pretty clever security measures in place. ### Stack Canaries: The Digital Watchdogs Ever heard of a canary in a coal mine? Stack canaries work in a similar way! They're like little digital watchdogs that sit between your local variables and return addresses. If something dodgy happens to the stack, these canaries get disturbed - and just like their feathered namesakes, they'll let us know something's wrong before real damage occurs. ```nasm ; Typical canary implementation mov rax, gs:[28h] ; Get the canary value mov [rbp-8], rax ; Store it on the stack ... mov rdx, [rbp-8] ; Get stored canary xor rdx, gs:[28h] ; Compare with original jnz __stack_chk_fail ; Panic if they don't match! ``` ### DEP: The "No Code Here" Bouncer DEP is like that strict bouncer at a club who insists, "This area is for dancing, not for hanging out!" It makes sure that the stack is used for storing data only - no sneaky code execution allowed. This really throws a spanner in the works for malware authors who used to love executing code directly from the stack. > [!tip] When malware hits DEP, it often has to get creative - look for things like ROP (Return-Oriented Programming) chains or other fancy techniques to work around this protection. ### ASLR: The Memory Shell Game >[!note] While these protections make life harder for attackers, they're not perfect. Sophisticated malware often finds ways around them - but that's a story for our upcoming post on [[Advanced Stack Exploitation Techniques]]! Remember playing that cup-and-ball game where someone shuffles cups around really fast and you have to guess where the ball is? ASLR does something similar with memory addresses. Every time a program starts, it shuffles around where everything gets loaded - including the stack! This means malware can't just hardcode memory addresses; it needs to actually find things first. ```c // ASLR in action - same program, different runs Run 1: Stack starts at 0x7FFF5DE00000 Run 2: Stack starts at 0x7FFE4A200000 Run 3: Stack starts at 0x7FFCB8600000 ``` ## Advanced Stack Concepts ### Shadow Space Here's something quirky about x64 Windows - it's like a restaurant that insists on keeping four tables reserved at all times, even if no one's using them! This is called "shadow space", and it's a bit of a weird one. Every time you call a function in x64 Windows, you need to reserve 32 bytes (plus 8 more for alignment) on the stack, even if your function doesn't seem to need it. Why? Well, it's like giving your function a guaranteed workspace - if it suddenly needs to save those first four parameters (which usually live in registers), it knows exactly where to put them. ```nasm ; Windows x64 function call with its parking spots sub rsp, 28h ; Hey, save me 32 bytes + 8 for alignment! call someFunction ; Off we go to the function add rsp, 28h ; Clean up after ourselves ``` ### Stack Unwinding Think of stack unwinding like cleaning up after an epic game of Jenga - when something goes wrong (an exception occurs), we need to know exactly how to take everything apart without making a mess. This is especially important in modern programs that use exceptions for error handling. ```nasm ; The cleanup instruction manual .pdata: ; Here's our cleanup guide .long @feat.00 ; Which function are we cleaning up? .rva .Lstart ; Where does the mess start? .rva .Lend ; Where does it end? .rva .Lunwind ; How do we clean it up? ``` Here's how it works in the real world: 1. Something goes wrong in your program (an exception) 2. The program looks up its "cleanup guide" (unwind information) 3. It carefully undoes each stack frame, making sure to: - Call any cleanup code (like destructors) - Release any locks - Generally tidy up after itself > [!note] Malware analysts love looking at unwind information because it can reveal function boundaries and exception handlers - super helpful when you're trying to figure out what a suspicious program is up to! This might all sound a bit complex (and it is!), but it's what makes modern C++ exception handling possible. Without good stack unwinding, exceptions would be about as graceful as flipping the table when you lose at cards! ## Stack in Malware Analysis Let's dive into some of the sneaky tricks malware authors use with the stack. Think of it like being a detective - once you know what clues to look for, these tricks start to stick out like a sore thumb! ### Common Malicious Patterns #### String Hide & Seek > [!tip] When you see lots of **PUSH** instructions with weird hexadecimal values, try converting them to ASCII - you might just find a hidden message! Remember, because of little-endian byte order, the strings often appear backwards in the code. Malware authors love playing hide and seek with their strings. Instead of storing text normally (which would be easy to spot), they often build their strings on the stack piece by piece. ```nasm ; Building "cmd.exe" on the stack, but backwards! push 0x00657865 ; 'exe\0' push 0x2E646D63 ; 'cmd.' mov rcx, rsp ; Point to our hidden "cmd.exe" call CreateProcessA ; Launch the command prompt ``` #### Stack Gymnastics: The Art of Stack Pivoting >[!warning] If you see code messing around with the stack pointer (RSP/ESP) in unusual ways, especially switching it to point somewhere else, that's a massive red flag. Legitimate programs rarely need to do stack gymnastics! Sometimes, malware needs to get really creative with the stack, especially when it's trying to run shellcode. It's like a gymnast doing a handstand - suddenly everything's upside down! ```nasm ; Switching to a new controlled stack location xchg rsp, rax ; Stack pointer shuffle! pop rbp ; Start our fancy new execution chain ``` #### Cookie Monster: Messing with Stack Protection Remember those stack canaries we talked about earlier? Some malware tries to outsmart them: ```nasm ; Attempting to bypass stack protection mov rax, gs:[60h] ; Sneakily grab the PEB mov rax, [rax+0BCh] ; Find the cookie value xor [rbp-8], rax ; Try to manipulate it ``` This is like trying to replace the guard dog with a stuffed toy - clever, but definitely suspicious! ##### Red Flags: What Should Make Your Spidey-Sense Tingle >[!note] Remember, seeing one of these patterns doesn't automatically mean you're looking at malware - plenty of legitimate programs do unusual things with the stack. But when you start seeing multiple red flags together, that's when you should really start digging deeper! When you're analysing a suspicious program, keep an eye out for: **Suspicious Stack Sizes** - Tiny stack frames (might be shellcode) - Massive stack allocations (potential buffer overflow attempt) ```nasm ; Suspiciously large stack allocation sub rsp, 0F000h ; Whoa there, that's a big chunk of stack! ``` **Stack Pointer Shenanigans** - Multiple stack pointer changes - Weird arithmetic on RSP/ESP ```nasm ; Strange stack pointer manipulation lea rsp, [rsp+rax*8+0x100] ; Why so complicated? 🤔 ``` **Messy Stack Cleanup** - Unbalanced push/pop operations - Missing function epilogues ```nasm push rax push rbx ; ... lots of code ... ret ; Hey, where's our stack cleanup? 🚩 ``` ### A Real-World Example Here's a pattern I spotted recently in a nasty piece of ransomware: ```nasm ; Building an API name on the stack push 0x00737365 ; 'ess\0' push 0x636F7250 ; 'Proc' push 0x74697845 ; 'Exit' mov rcx, rsp ; RCX points to "ExitProcess" call resolve_api ; Try to find the API address ``` The malware was building API names on the stack to hide them from static analysis. Sneaky, but once you know what to look for, it's pretty obvious what's going on! ## Debugging Stack Issues Common debugging commands for stack analysis: ```nasm WinDbg: !teb ; Show Thread Environment Block (includes stack limits) dps rsp ; Display stack contents k ; Show call stack GDB: info stack ; Show stack information x/32xw $sp ; Examine stack contents bt ; Backtrace (show call stack) ``` ## Going Further To deepen your understanding, check out: - [[CPU Registers Building Blocks]] for how registers interact with the stack - [[Register Operations in Assembly]] for detailed register manipulation. - [[Windows API Basics]] for how the stack interacts with API calls - [[PE File Format Foundations]] for understanding program structure - [[Advanced Register Techniques]] for complex stack-register interactions Remember that the stack is fundamental to program execution. Understanding stack operations is crucial for success, whether you're analysing malware or debugging legitimate software.