x64 ASM Fundamentals 0x03 – The Stack (Push it Good!)
This post will cover another fundamental building block of the ASM language (almost all flavours of ASM, too) – the stack. At the end of this post, you’ll be in a position to understand even more reverse engineered applications.
What’s the purpose of the stack?
When reading the previous posts, you might have wondered to yourself “There are only a finite number of registers (17, in fact), what happens if my program needs more than 17 variables at any one time? Also, oh man.. This blog is so awesome!”
Your points are VERY valid. The stack addresses this issue rather neatly. The stack is part of a process’s allocated memory space which serves as a general purpose data storage area.
Imagine your process’s memory looks like this –
The Stack 🔻
🔺 The Heap
.bss Section (Initialised Data)
.data Section (Initialised Data)
.text section (AKA your program code!)
(no need imagine, this is EXACTLY how your process’s memory is laid out 😊) “The Stack” is an area of memory reserved for short term storage of variables. It grows from high memory downwards towards lower memory areas. It grows into the area of unallocated memory in the middle of the table. Similarly, The Heap starts in lower areas of memory and grows upwards towards higher memory. It is possible for your heap and your stack to grow into each other (although this is exceedingly rare). The heap is out of scope for now, we’ll cover it one day. maybe.
Why is it called the stack?
Why’s it called the stack then, if it grows downwards towards lower memory? It is called a stack because it is Last In First Out, like a stack of plates. Imagine if you pushed a plate on top of another plate, and then pushed another on top of that one. If you wanted to get to the bottom plate, you’d have to pop the other two plates off of the top of the stack of plates first.
The stack in ELF programs (and almost every other architecture) works identically, you are able to push a piece of data onto the stack with the PUSH instruction and then you are able to pop a piece of data off of the stack with the POP instruction.
PUSHing adds data to the stack and POPping removes it again.
Imagine a hypothetical situation where we have all of our registers chock full of important data and we need to temporarily put a different piece of data into a register in order to perform a particular operation. We would –
PUSH the register’s value onto the stack
Perform which ever operation we need to do (file IO, network stuff, whatever)
POP the original value off of the stack back into the register
The registers are all back to where they were before the operation even happened.
In the above hypothetical situation, if we had two variables which we wanted to persist throughout a long operation, we’d PUSH both of them and then POP them in reverse after the operation.
push 0x40 ; TOP of the stack is now 0x00000040, for demo purposes
mov RAX, 0x41 ; put 65 into RAX
mov RBX, 0x42 ; put 66 into RBX
push RAX ; TOP of the stack is now 0x00000041
push RBX ; TOP of the stack is now 0x00000042
mov RAX, 0x43 ; put 67 into RAX
mov RBX, 0x44 ; put 68 into RBX
; perform some long running operation which requires RAX and RBX
; the stack looks as follows right now -
; | 0x00000042 | < TOP of the stack (lower memory!)
; | 0x00000041 |
; | 0x00000040 | < BOTTOM of the stack (higher memory!)
pop RBX ; RBX is now 66 again, top of the stack is now 0x00000041
pop RAX ; RAX is now 65 again, top of the stack is now 0x00000040
pop RCX ; RCX is now 64, top of the stack is unknown and irrelevant
So we PUSHed RAX then RBX, and later we POPped RBX then RAX. This is important, it’s just like a stack of plates.
The RSP register
The RSP register is the Stack Pointer. this register is responsible for keeping track of the address of the top of the stack.
Whenever we PUSH a value onto the stack, the RSP register’s value shrinks by 8 bytes (64 bit value = 8 bytes). Whenever we POP a value off of the stack, the RSP register’s value grows by 8 bytes. If this feels backwards to you, it’s because the stack increases from higher memory downwards towards lower memory, so adding a value to the stack means that the TOP of the stack actually resides at a lower address in memory.
Here’s a screenshot of an application’s stack in GDB –
We can see that the RSP register’s pointing at the top of the stack at address 0x7ffffffffdb70, which is an 8 byte wide piece of memory containing 0x45. If we POP that value off of the stack then the following happens –
Note that RSP is now 8 bytes LARGER because the stack has shrunk by 8 bytes, because we popped a value off. 0x45 is now gone, and the current top of the stack is 0x44. If we PUSH a value back onto the stack then RSP will be back where it started at 0x7fffffffdb70 again, because it will have shrunk by 8 bytes to accommodate the stack growing a little bit.
A real example
We’ll write a small Assembly program which populates some of the registers with data (1 – 5, for example), and then reverses them such that their values become 5,4,3,2,1 instead.
Copy and paste this into a text file named stack.s –
mov RAX, 0x0
mov RBX, 0x1
mov RCX, 0x2
mov RDX, 0x3
mov RDI, 0x4
mov RSI, 0x5
pop RAX ; RAX is now 0x5, when it USED to be 0x0
pop RBX ; RBX is now 0x4, when it USED to be 0x1
pop RCX ; .....
pop RDX ; ....
pop RDI ; ...
pop RSI ; RSI is now 0x0, when it USED to be 0x5
; Quit the program gracefully
mov rax, 60 ; system call for exit
mov rdi, 0x41 ; exit code 0x41
Assemble and link the program with the following command – nasm stack.s -felf64 -o stack.o; ld stack.o -o stack
And now open the resulting binary up in GDB.
At the prompt, enter “break _start” to set a breakpoint at the beginning of the _start function, then enter “run”.
The program will begin running and immediately halt at the beginning of the _start function.
Enter “n” at the prompt to single step over the first instruction (mov rax, 0x0). Notice at the top of the screen that the RAX register now contains 0x0.
Single step 5 more times (by pressing return 😉 ) until you’re about the push your first register onto the stack. At this point your registers should look as follows –
RAX 0x0 RBX 0x1 RCX 0x2 RDX 0x3 RDI 0x4 RSI 0x5
Now press “n” again and notice at the bottom of the screen that RAX’s value is pushed onto the stack. The stack pointer register has shrunk by 8 bytes and the top of the stack is now 0x0. Step 5 more times until all of the PUSH instructions are complete. At this point your stack should look like –
This means that the next POP instruction will take 0x5 off of the stack and put it into a register. Single step 6 times until your registers look like this –
RAX 0x5 RBX 0x4 RCX 0x3 RDX 0x2 RDI 0x1 RSI 0x0
We’ve successfully reversed the order of our registers using the stack. Easy, eh 😊 Also your stack pointer is back to the original value that it was at the start of the program. This would’ve been a reasonably annoying task to accomplish without the stack. We would’ve had to rely on the R8-R15 registers to swap things around, using the stack is MUCH more elegant.
In this blog you’ve learned what the stack is, what it’s used for and how you can harness its power in your programs. Understanding how the stack works and how to assign a register with a value, you’re genuinely in a position to understand a vast array of ASM programs.
The next blog post will cover a few basic arithmetic operations in the ASM language and the post after that one will cover conditional statements (the ASM equivalent of an if statement.)
Thanks for reading, give me a shout on Twitter if you need some clarifications on things.
Privacy & Cookies Policy
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.