Sunday, September 15, 2024

Wherein We Investigate A Function Prologue: Finally!

 

And here it is, finally: the majestic function prologue.


This is, in fact, part of a small C program and its Assembly version, as we can see here in Godbolt (again, use this - it's great).



Godbolt even distinguishes the different sections of our program (both in C and Assembly) in different colors, which is just a neat feature.


Assembly Prologue:

For starters, we'll represent the stack on the upper left with a crude drawing.
Items are added and placed on top of the pile of values, and memory addresses are represented in hexadecimal. These values decrease as we go up in the stack.



Notice that the memory locations are very much fictitious. As an added note, a memory location like 0x70 would be represented in 32 bits as:
0000 0000 0000 0000 0000 0000 0111 0000. So, if we were being very explicit in our representation, that memory address would be represented as 0x00000070.
Now that we have an initial state for our stack and for ECX which we'll use, let's carry on with the first instruction:





We are loading the effective address at ESP+4 onto ECX. Note that LEA doesn't actually access memory, it just performs address calculation.
In this context, ESP+4 is typically pointing to the program arguments.
Also, FYI, we could arguably, substitute LEA ECX, [ESP+4] with:

MOV ECX, ESP
ADD ECX, 4


But this would require two steps instead of one. Either way, it's good to know what LEA is doing behind the scenes. As we can see, the stack remains the same, but ECX is now pointing to 0x68.
Let's carry on to the second instruction:



AND: You probably remember this AND fella from boolean logic. It does exactly what it did back then. We're comparing the value stored in ESP with the value -16 and turning to 0 anything that isn't 1 in both numbers. Here's what I mean:

(ESP AND -16):
0000 0000 0000 0000 0000 0000 0110 0100
 1111 1111  1111  1111  1111  1111 1111 0000 (AND)
--------------------------------------------------------
0000 0000 0000 0000 0000 0000 0110 0000 (0x60)

What's with all those 1's, you ask? Good question. Go check out two's complement. It's really useful and it will be a great aid in the future.

This operation guarantees that the final 4 bits are zeros. Think of those bits like the remainder of a division by 16. If we have no remainder, then it means that we're working with a multiple of 16. And that's exactly why we're doing this. We're ensuring optimal performance by allowing the CPU to access memory more efficiently. Misaligned memory access can, in principle, lead to performance penalties or even crashes. 
This new position to which ESP is now pointing has 'e' stored in value, just for our sake.

Next instruction:



Now we're pushing whatever value is stored at ECX-4 (which is the previous ESP position) onto the top of the stack. So, we do just that. 'd' is sent upstairs and our ESP register follows accordingly. Why DWORD? That's a double word. And since in a 32-bit architecture words are 16 bits in size, our DWORD will be 32 bits, or 4 bytes in size. The PTR lets us know that it's a pointer to a value, so we know that we're accessing a value stored at a memory address (pointer).

Let's carry on:




That's a pretty simple one, we're pushing EBP onto the top of the stack, and so we did. We're saving the previous base pointer onto the stack and allowing the function to establish a new stack frame by setting EBP to the current value of ESP (and later restoring the previous stack frame when the function returns).

Fifth instruction:




And voila, we move EBP, setting up the new stack frame and setting EBP to the top of the stack. These two instructions allow for easy access to function parameters and local variables (relative to EBP).

Sixth instruction:




We're now pushing the value of ECX onto the stack, tracking the stack alignment and assisting in restoring the stack at the end of the function. ECX will later be used at the function epilogue to revert the stack to its original state. It's also a way to ensure that the stack is properly cleaned up when the function returns.

Final instruction of the prologue:



We're subtracting 20 from what we have at ESP, resulting in ESP = 0x40.
This creates a bubble of sorts, some room on the stack for local variables and temporary data used by the main() function.


I know... as drawings and sketches go, this one is a bit rough around the edges, but I hope it carries the message through.

Either way, you should be doing your own drawings! Start exploring this stuff until it starts making sense. A great way to learn these concepts is to keep being exposed to them, write them down, try programming them, rinse and repeat.

Have Fun!








No comments:

Post a Comment

How a Spy Pixel Crashed Into My Friend's Vacation

              So it goes.   A friend of mine, a freelancer, recently went on a much-deserved vacation. Like most of us in today's always...