 All right, gentlemen, Wednesday, 4.35. Let's get started. So I think it's good to see you before filming good things. I think it's turned out to presenter mode somehow makes quick time pause whenever I switch from a slideshow back into the normal desktop so it wasn't recording anything. But without presenter mode, I just tested it. It seems to work, but who knows what's going to happen. You can also blame your fellow classmates because I posted some videos and I didn't understand why I was recording 15 minutes of class lecture when we got 115 minutes of class. And so it turns out it was because it was not recording kind of what I posted over, so yeah, that's the way it goes sometimes. So I guess a little, yeah, I guess this is everything that we had last week. I won't talk about it, but you can see this nice jumble of handwritten notes. Beautiful code that we wrote. Look at us coding in a similar way. OK, so now we'll continue. So now we're going to go fairly quickly through this remaining material and slides we have on the x86 stack on function frames and on calling the vengeance. Any questions for us back? I guess we can also do something else. Do you guys want to hold our board for a break or after? It'll have to be the same amount of time you have to do after. But OK, good, I have a happy competition to set up on Friday, so I'm a little slammed anyway, so I'm going to go on the same thing. All right, you know what I was saying? I think they just said there's 310 rendicers now. And so we're setting up the infrastructure to host everything on AWS in the cloud. But not only do you have 300 teams, each team is running 10 custom services that we've ever written that have specific vulnerabilities. And so what you have to do is about every five or 10 minutes you're going to test every single service on everybody's team to see, is this service still up and functional? Because it's really easy not to be hacked when you do. Shut it down, yeah, shut it down, right? You only have access to it, it's great, it's super secure, don't need to hack any of your flags, right? But then you should do points because that's not fun, right? Because everybody just turns their services off, it's not a fun game. So we have scripts that are running and constantly monitoring everything, so that's actually the key component right now. We're trying to scale to see how we can test all these services correctly. Because apparently 32 cores and 60 days of memory is not cutting it, so we need more. Yes, any questions? Yeah, can we use reflectors? You can try. I don't think it'll do anything. We'll see if it works, you should let us know because that's not a bad thing. All right, OK, so back to binaries. So we need to focus and we need to study and really understand at a very core technical level what exactly happens in an x86 binary and how exactly functions look. So we saw that applications on the stack, sorry, functions on the stack will create a function frame and what information is stored in this function frame in addition to local variables? It's not a line, what is it exactly? Who's memory location? The calling function save. The save VIP, so the caller's instruction pointer. So that's in the function function frame. And how does that get onto the stack? What instruction, what is that there? Call instruction, right? So when the caller calls the calling, the call eat. When it says call food, that instruction pushes the next instruction pointer onto the stack. So that becomes the save VIP. So save VIP and local variables, what else? Arguments. The arguments, the parameters to the function, which will be where? On the stack, where in relation to save VIP? Above, in what order is starting from the bottom up? Left to right starting from the bottom up? Yes. Always tricky, but just got to remember exactly how it goes. So the leftmost parameter will be the one right above the VIP, the second parameter will be right above that, the third parameter will be right above that. So if you're going to push them onto the stack, you go right to the left. Push, push, push. Okay, here. Plus, so you got back, you got VIP, we have parameters, we have local variables, somebody over here. Who, what's the save base pointer? I've got to give you an overview. I mean, where you can start looking at the program, those for the next. Right, but where did it come from? Yes. Yes, so in a call well, but what about the same base pointer that's actually saved on a stack? Where did that come from? Whose base pointer is that? The caller, yes, exactly, right? So the caller gets called, and then it sets their base pointer to the current stack pointer, but before it can do that, it has to save whatever was in the ESB register, EMUV, yes, EMUV register. That's the same as that on the stack. And where is that in relation to all the other variables we were talking about? Do you want to go? Yeah, well, the word was where is it in relation to all the other things we were just talking about? Where's the same? We talked about five things, yes. The low EIP and above what? The local variables, yes. So remember, we have arguments and maybe we can see if this thing works. That's seven minutes, we're still recording, hopefully. Yeah, it's taking, that's good. Okay, so just like we were here, so we can go back to our viewable restaurants diagram. We have arguments, so we first have the arguments on the stack, then we have the save EIP, then we have the same base pointer and then the local arguments. So this is exactly what the stack looks like for every function, so every function will have its own function frame. And we talked about we cannot use the stack pointer because the stack pointer constantly moves at the program executes. And so the base pointer will point to the start of the function frame, although in X86 it's a little bit weird because it kind of points in the middle, right? So when it wants to access local, and when it wants to access parameters, what offset is that from the UV? Positive. Positive, which in the direction we draw, the stack's which direction is that? So the parameter's gonna be up and local variables will be where? No. Negative offsets, yes. And that's how you can always tell. Okay, now we're gonna start the UVP. So we'll go very quickly to this example. We have a function main. It has three local variables, A, B, and C, different types. We set A equals A plus B, and then we return zero. So we can think that the compiler, we talked about this a little bit, right? At high level, we'll just decide on an offset of the base pointer for A, B, and C. And we talked about it's a local variable, so the offset will be negative from EVP. So it will say that A is at some offset from EVP, B is at some offset, C is at some offset. And so the pseudo code, if you think about it from a high level pseudo code, is A. Set the memory location at EVP plus A to be 10. Set the memory address of EVP plus B to be negative 100. Set the memory location of EVP plus C to be 10.45 and add them all together. And so when you compile this, right, there's no specific order that things have to be in to correlate between the C code and where they are stored in relation to EVP. When I did this, it just happened to be that A was at EVP minus hex C, B was at minus eight, C was at minus four, and the code translates very similarly. So we have moved the stack pointer into the base pointer. So what's this doing? Setting the current function base pointer. What are we missing here? What did my glue include here? Saving the old base pointer. So there better be a push EVP above there and we'll get the real program. Then we subtract 16 from the stack pointer. We move, what's hex A in decimal? Move 10 into EVP minus C, which is doing what? Right, so assigning 10 into the memory address that's associated with variable A, exactly. Then we're moving hex 64 into EVP minus eight. Then we move this crazy hex value into EAX, what is this? If you wanted to decode this, how would you figure out what to, how to interpret this? Well, the next one is just moving EAX into EVP minus four. We haven't gotten to that yet. Say that again? The decimal representation. So what type, so what type of decimal representation? Yeah, I truly floating point, yes. There's a specific specification you can look up to know exactly how to represent the floating point number. And so how do you know if it's a float and not a double? Yeah, there's 32 bits here, right? A double is 64, I believe, or is it a bigger? I think it's 64. So why did a compiler do these two steps, right? Think about what it's doing. It's moving this hex representation of 10.45, the IEEE floating point representation of 10.45. It's moving that into EAX, and then moving EAX into EVP minus four. Why? Make sure to know the length. What do you think EAX will use 32 bits? Yeah, who knows? Because the compiler decided it's faster for whatever reason, it's kind of a trick question, but it can get you to think about, you need to think about the semantics of what are happening, right? Not the actual instruction. Do you need to think about a high level, hey, this is just moving this value into EVP minus four. The fact that it takes two instruction versus one instruction from the other two, it's essentially the same thing. This compiler just decided to do it this way. Then we move EVP minus eight into EAX, so what's the value that's gonna be inside of the EAX register at this point? Eight, a hundred, and then it'll add EAX to, let's say an EVP minus C, and where does it put the result? How many arguments do you usually have for an ad instruction? You need what? The two numbers to add and what else do you need? Where to put the result? Where to put the result? So why is there only two here? Someone else in there? Yes. The answer goes to the second prep. Yes, the answer goes in our syntax in the second parameter. So this is add EAX to EVP minus C and store the result into EVP minus C. So this is doing this A is equal to A plus B, and then what's gonna happen right after this? Not yet to the ethalog. We have one more thing to do in the C code that we haven't done yet. Yeah. Is that the return value? How do we do that? Move to zero and DEAX. What's the exact assembly instruction? Move to zero, come to the end. Yes, move zero into DEAX, then we're gonna have the function ethalog, which will be a leave and then a return, and then we should be good as it's thought that it could be fine. So we can actually step through this very quickly so you can see how this plays out. I think we can do this pretty fast. So there's some value in this, so if some value for the stack pointer, some value here for the base pointer, we set the current base pointer equal to where the stack pointer is. We then are subtracting the stack down by 10 at X. So now the base pointer still points at X, what is it, X1000, not 10,000, and then the stack pointer's gonna point down. So we're gonna have 10 into EVP minus C, X64 into EVP minus eight, moving that value into DEAX, and then into EVP minus four. Finally we move EVP minus eight, which is going to be 64, so we'll move that into, yes, into DEAX. Then finally we will add this to whatever's in EVP minus C, which is A, so that should be 6E, which should be 110, and then others just gonna happen. So questions on this part? I think it was the ESP moved downward by four bytes, instead of three bytes. Yes, I think Yegana's working on the ICP app, so she's not here, but if you see the email she sent out on the mailing list, that talks about why the offsets will be, it's probably because it wanted to be on a 16 byte or whatever this is byte found, so it's not a system that can be faster or something like that. Basically the compiler makes a lot of optimizations like that. There are settings you can specify when you compile your application that says it's fine, go kind of as short as you can. Yeah, it's really interesting. It's also really interesting to look at the actual binaries that your compiler produces because they can do kind of whatever they want. Yes. Not easily. How pretty it is. Where do we have the C code? Look, maybe, let's type it back in, and I can scroll those lines. No, P bar. Here. Yeah, here we are not using C anywhere. So doesn't the compiler's model have to know that it's gonna have to be A, B, A and B and then store U only to the Aster to store A and B and then compile it and give it back to the program? Why doesn't it even care for C? Why does it even care? By that logic, why does it even care about A? Because it's gonna print A for it. Yeah, in fact, it's not even orchestrated, so why doesn't it even care for all the Aster? Because the compiler's really stupid and I didn't compile this with any optimizations. And I mean stupid not in the, not in a derogatory sense, but just in the, when you compile it, normally you're not doing any kind of optimizations, right? And so everything just straight compiles one to one. And so you want your compiler to try, the worst bugs to ever debug are bugs that are in the compiler and not in your code. And the worst thing to ever know is to think that there's a bug in the compiler because there almost never is, it's always in your code. So you think the compiler's broken, but it never is. Except for that one time out of a thousand that it actually is, and then you go crazy. So by default, compilers, when they compile things, do it in a very stupid way, but in a way that they know is correct, right? So just line by line, compile it. The other, so as you crank up the optimization level, then they will try to do things like dead code removal and they'll do use analysis to see that nobody uses A to try to remove it. But even doing that is tricky because who's to say that I haven't memory mapped this region where A is to some other process and we're sharing that memory region? Or what if, in general, you can have memory mapped IO, so you can talk to IO devices through memory locations. So there, I would always want whenever I specify a sign to that variable, that memory to be updated. You can add the volatile keyword before any, I believe before any variable type, and that will make sure that it always pushes that value back to the memory, but, so it's kind of a complex interplay there. If we compile this code, compilers gonna check this, check it for semantics, and then it's gonna recompile the code so that the instructions which are similar, they get signed together. So this is the setting that... Not always, it depends, they can't. So the compiler needs to make sure that the generating code still matches the semantics of your program. So at a high level, if you compile it super smart, it can just compile this down to something that just returns zero. There's nothing, this actually does nothing externally. And it could switch, so if it decided that setting B equal to 100 was faster than setting A equal to 10, or that border was switched, it could do that. But it could never lift this A is equal to the A plus B above the A equals 10 and B equal to 100. So yeah, there's all kinds of, if you wanna get into this, program analysis and static analysis techniques are all about dealing with these kind of issues. So that's the stuff I used to find more abilities in research. So, we talked about the calling convention. So we need to know, when we call a function, we need to know how do we get the return value of that function back? How do we pass the parameters to that function? How do we make sure that that function doesn't clobber our frame pointer? Because our frame pointer points to somewhere very special on the stack for us. And where should that function go when it stops executing? When that function's done, how does it actually know to come back to the program that called it? And this is very key, because a function can be called by essentially any other function. We can't hard code where it's gonna return to, and so we need that information stored on the stack as part of the function frame. Also local variables, and some temporary variables sometimes. So, we need a convention. So this is where you just, somebody makes a standard that says this is the calling convention for this thing. And it's actually incredibly annoying because it varies by processor, OS, compiler, or the type of a call. So we talk about C-deckle is the x86 on Linux calling convention. On Windows, Windows same architecture x86, completely different default calling conventions. Calling into system call, so calling the Linux kernel through a system call from user land to kernel is a different calling convention, the syscall calling convention. So actually, the connection right on binary that does multiple of these calls as long as you know which functions to call using which calling convention. There's a way you can tell the compiler, hey, this function uses this type of calling convention. So it's pretty cool. But the Linux x86 calling convention, this is also something you need to commit to memory which is just what we talked about. So the caller first pushes the arguments onto the stack from right to left, right? So going from the top of the stack to the bottom is gonna be right to left, and going bottom up is left to right on parameters. Then pushes the address of the instruction after the call as we talked about with the call instruction. So that's all the caller has to do. So actually, this is a pretty nice calling convention. If you wanna call a function, all you have to do is this, these two things. The callee has to store your previous spring pointer onto the stack. It has to create space for local variables, and it must ensure that the stack is consistent when it returns. This is one of the most difficult and painful errors to ever debug, because everything gets really weird. I was doing some like, this was in like dot net, is it the CLR? It's the dot net. Is that like the assembly language or like the third equivalent of binary? It's like the dot net, I don't remember, but we were rewriting dot net binaries essentially, and then you make some of the sake where the stack is messed up, and you're just feeling like, this is not about the program. It's like, okay, that's an incredible amount of error in the standard code that you're gonna throw. The other important thing is the return values put in an EAX register. So when you call a function, should you have something important in the EAX register? No, because it's gonna be clobbered by that function. There's actually also as part of this, there's part of the calling convention which registers can the calling, can the calling clobber and use, and which registers does it have to save like EVP before it uses it. And that's not super specific to what we're going through, so we don't have to worry about that too much. Okay, so let's look at an example of this. We're gonna main function in IA equals calling 1040. The calling has two integer primers, A and B return A plus B plus one, super simple. So just rewriting what I said on Monday if I didn't make it in the recording. This is something that you should be able to compile this by hand from the C code to x86. And it looks something like this. So what we're gonna be? What's the first thing that main has to do? What's GDP? What's GDP? It has the same, whoever called it, EVP. Next, what does that do? Do we wanna get to GDP? Yes, move just the EVPs. We're gonna set up its base pointer. What's the next thing it's gonna do? Create some space on the stack. Let's think minimum value it must have to create space on the stack. It has to be at least four bytes. It can be more like in this case, when it's, what our x18 is, what was that, x16, x32? So if we do more, but it has to do at least four, right? It has to create this space for its local variable A. The next thing that we're gonna do is we're gonna move x28 into ESP plus four. So it's ESP plus four. We don't know where the stack is, but it'll be four. What does that correspond to in the program? 40, why? Why is it doing this instruction? I don't know. I don't know. Is that argument? Do you think the variables, the arguments? Yes, the arguments to the function, right? They're not local variables, right? We're about to call this function call E. And we need a first, the first variable that has to be on the stack is 40. The next one right below that, four bytes below that has to be 10, right? So we're going bottom up. Right there has to be 10, and above that has to be 40. Now we've pushed all the arguments to the function on the stack in right to left order. And now we're gonna call this function, which is gonna push something on the stack. Now what do we do with the result? What's the next instruction gonna be? Move what into where? Move EAS into where? Okay, so we're gonna move EAS. So we need to finish this A equals call E. So remember, we're a stupid compiler. We don't look at anything in the rest of the program. So we're gonna move EAS to where? That. EAS, again, is it returning? Not yet. We need to do something with EAS. Where are we putting EAS? ESP minus four? EPP minus four. EPP minus four, yes. So we need to get to the base pointer. We need to subtract. So we're a local variable A, we subtract four. We're gonna move EAS into that local variable. This is very important. This whole, you can think of this C line, this line of code, this line of main, corresponds to these four lines of assembly. Because we first have to set up the arguments. We have to call the function, and then we have to store the return value into A. So now what do we have to do? We have to do the final line of main, exactly. So now we're gonna move EPP minus four into DAX. Now we're gonna clean up, leave, and return. Oh, that. So we have to put a log in the app log here. Now we call E. What do we do? Push EPP, so we're gonna save EPP. Then what do we do? Move EAS to EPP. Move EAS to EPP. Next, what do we do? What's different about this than the main example? There's no local variables. Yeah, so we can actually just not, the compiler can actually decide not to move the sack of all because there exists no local variables. So then we need to move EPP plus C into DAX. What exact parameter is this? Who says A? Who says B? Four of you raise your hands. How would you know? So EPP is right where the stack currently is. What is EPP currently pointing to? What is stored on the stack where EPP is currently pointing? Saved base pointer. So the very first thing we did in this function was push EPP. So that's the base pointer. What's directly above that? The saved EIP, right? So right above the same base pointer is the saved EIP, which got pushed on there from the call instruction. And then what's directly above that? Which argument? And then B. So EPP plus zero is saved EPP. And EPP plus four is what? Saved EIP. Does that vary between functions? That's, this is how we've said the stack always is in C-deckle functions, right? This EPP and saved EIP and saved EIP are always in this order. So the current base pointer points the location of the saved base pointer. Four above that is saved EIP. And then eight above the base pointer is what? The first argument, so let's C above the base pointer. B. So it's gonna move B, which in this case is 40, into EAX, then it's gonna move A into EDX. It's gonna load the effective address into EAX. What does this do? What were the exact mandits of the load effective address? What was that? EDX plus EAX times one. Yes, exactly. Into EAX, right? So a question you should ask yourself is why didn't it just use an add instruction? Why didn't it do add EDX, EAX? Because it just decided to, I have no idea why. But the compiler does this frequently for doing additions. It does this load effective address and mentally it kind of always trips me up because I think of, oh, an address it must be dereferencing memory or something like that. No, it's just simple addition. The actual, the load effective address specifically means don't dereference any memory, just do addition. All it does is EDX plus EAX times one into EAX. Now what do we do? So the question is, are we done with all the functionality of this, of this colleague? No. Why not? What do I need? Yeah. We need to add one. So we need to add one to EAX. Now what? Are we done with the functionality? Yeah. Yes. We should be done. So now what do we do? Add log. Yeah, what is it? It's right there. Except it's not. So why did it not do either return? What does lead do? Because it didn't use the stack. Yeah, so what does the lead, what does lead do? It puts the base pointer and the stack pointer. It puts the other way around. It puts the stack, yeah, it puts the base pointer into the stack pointer. So it puts the stack up where the base pointer is and then what does it do? Pop what? Pop EVP. So it takes that value and puts it into EVP. So it takes same EVP, puts in EVP. So again, the compiler decided, hey, I actually don't know, it'd be interesting to look how many bytes the lead instruction is. I bet it's at least two. Here, the pop is one instruction. I definitely have one byte, so I definitely know that. So it's saving a byte because the compiler knows, hey, I didn't subtract anything from local variables so I didn't do any pushes in this function. So I know the stack is exactly the same as the base pointer. So I can skip that part of lead and I can just output pop EVP and then return. Okay, once again, we have a prologue and epilogue. We just have a different prologue or epilogue. Okay, so the important thing is when this is compiled, right, we can see in the program and you should have seen this when you're looking at the object dump of part three of assignment two. And based on the Elf header information tells the Elf header information tells the OS where exactly to load these code segments in memory, right? So that every time this program runs these addresses, this code is at this fixed memory location. And this is why what will happen is the linker will say, oh, you want to call the function call e, great. That's at memory address 8048394, perfect. So that's why we can put a fixed address in here. Now the function food name basically evaporates, right? It really doesn't matter at all in this context. If you'll notice maybe on part three there was only like two function names in that program and that's because you can use this strip function to strip out all of these symbols from a binary but I didn't want to be too hard so I kept some of them in there. The check password is a little bit nice, right? Because it kind of focuses you where to look. But I could have got rid of all of them and then you just see like an entry point and you have no names to talk about anything. It's also cool to get rid of all of the debugging symbols and everything else so you don't have any variable names that you can kind of look at, yeah. So you mentioned about temporary view, right? Yes. So in case we have a temporary view, where does it go into the stack? It depends, I think that it would depend on the compiler. The compiler could decide to just make it a local variable, so to create kind of a fake local variable and add it to the local variable so it allocates more space for the local variable and then when it needs it, it copies it into there. It could also, for that part of the assembly that it uses that, it could push something onto the stack and then later pop it off into a register. So it really depends on the compiler and how it decides to do things. But both ways use the stack. So main is just at some location here. And so the only thing we need to know to start executing and simulating this function is what? What address in the memory do we need to know? We know main, perfect, 8043A5. So if we start executing main and what's gonna be in EID? 8043A5, what else do we need to know? We didn't know where the stack currently is, right? Because we're gonna be moving the stack. So let's say it's at FD2D4. This is actually something I observed on a real system. Yes. And so we know this, so we know because of that the value inside ESB, the stack planner, will be FD2D4 and we know that the next instruction that's gonna be executed is gonna be main because it'll be the value 80483A5. One other thing to note again. So looking at the diagram, are we assuming this program that's running this code has exactly 40 bytes of RAM like from, because it has the memory address all the way from FFFFF to the all zeroes. So this is a 32-bit process, right? We know that because we know it's an x86 program and we know that 32-bit application could potentially access 32, four gates of memory. Yes, and also since all the instructions are stored at the fixed memory address, what if it's running on a computer that has a tiny memory that doesn't have those on the desk? So the operating system takes care of all that for us. So that's why we love operating systems is because they let the process think it has four gates of memory and the operating system deals with this fact of, hey, I actually only have a gig of memory or I only have five to 12 megs and so what the operating system will do is that's what it uses a swap file for. So it will try to keep the most recently used and the most frequently used bits of pages of memory in actual memory, but if some other process needs more memory, it will swap out a infrequently used page to disk and then it will try and bring that back in. So yeah, the operating system deals with all of this using the virtual memory management system. So what do we know about what's currently in EVP or what's a valid assumption we can make about the value inside EVP? Yeah, it's the previous function's base pointer so what does that tell us about the value? It should be greater than or equal. It needs to be somewhere above us, right? Because if it was below us, it'd be garbage and we could overwrite it. So we know it must be something above us. I'm not actually, no, I think this is wrong, right? Your net is above us. That's not what you're supposed to catch. So then when we first execute this, we're gonna push EVP and so we did draw it but once right at that memory address, fd2d4, what is at inside that memory location there? If we tried to dereference that, what would that tell us? What is that? It's an instruction on the stack. So the main function will, yeah. The end of the stack of what we're calling. What do you mean the end of the stack? So isn't there a function that called me? There it is. So what's on the stack right there? Oh, EVP, right? No, so close. Next to 13. Yeah, wait just a second. What was that? No, thanks for that. Next to 16, do we have to call it callE function? Yes, ooh, not after callE because main, right? Some function called main, so it had to do call80483A5 and as part of that, it pushes the next instruction to go to after that to execute, right? So it actually makes sense if you think about it, when you return a value from your main function, what actually happens with that value? It's returned where? Where can you get? It's the exit code of the process. It's the exit code of the process. Does your code, does main set an exit code of the process? No, it's just returning a value from its function. So there's actually another wrapper program that runs main. It calls main, it gets whatever this return value is and it calls, I think, probably system.exit with that return value and that becomes the exit code of your program. Sorry, people are hanging on the line. So we know that right at memory address fd2d4 is the return address, the saved EIP of whoever called main. What's right above that? ArcC, yes, the first argument to main and what's above that? ArcV and what's above that? ENVP, yeah, we'll see that next. That's actually also passed in the main. Great, so now when we think about main, what main does is it pushes EDP which moves the stack down and copies whatever was in EDP onto the stack. So now we've saved the 3D function base pointer. Now we have to move the current stack pointer into the base pointer. So now the base pointer points to where we are and not some more way above us on the stack. We subtract hex 18 from ESP. We then move hex 28 onto ESP plus four and then we move 10 into where ESP is and then we call 80483.84. So when we call what is the value exactly that's gonna be pushed onto the stack? 80483bf, because that is the next instruction we want to execute when this function returns. So when we call this, remember call is basically pushed the next instruction and set the EIP to whatever this value is. So we'll move down and it will copy 80483bf into the stack and instruction pointer will get changed to the first instruction of callee. Then this will execute, right? Callee just got called, right? And this is why we can know that main is no different than any other function, right? So this is the same circumstance that happened right when main got called. And we said what exactly is the stack pointer playing to? It's the same EIP, whatever we call it. And we have to push R E. We have to now set up a new stack pointer. So we push EVP. So now we save main's EVP onto the stack. Then we set the current stack, the base pointer to the current stack pointer. So we move the stack pointer to the base pointer. So now we've set up callee's base pointer and now we're going to write it all. So now we can move EVP plus C which gets the parameter that's passed into this function. And we know it's one, two, so it's going to be four, eight, 12 of the stack. So it's going to be 28. So it's going to be 28 into EAX. And we can see that we're kind of thinking about the stack frame, right? We have main as its function frame and then we have callee as its function frame. And it's a little weird because there's a little bit of overlap there with the arguments, but that's kind of fine. So we can continue. We can move 28 into EAX. So we can move A into EVX. We can add them together, look at the result in EAX. We can add one to EAX. Then we can pop EVP. So now we're all done. We're going to pop EVP. We're going to put fd2d0 back in the base pointer which is going to restore main's base pointer, right? We should put the base pointer back out here. And now we're almost good to go. So now what does the return instruction do? Yes. So it's essentially a pop EIP. So dereference the stack pointer wherever the stack pointer currently is pointing to. Take that value and copy it into EIP. So that's the next instruction that we're going to execute. So we do that. We're going to go down here. And now we're going to move EAX which is x33 into EIP minus 4 which is going to be a local variable A. Then we're going to move that back to EAX with your numbers and change things. Then when we leave, what's the result of the leave function going to do? Where's it going to put the stack? So you have all the memory addresses. Which memory address is ESP going to be at after this is done? Yes. Fd2d0. Fd2d0. 2d0. 2d4. Right, we're going to leave this two things. It sets the base pointer to the current stack pointer. So that's the first part of what it does. But it's not done yet. It's not the second part. The second part is pop EDP. So then take the value that we're currently pointing at, pop it which moves the stack up four more bytes and copies that value into EDP. And now we're done with that. So it was very tricky, but very precise to the one that does, right? The timer pops it up. Then we leave, or then we return. So now we're going to start going and executing out whatever address is up here at memory address Fd2d4. So, key thing to think about. How did Colleen know to start executing this instruction? After we rewind this program, was actually, if you've ever used a debugger, or used a language that you can go backwards in, it's actually really cool. If you ever do any O-CANEL programming, I have a debugger where you can actually move backwards through the program's execution. It makes tracking out bugs really cool. Okay. So this is actually going to be a critical thing when it comes to buffer overflows, right? The only way that this Colleen function knows to go back and start executing this 80483DF is the fact that there is the memory address 80483DF on the stack here. If there was any other value in here, what would this return instruction do? Go and start trying to execute from that value. What if it's 80483A5? Many will start again, and it will do exactly what it does, right? It doesn't know that it was called from the return of Colleen. It just, these instructions just start executing. It pushes ADP, it moves the stack forward to ADP. It's the craft side. It does all this. So, let's think about this, right? Oh, yes, question. Mean subtract 18x from ESP, when it doesn't need that. It's doing this, yes. If you're trying to jump to a function that had arguments, you'd have to also, in your buffer overflows, write to the registers or numbers of the addresses above that, so when you jump to it, it goes. We will get there, yes. But, key thing to think about here, right? When we think about, we looked at ELF headers, and we looked at the different sections of memory, right? And there were different permissions on each section of memory. So, is the stack writable? And how can you prove that to me? Not know that you can't, no, I don't want to. I want you to prove it to me. I mean, function is the right way. Yeah, look at this. We're pushing things onto the stack. When you push something onto the stack, what are you doing with the value that was previously there? You're overwriting it. Literally, to call any function, or to do push EVP, we have to write to the stack. Fundamentally, for the stack to even be useful, it must be writable. It also must be readable, right? We gotta be able to read stuff off the stack that we write there, otherwise it's also useless. So, what's the stop callee from either accidentally or on purpose writing to this value here? Should a well-written program ever write to that? Because a well-written program will only write to the memory addresses that was specifically allocated to it, right? And there's nothing in the C code that corresponds to that memory address. It just happens to be because of the calling convention that the variables are mixed in on the stack with these return addresses. What if I also, before we get here, before this pop EVP, what if I alter the value that's at fd2b0, what would that do? Change main's base pointer when we return. So now, main thinks its function could be somewhere completely different, right? That's actually another type of attack that I will end up looking at. So there are really key things to understand about buffer-overflow vulnerabilities. Fundamentally, I think it would be safe to get keys like, actually I asked this another last one. This is similar to, if you've never heard of it, the story of tonsil and gretel, which is like this fairy tale, where these two kids go off into some scary woods and they have this really smart idea to not get lost. They'll take like a loaf of bread and they'll put breadcrumbs on the ground at every few feet, right? And so without what they want to get home, how do they get home? They just follow the breadcrumbs home, right? This is exactly like this. You can have a function, call a function, call a function, call a function, and call a function on the sack, right? And all of those safe EIPs are your breadcrumbs of how to get home. So what happens if you're in the woods and some birds eat your breadcrumbs? You're lost. And what if an evil witch takes your breadcrumbs and instead of going back to your house, changes the breadcrumbs back to her house? Where are you going to follow and go? Her house, that's exactly buffer overflow. So the idea is you want to change an overflow, this return instruction pointer to go to your code and not the program's code of where it was originally intended to go. And being able to do it precisely and reliably requires really understanding at a fundamental level what's going on here. Not only the x86 code, but also the memory and the sack. Sack overflow, or a buffer overflow, is really any time that an attacker that we can try and write to the stack and try to overwrite this either safe EIP or the safe base pointer. We're gonna focus mostly on safe EIP, but the principles usually apply to safe base pointer as well. So when you declare, do you guys can see code? It's like you and me, we'll do another one. How much space, how much space does the compiler have to allocate for this function foo? How much, a thousand? More than a thousand bytes. Not very specific. Yes. A thousand and four bytes just for the variables, right? The local variables has to be at least a thousand bytes for bar and another four variable. Another four bytes for x, right? So this is, your stack can actually grow pretty big because you have people declare buffers like this. So what's the alternative to doing this? You need to have a sack. How much memory is allocated on the sack? Eight bytes, four bytes from the character pointer. Remember pointers are four bytes because they have to refer to any memory address even though it's only pointing to one byte because it's a character. And int is four bytes, but here we're allocating on where. You need a hundred bytes. So now what happens, so let's get rid of this x. And let's say the compiler gave us exactly a hundred bytes for our local variables. And let's say I did, what did I do? So it depends on exactly how the compiler lays out this program, right? So that's another important thing I want you to understand is that really the C code is just your guide. You should always look at the assembly code to understand exactly how to do it. This will, you can get by by kind of looking at the C code and doing stuff, but really you can be much more effective. You can be a lot faster when you're solving these challenges and problems by looking at the assembly code. And actually it's super, I mean it's a skill that like security professionals love. So we think that this program has a hundred bytes on the stack. So this would be the address of R, right? That has x, it has a hundred bytes on the stack. So we set up EVP, so EVP is here. And we subtract a hundred from ESP for the local variables. So what's at ESP plus a hundred, right? Save EVP and what's four bytes above that? Ooh, so this is wrong, right? So where is this gonna address? Will this type check? What type is this? Character, what type is this? Character, what's this type? An address, can an address and a character be the same type? Ooh, we have a problem, are you gonna ask something here? So specifically we're looking at a hundred bytes, what's the hundred and first byte plus ESP or plus R which is where that ESP is. It is one byte inside EVP, so it would be this byte here, right? So to get to E-F-E-I-P, which would be above that, I have to go one, two, three, four, and I can only control it by the time. So that's the example of the character of it. But the idea is I can access, right? There's nothing, as we saw in C, there's nothing that prevents me from accessing outside the bounds of an array. And if I know exactly where that array points to on the stack in relation to EVP and the same EVP, then I can know exactly how much I shouldn't overflow to get to that same EVP. And so if we're just writing random gibberish, right? Let's say we just, we have a buffer of a hundred bytes, and we give it a thousand A's. So it does a string copy. So it does a string copy from our input to the buffer on the stack. And string copy is just as stupid as every other function. It's gonna take a byte, copy it over, increment the pointers, say, is this the null byte? Nope, copy it, increment, copy it, increment key going. And it'll copy a thousand bytes over. And so what happens then to the EI, the same EVP and the same EVP on our stack? Completely overwritten, right? And so then what happens when that return statement executes, where's it gonna try to return to? Yeah, it depends on if we use uppercase or lowercase a's. If we use, I don't remember which is which. I think lowercase is what, 62? What? 62? Lowercase a, hex in hex. Decimal, things are on decimal, asky characters. I think it's 62 in hex. So it would try to access, it would try to start executing from memory address hex, 62, 62, 62, 62, 62, which is probably not allocated to the program so that we can get a segmentation fault. And it'll crash. But normally this can cause a segmentation fault. But if we craft our input correctly and we put the bytes in exactly the right order, we can control what value is copied into that same VIP. And therefore we can force this program to execute any address of our choosing as it's next instruction that it goes to when it returns to that object. And so as we'll see, so this is a little bit going forward, we're kind of exploring this in a historical perspective. So it used to be that the stack was executable. So the program would actually jump to and would execute anything on the stack. So as long as you can put some code that you want to have executed into the process, and if you change that VIP to be at the start of that code, now the program will start executing your code on the stack. Yes? Should they use that non-malicious thing in their code? Yes, so this is exactly how JIT works. So when a process is jitting, right, it takes like, let's say, JavaScript code and it looks at it, it sees how it's executing and it compiles it on the fly to x86 code. So it really takes and generates x86 code and has to jump and start executing that x86 code. It's usually on the stack where they have to deal with memory regions and making them executable. So yes, so there's good measures taking a sense there, but we will kind of go and take this historical perspective of, okay, this is how exploiting all forever's load was done and so we'll see what prevention techniques were made from that, and then we'll see ways that clever, crappy attackers have got around that. So hopefully by the end, you'll have zero faith in any defensive measure because attackers are very smart. And so we'll get into exactly, we'll call this the shell code right now without thinking about it too much. We'll look at exactly how to write shell code because this code has very certain properties that we need. This code that we want to start executing from could be part of our buffer, right? So if the original buffer's 100 bytes, we know we basically have 100 bytes at least up until the saved EIP and so if we can make our shell code fit in those 100 bytes, we can actually put that there and try to jump back to that shell code. We also may be able to put it somewhere else. We may be able to put it somewhere else in memory as long as it's somewhere in what kind of memory region executable, it has to be executable, right? For us to jump into our executable. And the key thing that we've been talking about is why we study Unix and Linux applications is that the code that's executed will be executed with the privileges of the program. So this is a setUID application and it will be executed with setUID privileges. So we can look at a super simple example of a function that copies from a parameter that's passed in into a local buffer and string copy doesn't know about any sizes. What are the parameter types to string copy? Character pointers, right? And see, character pointer has no metadata about how long this string is that, or how long, how big the buffer is that this character pointer points to, right? So it's nothing to stop string copy from copying over food and string copy itself is really fundamentally insecure because the semantics of string copy is copy bytes from string into food until you've reached the null byte of string, right? So if the left side is ever a fixed location and the right side depends on unbounded user input, you can always have some sort of vulnerability from that because if you can't mount the input, then you're toast. So the main function, I took this from my old CSE340 class as a hint to get our professor to never put dates or class names in your class, this is impossible. So we do this, so we can print after, we can return zero just to see what happens. So if we compile this, we'll see that we do our normal epilogue, we push EVP, we move the stack pointer into the base pointer, we subtract 10 from the stack pointer, we move 804, 8504 on TDSP, what is this? It's what? Address about string? Yeah, how would you go about verifying that? Object on my help, what else? How would you figure out what section of memory this is? What was that? Well, hopefully it's read-only data, but how would you verify that assumption? Read-elf, you use the read-elf command to look at the elf segment, the segments of this binary and you'd see that hopefully 804, 8504 is inside the read-only memory region because this is a constant fixed string and that's exactly why the compiler can put a constant address here because it knows that EOS will load that at a fixed location. And if you look inside the binary, you will see at the offset that the OS says 804, 8504 starts acting in the binary, you will find the bytes that correspond to ASU space, CSE space, 340. Then we'll call this my copy function, so now we've set everything up, right? We've passed it up forever, we've copied that value onto the stack, we call my copy, we then move 804, 8504, 18 and TDS, what's this? We're going to show you the string AFTER0, we're going to show you a null byte at the end of this. Then we'll move EAS on TDSP, that's weird, that's not exactly the same instruction, but this one's too interactive, it's using EAS and this one's doing it wrong, I don't know why. Call it printf and then move 0 into EAS called leave and then return. So my copy is very simple, it's going to subtract 28 from ESP hex, then it's going to move EDP plus 8, what's EDP plus 8? ARG0, which in this case is the STR, the string parameter, which will be 804, 8505, 17 when it executes, it will then move EVIS value EAS into ESP plus 4, which is going to be the right-most parameter to string copy, then it's going to put what into EAS? 0? No return statement. Oh no, okay. No return statement. No return statement, but yeah. So this is actually the, so this is the important point of load effective address, right? It is not saying dereference whatever is that EDP minus C, so we know it's minus, so we know what about that. It's below EDP, so what do we know about that? It's a local variable, right? It is not saying move the value that's at EDP minus C into EAS. What's currently the value inside EDP minus C? We don't know, it could be anything on the stack, right? All we did was move the stack pointer down hex 28, whatever junk was there will still be there, right? We don't know what's there, and so now we need to, so remember the load effective address takes the value in the EDP register, which points to the base pointer, so it takes the address of the base pointer, and it subtracts C from it, and it moves that value into EAS, without dereferencing it. So this will be essentially the address of food. So how many bytes exist between the buffer food and let's say the current base pointer, C. So then how many bytes between food and the save EIP? There's C, and then you have four more bytes to get to the, so you have a C, let me guess how many there's C. 12, we have 12 bytes, we have four more bytes to get past the base pointer, so that's 16, and then the next four bytes will overwrite whatever is in EIP, and we know that not from looking at the C code, because from the C code, we would think that it's, would be four bytes only, right? There's only one local variable with four bytes, but from looking at the assembly code, we know exactly how many bytes it is. So then we move EAS into ESP, and then we call string copy. So what's a string copy gonna do? It's gonna do exactly what its specification and its vantage says that it's gonna do. It's gonna do reference to the byte of string, and it's gonna copy it to dereferencing food, it will increment whole by one, it will copy a byte, increment whole by one, until string points to a null character. That's all it does. So it's good. Then it leaves in returns, because there's no return value here, so it's up. So, if we look at this and step through this quickly, we will see, we'll push EDP, we'll move that, we'll subtract the stack down, we'll move that on there, we'll see we actually need a much bigger stack than we previously had. We will then move, so the important part here is right before the string copy. So the arguments of string copy, the first one, so the first argument is FD2AC, which is this address on the stack. Above that, the second parameter is 804, 8504, which is the string that we passed in, that constant string. So when we call string copy, what it's gonna do is copy that A, ASU space onto, so A will go at FD2AC, ASU will go at FD2AD, and so on and so forth, those four bytes. The next four bytes will be copy of that, four bytes of that, four bytes of that, four bytes of that, four bytes of that, four bytes of that. And so now this returns, now when we get to the leave and return, the leave puts everything in here, now we return, what address is it gonna try an execute from? Let's start by an execute. I guess we're gonna run it down to the leave. Oh, yeah. So leave instruction, so RET is gonna do what? Pop the IP. So it's gonna try us to start executing from 3.1, 3.0, 3.2, 20. So it will probably cause a set fault, and when I run this program, when you run it, you will get a set fault, and you can do this too on your own computer, you will definitely get a set fault, I think, depending on how you're facing others. And the very cool thing is if you run this in GDB, and you run it, it will say, hey, I'm starting this program, and it will go, ah, I got a set fault, I got a set fault, I tried to access memory 31, 30, 32, 20, which means we know exactly where that happened. So you can do fun things like change the program, and these four bytes, let's see, is it one, two, three, four, was that fault? No, it's the space in there. This one. This one? You can change these bytes, and you can change them to whatever address you want, so you can make it loop back to itself, you can make a few of all kinds of cool stuff. And if you look at the registers, so on GDB, you can do the info registers, you'll see exactly these values in there, you'll see that EIP has this value. And fundamentally, once we can control EIP, we can make the program do whatever we want. And that is ultimately our goal with all of these binary expectations, is we want to control EIP. So, remember all this, we're gonna get back to it on the break.