 Okay. Great. So let's, let's get started today. I want to, we've got a lot to cover. Can somebody mess with the light switches on the sides to see if we can get these ultra-light, ultra-bright lights on the screen to go away? Okay. While we do that, nice. Look at that. All right. Great. So let's talk. So we talked last week about stack overflows and we saw how a stack overflow can work. But now our main goal is, and as some of you have probably found in your, on assignment four, right? You want to see, well, how do we exploit a stack overflow? So assuming that we can control and modify the saved instruction pointer on the stack, how do we actually get that to execute code of our choosing? Right? And so this is actually, there's a lot of different techniques to do this and they depend on exactly what type of architecture the system is running. Right? If you think about it, right, you're trying to essentially inject some code into some other process and have it run, right? So this depends on what kind of machine this is. It's a x86 machine, if it's a 64-bit machine, if it's an ARM machine. And so this will depend on the assembly instructions, what operating system you're using, the specific stack alignment could create issues. And so this is probably the best. I think Gavin sent this out on the mailing list. This is a great paper that can help you as you're going through and trying to deal with this stuff. Smashing the stack for fun and profit. Still a fantastic resource, yeah. I think you said you used another GCC to compile some of that stuff. Yes. Could you possibly put that on the server for us so that when we're going through this? They're on the slides and the server. I mean, it's not that it's an old version of GCC. It's that there's a bunch of flags that we use to... So we'll see in a few more slides there's the exact command line parameters that we're using to compile these things. Yeah, so this way you can practice. With those you can use all these examples to try to practice as well. So the goal of this, what we call this code that we're injecting into this other process, we call it channel code. So the goal is we want to execute code of our choosing. And this is the entire point of trying to subvert this other application. And so, and the key point as we saw is that this code that we execute has the exact same privileges as the vulnerable application. So this is how you're able to move up through the levels because every level is a group set UID as the next level. So once you break into that, you have permissions of that group to that next group. And when you run the leap command, all it does is add you to that group. So permanently, so now you're permanently part of that group. And so shell code is the term, standard term for this type of code. It's called shell code because you're trying to pop a shell or basically execute slash bin slash sh. So why bin sh? You get this everything, right? We have a beautiful interactive prompt that we can type in commands just like you have in your command line, but now that command line is executing as this user with higher privileges. And really, so shell code is kind of a misnomer at this point. It's really just assembly code to perform some specific purpose. If you're attacking a web server remotely, you don't really want to run bin sh. You want to do something else, right? You may want, there's all kinds of other stuff. You may want to drop a file, you may want to steal a file. You may want it to open up a reverse shell. So connect to you on your system, on your port, and run a shell like that. So there's all kinds of cool ways to do this. So we're going to look at building some shell code from scratch to execute bin sh. So in essence, this is what we want our shell code to do. So we have our main function where you have some character array. We want the name, the name zero, so the first argument of this character array to be bin sh. We want the next one to be null. And then we're going to call the linux sys call exec ve, right? So what's exec ve going to do again? System call. Yeah, it's a system call to do what? To run the batch, to run the command. Yeah, so exec ve is going to take this first character pointer, right? Execute that command, look up a command bin sh on the server, and then it's going to pass as the second one, the rv vector, right? So here's the rv vector, rv zero is going to be bin sh and rv one is going to be null. And then find it, what's this last one? It is null, yes. End of arguments. The end of arguments is specified by this null as the name here. So this is a null terminated vector of character pointers. This is the environment, so this is the new environment. Remember when we exec processes, they inherit our environment. We can change the environment. Here by specifying null, we're saying it has another environment. So what is exec going to do under the hood to this process? What is the operating system going to do when it gets this call? Call into the kernel to get the kernel to do this. What's the kernel going to do with our process that's currently running? Almost, not actually going to for it, right? Because we would have to for it. What's the next thing that's going to happen? What process can do this tech and jump to this new process? Yeah, so exactly. This is going to completely wipe out our process and replace it with bnsh. So you can think of an exec as replace this process with whatever I'm passing in here. So it's completely started new, blow away everything. It doesn't matter. Replace this current process with bnsh. So should this ever return? It should never return, right? Because we call into the kernel and the kernel sets up a whole new process space getting rid of this code that we just loaded, right, of this program. But in case there are any errors, right, we'll call exit. Just because we're next programmers. Just in case. So this is a C version, right? So we'll see exactly how this looks like on a stack. Sorry, how this looks like an x86. So we can see how we can create some shell code for this purpose. But yeah, as we'll see system calls in x86 assembly, we're going to save the parameters for that in mostly registers for the system calls we're interested in. And then we call a software interrupt as we'll see as int x80. So the system calls are interested. We're interested in the exec ve, which takes in the filing, the program to execute an rv pointer and an environment pointer. So what this means is it needs the value of oxb, which is 11 in eax. So now this is not the C-deckle calling convention. This is a specific syscall calling convention. So we're telling the kernel, hey, I want you to execute the exec ve function. And I know that because 11 is going to be in eax. And there's a syscall table that you can look at. If you just look up the syscall table on Linux xa32 bit, it'll tell you exactly the mapping between integer numbers and sysfalls. So 11 means exec ve. Then in what is the first argument to exec ve, we're going to put the address of the program named into ebx. So ebx is going to have the filing. So then ecx is going to have the rv argument and edx is going to have the last argument. So in ecx, we're going to have the address of this null-terminated rv vector. So it's going to be a pointer to something. And that first something is going to be a pointer to bin sh. And the next thing after that is going to be null. So finally, the environment pointer is going to be null. That's pretty easy. And then we're going to finally call int 80. So that's an x86 instruction int 80. It's execute a software interrupt. And the specific interrupt is 80. And so the kernel sees that when it gets the interrupt in its interrupt handler. It sees, OK, what interrupt was it? Was it? Oh, it's an 80 interrupt. That means it's a syscall. Let me check eax, the value of eax to see which system call to call. And then it finally goes and transfers into the exec ve function. And the exit, just for completeness. So the exit is a lot simpler. The exit is value one in eax. So the syscall table, this means one in eax means exit. We put the exit code, the status code that we want to exit with in ebx. And then we call int 80. And so this is how we're going to build up this exit. So what we need to build this shellcode. So we need a null terminated string slash bin slash sh somewhere in memory. So what does it mean, null terminated? Yeah, null by the end, right? Slash bin slash sh, null, right? Zero, that's what we need. So we've got to have that string somewhere in the process. Process is memory. Then we need the address of that string somewhere in memory. Followed by a null pointer. A null pointer, so it would be not a pipe, but a whole null. And this is going to be our RV parameter. And then we need to have the address of a null word somewhere in memory for environment pointer. So our steps basically are going to be for our shellcode. We're going to first copy b, which is 11, into the eax register. We're going to copy the address of the string bin sh into the ebx register. So this is just putting what we want to do with those definitions of the syscalls. Then we're going to copy the address of bin sh, right? Whatever bin sh is into ecx. And finally we're going to copy the address of a null word into ebx. And that's executing it 80. And then we'll do our cleanup. We're going to move one into eax, zero into ebx, and finally execute 80 again. So we can write some shellcode that does exactly this. So we can write some preliminary shellcode. So we're going to do it. We're kind of going to refine our shellcode step by step. So here we have in the data segment, we have a label called sh, which is the string slash bin slash sh, followed by integer zero. So here's my text segment. So here's my code. And the global means that export main is a symbol. So main is going to be this way the linker knows how to call this when we compile this. So in main we're first moving 11, which is the same thing as b, right, into eax. Then we're going to move dollar sign sh into ebx. Is dollar sign sh a constant? Right. So it's going to be the compiler is going to, or maybe the linker is going to replace this sh with the address of this string slash bin slash sh. So this is going to be done by the linker as we'll see. Then we're going to push zero onto the stack. Right. So this first part, right, moving sh into ebx. So now in ebx is the address of sh. But now we need to create that array, right? We need an array who's first, who has a zero followed by the address of this string slash bin slash sh. We're going to use the environment pointer for that. Luckily we have a nice handy way to store and save values. So we're going to use the stack for this. So we're going to first push zero onto the stack. That's going to be the end of our array. And then what are we going to want next on the stack? The address of the string, yeah, sh. So then we're going to push sh. Now the stack pointer. So the stack pointer is the address of this address of sh followed by zero, which is exactly what we want. So now we're going to move the stack pointer into ecx. Right. So this is the rb parameter. And then we're just going to move zero into edx, right? Because this is old. So we're just going to move zero into there. So this is going to be a software interrupt. The kernel is going to handle everything. Then hopefully, if everything went correct, what should happen? Yeah, we should just see as if we executed slash bin slash sh, right? But in case that didn't happen, we'll move one into dax, move zero into epx, and this is the same thing as calling exit zero. Any questions on this? Let's repeat them. Yes. Yeah, so it's actually pretty cool. So the idea is, yeah, so the idea is we need an address to an address, right? So rv is going to be, rv zero is the, is, rv is a variable length, a variable number of character pointers terminated by null. So the first element there is rv zero to our program. So the idea is, so we have our strings, right? Slash bin slash sh followed by, followed by zero, right? We have this string in memory somewhere, right? Based on this code, we know this is going to put that string here in memory, and it's going to give it the memory address sh. So here, so the first thing I'm doing is I'm moving this memory address into epx, right? So that's pretty straightforward. So this is that string. So for exact ve, I pass in a string, right? So exact ve, the first argument is a character pointer. So I just pass in the address of a null terminated character array for bin sh. But now what I'm doing is I'm pushing, so you think about the stack is here, right? So esp points, let's say here, right? Now when I push zero onto the stack, right? That's going to be 32, 32 bits of zero. And now the stack pointer is now going to point to here, right? And then I'm going to put, so let's call this, let's use alpha right now, right? Because we don't actually know what it is right now. So we'll say alpha is the address of this bin sh string. So the next line is push alpha onto the stack, which now means esp points to here. This points up to here, right? So now I'm going to move the stack pointer into ecx right at this point, right? So in ecx, it's going to be pointing here, right? And so our exact ve says, okay, the second parameter, right? The second parameter needs to be basically a character star star, right? An array of character star pointers, which it's null terminated. So we need to put the address of slash bin sh followed by a null. So here when we dereference this once, right? ecx zero is going to give us a character pointer, which points to the string slash bin slash sh. So this is going to be the first rv parameter. And then exact ve is going to say, okay, what's the second parameter? It's going to increment ecx, technically by four bytes, or one character star pointer, or one character star. So it's going to point here, and it's going to say, okay, this is null. So this means there's only one argument past here, right? So rc, when I execute this, is going to be one. And the rv is going to be the string slash bin sh followed by null. So we need to see with the alpha, we could get alpha, right? The program is going to give us alpha, it's going to give us this address. But we needed this pointer to that address, and that's what we're using the stack pointer for. Bin slash sh goes into evx. It also goes into evx, yes. So when we execute a function, right? So exact ve is the final name, right? So that goes into evx. And then rv, so it's rv0, the file name that I probably mentioned, right? You could actually technically do this with null, but bin sh executes a little bit funky if that parameter is null. And this is because it's usually linked to bin dash, or bin dash depending on your system. And that program will look at how it was called to try to determine how to behave. So we're actually just being nice here. But it shows a good kind of strategy here, how we can build up this thing using the stack. So does this terminate with a null, I mean a zero as well? Yes, rv in ecx, it terminates with... Yes, so it's a character, so the type here is character star star, right? And so it's an array, and it's a variable length array. So it says when you hit a null, which is the null here, that's when you know you've reached the end of the rv. So full 32 zeros. So we solved this. And so you can actually compile this with gcc, right? As we saw with the assembly code. We're using m32 to say we want it to be 32 bits. So this will compile just fine. And then we can execute it. And so what do we expect to have happen? Yeah, a new process. It's just as if we called slash bin slash sh. And so when we do this, it'll show us like this. It'll wait for us to type in commands, so we can type in any command that we want. So let's dig in a little deeper to see how this works. So after we compile this, we can use object dump to see what exactly, how did the compiler compile our assembly instructions. So we can see it looks very similar. What's maybe one thing that's changed? Yeah, right? So the linker replaced the dollar sign sh with 804.961c. So what we wanted, so let's think about this. So is this a good test of our shellcode? Is it a good test of the shellcode? Yeah, right? If this doesn't work, our shellcode is not going to work, right? If the shellcode, if it failed, if it exited, if it exactly E didn't work so it didn't set up the arguments properly, this would absolutely fail. But we want a better way to test the shellcode, right? So this code is executing inside its own process, right? Everything's being set up nicely. But what we really want is we want the ability to inject this code, to have this code be inside another process and then have that code get executed. So to help us do that, we're going to use shellcode tester, a little function, a little program to help us test the shellcode. So this is something I recommend you do. So we just have a main function and we have an array of shellcode. So here, so how did I get these bytes? Object down, yeah, so I just took these bytes, right? And I just copied them directly into the string. So in C, this is how you specify specific bytes in a string. The slash x means I want, actually, the integer value of the byte value of xv8 as the first character, and then x0b and then x00. So you can use this to specify a constant string literal as non-asky characters. And then I'm going to declare, okay, I have, so this is the way you declare a function pointer in C. So you say, okay, I have some function called shell. Then I'm going to set shell equal to the shellcode. So this is going to copy the address of shellcode into shell. And I'm going to call shell as if it was a program, right? So it's under the hood, it's just going to call jump. It's going to say, actually it's going to copy this into a register and I think it's going to do jump to eax or something like that. So what are we looking for here? How do we know if this is going to work? What's our test conditions? What's our hypothesis? How do we know if it's going to work or not work? Yeah, I forgot what we did before, right? If it pulled up the dog sign SH 4.1. So you can compile this one like this. For this one you have to have the dash Z exec stack flag to make sure that it doesn't do an executable stack. But you can compile it and compile it's fine. You run it with A got out and it does not do that. So it calls the exit function. So what was the problem? So look at our code. So what's the problem here? Yes, exec is returning. And then we're exiting. Exactly, so that's what happened. But why did it fail? So why did exec be e-fail? So what about the hard coded value of the address? Now we're inside a new program. Our old program is referencing the string slash bin slash SH specifically by this memory location. Because the compiler made sure that at that memory address were the bytes slash bin slash SH 0. It made sure it was exactly there. But once we take this shell code and put it inside a new program, who knows what's at that memory address? We're referencing some hard coded address. We're executing inside some other person's process, some other program's process. So who knows what's at that memory location? So this should give you the hint device of the preliminary shell code. This is the first thing you write to make sure it does what you actually want it to do. So these are the key problems here. So A, the first thing is bin SH is not lived in this code at all. So bin SH may not even be in that program. So we have to introduce bin slash SH 0 into that program. And then we have to make sure that we can get that address. So what did we use to get the address of RV? We used the stack, right? We used the stack to get the representative. We pushed the things onto the stack. Because we know no matter where that program is executing, the stack pointer is a pointer to a current location on the stack. So why don't we use that same trick again to get a pointer to bin SH? So if we push slash bin slash SH 0 onto the stack, then we can use the stack pointer to get a reference to that. So now, we want to make sure, okay, now let's rewrite our shell code, but let's not use any data segment, because we don't want any hard coded data in here. And so this is what we're going to do. So we have the main functions, so we're saying we're going to move 11 into Eax, which is hex B, which is the value of exactly E. Now we're going to use the stack. So we're going to push onto the stack the string slash bin slash SH 0, or the bytes. But remember, bytes go, or strings go up, right? They're red from the first string up. But when we push things onto the stack, they're going to be pushing them on high to low. So we have to push the string onto the stack in reverse order. And we're going to use that, so basically we're going to want to push slash SH 0 and then push slash bin, yes, slash bin and then slash H 0, SH 0. So this is four bytes, so we can do this with one push operation. So we have to do it in reverse order, so we're going to push 0 at HS slash, and then we want to push slash bin onto the stack. So we're going to push N, I, B, slash. And so now SH points to the string, the null-terminated string, slash bin, slash SH. So now we're going to move the stack pointer to EBX. So now we've introduced that bin SH into the program. So this was just to get that string into memory and get the address into EBX. But now we have to create the RV parameter. So first we're going to push 0 and then push EBX, where EBX now controls that address higher up on the stack of where slash bin slash SH lives. And then we move the stack pointer into ECX. And then we move 0 to EDX. And finally we're all done, so we can call our exec of BE. So now we can call in 80. And in case this didn't work, we can clean up and call exit. So questions on this? So everybody believes that wherever this executes, it's going to be, it's going to work, no matter where in what address this starts executing at. Yeah. What was happening when you were hard coding the address? What was happening with the exact BE function? I was returning, but... You basically, the kernel tried to access that memory location, right, 804, whatever, whatever. Either it was not mapped, in which case it failed, or it was some string, some weird string, and then it tried to find a command and it failed that way. So what was the command that existed? What was the site fault? It's kernel space, right? So you as the user should cause the kernel to site fault. So it'll just return an error message. If we had tried to g-reference that memory, it would have given us an error. But because we're just passing, we just pass the value to the kernel. So you're like, since it was a sys call. Exactly. Yes, because it's a sys call, right? The kernel handles everything. It's not going to kill your program, because your program didn't do reference memory that didn't exist, right? The kernel did on your behalf, kind of. Okay. So we want to do our very first sandwich act, right, by compiling this. So we compile this, and we run it, and we get our shell. Yay, great success. Okay. Now we want to look at it again, right? So we want to take the object out, so we can look at the output here. And then we want to see, so are there any hard coded references in here? No, right? We're pushing constant values onto the stack. We're moving things around. Yeah, no hard coded value, right? Wherever this code starts to execute, no matter where it exists, or what process it exists in, right? As long as there's a stack that it can push things onto, then we're good, right? That's a pretty safe bet, because your program has to have stacks, right? That's kind of the standard way of doing this. Okay, so now let's test this shell code. So we put the shell code into the buffer. We're going to set this function pointer to be the shell code. We're going to execute it. And so we're going to compile this with an executable stack, and we run it, and we see that, yay, it gets us what we expect, which was the same thing as our original one. But what's the problem? So can we use this? We could. We can use this. What could be one problem? What are most buffers composed of? I'm going to say buffers. Like what program is using buffers for? There's a lot of, yeah. Character strings. Character strings, right? There's strings. What's a character string in C? A bunch of bytes. A bunch of bytes. A bunch of bytes. A bunch of bytes. Nole terminated, right? So if we wanted to trick the program into thinking this was a string, what would we have any problems? Yeah, there's noles inside of it, right? So if we try to do a string copy from this into a buffer, it's going to read v8, 0, v0, and then stop. It'll only copy those two bytes over. And some, as we saw with some of the overflowing functions, like the scanf or gets, I think, they'll also stop with a new line. So we want to make sure there's also no new lines in our shellcode. So I won't go over this. It's actually a lot of fun, and there's a whole, you know, this is x86, right? So it's completely, you can do whatever you want with it, right? It's turn complete. So you can, there's all kinds of shellcode. You can write shellcode that looks like ASCII characters. You can write shellcode that doesn't use, I don't know, any other random weird characters with that. You can write shellcode that has a very specific check sum too. Maybe you have to pass some weird check sum thing. So I'll share with you. So this is some shellcode. Yeah. So this is some shellcode I wrote by tweaking that original program, right? So you're only going to start with something that works. And then you want to tweak it to try to eliminate all the null bytes from the resulting output. So it uses some tricks, right? So we can see that right here, this first null bytes comes from here, right? Moving V into EAX. Well, so what can we assume is inside EAX when we first start our shellcode? Is it going to be zero? Maybe one? Maybe two? Three. It could be anything, right? I mean, our code, we're trying to get the program to execute our code. We have no idea what this state of the registers are at this point. So pretty much the first trick we're going to use is before we use a register, we need to clear it, right? We need to set it to some known value. So what happens when you XOR a number with itself? Zero. Yeah, so we can use that trick. And it turns out that the XOR EAX EAX doesn't have any null bytes in it. So it's going to clear the EAX register. I'm going to use another trick, but I'm going to do it later. So now I have a register that I know is zero. Eventually the value 11 has to be in this register EAX. We're going to wait to see how that gets in there. So then I'm going to push EAX onto the stack. So what's that going to push onto the stack? Zeroes. So this is going to be the null termination of slash bin slash sh. So if we look at this, we can see the constant value that we're pushing is actually directly in the shellcode, right? So by pushing that zero in here, that's causing a zero byte to be in our resulting shellcode. So I'm going to say, OK, let's not include that in here. Let's push that zero onto there. And then we're going to use a weird trick. Oh, the comment's not correct, but what we're going to do is, we'll just show you. We're going to use a trick that if we do, so we want eight bytes, because we want this to be four bytes and then four bytes. So we've been pushing slash bin slash sh zero. So I've already pushed the zero, but now I have an extra byte that I don't know what it is. But if I preface this string with a slash, it'll be slash slash bin slash sh zero. And to the operating system, slash slash is the same thing. You can do this as many times as you want. You can do ls slash slash slash slash slash slash, and it will just compromise that in one slash. I guess I can even index that. Thanks. That's my part. So that's what's here. So it's backwards. It's like sh slash n i b slash slash. That's what we're pushing onto there. Now we do exactly what we did. So we move the stack pointer into the base pointer, and we look at our original one and see a stack pointer into evx. There's zeros here. There's a zero here. So we're pushing zero onto the stack, constant zero. But what do we already have? That's zero. Eax, right? Yeah, we can just push Eax. So we can push Eax. Then we can push evx. That should be fine. Move the stack pointer into the ecx. Move eax into edx again, right? Because we have move zero into edx, but we already have zero in eax, so we move zero into there. So we need to call it 80, but we never set b into eax. So one thing we could do is we could just like, you could call add a bunch of times, like 11 times. We could just add or increment. I think there's an increment function, right? To just increment eax 11 times. The other trick we can use, remember when we talked way back about the size of the registers in eax, right? So eax was the full 32 bits. And so what was like the 16 bits in ex? And then you would split that up into ah, which is the high and al, which is the low. So what you can do is you can reference al. So now this byte is only instead of here where we're moving along, right? We need to move 0, 0, 0, 0, 0, 0, 0, 0, b into register eax. Now we're just moving byte 0b into al, the lower 8 bits of eax. But we know what we know about the rest of eax. Zero. Zero. Yes, this will work. So we move 11 into there. And then we call our hand 80. And then here we set eax to 1. And then we set eax to 0. We call hand 80 again. So now we can compile this, run it, and we get a shell. Yay. So that we can test it to make sure that it works. Hopefully it was anything like this. But, morning. So this is something you got to do yourself. So I messed up some bytes on these bytes here. So I want you to do this on your own because it's really fun. Get your own shell code. There's also other websites you can get it from, but this is cooler. Give me a bite of your hand. OK. So now we know, OK, we know how to create, crack that code, right? We need to get, right, so that buffer that we have, that shell code we just created, we know if we start executing exactly at the start of that shell code, we will get a shell, right? So what we want to do is overflow. We want to get the instruction pointer to point to the start of this buffer. And so what we're going to do in general to overflow, we're going to overflow with first the shell code, and then the address of that shell code repeatedly. So this will look kind of like, so we have our buffer here. We have the base pointer. We have the save VIP. And then we have other things on the stack. So we overflow it, right, with a long string where first we have our shell code, and then we overflow with that address of the buffer that we want it to go to. So that way when that ret instruction happens, right, whatever's inside here is taken as an address and we jump to it and start executing right at the shell code. The problem is, is that in most cases, this address is not exactly known at runtime. As we saw, the environment can change the stack because this address of this buffer is on the stack. And you have to essentially guess it and your guess must be incredibly precise because you have to start executing on that first byte. Otherwise, if we executed anywhere below, it's going to fail, and if we execute anywhere above, it's going to fail too. So one thing you can do is to use GDB to try to get the address of that stack inside GDB, and that will give you at least a good target to shoot for. And if you basically have the same environment and you know the size of the plan line parameters, you can guess roughly the size of the address of the stack. You also have to guess that offset. But there's a very cool technique called a Knop sled. So a Knop means a no op. And so the idea is, well instead of having to execute directly at that very starting byte for your shellcode, if we use a series of no ops. So an x86 of no op is x90. So x90 just means do nothing. And so what you do is you first add a bunch of no ops to the start of your... So what you're going to do when you do your overflow, you're going to overflow first with some no ops. And then your shellcode. And then the buffer address. But now, instead of having to be precisely right at the start of your shellcode, if you jump anywhere inside this Knop, it'll just do nothing, nothing, nothing, nothing, all the way to your shellcode and then start executing your shellcode. So in here you're essentially increasing your chances of taking your shellcode by doing this. And so yeah, as long as you get it similar within that Knop, then you're fine. We want to exploit this following program. So this is a simple vulnerable function that we're calling. We're passing in rv1. Who controls rv1? User. So the user, yeah. So we're copying that into copy, right, which is a buffer that's allocated on the stack. That's only 512 bytes. All that we know really is that anytime a string that's bigger than 512 characters is passed, then we get a stack fault. So I'll kind of just walk you through it but you should think about how you would go about doing this. So these are the command line parameters you have to use to compile these programs because these are so complicated. Also, on our server, ASLR is disabled. If you do this on your server, you'll have the SLR enabled. So let me know. I can send you. There's a low command you can do to disable an SLR only for a certain pass session. So then we run this. This runs fine, right? But we need to get 512 characters into there. So do you want to just type out 512 AS? No, it's terrible. Don't do that. Use an awesome use command substitution. So the back takes here, right, is going to take the output of that program and pass it in as that rv parameter. So Python-c means here's a Python program on the command line. And then the double quotes means inside this is a Python program. So this prints out a times 512, right? So the Python program is going to give us 512 capital A's, and then that's going to be passed as rv1 to this vulnerable function. And so we get a segfault, right? So this is our first indication that this is a buffer overflow. But we want to test this, right? So we want to run it in GEV to see did we actually control the instruction pointer? So inside GEV we're going to run it and inside GEV you can run it with the same parameters, which is very handy. So you can do r as for run and then you can run it with the back takes here. And we'll see that when it runs it will say that it got a segfault and it will say that it tried to start executing 41, 41, 41, 41, right? This is where we are. This is golden. So now I'm going to set a breakpoint in the vulnerable function because I want to see where is that buffer, right? Where does that buffer live? I need to know how to jump into that buffer. So I'm going to set a breakpoint. I'm going to run it. Once it hits that breakpoint I'm going to print out the address of copy. So copy was my buffer on the stack. It's going to tell me that it's at ff, ff, c, e, 0, c. So then I want to try to craft my exploit. So I'm going to run it, right? a.out, I'm going to run it with in this case I'm going to do 400 of this hex of nops, 400 bytes of nops followed by my shell code followed by, so why is my, I thought my address was ff, ff, c, e, 0, c. Endian-ness, endian-ness. You're always going to have the addresses backwards in byte order backwards. So I'm going to try to overflow this so if I'm successful I want to jump and start executing at memory address ff, ff, c, e, 0, c. So this will segfault, yes? Just to clarify, that's not true on the inside? That's true for everything. Is that true for everything? Yes, because bytes are writing up. When you do string copy you're writing bytes up the stack and then when it's interpreting four bytes as a hint it reads the highest byte as the first, the most significant, where you wrote the least significant byte first. Does this side effect a string copy? Yes. It's a side effect of how the overflow happens. It's not like copying this address there. Okay, so then we want to say, okay, well, we think that shouldn't work so let's use gdb to find out why. We're going to set a breakpoint at vulnerable function. We're going to run it again with this exact same input now, right? Because now we want to know, okay, how did that thing change with this input? So we print out the address of copy and we see that it moved a bunch, right? F, F, F, F, C, 6, 5, 7. This was C, E, 0, C, right? It makes sense because we doubled the data that we sent here, right? We sent 500 plus the length of shellcode plus another 512 bytes when before we were just sending 512. So this makes sense, right? Because on the stack the RV actual parameters are stored so that shifts the starting stack location down. So let me say, okay, what happens, let's run this program again but change this address here to this C, 6, 5, 7. So should this work? Because that changes the address I'm giving you now to the address of copy. Yeah, this should work, right? It's working in gdb, so we don't get any additional privileges, right? Because we're executing with our privileges. Yes, but it doesn't work. But what is this... So this memory address, right? How does this look? It looks kind of close, right? There's an FF, a C6, and a 5C and we passed in FF, FF, C6, 5C but it's not in the right order, right? So this is because we have to hit that address. We have to hit those exact 4 bytes exactly in the right order. Otherwise, if we're offset, right? So we're copying the same thing over and over again. So if we're offset, then the bytes are going to be all mixed up. So then we have to add, basically, I like to add padding after my shell code to pad out all the rest of my addresses. So I try with just 1a to offset them by 1 based on where they were. I would see it received a seg fault but we can see it actually is receiving a different address. Do it again with 2as. We'll see it almost got there. And then finally we can do it with another one and we can see that this process is executing a new program in bash which is exactly what we wanted to do. Then I'm just going to finish this. Let's just run through here. So then we try this. So we say, yes, we've got something that almost works, right? It works in GDP. So we run it here. It doesn't work. Why doesn't it work? The environment variables, right? GDP sets up environment variables. It changes the environment. So then we've got to think, okay, well, I have 400 bytes of shell code, right? And I know that if the environment is only getting bigger from GDP, right? That means I should try farther up the stack, right? I have to cheat ahead. Yes, farther up the stack which means I'm going to increase this number, right? So does it make sense to just increase to do like 5D? No, right? Because we have a 400 byte window. So I would try incrementing this here, right? I'm just going to do 256 bytes up. So change this to 6C7. We run that and we get our shell. So it just happened to be there. I would keep trying that, right? Just try. You can write a script that just tries every 256 bytes. Cool. All right. So that is stack overflows, shell code. Perhaps everything you need to know. Everything from here on out is like refinements of that technique. So looking at different things like what happens if we have small buffers. Actually, let's go over this very quickly because it could help. So a buffer could be too small to contain your shell code, right? But we can control the environment, right? Locally, we can execute processes locally. So we can put the knot plus the shell code into an environment variable. Then when we execute that program, we know that environment variables can be copied to its address space. So this means we only have to overflow the address of this environment variable. So the big advantage is your knot can be as big as you want it to be, right? There's probably some limit to environment variables, but your knot can be very, very large. You're an obsolete. So this actually is very useful for doing homework stuff. So, all right. Cool. Thanks for being with us.