 We're here working on the Robosaurus Rex challenge. So Robosaurus Rex was a challenge from plaid CTF 2013, so it's a pretty old challenge. There is a huge gigantic hint in the name, Rob, so this should set you on the right path. In the regional challenge, we have a binary and we have the copy of libc. In this image, we have a copy of the Docker image. So basically we have a Docker container that if you run this command, it will pull it down if you don't have it, and it will run the Docker image when you see that it's listening on port 31337, so that if we netcat a local host 31337, what do I do? Just type in, yeah. It says win, but clearly I didn't win because I need to get a flag. If I read this whole thing, you can see that the flag is located at slash challenge slash flag. Yeah. Super interesting thing here. Apparently the current version of Docker by default uses setcomp to restrict the system calls that the binary can actually use, so if you actually want to go in and debug why your program isn't working or why your exploit isn't working, you have to run this command to disable basically this setcomp because otherwise you can't use ptrace and debug in any program in your Docker container. It's incredibly annoying. All of last week, we couldn't figure out. So Will thinks he solved this, so let's have him walk us through what he found. I'm in the hopper looking at the binary. Yeah. So, well this is a little different. You have to go to the dot text portion because there is no main function per se. Okay. So this is a bunch of jump calls to each other. And so really, well the way I found out, I originally ran an strace to figure out how it was getting our input and it uses a read to get input. So I just went ahead and looked for the actual read call in the binary. And you can see that it calls read. I think it hex 0804, it should be 8466. Yeah. That's the actual read call where it gets our input. And so the next thing that I did just to check when you want to see have a buffer overflow is you just give it a lot of input and see what happens. See what happens. All right. Why? Do you want to go through the... No. Or you could look at this for five seconds. So you can see here that this is the call to read and we can see the hopper here. Oh, well, I see what you're saying. Yeah. Oh, yeah, that's right. It gives it... Yeah, you could look at the assembly because read has three parameters, which is the file descriptor, the buffer, and then how many bytes you want to read in. Exactly. And so hopper is nice because it automatically kind of parses this out. Yeah. So it shows it that far... So 0 is getting put on to far onto the stack. So 0 is the file descriptor. So we know we're reading from what? STDN. STDN. And then the buffer then is going to be whatever was in EAX onto here. And what is here? A load effective address from EVP plus far 88. Then we can see here that it's actually EVP minus 136. So then we know that that's where our buffer is located. So we know that that's... So from EVP to our buffer is 136 bytes. And if we just look at the next argument, we can see how much is this? That... 100? It's just 512? What? I can't see very well. Oh, 256. I was... Where's your next skill? It was... They deteriorated. Okay. No, it's easy. Because ff is 255. Yeah. So 100, 256. Anyway, so we know that we're copying at most 256 bytes just by looking at this, right? 256 bytes onto a buffer that's only 136... Well, the buffer itself is probably less than 136, but we know it's 136 bytes up until EVP. Yes. And so we know after that is EVP and then say VIP. And then we have whatever 130... 140 bytes minus 256 bytes to put extra on top of it. You can also see in the S trace that the read reads in 256 bytes. Cool. You could also do that. But as I showed to actually test that... Well, you could download it on your local machine and that would actually work. Or if you run this command in a different container... No, it gives you... Because you're already using the port? Yes. I think if you change the port, it'll be fine. Yeah. I don't see it for this. You're right. I don't need any port binding. You can see that it called read. Just wait a second. And we passed it to 26. Cool. Just like that. Cool. So we know that we have a buffer overflow that we can execute. So now we need to... What? What would be the first thing that we could do? Well, to test to see if we can actually execute it. Yeah. So how do we do that? We know that on the stack, so there's 136 bytes between the start of the buffer and EBP. So we need four more bytes to overflow EBP because we don't care about EBP. And then four more bytes to overflow EIP. So it's 140 to overflow everything up until EIP. And then, yeah. So I think... I don't know if it'll tell you doing it like this, but if you do it in GDB, it should just tell you that you get a seg fault at, I think, 63, 63, 63 is lowercase here, yeah? We can do this. We can pipe it through challenge. We can see a seg fault there. We can also run GDB challenge. And I do it like this. I do it. So I see it going. I know there's a way, but I think the easiest way is probably just to pipe it to a file outside of GDB. So nice music taste there. Just R. You don't need challenge. I didn't write GDB challenge. 63 is what character? Lowercase C. What's lowercase A? 61. Yes. What's uppercase A? 41. Yes. All right. Useless trivia out of the way. It's not useless. You can use it. That's hex. That's hex. Yeah. Hex 61. Okay. Cool. So now we did that. Now what? So now we want to figure out how or what we want to do, right? So we have a buffer overflow that we can execute. So what do we want to do with that? What's our goal in this binary? Generally, we want to get a shell of some sort, right? Yeah. Well, we want to get a shell. We want to get into their system. In this case, you want to retrieve the flag file. At least we want to get the flag, right? Yeah. In this case, we know exactly that we want to steal the flag. We actually don't care. I mean, executing a shell is usually the easiest. And it gives us the opportunity to do other things if we want it to. But with a bare minimum, because that's another thing you got to think about, right? Is maybe they restrict running a shell or they restrict other things. But if you can still read that flag, that's the key that you want to do, right? So. Objective. Exactly. You're always going to keep in mind what's your objective that you're trying to do. The shell actually allows you to do more things, right? But if you can't do the shell, maybe you can still do what you want to do over reading that flag. So now that we know that we want to get a shell. Yes. What's the very base level of how you, let's say you were writing a C program. How would you get a shell, right? You could use the system call. And system takes one parameter. Do you guys know what that parameter is? Is it a zero or a one? No. For system, it's a pointer to the location of the file that you want to read it. In this case, bin bash. Or bin, bin SH. I'm sorry. Yeah. Another important thing is system is in libc, so it's not a system call. Yeah. So our goal is to basically call system, which is a library called bin SH like this. So what are some interesting things about this program that we know from just running a file on it? It's dynamically linked. It's dynamically linked? 32-bit. First level. 32-bit. That's the first thing, right? So 32-bit program, so we need to do that so we shell code everything is good. It's also dynamically linked. So if we were to run, let's say, ROF gadget. Oh, yes. And I say, I want to do the ROF gadget to generate a ROF chain. It's going to say it actually can't find any gadget. It can't find a right-what-where gadget. So this means that there's not enough gadgets in this code to just throw it into ROF gadget and have ROF gadget throw you back a huge payload to put in that would just work automatically to call. And usually, exact in SH is what ROF gadget tries to do. So we need to be more clever and do it our own way. So what's the goal? So we want to call system somehow. So what? Where's system? Yeah. That system is in libc, but system, because the library is dynamically linked, we actually don't have a hard address for system. Yes. And another key is that they actually provided a hint to us because they gave us the copy of libc. So they gave us the libc version, the exact libc version that they're actually using, which means they think at least that we shouldn't use this information, right? So if we were able to just throw ROF gadget at it and just get a payload, there's no reason we need the libc version. And so it would kind of be like, why the heck are they giving this to us? But they are giving us the libc, which probably means we need to use that. So if we look, let's see, we can just look in case we've never done it before. If they didn't, what happens then? If they didn't give us the version of libc? Gotta do everything by hand. It'd be much more helpful, but you could talk to them. Yeah, yeah. You'll see why we need their libc, but there are tools online where you could actually... I saw something which was really interesting, which... It'll give away the solution right now, but yeah. So you can see that all the program, all of the possible functions in libc are all in here. So things like read. How'd you get that? Loaded it in the hopper. It's libc.so. It was down provided by the organizers. Hopper's free, right? That's a free thing? Yes, it is free, limited time or something. You can't say... It's also $100 or something. Yeah, it's not. I mean, that's a lot. It's a lot, but not as far as these disassembly programs go. Interesting. So we have read here. This is what actually... So we've got to remember when the program loads, the dynamic linker, when it sees a call to a libc function, we'll find this libc on the system loaded into memory. But the important thing is remembering... It gets loaded at a different offset. So the offsets in here, like this library, can be loaded dynamically into any different memory region. So even though we know from looking at ROP and everything in the past that the program's code is always loaded at fixed memory addresses, right? So we saw... That's what Will was talking about. ROP source rex has fixed memory locations, right? So this jread is happening at 08048416. It doesn't change at all. This is always where this code's going to be. But libc is always going to be at a different location. How do you know that that one's always fixed? Generally, if you can see it in the object dump, then it's not going to move. The disassembly, whatever program you use, the disassembly address is not going to move. Will PIE move that, actually? Or no? I honestly don't remember. Luckily, there's a nice CheckSec program, which you can run to say that this program does not have a canary. It says another thing we didn't do, right? This program doesn't have a canary. It does have an X, and what does that mean? That's a good question. A non-X executable what? Stack. Stack, yeah. So the stack itself is not executable. This is what tells us we can't just use a buffer overflow on the stack to put our shellcode on the stack and jump to our shellcode, because we cannot execute anything on the stack. Yeah, the PIE and reloader, I actually don't remember exactly what all these things do. One of them will make the process. Well, I still think the code itself is usually not completely reloadable. I don't remember. I think this is set for every segment in memory. Correct. Yes, exactly. So every segment is, the other way to think about it is write, X or execute. So you can each segment in memory is either writable or it's executable. No segment is both. And that's a core security mechanism here. But we know that we can still get around to it. So how are we going to get around that? So we have to break down kind of what we have to do, right? We want to call a system with BNSH. So this is what we look at when we go to the whole state we want to get into. This is what we want to get to. So how are we going to get there? So there's two things that we need to really get there. One is we need to put the string BNSH into memory, because the actual system call asked for a pointer to the location. Not just the string. And the second thing we need is we actually need the address of system so that we can call it. The address of operator. So that's why this. Right. So exactly. So we need to find the address of system and we need BNSH somewhere in memory. So how, so system isn't located in our program's object dump anywhere. We need no call to system here. Otherwise it would be much easier, right? Yeah. We would just kind of jump because right with the ROP and overflowing the stack, now we can control all the function frames on the stack and so maybe we could make it look different or change the stack to call into system. It would be so much easier because we know when we make the payload of what we want to be written on the stack and overflow, we know exactly what we want things to be. So we need some way to find the address of system and it's not anywhere in our program, but we do have other libc functions in our program like read and write, right? Because we use read to read out the input and if you looked it uses write to write out when at the end. So we know we have the address for those two and we can actually use those to find the address of system. We have to do it locally and use GDB but because we're executing this on their machine and they gave us their libc, we can actually use their libc to find a offset between read and system. Yeah. Because we know in global offset table, we know that exactly at 0804... Do you guys know what the global offset table is? I think so. I don't. Will. So the global offset table basically, so you have a local... Wait, wait, wait. Let's step through. I think that actually... So if we go here, we can actually just step through and see, okay, this is the read call that we had. We know it's... And we know if we look at the man page, it's like man to read. Is this taking so long? So we know first that it's a libc function. So this is good. It's calling it libc. It's read file descriptor, the pointer to the buffer, and the number of bytes, which is exactly what we thought. So if we look here, we think, oh, this calls into the libc library, right? Because that's what we're doing. We're calling the libc function. If we look here, what it's actually doing is it's going to what Haver is calling j underscore read. And we can see here what it's doing is jumping to whatever is at memory location, read at GOT. So this is basically an indirect jump. It says whatever is, and read at GOT specifically is 0804961c. So the idea is, and this is how dynamic linking works. So instead of libc being compiled into our binary and included as part of our binary, instead of that happening, libc is loaded at runtime somewhere in memory, and the dynamic linker says, I know if I put here at, exactly at 0804961c, if I put the address of the read function here, those four bytes, that's what we'll jump to whenever we call read. So this is how you get that dynamic linking component, which is great because now if there's vulnerability in libc, instead of having to recompile your app to distribute it to everyone, you can just update your libc version on your system, and now new applications will use that new libc version. So we know basically what Will is saying, we know at runtime, at 08049614, will be the address of write in libc, and that 0804961c will be the address of read. So we actually have, in memory there will be two locations into libc. The read and the write. So how does that help us? Yeah. So, okay, so now that we know that we can get an address for read and write, we need to find some way to get to system. So you can either do that, again locally through GDB on your thing, or use their libc that they provided, and literally subtract the address of where read is in their libc from the address of where system is in their libc. Do you want me to save you some time by just telling you what the... Yeah, well, let's look through here because we can see we have libc here. We actually already know where read is. We know that in the program, read is at offset D5980. So in this libc, add this of read is at offset through x D5980. And while system is here, is at offset 0x 3a DA0. So basically, and actually the way, I did it a weird way because I always found that I ended up mixing up, do I get the address of read and then subtract or add from it? So I'd always have to test. So when I was doing this last time, I figured out, oh, I should just get the address of read. So let's say I get x. So let's say x is the address of read on the system. But if I have x, then I subtract this offset. Now I know I'm at basically the first 0th byte of libc. And then I know if I take that and this offset to it, then this is the address of system. So if I take whatever read is, I subtract D5980. And of course you could simplify this very easily, but writing it like this actually makes a lot more sense to me. So D5980 is just a number? It's a number based on inside libc. And it's based on the offset of read in libc. So maybe different for different operating systems or different versions of libc. This is why they provided you exactly this is the libc version that reads. That's why you need to use the D5980. Yes, and this one too. So you subtract that and you have the other one and then you get to the right location. Then we know the address of system. So how do we get the address of read? Yeah, so we don't know what the address of read is, right? We know where the pointer to the address of read is, but we don't know what the address of read is. And this is where the first part is actually just your basic buffer overflow in a sense, right? Just what we did before, right? It's basically going to be, like the payload is going to be A times 136 to get us to the A's plus B times four. This gets us to EDP. Now we're at EIP. So now the next four bytes we go to is going to be the next address that gets executed. So we actually have a function that'll let us print stuff out to the console. We have write in our program. So we can actually write out, and write takes three parameters, which is a file descriptor, a buffer to read from, and how many bytes you want to read out. And so we can give write, the file descriptor for a CD out, we can give write the buffer, which is the pointer to the address of read, right? And that'll then read out what that address is to the console for us. The goal is to get the system basically been SA, right? So what Will's saying is what we really want to do is write out to file descriptor zero, which is standard out. We want to write out... Is it one? I'm pretty sure. Yes, correct. See, that was a test. We want to write out what do we want to read? Read, yeah. 0x0804961c How many bytes do you want to write out? Four, four bytes. Because we're leaking an address. So this is going to leak four bytes and send it back to us from the other side. So essentially this is the first function we want to execute, and then what do we want to do after that? So now we have the address of read. So to get the address of system, we just have to apply our offset that we found. Right, so then we essentially... So let's say calculate address of system based on a level. So now we have the... Now we have where system is located, so we need to put bin s h into memory and then call system, right? So we probably want to put... We need to put bin s h in first before we call system, and so we can use read to do that as well. Right? I want to read from what? You want to read from standard in, which is zero to... You're not even putting any random address here if I do like 41 for that one. No, it needs to be a valid address that you can reach. That you can write. Write to, yes. Yeah, that you can write to. So how do you find that? You can use the read elf. I think dash e will give you what you want. Dash... Oh, dash g. I don't know. What did you say? E. Scroll up a little bit. So this gives us all the segments in the binary. We're looking for something that's writable. Yeah. W's are important. And so you can pick anything. So we're going to write bin s h, but also the string, we have to null terminate the string, right? Because if we don't, and we start writing into some other segment, it's just going to... It's when we go to finally call system and point it to the buffer, it's just going to continue to read everything that we have after that. So we need to write bin s h, which is seven bytes, and then one null byte. So we need something that's at least eight bytes in size. So we don't want to use, let's say this, JCR for whatever that is, because that's four. We don't want to use detours or ctours because those are eight. So we either want to use... Which one did you use? I used .data, I think. BSS? BSS. That's only eight, too. The .dynamic, I'm pretty sure. Yeah. Scroll up a little bit, I think. Oh, no, it's right there. Yeah. Yeah. So, yeah. So we want to read in from standard in into this memory location eight bytes. So this... So that means on our side, we're going to have to send... We'll first read in four bytes that's this address of read. We'll then send eight bytes, which will be slash bin slash s h, null character. Then what do we want that to happen? Yeah. So now we want to call system, but our problem is, is that we don't actually have a way to call it right now, right? We don't have any system anywhere in our program. So what we need to do is we need to put system in our program. And the easiest way to do that is overwrite the address that we found of system with some other function. Or the... We need to... Sorry. We need to overwrite some other function with the address we found of system. So you can really choose any address that you want to execute. Yeah. I learned that you can actually save a line, Adam, if you just overwrite exit, then you don't need to call at the end. I don't think... No, you still need the... Yeah, yeah, yeah. Okay. So for this, because we already know what the address of read is, and we're lazy and don't want to type it out again, we're going to use the address of read. So we're going to overwrite the address of read with our address of system. So here, this is going to overwrite the address that was at 804961c, which is in the global offset table right here, 1c. So those four bytes will now, instead of pointing to... Instead of having the address of the read function at libc, we'll change that to point to the system function. Can you print that out and see it as it happens? Maybe just write out what you're reading into. Yeah, we'll look at the actual code in a second. Wait, what do you mean? Maybe just put, like, binsh, and then the second one, just address of system. So that's what we're we're reading into. And then, we're calling system, but really we're calling read. We're calling the... So we actually want to go here to j underscore read. So that's actually really what we want to do. I mean, this is how it is in Opera. We want to go to this j underscore read function and pass it And so, yeah, that's just what we want to do. Regular, so you would call read again with the same address you've been calling read with previously. Except in this time, instead of giving it three parameters, you're only going to give it one, which is the address of dot dynamic up in SH. So we'd actually call that read with this. So how should we look at your answer? Sure. Yeah. This is... I somehow messed up... Well, yeah, I had one that was, like, commented, but I messed up something and it wasn't working. So I pulled an old version down that I was doing with Voo. But so, anyways, yeah. So I did... I was trying to see where stuff was in memory, so I just did ABCDE to see how it was recycling. Why? Why not? Oh, no, I know why. Because it divides evenly into 140. I mean, I know that. I know that's why it works. Yeah. No, I was doing 4 bytes and I was like, this isn't going to divide evenly 4 bytes. So I did 5 bytes. Okay, so you can see I have two offsets up there when I commented out. I had a lot of trouble because I used the offset of Lipsy in my system, and I had a different version of Lipsy. So I had to use the Lipsy version that was provided to find the correct offset. Interestingly, Voo's Lipsy was the same as your Lipsy, so he didn't need to change the offset. And so I have up there... I know it's kind of poorly laid out, but the address of write, the address of read... Yeah, I remember it's in little end, because somebody doesn't know how to use that function, too. 080483.0c 080483.2c And that's actually... that's not the pointer to read. That's the... that's like the read at PLT. Oh, oh, oh, this is the... Yeah, yeah, yeah. So those are the calls into read and write. Yeah. Okay, what's this pop rock? So, oh yeah, we didn't really explain that, did we? So the... So this is the reason why this is a rock exploit, right? It's because the purpose of a rock is we want to use what are called rock gadgets, instructions that are already in our binary to do some things for us. So, for instance, let's say... Oh, okay, okay. It's fine, it's fine. Just because it's a little bit easier. So you can see the layout exactly. So we just focus on, let's say, this part here. So we see we have 136 As and we have 4Bs. So the 136 As gets up to EVP. The 4Bs overwrite save EVP. The next thing is where we're going to go. And this is 0804-830-C. So if we look at Hopper, we see... That's what you're injecting. When? On to the stack. Because we're overwriting the stack. And where we're overwriting is the saved EIP. So this is when this function is done executing. Control the return instruction will go and start executing from 0804-830-C. That's a function called basically to the write function. Now, the question is, this is actually what I messed up a lot because I didn't draw the diagram out while I was doing this. What? The thing that's above the address of... So our three parameters are the file descriptor, which is one. Which is exactly what we have on our scratch here. I don't know if I should go back to that. On our scratch. So we want to call write. So we're calling into the write jwrite. We're passing in one. We're passing in IU's write. So 0804-9614 which is write in the address of the GOT. And then we want four bytes. So this is what the stack looks like. Now, the question is when write is done executing so when I call this function, it's going to go to jump to the saved VIP onto the stack. And where that saved VIP is located is right here in between the function I was going to and the next three parameters. And to really get this, you have to draw out the stack and look at exactly what happens as soon as you start executing that new function. I had a problem where I had too many things in the stack there. I didn't have those in the right place. But fundamentally when we get here ideally we want to then call read 0 this other gadget. So we've written out correctly. But the problem is the stack, the next things on our stack are three arguments that we just had to the write function. So the trick is using finding a pop gadget basically. So if we look and we run rock gadget on here. So this is even if you can't make a chain, it will show you all the different gadgets. So we basically need a gadget that does pops we want. And how many pops do we want? Three. Why? Because that's how many parameters that we put on the stack. You want to go pop, pop, pop. So that way the next address that we go execute at is whatever next thing I want. If I do this, we can see that it's now the read function is where I want to go to. Because we want to call read 0, pop. So we look for a gadget that does three pops followed by a return. And there's actually a lot of those. Yeah, so here No, that's a four pop. Yeah, couple of that. 080484B6 there's a pop, pop, pop return. So each of those pops will change the stack pointer until right before that return this is the address that's on the stack. So this is where we will go start executing at. And then I think now we can go back. Because yours is going to be the same. So you have the right instruction, the pop rock, which is 080484B6 which is the same thing. And so you have the right address, we're going to call right. Afterwards we're going to pop three times. And then we have a file descriptor of 1, read global offset table 080496 1C and byte number of 06004. So file descriptor read that. And the next thing we're going to do is then call into that's actually the BSS address which actually worked. But you can use the dot dynamic as well. Wait, but where's... You're going to pop three times, right? Yes. Yeah. Yeah, that's not part of the payload. That's not part of the payload. I was just writing code, okay, and this is how it turned out. This is much better. So in the future, to structure it, it's better if you kind of have each of the chunks by themselves. I mean, it's fine to use the names. Like, you can see I didn't really do a good job of using the names. I just used constant values and then I messed up a few times where I put different values in different places which is not good. See, I was doing it as I went and then I realized I needed those parts. So then I just added them in as I was... Understandable. Having these in here kind of ruins the flow of like, here's my payload chunk and then here's my payload. I just want to see the entire payload laid out like that. So maybe a good strategy would be to put all those constant values just up at the top. Okay. So we have a read address. We're going to have a triple pop again. And where are we going to read? We're going to read from standard input. Zero, standard input. We're going to read into the VSS address which is this 08049628 and we're going to read into 8 bytes. Then after that since it's going to happen we're going to pop 3 times. We're going to pop, pop, pop. Go to here. Go to the read address. We're going to then go triple pop again. Read from standard input. Into the GOT 4 bytes. Yeah, that's right. Pop, Roppel, execute. We'll go pop off 3 times. And now we're finally going to write... This is actually, I did this because I was messing up a couple times. Yeah. This is debug output. What Will's doing is writing out so everyone can see. So he's calling the write function because he hasn't overwritten the write function. He's only overwritten read. He's calling write, he's doing a triple pop and then he's passing in... That's standard out. He's passing in the read GOT and then he's passing in the write for here. You shouldn't use byte now. Yeah, but I didn't. I can see that. So what's your rationale? Why did you do this? Yeah, so this doesn't actually affect the Ropp chain at all because we're just popping everything off the stack. But what I was doing is I was having some problem because I wasn't getting a shell a couple times. So what I did is I wrote out the address that was at the read GOT which I had thought I had previously written into read GOT. So I was using that write to write out to compare if the two addresses of system that I had were the same. Right? And that's just... I also did it for the BSS segment to see if BNSH was written to the correct place as well. And so you can just use write in your rock gadgets to debug internally. But at this point, our read has been overwritten. It should be pointing now to system. We have some junk because we don't really care. This should never execute. And then we pass in the argument of the BSS address where we put in slash bit and slash message and send it to the payload. So are you done? No, you have a couple more things. So after you send the... So we wrote in there. We're reading in from standard in. So it's expecting us to give some input to it. Right? So we can't just send the payload and get a shell. We need to do a couple more things. Yes, so you've got to think of... Remember this is an interactive shell, right? Because... This is an interactive exploit because essentially we're leaking those four bytes. This is the pointer to read. And so our code, our exploit code has to read those four bytes. Sorry, read that. So we have to figure out where the address of system is in this specific execution. Then write that back now so that when it... We have to first write out slash bit and slash sx0 because that's what it's expecting here. And the next four bytes that it's expecting are the address of system that it's going to write in. So when this rock chain executes... Sorry. When this rock chain executes is actually going to... When it hits the read, it will wait for our input. So that's how we're going to do it. And so this next section, I was very tired and don't judge me. Oh, good. It's Adam's code instead. So we send... So we send the payload and you can see here I also added debugging stuff because I... My payloads were kind of funky so I printed out the representation of p so I could see all the bytes. So we send the payload and now we know we need to receive specifically four bytes from the payloads because the first thing it's going to do is leak that pointer. So it's going to output four bytes therefore I can just say I want to receive... It took me a while to look up in bone tools but they have this receive end function which waits specifically for four bytes. This is also why I want to show this to you because it's going to simplify your life too. So I know that I've now gotten four bytes from remote. I turn that into a U32 function in bone tools so this turns it from the string into an integer 32 bit integer into the address of write and I do exactly what I was doing before. I do that and then I subtract the offset of the write function which is that df9f0 which is the baseline and I add 3a da0 It's pretty close to happen. So the baseline system then in my code I first sent the address of system and then I send the binsaf0 and then I get an interactive show. Will does this in a different way. I was too lazy to look up how to turn bytes around so I just did it manually and I didn't even do it the good way and I used a for loop I'm not proud but it works. Sometimes you just have to do it works but the nice part in here is learning better ways so I'm going to run Python I'm going to run Pone Devils CTF Solutions Xploit that will work if the libc version changes then we're going to have problems because our offset is going to be different but at least here now we have a repeatable exploit against the specific version. Questions? It's a pain to get 100% right. There's a lot of little details that's why these debugging tricks like writing out whatever you wrote in these kind of things and I guess just to prove mine also works. Hello we got root shell It's rm-rf please Sad Can you even exit? Will it allow you to exit? No it doesn't exit. Alright say goodbye everyone. Bye everybody.