 It doesn't make any sense. Oh, Idaho. Nice. Cool. All right, let's get started. OK, first thing, I'll recap a little bit the CTF. So I was super happy with everyone during the CTF time. And I especially appreciate everyone's patience with some of the challenges that weren't working or when the website itself wasn't working. So yeah, that was great. It was really cool to see everyone continue playing after the CTF was over. So we have, yeah, if we look here, you can see that I have the settings, I believe. Will I show you anything important here? Yeah, that's fine. So you can see kind of the settings here of all of the solve counts. So very good that all the teams solved strings. That was great. Then we can see kind of a nice distribution here of solves. So that was pretty good. Ooh, yeah. So no, there's no extra credit for this. Yeah, there may have been some extra challenges waiting, but we didn't get to all of them. So that's how it goes. Cool, which then leads me to, all right. OK, so I'm thinking about the final exam a lot. And I'm sure everyone else has been thinking about it as well. To be perfectly honest, if you think philosophically, the main reason why we have a final exam is essentially to force you to study the material that we've talked about since the midterm. Particularly the stuff we're talking about here about application security is really important, understanding x86 code, and especially these different types of vulnerabilities that we talk about here. So really, that's kind of my goal with a final exam, is it acts as a forcing function to essentially make students study some of this material at least a little bit. So and with that said, I know, let's say, probably, I think, I haven't thought of a good way to do a final exam that would translate nicely into online. And I know there's a lot of problems with online final exams. So here's what we're going to do instead. We're going to do essentially, so we're going to have next week, so a week, two weeks from now. What's that? I don't want to bring up my calendar. But the next CTF, oh no, it'll be in a week. So it's in a week, the 28th. So it'll be just like the previous one in class CTF. And what we'll also do is basically the same style report right up April 28th, April 29th, so the next day. And you would think, Steven, but that's actually not true at all. So what we're going to do is in lieu of a final exam, we're going to what I'm going to basically ask all the CTF teams to do is to create video walkthroughs of video. This will be due when's the exam of the final? What was it? It was the second? Is that right? I feel like this timing is wrong. Let me check on my phone so you can't see all the time. Yeah, the fifth. OK, great. Yeah, thanks. OK, and this will be due May 5th of this. Oh, do you spell that word? There we go. There's some combination. Yeah, so you'll do video walkthroughs as a team of N CTF challenges. We're going to decide basically after the CTF, depending on how the CTF goes. And so the idea is you'll make a video walkthrough describing what the challenge is, how to approach it, what tools to have, and then actually demonstrate solving it. So the idea here is I want you to put the effort that you would spend in this final exam studying the material into studying these challenges and being able to demonstrate and your network analysis skills, your reverse engineering skills, your pining skills, all of this stuff. And you'll be able to do this as a team in lieu instead of a final exam. So this gives you an opportunity to learn from your other team members. And I think we'll be much better than having a remote exam over Zoom or one of these other terrible proctoring software. Now it'll be N videos per team. So all right, it can be the number of videos doesn't really matter. Okay, let's take these questions one by one. So the number of videos doesn't really matter. It's the number of challenges. So you could film a video per challenge. That's fine. You can film one long video that's the challenges. If you don't solve any challenges, then that's a problem. So just like homework five, you have to actually solve the challenges. So what this relieves is, so if you'll notice, the CTF points are decoupled from the video walkthroughs. So the CTF will operate just as normally. You'll write a report describing the challenges that you solved and which ones you attempted to solve. Then after the fact, then we will tell you what were the end challenges to solve. Either tell you, we're not exactly sure. We'll communicate it'll be very clear which challenges to create video walkthroughs from. So maybe N, let's say N is a set. So it's a, there may be a choice from different ones. So the TAs already, Mars Day already went through in virtual recitation, one of the previous ones. But yeah, you can, and then you can, so let's see. Yeah, we'll specify the due date. It'll be on the fifth sometime. I don't want it to go to, yeah, what if the challenge is like the last one where we shouldn't finish all of them and will not be all of them. So that's fine. How do you balance people not participating? You're feel free to narc on them, put it in your read me, say that this person didn't do anything. You know, I'm happy to dock people points for that if the captain says so. Well, N be the empty set. I think you can guarantee no. Yeah, let's say, so maybe, I haven't actually, I think the undergrad TAs were on the call now. This is the first they're hearing about this. So, yeah, I can maybe ask the undergrad TAs to do another kind of like CTF prep class. I think that would be very helpful. We'll do. I'm looking forward to it. Cool. Sounds good. Yeah, so you can continue. Yeah, so the like, so this is why we're separating it, right? So the CTF write up time is in a day. So it's just about what you did during the CTF. And by the way, if you ask me private questions, it shows up in the chat for everyone. So don't think your questions are actually private. The other question. Okay, so, yeah, so the CTF, this is just for your CTF points. That's for the 2.5% of your CTF grade. And then the video walkthroughs will be in lieu of a final. There will, I'm not gonna tell you anything about the private, but you can, oh, you guys can't see my chat window on my screen? No, you can't see it on Zoom, but you can see it on the recording. Yeah, it shows up on the recording. Yeah, that's right. Okay, cool. Yeah, so no, I'm not gonna say anything about the challenges or categories or number. You can, you decide as a team for your walkthrough. So as a walkthrough, you either decide to make one group as a team or one individually, it doesn't really matter. Yeah, you can submit a Zoom recording, whatever. I think that's great, but make it beyond YouTube so I can see it. I don't wanna get just like 200 megs of video from all of you. Any other questions about this? And I'll try to post more clarifying details on the website. That's true. But you're an undergrad TA, you're not in the class. I don't have to worry about your recordings, Riley. That's fine. Yeah, you don't have to post it publicly on YouTube. You can post it on YouTube as an unlisted link and then just send us the link to that as long as we can view it. That's fine. Cool, all right. Then let's move on to now we gotta cover content. So to make sure you can solve those CTF challenges. So, okay. So now we've looked at so far, command injection vulnerabilities, file, path permission style vulnerabilities. And so now what we're going to look at is one of the most classic style of vulnerabilities that really traces all the way back to when kind of languages like C specifically were first created in, I believe, the 70s, if not earlier. That essentially, one of the key problems is, let's say we have some C code into main. And I need to store some characters, some buff 200, I need a buffer to store 300 characters. And I am going to say, so I can do kind of anything to this. The thing to think about, and you've all written C, C++ programs before, does anything stop me from doing a buff 400 equals C? Does anything stop me from doing this? Yeah, only yourself, right? So you have to actually keep track of how much memory you've allocated because there's no way for functions to know. So when you do things like, but this seems a little counterintuitive, because I can say what's the string length of buff and how does this function operate? So let's say I set, yeah. So let's say I did SCR, CPY, I believe it's destination source, buff, right? So this string length buff, how does this know the length of that? Yeah, it just reads this string and it says, okay, it keeps incrementing it until it gets to a null byte, a zero. So H-E-L-L-O-N null byte. And then it prints that out. So it fundamentally, in C, there's no way to know this buffer has 200 characters in it. We actually have to pass this length along with the buffer all over the place. And this makes things essentially kind of insane. And this causes these major problems right here. And if we think it again, and it always comes back to these types, all these vulnerabilities is what did the developer intend? They intended to create a buffer to store at most 200 characters. No, in C, they're pointers. So this is actually a character pointer. They're not really arrays. I think the major difference here is the compiler knows the size of this buffer, but at runtime it gets compiled into pointers. But cool. And then so, yeah, so there's nothing to stop us from doing this. And in fact, and so what we'll see is while the developer intended for this buffer to only be 200 bytes, there's nothing that enforces that we can't write past or before those 200 bytes. And this gets to actually the heart of, essentially all of the vulnerabilities. And one of the major ones in C and C++ is this concept of overflows or overwrites. So the problem is that there's no boundary checking on these arrays or these buffers. And so if we as an attacker can control what data is overwritten, we may be able to take over the program completely and make it do whatever we wanna do. So we can, and as we'll see, we can use this to modify data of the application. So we may be able to change variables in the application or alter the control flow, right? So we may be able to make the program go and do things that it wasn't designed to do. Really cool stuff. And this is part of actually the work we do in our lab is how to make exploiting these overflows basically completely automatic. So being able to analyze a program, generate inputs that crashes it, and then turn that input into something that is completely exploitable. And the other important thing that we'll discuss here a little bit, because we're not gonna get into it in detail, but basically there's been a lot of progress in designing different types of protection techniques. And there's a lot of different mitigations here, but still even with a lot of these mitigations in place, this is still one of the most popular vulnerability types. And it all comes down to, in order to understand actually what's going on, we need to talk about the stack. So here I'm gonna switch to kind of the view here. So we hopefully, and this should be more of a refresher about what is a stack in terms of a data structure, right? So a stack is something that you push items onto and you pop things off of. And our programs as they run actually need this concept of a stack. And the way to think about it, and we won't get into all the things, but essentially you need scratch memory for a function, right? So you need, where does this main function store these bytes? Well, it turns out it's gonna store it on the stack. And as we'll see, the stack is actually used in order to do function call. So you can have one function call, another function call, another function. And so the CPU knows where to go back, which is a super interesting thing. But we can think of it and this is used, and almost all architectures have this notion of a stack. And the way we're going to always draw the stack in this class and all the rest of the slides, it's gonna start at high memory addresses and grows down. So it's gonna start at, let's say, FFFFFF and it's gonna grow down. And functions can basically store data on the stack as long as they essentially clean up after themselves. So as long as the stack is exactly where it needs to be when the function returns, then that's totally fine. So x86 natively supports this notion of a stack. So there's a register called ESP. This register holds the address of the top of the stack. And then there are x86 instructions. So push a register says, decrement the stack pointer. So move the stack pointer down and store whatever's inside the register eax onto the stack. And pop eax does exactly the opposite. It says essentially dereference ESP. So dereference ESP, grab whatever that's pointed to up on the stack and copy it into eax and then increment the stack pointer. So we're going up. So what this looks like, so we can use our example here. So here again, we have high memory at the top, low memory at the bottom. And let's say we have our stack pointer is pointing here and that memory address happens to be 10,000 hex. And what this means is, so remember the stack in our example grows down. So this means that everything above our pointer, our stack pointer is part of the stack, but everything that's below it is garbage. So to get rid of something, we just move the stack pointer above it and now it's garbage and can be reused. And if we need more memory, we move the stack pointer down. So we'll just walk through a very simple example of, so the heap is very, very far underneath the stack so far that we're not going to worry about it right now. So what we're going to do, so we're going to look at a very simple function, push eax, pop ebx. And so semantically, what should the, what should these two operations do? After we execute these two instructions, what should happen to the registers eax and ebx? Yeah, so it's essentially a copy, right? So it's copying what's inside eax to ebx using the stack and so we'll see exactly how it does that. So here we have our registers, we have eax, let's say it has the value hex A or 10, we have ebx, hex zero and ESP. So what's the value that's going to be an ESP based on this diagram? Yeah, hex 10,000 remember, right? So it's important that we get our essentially units correct, right? But here we have 10,000 hex and we know this because we're saying that pointer points to there, right? This arrow, this blue arrow, if I said this is the stack pointer and this is that address 10,000 hex, the only way that makes sense to the system is if the value 10,000 is in ESP, right? They're inextricably linked. If I said the stack was at any other value, that means was at any other memory address, that means this ESP register must be whatever that address is, does that make sense? Cool, okay. So now we're going to walk through this kind of step by step. So at the first one, so this is the kind of the state of the computer and we're not getting into it yet, but you can think that there's another register that we're not showing called EIP, the instruction pointer, and it will point to this memory region and that's how the CPU knows what instruction to execute. So we look here, then it's going to execute a push EAX, so our instruction pointer will increment and we'll go through the exact semantics of push EAX. So what were those? So first things first, how did EAX get 0XA in it? Because I said so, right? So I'm saying that EAX has A in it at this point, EBX has zero and ESP has 10,000 hex. So it's like pretend like I'm setting up the system like this and these are the next instructions we're going to execute. So first things first, I'm going to decrement because I'm pushing something onto the stack, so the stack is growing down, so I'm going to decrement it by four. So now the stack pointer is going to be FFFC. Why four? Why did it decrement it by four? Yeah, it's the size of the register. Wow, there's a, no, a byte is eight bits. Registers are four bytes, 32 bits, yeah. So it has to move down by four bytes so that it can store a 32 bit value there. Cool. So FFFC. And there we go, okay. So then, and then the last thing that happens with the push is whatever's inside EAX gets copied to that memory location. So essentially in our diagram, at memory address FFFC is a four byte value that will be in little endian but will translate to the integer zero XA. So that's one instruction. So all those things happen when the CPU executes our one push instruction. Now, when we execute the next instruction it's gonna be a pop. So a pop is gonna be exactly the opposite steps. So a pop is gonna say dereference ESP. So copy whatever ESP is pointing to, in this case it's A. It's actually memory address FFFC. What's there is the 32 bit value A. Copy that into EBP so we can see, sorry, EVX. We can see EVX changes to A and then increment the stack pointer by four. And again, the four is because of the size there. So everyone get kind of the simple stack operations, push and pop. So now we need to see and we're gonna go deeper to understand how the stack is involved in function frames. So in this case, no, EAX's value does not get changed by a push. So a push says store the value that's in this register on the stack and don't touch this register. Cool. So this is actually the interesting thing. Yeah, so if we look at this, right? So this is a good question. So pop doesn't actually modify the memory at all. The only thing that it modifies is the stack pointer. So remember, like we said, anything below the stack pointer is garbage. What's at FFFC, this zero X A value, that's essentially we consider that garbage even though it's technically still there in memory. So this pop is moving this into EBX. Yes, it's as if the programmer has deallocated it and we'll see how that happens with functions. So this is why we'll see when you have a function, your local variables are stored on the stack. So if you ever return from a function or reference to a local variable and then the stack that function goes away, that variable can change in very weird ways. Yeah, so destructors are about, essentially the heap operates in the same way. We'll see that essentially you can think of the stack is and local like function storage is kind of more automatic. The programmer doesn't have to explicitly deallocate that memory because it happens when the function returns. Whereas the heap, you have to do more manual memory operations and explicitly alloc and deallocate things. Cool. Cool. So we need functions need to allocate space for their local variables, right? We need to be able to reference inside of a function, local variables. And so one of the things to think about is, can we use the stack pointer for this? So can we say, okay, at different offsets of the stack pointer are different local variables? Yes, but the problem is as our function executes, our stack pointer could change. And so that makes things a little bit more complicated. So there's actually another register that's used that we'll see is called the frame pointer because it points to the start of the function's frame on the stack. So this'll make a lot more sense when we look at example. And essentially the compiler, when it compiles a function, it says, okay, all of these local variables are gonna be at different offsets of the frame pointer. And so every time I reference variable A, I'm gonna reference it at a certain offset of the frame pointer. So let's see what that looks like. The important note is on x86, the frame pointer is called the base pointer and it's stored in EBP. Yeah, so we'll see every function needs to have its own frame pointer. And the reason why is very important, let's see. So, anybody ever written a recursive function before? Unfortunately, come on. Let's see, if num is greater than zero, then, oh, I didn't give it a name foo. Yeah, okay, cool. So, I guess I can actually make this a little bit better in, let's call this local. I don't know, whatever. And then after this executes, we'll print out local. Okay, so little function, right? So we know kind of by reasoning through this function, foo, right? So main is gonna call foo. So if we look at the call stack of this, we have main and then we have, we'll draw something like this and then we have foo 10 and then we have that will eventually call foo nine and that will eventually call so on and so forth. The important thing to remember, right, is so each of these different invocations of funk foo have a local variable called local, right? A variable that's only accessible inside that function, each with a different value. So this is why every invocation of a function needs to have its own frame pointer because as we'll see here, that's how different invocations can know which one is their local. So that goes back to the question about, yeah, each function will have a different frame pointer and we'll actually see there's very cool mechanisms of making it so that you can call a function and it sets up the frame pointer correctly and then the next, the function that called you but when you return, you set up its frame pointer correctly but let's walk through this with a little example. Careful that's a function, Skylar, you may get a stack overflow. Okay, cool. So here we have a very simple function, int main. We have variables A, B and float C, right? So just three different local variables and it's a very simple function. A is equal to 10. We will get into, trust me, we're gonna get into exactly what everything does, all those instructions, yeah. So we're gonna set A to be 10, B to be 100, C to be 10.45. We're gonna set A is equal to A plus B and we're gonna return zero. So incredibly simple function. And so essentially what you can think the compiler does is it goes through all of your local variables, A, B and C here and it's gonna just create offsets for them from the base pointer. So internally it says, aha, I am going to have, I'm gonna say that A is at E, B, P plus some offset A and B is at some offset of E, B, P plus B. Same with C and then these memory operations are incredibly straightforward. So this A is equal to 10 is essentially set the memory address at E, B, P plus A to 10, set the memory address at E, B, P plus B to 100, set the memory of E, B, P plus C to 10.45 and then set them and finally this is the A is equal to A plus B and then the return. And so what the compiler does is then figures out what these offsets are. So for reasons that we'll see in a second, all of these offsets will actually be negative. So A, and this is actually from a version that I compiled. So it says A is at E, B, P minus C, B is at E, B, P minus eight and C is at E, B, P minus four and this is just how the compiler did it. So then there's some setup here for the function that we'll get into in a second but then what we can see is this A equals 10 gets directly translated to the assembly instruction move hex A into E, B, P minus C, right? So this is what it was important to understand the displacement syntax here. So here we have E, B, P minus C. The next instruction is just B is equal to 100, move hex 64 is decimal 100 into E, B, P minus eight which we know is B and then move hex 41273333 into EAX. What's this hex value? Yeah, it's the floating point representation of 10.45. Yeah, that's great. So you can look up IEEE floating point format to figure out exactly what that means but then it moves it from EAX into E, B, P minus four and then finally the return zero or no, it has to add them together, that's right. So first move EAX into, no, no, sorry, I'm getting it backwards. Okay, yeah, E, B, P minus eight. So this is move B into EAX and then add what's in EAX to E, B, P minus C which is A and store the result in A. Some interesting things in case you're wondering why does it take two operations to move this floating point value into C into E, B, P minus four? Any thoughts? Yeah, so this value right is 32 bits. This move instruction, well these move instructions are smaller values, there's a move instruction to move those into a memory address but for a value this large, you can only move it into a register. So yeah, it takes two operations to do that. Anyways, the other answer that's always appropriate when you say why did the compiler create this output is because the compiler decided to do that. There's actually many, many different ways to do different types of things. So depending on what compiler, what version, you may get different output but it usually, it should be semantically the same. Cool, so then let's look at what this actually looks like and what our function frame looks like. So we'll walk through this. So we have high memory, low memory and we'll start with our stack at 10,000 hex and we'll start at this instruction. So I haven't covered these two instructions right now. Because we're gonna go into it more in depth but essentially what this is is the start of every function has to set up its base pointer. So what we're doing, so right now when we get called, the only thing we let's say know is the stack value ESP but we don't know what's in the base pointer because somebody else called it and we don't know what's in the EAX or maybe any of the other registers. So what we're first gonna do is set things up. So the first thing we do is move the stack pointer into the base pointer. So now the base pointer points at hex 10,000 hex and now essentially you can think of it, we need to allocate memory for our function frame. So we do that by moving the stack pointer down and that's exactly what this next instruction is, is essentially move the stack pointer down by hex 10 or decimal 16. So move the stack pointer down and now the stack pointer points here and the base pointer points still up to 10,000 hex. So all that did was essentially and now we can maybe see why. So the base pointer is at the top and so negative offsets from the base pointer will always be in allocated memory because the stack pointer was moved down, cool. So now we're just gonna walk through this. So we first move a hex A. Yeah, so what are the extra four bytes for? Great question. The short answer is because the compiler decided on it and the longer answer is the compiler decided on it because on some architectures you need your stack pointers to be aligned at a certain memory alignment. So even though we only use three 32 bit values in our memory because we have A, B and C, those are our three values, the stack pointer decides to align or the compiler decides to align the stack pointer at this value. Yeah, and usually there's options you can set and change things like that, but that's what it does by default. This was a version of GCC a while back. And so we can actually see each of those different memory locations here. So this is the address. And so now what we're gonna do is move hex A into EBP minus C. So look at EBP minus four, minus eight, minus C. So it should copy A into that value in memory. There we go. Move on to the next instruction, 64 into EBP minus eight, 64. And then finally that two-step process to move 10.45 and floating point to A. So first into, sorry, into what was our variable C and is now in EAX and then move that onto the stack. And so now actually we can see the layout of these variables in memory. And this may be, I mentioned it briefly a while back and hopefully you've kind of seen some of this as you've looked at the reverse engineering challenges. This is why it's so difficult to kind of understand a program just from the assembly, right? Because when we looked at that C program, it was very clear we had these different variables named A, B and C, but once they get compiled down, all of that semantic information about their variable names, everything goes away. And all we have are offsets of the base pointer to see that these are different variables. And so one of the important things when you're trying to understand a function and look at it is anything, any reference to a base pointer minus some value is gonna be a local variable. So then we look here and we see, okay, what's the next step that happens? Move EBP minus eight. So EBP minus eight is B. Move B into EAX. So EAX now has the value hex 64. And now add, and remember, add is kind of an interesting operation. It's add the first one with the second one and store it in the second one. So this is add hex 64 into EBP minus C. So this is doing A is equal to A plus B. So we're taking B, we're gonna add it to A and store it in A. So we do that, it becomes 6E and then we're done. Questions on this example? Cool. Okay, so as we saw, we can allocate memory for the function's local variables, but we also have this other question, right? Of other types of things, even we can see in this foo example, right? Every different copy of foo has a different argument. It's num argument. So how does that work? And so we need to think about there's, so we can, we saw how it allows us to allocate memory for the function's local variables, but there are other things we need when we're calling a function. We, for instance, where do we put the return value, right? How do we pass a value back from a function to the function that called it? How does, when I call a function, how do I actually specify the parameters to that function? And this is the kind of really kind of cool thing. If we think about the CPU, if we think about the CPU, right? And we think about, we saw some registers, oh, that's ugly, EBP, ESP, and even EIP, right? These are registers that control, so ESP is the stack pointer, whatever that's pointed to is a stack, EBP is the current frame pointer, and EIP is the instruction pointer, right? CPUs are very dumb, right? So once I change, if I jump somewhere, like if I jump from, if I jump from, when I call from main, when I call foo, and then when foo calls itself, essentially the, how this has to happen is the EIP, the instruction pointer gets changed to foo, but we need some more information. We need to know how do I go back and where do I go back to? Who called me, right? So we need information about where do I start executing from? And so we'll see that all of this information in order for this to actually happen, we need a calling convention. So we need a way of how does one function call another? So we need to essentially store information on the stack in order to call functions. And again, it's really essentially arbitrary. There's, I guess, different trade-offs that you can kind of go into if you want about different ways of doing this. Different architectures, as we'll see, different OSs have different ways of doing this. But basically, we need to look at who calls what. So does the caller call store information? Does the call E? And so we need an exact, precise convention about who stores what in order to even call a function. And it varies based on the architecture, the operating system, the compiler, or sometimes even the type of call. So what we're gonna look at now is, let me move this on the way, there we go. What we're gonna look at now is the Linux, so it's x86 Linux calling convention, Cdecal is what it's called. And these are precisely defined, you can go look this up, and it really helps in understanding what's going on. And in particular, and what we're trying to get to is what happens when you're able to corrupt memory on the stack? And how could that allow an attacker awesome and interesting capabilities? So the caller first does, in this order, pushes arguments onto the stack in a right to left order. So arguments are pushed onto the stack first from right to left, then they need to push the address of the instruction after the call. Right, and this is because when we call that function, we're gonna now start executing some code. When we're done with that function, the CPU has no way of knowing where do I go now, right? It's very dumb, it just goes and executes whatever you tell it to. So we need some way to say, hey, after you're done, execute this next instruction in the function that called you. Then when a function is called, so the call E, will first push the previous frame pointer onto the stack because it needs to set up its own frame pointer. It then creates space on the stack for local variables as we saw, and then ensures that the stack is exactly consistent on return and then places the return value in the EAX register. So let's look at this in action. So here we have C code, we have our int main, int A, we're gonna call some function, pass it the values 10 and 40, set that to be A and then return A for main, and the function that we call has two integers and we're just gonna return A plus B plus one, right? So pretty simple function. And so now we're gonna look at the exact code and how that looks. So when we look at this, main and main is not special. So main is just like any other function, it can be called. So a main, the first thing we need to do is buy that calling convention. We need to first store the caller's frame pointer, right? So we don't know what it is, but we save it onto the stack, we do push EPP. And then we use that trick that I mentioned earlier, we're gonna set our base pointer, our frame pointer to be wherever the stack currently is. And then we are going to move 18 hex down from the stack to allocate memory on the stack for us. And then we're gonna, as we'll see, we're gonna push the arguments onto the stack. So we're gonna push, this is 28 is 40. So we're gonna first copy 40 onto the stack and then 10 and then call the function call E. So we'll go over exactly the semantics of this call instruction in a second. And so this is the nice thing just like in programming, we call some function, it does whatever, and then we get something back and that something back is in the EAX register. So this means take whatever was returned from EAX, copy it into EBP minus four, then move what's in EBP minus four to EAX, leave and return, which as we'll see, leave essentially does the opposite of the prolog. I guess we can do that here. Leave does the opposite of these steps here and return returns to whoever called us. So we'll look exactly at all these semantics. But one thing I wanna call out first is why this, what's that EBP minus four in our function main? What does that value translate to in our C code? Yeah, it's the variable A. And why does it do these two instructions? Do you actually need these two instructions? Yeah, so the, it's super interesting, right? So we're doing this return A. So when a function, by our calling convention, a function puts its return value into EAX. So this last move, EBP minus four into EAX is doing that. But the funny thing is, and this is, this is the different optimization levels of your compiler. I believe these were compiled with no optimization. So in this case, this A is equal to call E1040 is these lines essentially. So this is saying, okay, I need to set A to be whatever's in EAX. So A is that EBP minus four. So I need to move whatever's in EAX into EBP minus four. What's the next instruction? Oh, return A. So I first need to move what's that EBP minus four into EAX. So different optimization levels can be done so that the compiler can look at this and go, oh, actually these two instructions do nothing, right? Because I'm moving a value from a register into memory and then back into that same register. I can just get rid of this. But you can see it's like a one to one straightforward translation of that C code. So let's look at that. So the important thing is to most functions as we'll see we'll have a prologue and an epilogue that will, so that are not kind of the meat of the program, right? So the prologue does all of those steps that we saw for C-deckle. It saves the caller's base pointer. It sets up a new stack frame for itself and it allocates memory and the epilogue cleans all of that up. So there's a question about, oh, how does leave no much to add? We'll look at leave. It's very clever and pretty simple but it basically just does this in reverse. Okay, so we look at callee and the really cool thing is because of the way the frame pointer is set up as we'll see arguments A and B to access them are going to be positive offsets of our base pointer whereas local variables were negative offsets. Yeah, great question. So how does, how do we, so the nice thing is we don't have to know the compiler knows. So if we look at main, what does main need? Main needs four bytes for A and then the question is you can see that main actually there was some optimization here rather than pushing values onto the stack. So pushing 28 and then pushing A it actually pre-moved the stack down more so that it would have places to put those on the stack. So the short answer is I have no idea the compiler just decided that it would be 18 hex on the stack, cool. Okay, so then the callee, this function stores. So again, the same prologue needs to store the base pointer, it moves the stack pointer into the base pointer and then it moves EBP plus C. So this is an offset. We'll see exactly what variable this is. I believe it's B. Yeah, and EBP minus eight into EDX. This is a way it's of adding. So it's load effective address Eax plus EDx move it into Eax. So add the Eax to EDx into Eax. Again, like I mentioned why it chooses to do this this way, I have no idea, probably performance reasons. And then add one to Eax. And now our return values in the Eax, we pop EBP and return. So what's one of the difference between the prologues and Apple, what's the main difference between the prologues in these functions? Yeah, that's the popping is the difference in the epilogue. But what about the prologue? Yeah, no subtraction. Why is that? No, exactly no local variables, right? So the compiler looks at this callee function and it says, oh, there's no local variables. I don't need to decrement the stack at all because I don't need to store anything locally on the stack. So that's why you don't have that subtraction. And as we'll see, this is why it can be slightly more efficient and it doesn't need this leave instruction. It can just do pop EBP. But let's walk through this and see exactly what this looks like. Okay, so we have our callee function. These are exactly the assembly instructions we looked at. We have our main and now there's a big difference. So why, so this one I looked at, it said call callee. But now when I'm looking at it, it says call8048394, why is that? Yeah, it's the address of callee, right? So now we're gonna execute this. We're not looking at it statically. So it's gonna actually be laid out in memory. So this is actually, I took, when I compiled this, I took the actual memory address locations of each of these things, right? So we can see when we call 8048394, as we'll see that essentially means go to 8048394. Okay, so we have positive memory, low memory. We have our stack. And surprisingly, what's kind of interesting is the only registers we actually have to deal with are EAX, EDX, ESP, EBP, EIP, and that's it. So from one run when I did this, the stack was at FD2D4. Ah, yes, so this gets into why are different instructions different sizes? So this is a very old architecture debate about risk versus sysc, like reduce instruction sets versus complicated instruction sets, I think. And essentially it's because of the way x86 is. So yeah, this is x86 instructions. So each of them are different sizes and that's what you can see here. So the delta between these tells you the size of that instruction. So a push EBP is one byte, whereas you can see different instructions are different sizes, which is cool. Okay, so I said the stack pointer is at FD2D4. And so the stack pointer is at FD2D4. And if the instruction pointer is here at the first line of main, that means that what's inside EIP must be 804, 83, A5, right? So, and one thing to think about is there's something in EBP? We actually have no idea what it is. We'll just say it's this value, but we don't really care what it is. It's just something called us. We don't know what's in EBP. Okay, so this is kind of the state of the world at the current point. Like what I literally did, I think was compile this at a breakpoint right at main and be able to see what's going on here and did it step by step. So, so we're gonna first do a push EBP. So this is the first part. Remember the, as part of the calling convention, the callee has to store the caller's base pointer. So we're gonna do that. So we're gonna push EBP so that decrements the stack and we're gonna store whoever called main's base pointer on the stack. We're then gonna move the current stack pointer into the base pointer. So whatever it was, it's currently pointing right here at FD2D0. Then we're gonna subtract 18 from ESP. So now the stack pointer moves down, but the base pointer stays at FD2D0. Then we're going to move hex 28, which was 40 into ESP plus four. So here's ESP is at FD2B8 plus four is gonna be here. So it will show up here. So we should have 28 there and then we'll move 10, which is hex A onto the stack pointer. So now if we think back to our calling convention, right, what I, as somebody who had to call a function had to do was to first pass, push arguments onto the stack from right to left, right? So if we recall, our function was call E, parentheses 10, 40. So what do we do? Look at the stack. We pushed from right to left, 40, 10. And now we're about to call this function. Every good question is up to here. Cool. Now what do I need to do, right? So the last part of the calling convention was that I need to store the address. I need to store on the stack the address that the program should go to, that the function should go to after we're done executing. So this is done in x86 with this call instruction. So called is very simple. It does two things. It's a push, so push, so question. After call E is done executing, where does main want execution to continue from? What memory address? Yeah, the next instruction, right? The next instruction is 80483BF, right? If it was 80483BA, it would just continue going in loops, right? But BF is the next instruction. So what we want to have happen is call E executes and then boop, it goes here. And the only thing that should change from main's perspective is the EAX register, right? So EAX should then have the return value of call E in it, but that's the only thing that should change from main's perspective. And so the way this happens is the call instruction does two things. It has an argument here and that's pretty clear. It's gonna set EIP to be whatever this value is, 8048394. So it says go to the top here and start executing from call E. But it also does a push the next instruction onto the address of the next instruction onto the stack. So what this looks like is it pushes onto the stack 80483BF and then changes the EIP to be 8048394. And now it's exactly as we were at the start of main. We're suddenly in a new function call E. And so we don't know who called us, but we know what we have to do is first store their base pointer, create space in the local stack if we need it, do our computation and put our return value in EAX. Okay. Yeah, so x86, to be perfectly honest, I don't know. There's some, you can look at the C. Dechle calling convention. There's all kinds of stuff about how to deal with values that don't fit in registers. There definitely is a way to do it. I don't know exactly what it is. It may be put it on the stack. It may be put it in different registers. Yeah. Yes, exactly. We're still in call E right now, right? And we still have all of the stack from before, exactly. And if we can look, what do we have currently right now on the stack? Well, we have what's on the top of the stack is the return address. We're gonna call this the return address, right? This is where we start executing after we're done. Above that, we have our first argument and then our second argument. And depending on how many arguments in call E will continue to have that. So I wanna talk a little bit more about this saved return address. It's kind of like, and this may be a little silly, but for the non-Americans, maybe this doesn't make too much sense, but hopefully you've maybe heard of the story of Hansel and Gretel, right? The abridged version. It's like two little kids that go out into the woods and that's like super scary woods. So they wanna be able to find their way home so they leave a trail of breadcrumbs when they go. And that way, when they wanna go home, they just follow those trail of breadcrumbs back. That's exactly the way you should think about these return values, right? So these return values, these return values on the stack are gonna be used as a breadcrumb for the CPU to be able to go back to the function that called it. So let's walk through and see exactly how that happens. So Kali executes. It first does a push EBP. So it has to store whoever called its base pointer. Why? Because it's gonna overwrite that base pointer register with what it's base pointer. So it overwrites that. It then moves ESP into the base pointer, setting up its base pointer, and then finally it starts executing. So now when we look at these and we see what's that EBP might plus C, right? EBP. So when Kali is executing, what's at EBP is the saved base pointer. And what's at EBP plus four is the saved instruction pointer. And what's at EBP plus eight is the first argument, and then 16 is the second argument, and so on up the stack. So this is how every function can be compiled to access its arguments, knowing exactly where it will be, right? It knows the first argument will always be at EBP plus eight. Cool. Okay, so walking through this, we copy EBP plus C, so 28 into EAX. And actually, before we do that, we can look and see that, okay, if we think about it kind of this stack location, so we think about the frame, right? This actually belongs to main and the next one belongs to Kali. And so that'll kind of help us when we're looking at these things, right? So, but let's go on from here. So we move, yeah, so we move 28 into EAX. Then we move EBP plus eight into EDX. So we move a hex A into EDX. We then add them together into EAX, hex 32. Add one to get 33. Now we're all done. So remember that Kali function was incredibly simple. It was return A plus B plus one, right? And where do we put that return value in the EAX register? We put it in the EAX register and then we, now we need to clean up. So we need to put the stack exactly where it was when we left off. So right now, and this is different because we didn't subtract the stack. We'll see how that happens in main. But what are we gonna do? We need to restore the base pointer. This is why we saved it on the stack because we know that nothing is gonna touch it. And so now what we do is we pop EBP. So now we're gonna take, so this pop EBP says wherever the stack pointer is currently pointing, copy that value into the base pointer. So essentially take the saved base pointer on the stack which was whoever called us and put it into EBP. So we walk through this. It will increment the stack pointer and copy that into the base pointer. And then now our base pointer points back up top to main. And now the only thing left to do is return. So the way to think about return, a return instruction is essentially a pop EIP. So what does that mean? It means take whatever the stack pointer is pointing to and start executing from there. And that's how we get our Hansel and Gretel breadcrumbs and being able to go backwards and find that next breadcrumb. So we look and we say, okay, where's the stack pointer currently pointing to? Copy that 80483BF into EIP. So it changes the stack, right? It's a pop. And now we get back to 80483BF. And now we look like, so the stack, remember where the stack pointer currently is, everything below it is garbage, right? So we don't care that there's all these values here. We just know that the stack pointer is exactly as it was when we called call E and we have our A plus B plus one into EAX. And then we just go on and start executing. So we move EAX into EBP minus four and then move that back into EAX. Now, I think technically with x86, you can't directly alter EIP like that. You can't do a pop EIP. You, yeah, I think you can only modify the EIP register through things like jump and return. Cool, okay. So leave does two things. And it needs to, the way that I think about it is it basically is one instruction that gets rid of these three instructions and does the opposite, right? So we had a push EBP, a move ESP into EBP and then a subtract 18 from EBP. So what a leave does is it says, okay, move the base pointer into the stack pointer. Essentially bring the stack pointer up where the base pointer currently is right here, which gets rid of this entire subtraction. It doesn't matter how much memory you've allocated. We're now, boom, moving the stack pointer back to the base pointer. And then as part of leave, then do a pop EBP. So then copy that value into there into EBP. So the leave will move. So first, yeah, we'll first change the stack pointer and base pointer to be the same value here. So they're pointing at the same place and then do a pop EBP. So then change EBP and increase the stack. And so what's at this memory address FD2D4? What's up there and why that I didn't draw it here in the diagram, but what is that? Yeah, so exactly, not exactly. Okay, so it's part of the calling function stack. Yeah, so it's the address of the next instruction, right? Because I'm gonna go return, so whoever called main, right? When they called main, they had to do a call instruction. There should be an address of wherever the CPU should go execute after that. And that's where we're gonna execute after this return instruction happens. Cool, so how are you feeling about that? Questions? Yeah, so I didn't draw it on this location because I, let's say I didn't know it or whatever, right? But at this return, main is gonna go somewhere else. And where that someone else is will be at memory address FD2D4. Just like at FD2B4 right here was where we stored where we should go after call E. So somebody called main and we can get, yeah. So leave is, let's see, we'll take this one at a time. So leave says it gets rid of it by saying, okay, I know where the base pointer was. The stack pointer may have moved a ton. I don't really care. Just copy the base pointer into the stack pointer. So bring the stack pointer up to where the base pointer is. First do that, and then do a pop EVP. So then move whatever was there into the base pointer. And now in two steps, we've been able to undo these three instructions in the prologue. Does that make sense, Ethan? Yeah, and the other important thing is like the wiping away, it's, again, it's more of a conceptual thing, right? As we can see, and this is why on this diagram, I didn't change the memory here because the memory doesn't get wiped or get changed. The only thing that happens is the stack pointer moves. And so now if you think if we called another function or whoever called main calls another function, it may use the stack, but it's free to do so. Yes, so the stack frames will be kind of on top of each other like this. Okay, there was Andrew. So pop EVP assigns EVP to it. Yes, it's part of leave. Yeah, exactly. So part of leave, and so we can maybe compare callee and leave the prologue, right? Callee just did pop EVP return because it knew, the compiler knew because it didn't subtract the stack that EVP and ESP were exactly the same value. So instead of a leave, for efficiency reasons, decided to do a pop EVP and then a return. Yes, exactly. So yeah, Eduardo, that's a good point. So yeah, this is us, part of this leave, just like callee did. We said right when we started main, the very first thing we did was store whoever called us base pointer. So we first store their base pointer. And then this means when we leave, we need to set their base pointer back up. And that way they can start executing just like, and the nice thing is it's all, I don't know if it's, you wanna call it nice because it's like recursion or whatever, but the nice thing is these ideas don't change. Like every function invocation is essentially exactly just like this, right? So here, callee had its own base pointer here, right? Its base pointer was at 2DFD2B0, but it's returning. So it says, okay, whoever called me and to set up their frame pointer. I don't know who it was. I don't know if it was main. I actually don't care who it was. What I do is whatever value I originally pushed here, I'm gonna pop it and set it back up. And then that way, when we get back to main, the everything, all of the registers, all of the stack, the stack pointer and the base pointer are exactly where they were right before we called that function. Cool. And so we need to do the same with main. So what we're going to get into on Thursday is to see how data being copied without checking bounds, essentially the idea is if we're able to alter data on the stack, what happens if we corrupt one of these breadcrumbs on the stack and are able to change it to be whatever we want? And how could we do that in order to execute kind of code of our choosing? And we can basically make the program go to any instruction that we want. But we'll write a good stopping point right here. When we come back, we will get into stack overflows here. Cool. All right, thanks everyone. And yeah, get ready for the upcoming CTF. I think it'll be very fun. See ya. No, no, CTF's on Tuesday. Lecture on Thursday.