 Oh, so cool. Man, Twitch, feeling like old. Just want to make sure this is working. Cool, all right. We will see how this works. All right, cool. Hey, everyone. All right, let's pick up where we left off. So we left off writing this program. So we had a very simple text program. So we're writing a very simple echo program. As we said, the purpose of the program was to read in up to 100 bytes from standard input and output that to standard out. All right, so I know you all finished your homework. So who wants to help me finish that? So why did we first subtract 100 bytes from the stack pointer? You said it was because we would be able to use that 100 bytes and nobody else knew it was there. Yeah, perfect. Thank you. Hopefully I said the right things, but who knows? OK, cool. So we first subtract 1,000 bytes. Now we want to call read. We want to call the read system call, pass it 0 for the file descriptor, because we want to output to standard. We want to read from standard input. We want to pass in the RSP as the second argument. That'll be the memory location of the stack, so we can write our 100 bytes there, and then we pass in 100 as the last option. So what do I want to do? What's my goal? How do I actually make this system call? What's the high level goal that we want to do? We need to do a read, so you want to put rax, well, 0 into rax. OK, 0 into rax. Cool, what else? We need to fill in the other registers that get used for the system call, which include RDI, RSI, and RDX. So RDI is the first one. Is that right? Yeah. So 0 into RDI. And what was the next one you said? What do we want? RSI? SI, yeah. And then what do we want to be inside there? I did RSP, but I feel like that's not entirely right. That is entirely right. Trust your gut. It is correct. Cool, we'll talk about Y in a second. And what's the third parameter? Our number of bytes, so 100 needs to go into RDX. RDX, so 100 into RDX. So we can see how we translated basically this high level of what we want to have happen. We want to invoke this system call. We want to pass in the parameters 0, RSP, 100. We use this nice link that Zion just put into the chat. We then add, we see that the system call for read is 0 into RIX. And great, so then we can translate that from this high level, hey, we want to call the system call read and pass in these values to the more mechanical, what do we actually want to do in assembly language? All right, so we want to move. So what's the instruction? So pretend you're, I'm just fingers here. What do I type in? Move RIX comma 0. Moving 0 into RIX. Next one, move RDI comma 0. What about this one? Anything special? Move RSI, RSP. Yep, so move whatever is inside the stack pointer. Do we know what value is going to be inside the stack pointer? No, because it's standard input. We're waiting for somebody to type something. Not quite, right? We know something about the stack pointer, RSP. There's 100 bytes waiting for us? It was whatever it was before main was called. It's now 100 less. That's what we know. So we don't know anything else, but we know that. OK, cool. And then we want to move into RDX 100. Sweet. OK, so this will then read in 100 bytes into wherever the stack pointer is pointing to. And it will fill that up up to 100. And then now we want to call write. So we do the same things. We'll look up in this fancy table, write as RAX 1. So we can then use the same logic. So we want to move 1 into RAX. We want to move into RDI. We want to write out, why are we writing out to file descriptor 1? What was file descriptor 1? Standard out. Standard out, yeah. Remember, you burned that into your brain. And oh, I'm missing something here. How do I actually get to the system call? No, you need to execute this call instruction. Yep, yeah, syscall, beautiful. Cool. So these should, and I'm going to get rid of these comments now, right? These comments were helpful when we were kind of outlining what we needed to do. But honestly, as long as somebody understands assembly, they should be able to understand this instruction here. So I'm going to get rid of those. All right, let's try compiling this. So I can never remember. Every control sequence to make the font bigger is always different. Let's go hacking. And this is, I've got to hate stupid Dropbox files. OK, so this is echo meeting. So I'm going to compile this, gcc echo underscore meeting dash s. It compiled just fine. I can check the exit code. The echo code was 0. I can see that a.out the last time it was compiled was September 29, which happens to be a week ago when we were last looking at this. So now I have a new a.out so I can run it. What's it doing? Why is it just hanging? Did we make an error? Are we stuck in an infinite loop? It's waiting for you to type things. It's waiting for us to type things. It's reading from what? Standard input. That's us. It's waiting to read from standard input. Beautiful. So I can type in foobar hit Enter. And then it outputs me foobar and a bunch of this junk. So first, before we dig into what's happening, let's look at what's going on. We can use S-trace to make sure we're doing the right system calls. OK, so I can do that again. And now this is kind of cool. We can actually see why it's hanging. So it has all this other stuff and all this other junk that we won't focus on now. But we can see that it's calling read. And it calls read. So it read in 0, ended up being foobar end 100, returned 7, and then we called write 1. So this is correct, the file descriptor. And it's not, I guess, because it's too many characters. It's not showing us the comma 100 there for the 100 bytes that we're writing out. So why am I getting all this additional junk? Where's that coming from? There's a bunch of data on the stack and the string input isn't null terminated necessarily. So it's just printing out all the other garbage on the stack. Yeah, let's do, I realized last time we didn't do an actual stack example. So let's quickly run through an example of the stack. First off, and this is something that Jan is 1,000% incorrect at and I am right. And so, especially this is your first time learning this, you're learning this from me, you'll never do it wrong in the future, you'll always do it correctly. So we saw that we subtracted 100 values from the stack. So we conceptualize and think of the stack as something that starts at high memory and grows down. And so we visualize that with drawing just kind of like a box with high memory at top and low memory at bottom. Now you may say, Adam, how do you know that you're right and Jan's wrong? Well, A, because he draws it left to right, or which he does bottom up, which is insane, which is even worse than left to right stack growth or right to left. That's just absolutely horrible. And the way I know he's wrong is in the Intel manual, it's specified like this. So it draws out memory and the stack from high to low memory with the stack growing down. And the, so the notion of a stack is essentially, when you get into data structures, you're gonna study all kinds of interesting data structures. And one of these data structures, and all this is, is you can think of like a data structure to have different operations. So a stack has fundamentally two operations. You push things onto the stack and then you pop them off and they go out in the reverse order. So if I were to put, let's say, you can think of it like, oh, I don't know, I wish I had it here. At home I have an arrow press that has a very, very good demonstration of this. But essentially, you can think of if we're putting things onto the stack, then, and if we put, let's say, first Zion onto the stack, he gets pushed onto the stack and then Will gets pushed on there and then Adam gets pushed on. When we pop something off the stack, Adam will go first and then the next thing we pop off the stack will be Zion and Will, and then after that would be Zion. So the way we think about this is, and this is kind of the important concept to think about when you think about this at the assembly level, at the high level, data structures, thinking about a stack in terms of push and pop is all you kind of really need because you have this nice interface to use it of just doing push and pops. Here, we're simulating that with a memory region that we say, okay, anything that is above, so anything at that memory location, which is at the current point of our stack and above, is memory that is used, it's in the stack and anything below that is garbage. So let's say that the stack points to memory address 10,000 in hex. This is essentially arbitrary at this point. We don't actually care. But once we know that, we know that, okay, anything above memory address 1,000 from literally, and I don't know how many gigabytes that is, it's definitely a lot. Somebody could calculate that, that'd be interesting. But basically, the way this stack is currently constructed, everything from, all those Fs, all the way to 10,000 hex, all of that is currently being quote used by the stack and this points to the head of the stack. So this means whatever is right at there in memory is what's going to be, is what the next thing is that will push and or pop off the stack. But this also means that anything that's below the stack, so below the stack pointer, so at a negative offset is garbage and we don't care what it is. It could literally be any values, but it doesn't matter. Cool, okay. So we'll just walk through this example of two instructions. So different programming languages have different, or sorry, different assembly languages have different ways to interact with the stack. And so in this example, X86 has instructions to push things onto the stack and pop things with the push and pop instruction. Otherwise you would move something into where RSP points to, that would move something onto the stack and then you'd have to decrement the stack operation. Push and pop actually does both of these simultaneously. Now, what are the values in my registers? So the value inside the REX register we'll say for these purposes is 100, or sorry, 10. So it's hex A, RBX, no idea, we'll say zero. Now, what is the value of RSP? Kind of a similar question that I asked when I were looking at the other code. So what's the value here inside RSP? What does it have to be? It's a hex 10,000. It's hex 10,000, why is it hex 10,000? Because in the drawing that you put, that's where the stack pointer is, that is the head of the stack. So that's where the most recently pushed value onto the stack is. Beautiful, exactly. And this is an important point. These are inextricably linked. The only reason why I'm pointing this stack at memory location 10,000 hex is because there's the value 10,000 hex into RSP. If I were to change that value, now my program or whatever, like as we said, when I subtract 100 bytes, that moves the stack pointer down 100 bytes. And now to our program, everything above there, those 100 bytes are in use, we're using them. And anything after that is garbage, cool. So it's important to keep in mind that the arrow here is just an illusion. The only thing that matters is that the bytes inside of RSP are 10,000 hex, cool. All right, so now we're gonna walk through step-by-step doing these instructions. Can I ask something really quick? Yes, you definitely can, please. One thing I kind of wondered behind the scenes is how does push and how do push and pop know how much to increment or decrement? Because I mean, when you're allocating space on the stack, you usually add a subtract, I actually kind of forgot for a second. When you allocate, you're subtracting because you're going down. Yeah, so how do you? So yeah, that's size, that's a great question. So at least a half to, so I know in this case right here, the assembler, I would assume, and actually let me look this up, let me look it up. I'm fairly certain that, let's push it, I think. So I believe it has to do with the, if you're using 32-bit or 64-bit instructions. So a push instruction in 64-bit, which is what we're looking at here, will always push, you can think of it as the size of a register. So how many bytes is a register in 64-bit? 64. How many, shit, how many bytes? Yep, perfect. So eight bytes, right? So it's going to push eight bytes, so it's gonna take whatever's in RAX, take all of those eight bytes, copy it onto the stack where it currently points to. So those memory locations, 10,000 is overwritten, 10,001, 10,002, 10,003, 10,004, 10,005, 10,006, 10,007, and 10,007, because that would be eight total, and then it's gonna subtract eight from there. On 32-bit instruction sets, it only does four bytes because the registers are only 32 bits or four bytes wide. So it's always a constant value. It's a constant value based on your instruction set. Okay, so it's always gonna be for 32-bit, it's always gonna be 32-bit, push or pop. Correct, I don't know if it's possible even to write. Zion, do you know is it possible to write assembly that pushes only like a byte? I don't think so. Even if you did like push AL, right? AL is the lowest byte. It's possible. You can specify these size. Oh. Through side effects. You can push just a single byte, but it will always crash your program based on whatever is called next because the stack will be on a line. Yes, yeah, yeah, that's. You can see this one. But that's more like if you call into libc and you try to use like XMM instructions and shit, is that what you're talking about? Yeah. Cause you can, yeah, you can simulate it, right? Like we saw, we can subtract 100 from, you can also subtract one from the stack pointer. And if you're executing only your assembly instructions, that should work. But when you start getting into other things, some instructions require the stack pointer to be aligned to a certain offset. And so yeah, then things blow up and become weird. Yeah, but I'm trying to see, I don't, yeah, I don't think you can just push one byte onto the stack using the push instruction. Because you can move member around and do whatever you want, you can do exactly the same thing using different instructions. That makes sense. Yeah, I totally agree. You can do it with push. Cool. Awesome. Any other questions before we jump into this, walk through this example? Yeah. One that actually goes back to the example and maybe I just wasn't understanding what you were saying earlier. But you said like when we told subtracted 100 from RSP, so that like if say it had been 10,000, it would now be, well, whatever 100 less of that is. Yep. Right. And then when we write into it, when we do the move RSP into RSI, are we then in a sense writing back up positively in a sense? Yes, 1,000% that is what happens. Okay, all right. We'll see that in depth as we, that's actually the result of, or that causes essentially buffer overflows and what is what allows you to take control from a buffer overflow. Now, the important thing is when we call this or when we execute this instruction, right? The one thing I wanna clarify from what you just said, when we execute this instruction, we're just moving whatever happens to be an RSP. In this case, it'd be 10,000 hex minus 100 into RSI. That's not writing anything yet. It's only when we call sys call that this read system call starts writing the bytes that we input to the program at that memory address inside of our program, so that when it returns from the system call and this next instruction executes, whatever was written get put at that memory address RSP. And because we tell it only write 100, it will only write at most 100 bytes. So it'll start writing at the memory location 10,000 minus 100. Exactly, and it will write starting there and it will, so if we take this example, I don't know, can you see my mouse cursor over there? Yeah. Okay, so it would start, let's say this is minus 100. It would start writing, writing, writing, writing, writing, writing, writing, writing, writing, writing, writing up all the way up and write 100 bytes from there up to 10,000. It wouldn't actually overwrite whatever was at 10,000. So that what we'll see eventually is that what's actually above us on the stack of wherever the stack pointer is, some function calls us and there's stuff that it needs that is stored on the stack. So that's when there's a problem is when a program overwrites what's on the stack above what it's allocated. Right, right, which is pretty cool. Yeah, anything else? This is important stuff. This forms the basis of exploitation and all kinds of stuff, that's why we're talking about. And feel free to interrupt at any time. It's hard for me to be on all of the Discord and the Twitch and stuff, but you know. So yeah, just feel free to interrupt me. I'll let you know if that goes far here. Thank you. There we go. Okay, okay, so we want to... So we're going to execute push a R-A-X. So this is going to be the next instruction that executes. Now, once that instruction executes, oh weird, I adjusted the dollar signs. Now, when that instruction is done executing, what happens? Okay, the first thing that happens is, oh, okay, yeah, okay. The first thing that happens is, eight bytes are subtracted from the stack pointer. So RSP is now minus eight bytes, and this is correct, right? Yeah, so 1000, okay, FFF eight is where we're at now. We subtracted eight bytes, and then we move the eight bytes of whatever's inside R-A-X onto the stack. So now that sticks in memory that stays, and we can now use R-A-X for something else. Cool, so first subtract, and then put whatever's in that register onto the stack. Now, the next instruction is popRBX. So the first thing of pop is take whatever is at the memory address that the stack pointer is pointing to. So FFF eight, take the eight bytes there and copy them into RBX. So what are those bytes there? Well, they're the bytes that represent the hex value zero A. So a bunch of zeros and then A. And copy that into RBX. So RBX changes to OXA, and then we're not done. We need to replace the stack pointer. So the stack pointer, we add eight bytes to the stack pointer. So the stack pointer is back up to 10,000 hex. What happens to this A that's on the stack? It stays there, but yeah, who knows? Yeah, it stays there, but it's, yeah, exactly. From the semantics and point of view of our program, it's now below our stack pointer, so it's functionally garbage. We don't actually care about that it's there, but as we'll see for exploitation purposes, you actually may care that some special value is there. Think about if a password happens to be on the stack, and you have a way to read that value, that can be something that's very cool. So, and so what was the effect of these two instructions? What did they effectively do? Well, could you do what they did in one instruction? What happened to RSP? What was the value of RSP before these two instructions, and what was the value afterwards? It got moved further down, so into the stack. It got moved halfway through, but afterwards we added eight back to it, and now RSP is right back at 10,000 hex where it was at the start, right? Oh, right, so it's back where it started. So this is basically just a long way of moving RAX into RBP, or RAX start. Yes, 100%, exactly, right? Yeah, it's effectively what happened. We're just using essentially, you could like scratch memory to do that. We just push it and then pop it, and so because we had a push followed by a pop, the thing that comes off, just like we talked about with the pushing things on, the thing that you pop off is the last thing that you pushed onto there. Cool, okay. That hopefully helpful a little bit more. Okay, cool. So that helps clarify things a little bit hopefully as to why we're getting this garbage, right? We, and so let's look. So we had here, and we're, so we're asking the operating system, remember, because Reed is in the operating system, hey, we want to read from standard input, write it onto the stack 100 bytes. So we do that syscall, and then we say, okay, write to standard output RSP, the stack pointer, onto the stack of that same exact same location, 100 bytes. So man to read, we want to because of that section. Cool, so we always gotta go back to the man page. When we get something weird, that's something that we don't suspect. Let's read the documentation. So read attempts to read attempts. This is very key. This is kind of an important general concept to remember throughout your programming career is programmers typically don't use things like intents or guarantee or can or may or must. They don't use these words arbitrarily. They use them for a very specific purpose. So in this case, when it says read attempts to read up to count number of bytes from file descriptor into the buffer starting at buff, this should give you a hint that, hmm, it may not be successful all the time. So let's keep reading. On file that supports seeking, the read optimization can't convince that the file offset and the file offset is incremented by the number of bytes read. If the file offset is at or past the end of the file, no bytes are read and read return zero. Okay, cool. So if we're at the end of a file, it would return zero. That's interesting to know. If count is zero, read may detect errors. Okay, count is not zero, so we can ignore the rest of this paragraph unless you're curious as to what happens when count is zero. According to POSIX 1, nope, don't care about that. That's gonna be some weird POSIX-y, Linux-y thing. Okay, return value. On success, the number of bytes, ooh, can I do that? Sweet. On success, the number of bytes read is returned. Zero indicates end of file and the file position is advanced by this number. It is not an error, it was important. It is not an error if this number is less than the number of bytes requested. This may happen, for example, because fewer bytes are actually available right now. Maybe we're close to the end of the file or because we're reading from a pipe, which we know about and talked about, or from a terminal, right? Which is our case, we're reading from the user. We have no idea how many bytes the user is gonna send us. They may send us zero bytes. They may send us 100. Or because read was interrupted by a signal. On an error, negative one is returned and error number is set appropriately. In this case, it is left unspecified with the file position, if any, changes. Okay, cool. So this means that, so let's run this again with S-trace, foobar, and we can actually use this output here. So it's calling read foobar in a new line 100 and it's returning seven. Somebody remind us, what was the return value of the read system call? Oh, no, we're being rated by Pone College. Yeah, the return value of read, thank you, is the number of bytes read. And so we're reading in seven bytes and read is telling us that through the return value. And what are we writing? Well, we're telling it just to output 100 bytes anyways, even though we didn't read in 100 bytes. So we actually have a problem in our program, right? This write and there's actually a bad specification so you can blame me, right? I gave you this specification that said, hey, we want to read in 100 bytes, then write 100 bytes. But that's not actually what we want. What do we really want from this program? We probably want it to read an arbitrary amount of bytes until it reaches a new line. Yeah, we may want to just read in until we reach a new line, or we want to, well, if we go back to the purpose of the program, we want to read in up to 100 bytes from standard input and output that to standard output, the amount of bytes that we've read in. So we actually want to do, and this would be, let's say, num bytes read or something like that. So how do we get the return value of read in our assembly program? That should be an RAX, right? Yes, so based on, when we talked about system calls, we call a system call and the return value, is it in here? It must have been in the slides, so let's assume it's in the slide somewhere. So now, after we call the system call, whatever was inside RAX is completely wiped out and the return value of whatever is, the return value of read, which is the number of bytes is now inside of RAX. Where do we want that to end up for this, in this program? Sorry, for this, not program, this system call? We want it in RDX. Yeah, so before, and what if we did move RDX, RAX? So if we run this, what's gonna happen? Let's go here, GCC, echo meeting. It's gonna be one byte. It's gonna be one byte, but I told it to RAX. Oh, sorry, I'm using foobar. Ah, it only output F, what the heck? You already overwrote RAX with your system call. Yeah, so this is the important thing to remember with assembly. This is a good lesson for us. At this point, as soon as I copy one into RAX, I now have no idea what that value is. I can never get that result back. It's gone forever. So what I want to do is before I do that, I wanna move into RDX, whatever was inside RAX. And that way, here, I will be moving the return value. Now RAX here contextually has the return value of read, the number of bytes that we read. Will it ever be greater than 100? Shouldn't be. It shouldn't be, right? Because I told read, I want at most 100 bytes and it returns the number of bytes that it read. So it should be anything from zero to 100. It may actually be negative one in case we hit an error code. So if we couldn't read for some reason, this may cause problems, but we'll ignore that for now. So let's compile it, run it foobar outputs foobar. Let's double check. Boom, and now we can see that's so cool. We're capturing this return value here of seven and passing it as seven here as the parameter to the right system call. Is that awesome? Say yes, type in chat, yes. People say yes. It's very awesome. Thank you. So weird, this is even worse than Zoom. One more time. I don't know what, you can't, I never remember what I say. Apparently it needs to be repeated. Oh yeah, so we're actually taking and capturing this return value and dynamically passing it to the right system call. So that if I do type in some other junk, here it was 21. Boom, it gets to be passed in 21 here. So we're only writing out exactly the bytes that we read in, cool. Now, what if I do something and type something in that's really long? How come that, well. How come that didn't write out? So here's what it wrote out. How come it didn't write out everything that I typed in? It was more than 100 bytes. More than 100 bytes, exactly. Okay, cool. So our program kind of sucks, right? It only reads in 100 bytes and outputs that. So, how do we know how much input there is? Like why don't we just change 100 to be 1,000 or 10,000? That leaves space for people to mess with stuff. Yeah, it leaves, it may lead and have a lot of weird space, right? It may, it is kind of like fundamentally the problem is we have no idea how much input. What if it's a gigabyte? What if, is it possible that we may want to have input to our program that has output more memory than we even have in our system? What if we have 10 gigs or a terabyte, right? Our program should technically still work. It's very simple. Just take input in and cat it out, like the cat command. You wouldn't want the cat command to fail because of some weirdness. So we need some loops. We're gonna go back to our old friend, loopy loops in assembly. So if we go back, we had conditionals, right? To compare things, and then we have this nice table of jump if equal, jump if not equal to, jump if greater, less than, less than or equal to, blah, blah, blah, blah. So let's go back to our handy read function. How do we know when we're done, when we have no more input? Let's go to the return value. Is it return zero? Yeah, exactly. So it's kind of interesting if you think about it, this read system call, right? If it just returned zero when it didn't read any bytes, you wouldn't know, does that mean because there's no more bytes to read? Like if we're reading from a file, have we reached the end of the file? Or is there literally just nothing more to read? And that's why when we call read, that's why read will always wait. And as long as it will wait and hang our program until we get at least one byte from that file descriptor. So that's why we're reading from standard input. That's why we're reading from standard input. It actually waits until it has at least one byte. Cool, okay. So we have our condition. We wanna keep calling read and write. So let's go back to our code. So we have our read, have our write. And what we wanna do is after this read, we wanna check, hey, is now, okay, we can do two things. Let's call this. So in assembly, we can define things as labels. So here I'm defining a label called exit. All that does is reference this code. So the important thing to remember when you're thinking about and doing assembly programming is labels don't actually mean anything. They don't get compiled to anything in the code in the program in the assembly itself. Exit, this becomes a label that I can then reference later in any place in the program. And so I'm gonna create a label called start. So this is kind of at the start of our, what's going to be our loop. So when do I want to exit? So how do I, so let's go back to this slide. So how do I use these insane things to create? So first, what am I comparing it against? R-A-X, right? Yeah, so I'm comparing R-A-X. What do I wanna, what is my case? So let's write it here basically like, if R-A-X equals what? So what's the value of when we want to quit when we're done? There's no more bytes to read. It was zero. If zero, yep. If R-A-X is zero, then go to exit, right? And if not, if R-A-X is not equal to zero, then write and go read again. All right, cool. So we need to compare this, and man, it's been a long time since I've written X86 stuff. So we should be able to what? Compare, I think we need a zero and we need a register. We're not using RBX for everything. Move zero into RBX, compare R-A-X with RBX. And then if they're equal, so jump if equal. And just compare the immediate value, R-A-X to zero. Does that not work? Probably, I have no idea. Let's try this. Comment to that. No, yes, we can. I'm gonna look at the assembly instruction here. Boom, oh, see, and we can see I actually have my labels here. So it shows up as a label, but it doesn't actually affect anything here. So yeah, I compare R-A-X with zero. Oh, sorry, the syntax is weird here. That's fine, we'll look at it a second. Compare R-A-X with zero, jump if it's equal to exit. Cool. Okay, so let's run this and see if we got this. Yeah, dash M Intel. That's only for people who are so lame Connor that they can't do multiple syntaxes at once. Okay, so did foobar foobar? Does this mean my thing passes? But I'm good. It's my test valid. Good morning. Well. What was the problem with this test I just made? It doesn't give you more than 100 bytes. Exactly, I didn't test the condition that it's more than 100 bytes. So the program worked, quote, quote, worked, but we didn't actually test what we wanted to do. So I'm gonna just put in a bunch of input and it only output this much, it didn't, and then it, the annoying thing is it took my input and then tried to feed it back into the shell, which is very annoying. Okay, cool. So that didn't work. We have to go back to the code. This is a pretty good way of approaching your code, make small changes and that you think will implement something and then figure out a way to test it, go test it, test that condition, make sure it does what you think it does, and then make sure you try to test things that it shouldn't do. Okay, so what's the problem here? How come it didn't, we gave it more than 100 bytes, how come it didn't go back into the start? I guess another question is how do we figure this out? How do we think about these? We need a label that goes back to start. So we have the label, but we don't have anything, a dump that will send us there. Yeah, we haven't altered the control flow at all, right? So if you think, and what you can do is actually walk through these instructions. Okay, subtract 100 from RSP, move all these values in here, call system call, which should call read, great. I called read, let's say I returned 50 bytes, compare Rax with zero, is Rax equal to zero? No, so we don't jump, we fall through and go here, and so we go, okay, move Rax into there, move one into Rax, move one into RDI, move RSP into RSI, call syscall, great, this calls right, we already checked that, and it also specifies the number of bytes read which comes from Rax, excellent. What happens after the system call? Not quite nothing, what's the next instruction that gets executed after the system call? Rax 60. Move Rax 60, move zero into RDI, and then syscall, which calls exit, right? So just like this system call, the thing that happens is the next instruction, and then the next instruction, and so the problem is we have nothing that goes back to the start here, right? We essentially accidentally fall through and go to the exit case, which is not what we want. So it should be, is it just J? Or is it branch, is it a BR? You want, what, are we not doing it? Oh, your branch, okay, remind. Yeah, so we just wanna always go at the end here as after we call write, yeah, we wanna go back to start to call read again, check the return value of read, and then call write again, and then go back. So this is where we get the loop, where we're actually jumping back into our code and control flow, the CPU will start executing start over again, and where we've made a loop, hopefully. Hey, jump, JMP. Ah, what is it doing? Did it break? I typed in foobar, it output foobar. I type in foobar enter, it outputs foobar enter. Yeah, it's looping just like we wanted it to. REX is never zero, but let's say. Do I do anything? Nope, okay. So let's look at why. I did control C to kill it, because I think it's important to look at this. So I can say foobar, read, write, hello. Now the important thing here is it's actually reading and writing my slash N, so that's actually my new line character that I'm typing in, and that's why if I just hit enter, it actually is reading, it's reading one byte, and that's the enter character. And so that's why it writes out an enter character of another byte. Thank you in the chat for my nice shirt. This is one of my favorite CTF shirts from the order of the overflow. Okay, cool, we're stuck in this hell loop. So how do I man to read? Okay, blah, blah, blah. The, so zero is when we're actually at the end of file. Right, so where does it say that again? So blah, blah, blah, blah. Zero indicates end of file, cool. So how do I, so now, how can I tell it I'm done? I know control D usually works, I think. It definitely does, so I can hit control and then D, and then I'm done. So what actually is happening here is control D, some special functionality. Supposed to simulate or send the end of file. I don't know if character is the right word, but right? Yeah, so let's see. So one thing I wanna show is if I do echo hello and I pass that into a.out, our program, we can see it actually works just fine. It reads it in and writes it out, right? Just like cat, which is exactly the same behavior as cat. Yeah, I can even do new lines, perfect. Right, all that stuff works. And we can actually look at S trace, we can run it here and we can see we're again reading from standard input, writing the standard output, and look, this is great. We actually get our read. So this is a read where it returns zero saying, hey, I've read zero bytes and it properly exited, cool. Well, let's do that again with our input. So I'll type in foobar and then I'll type in control D and I'll see that there. Okay, what's happening is we have to think for a second when I'm running a.out here and I type in foobar, what's waiting for my input? Yeah, from our program's perspective, it's calling read which is the operating system, right? So the operating system is getting invoked it's reading from standard input, which in this case is coming from us, the user. Now the problem is read didn't specify what characters I can read in and what their values are. So we can't use something special like, hey, when you see a null byte, when you see a zero byte, that means I'm done because we can have files with null bytes, we can have files with any kind of bytes. And so to represent that, what we need is when we do control D, we're actually telling the shell bash that, hey, I'm done now giving input. Consider my standard input that I was typing in at the quote end of file. There's no more bytes that I'm gonna send. And I believe if we search for bash and file, yeah. And da, da, da, da, da. See, when you press control D, the terminal signals of blah, blah, blah, we've reached the end of file and this is great. So then the operating system knows to do all that and it is excellent. So you can look at that, whatever. This is a kind of a nice general tip for any kind of system you're working on. This is for almost all command lines use this kind of thing. So you can learn for, oh, on Windows, blah, blah, blah, this is how you input end of file. Or on your, I don't know, I think all of you like young whippersnappers use like visual studio code or whatever. And so like, how do you type in an end of file in the console that exists in visual studio? I would assume it's control D, but who knows. But anyways, cool. So yeah, using that, we just were able to write our own version of cat. So we could say cat and so see I can use it just like cat. Anywhere I was using cat to read from standard input and write to standard output, I could use that. And we did that. So now that we've been able to write a program to echo all of the input as output, now I want you, this will be a fun learning exercise. So let's write a program that writes the string foo bar to a file called foo. So what's the basic outline here? So we have the man to open. We want to call open with a path name parameter and flags. And we're gonna do that. So we're gonna open. And if we do open, what's the bup bup bup? The open system call opens the file specified by path name. If the specified file does not exist, it may optionally if Ocreate is called be created. So we'll need to make sure we specify this flag Ocreate. I'll help you with that in a second. The return value of open is a file descriptor. Just like the exact thing that we were talking about that we were using of standard input and standard output, right? Once we have that, we can pass that file descriptor to read write close is the alternative of open. So we read it and then open it. And yeah, so okay, things we'll need from this. So we'll need, we had our hello world example if you recall and we had our hello world example. So this will help you with the strings. So let's call this open file.s. I will steal all this from here. And I will make, okay, text global is what we need so that it knows that, hey, I want you to start executing from main. I always like to end with exit. This is always so nice. And now let's just like we did before with echo. Okay, purpose of program. Open a file called foo, create it if it does not exist and write the string foo bar to the file. Okay, so what strings do I need? Do I need the string hello world new line? Yeah, don't need that. What strings will I need? Yeah, I want the string foo bar and I got to change the label to be foo bar. And oh, do we say we want it slash n? Sure, we can add a slash n to it. It's usually nice to do that, but okay. And then we want another string of foo. So foo will be the file that we wanna open. Right, we have the exit. Now what we'll wanna call is open foo. And the question is we have these man to open. We saw that open has this flags parameter. The argument flags must include one of the following access modes. Oh read only, oh write only or oh read write. These requests opening the file read only, write only and or read write respectively. In addition, zero or more file creation flags and file status flags can be bitwise or in flags. The file creation flags are oh close exec, oh create, oh directory, oh Excel, oh no CTTY, no follow, temp file and truncate. So if you want to learn about all of these beautiful flags, you could go and literally read, I see as I'm scrolling this, you can see that there's a ton of flags that you can specify here. All the ways that control, how your program is asking the operating system, hey, to create that file because for instance, so the one we're interested in, oh create. If path name does not exist, create it as a regular file. So it's kind of interesting that if you think about it on its face, the system call of open should be very, very simple. So if you think about it, this system call should be very, very simple. We have, we're calling open, we're specifying a path name and we're just like, hey open this file for me, but what do we wanna actually do with this file? Do we want to read from the file? Do we wanna write to the file? Do we wanna both read and write from the file? What if the file doesn't exist? What should we do? Should we create the file? What if we open up a file for writing, but there's already content in the file? Do we wanna make, truncate the file, make it be zero bytes? So you can see that actually this simple concept of, the simple concept of, hey, what do we want to, the simple concept of, hey, I wanna open a file is actually not so simple. And this is why you have all this insanity in terms of flags and all of the return values and all of the cool stuff that can happen. So what we're doing here is, okay, so we need the ocreate flag because we wanna create it if it doesn't exist. And we want, so ocreate, and we want, what did it say? Okay, cool. And owrite, so we'll do open it with read write, it doesn't really matter. And it said bitwise or, so we actually need to order those two values together. Now we need to look up, what are these values of ocreate and oreadwrite? So for this, I would open up my hand dandy ocreate value in Linux, I don't know it off the top of my head. There we go. Oh God, these are in octal, that's so annoying. That's okay. We'll see if our assembler supports this and oreadwrite is o2, okay, cool. Yeah, so this is what's tricky if you're not used to readings. Can somebody verify that that's correct? This is octal, right? That's octal, escape sequences and double quotes. C, octal, or literal. I think it's, you start the number with a zero. Okay, that's why I thought I just wanted to be very clear. Yeah, so we were talking about bases when we talked about hex being base 16. So every digit represents zero through 15. Similarly, octal is eight. So that's why we're getting different values here. Cool, okay, so yeah, this was what I thought. So beginning with zero, yeah, that's why zero, one, one. Isn't that a Python thing? Is that a C thing, Zion? The O, zero, 10. Yeah, it's a Python. It's much better, it's much clearer. This is insane, it makes you think that like, oh, maybe this value, but we can check. Bracket, Thon, what the hell are you talking about, man? Okay, but we can actually just use these values. Actually don't care. The important thing is when you're writing the C code, you actually don't care what these exact values are. It actually only matters to talk to the operating system. And your C program would have a substitute in the values at compile time. But we just stole the values from some code. We think they're these. Let's use them right now and see, hey, maybe these are the same. So, so this will be, okay, we'll look at open. So open is two in RAX. So, and move into RAX two. That's open. So I need the file name. So the file name I wanted was foo. So move into RDI foo. And now I'm gonna do something tricky. I'm gonna say, well, not really tricky, but let's move into RSI or the flags, RSI. This, and it needs to be, they said bit-wide OR. So I think I can just do OR into RSI 02. And that should take whatever's in RSI or it with whatever's in 02, and then put that value into RSI. And, okay, this is saying I need a mode. So now I need to actually read about the mode. The mode argument specifies the file mode bits when a new file is created. This argument must be supplied when O-creator, O-tempile is specified in flags. Great, okay, we specified O-create. This means as a little hint, this is the permissions that are on that file. So this is what we're setting here. For now, we actually know we can do octal. I think it's, yeah, 777 should be just handy-dandy fine. But this needs to go into RDX. Let's go back here, remove RDX and syscall. Okay, so this should create a file called foo. So let's compile this bad boy. And, okay, and this was called open file. Wow, guys, I'm really good at naming things. Okay, cool, aha, I have the same problem before. While I'm doing this, Zion, can you check out how to, I can't remember, how do you reference these things in a PIE? Do I have to use like RIP plus or something? Do you mean the jump where what? No, the problem is this label. So, yeah, using this label when I just move it in here, it obviously. We'll move the address. God damn it, did that work? Yeah, it did. See, man, I don't even need you. Why are you even here? Dang, it could be me through the punch. No, I didn't know that that would work, but basically this is how you would reference and let the fact that foo can be anywhere. So RIP is the current instruction pointer. So this actually calculates the memory location that foo will be in relative from the current instruction pointer. And that's so that the program, when the operating system loads this program, it can actually put that memory and move it anywhere in memory. Whereas before when we said, hey, don't do position independent code, compile it at fixed memory locations. So this actually helps us and it means that we're writing nice code that will be future perufe, which is super cool. So, okay, so yeah, we have loading that into there. Cool, so look at this. And we just made, and look, again, so we called open with foo. We passed in, oh, read, write, oh, create, 777. And what's this return value that opened returned? It's the file descriptor. It's the file descriptor. Yeah, exactly. Which makes sense. Why did this make sense? Because I just said that it makes sense. Because that's what the man page said. It is what the man page says. And also, what are the file descriptors that we know about? Zero, one and two. And so the next smallest open file descriptor is three. And that's why it returns it. I don't know that that's officially part of the standard, but it is normal behavior of most operating systems that I've seen. Okay, yeah, question in the Discord. Do we have to close it? We should to be good people. But when we call exit, because remember, who knows? Who's keeping track of what files we have open? The operating system. The operating system, exactly. So when we tell the operating system, hey, I wanna exit, I'm all done. At that point, the operating system actually will close all of the open file descriptors that we have. So in this case, we don't have to do it, but yes, in general, that way, if our program, as soon as your program is done with a file, you should close it because you don't need that anymore. Okay, this is sweet. So we did this, and then we called syscall. And now we want to call right into the file descriptor that we just got, which is in where? The Gasp at RAX. Yeah, into foobar. And how many bytes? One, two, three, four, five, six, seven, eight. Eight bytes. Cool, so this is when we switch to me doing stuff, to y'all doing stuff. Cool, so finish that. Implement that right function. Stop streaming that. I need to switch this OBS over. Okay, and for those of you that want the flag for today, it is now up exclusively on the ASU Hacking Club Twitch, right above me, QPU. Feel free to ask questions. Oh, somebody asked the difference between test and CMP. I have no idea. I have to look it up. I literally don't know. Sorry, I wasn't asking about the difference, but why is test use versus CMP? Yeah, I still don't know. This is a fascinating Stack Overflow response. I think the main thing is that test EAX EAX is shorter than compare EAX with zero, so it uses less bytes. So it's a performance optimization. Yeah, exactly. Okay, thank you. Yep, hey, Zion? No. Is the invite for the Discord still in the browser, like mentioned on the website? Yeah. CryptoMathican is wondering. Oh, cool. But it should be. Be really sad if it wasn't. But that just gets access to the public space. Man, people are making millions of dollars doing Twitch as absolute insanity. The invite's still there, and it's still very real. Cool. Why is Twitch turning my smiley faces into robot smiling? What the hell is that? Man, I'll never understand this thing. Pretty sure it's because robots are gender new. I have a question. Shoot. I did read it right like we did in the Echo Meeting script, but obviously passing RAX into RDI, which is our file descriptor. And then I used the RIP addressing, so RIP plus foobar. But when I run this whole thing and I do an S-trace on it, it tells me that I've got a bad address, a bad file descriptor. Did you do LEA instead of move? No, I did not. They're all moves. For the RIP plus foob? Oh, does it need to be an LEA instead of a move when you use the addressing? Exactly. Because a move would say calculate what's at RIP plus foob. Whatever's at that memory location, move those bytes into RDI. What you wanna do is calculate what's the value of foob plus RIP and then move that into RDI. So it's like the pointer equivalent, yeah. Okay, thanks. Yep, look at this, Zion. We got Discord, I mean Twitch people making their way into the Discord. You're starting a revolution. I know there's two, what? Yeah, we have a public session, right? Like what? In Discord. Yeah, yeah. We got a public channel and we also got a public challenges channel. Yeah, so crypto, mathikian, mathikian. Yeah, welcome, we do it. We have a public thing. We also have public challenges. If you wanna dig more into this and learn more, happy to hang out. We're doing this weekly at five, five Wednesdays. Yeah, no voice channel at the moment. Currently a member's only thing because we're an ASU club. But feel free to, you know, you can ping and chat with our illustrious president, Zion, AKA Mahalo's, who has always got an ear for your suggestions, right, Zion? Yeah, yep. One of the suggestions. Give me the suggestions. I don't know. I mean, also if you're whoever is in that public channel, it's pretty neat if you get the membership challenge because then you'll be one of the people on the membership board who is not a real ASU student. You can forge your own signature. Oh snap, gotta hack your way in. Exactly, you gotta hack your way in. It's the only way. I think as of right now, there's only one person on the list who is either not a real ASU student or is and didn't want to verify they were real. Everyone wants to know. That's great. And they didn't try either. If they didn't put like a fake signature, they just put nothing. But I actually encourage all the fakers to put a fake signature so nobody really knows. Yeah, then they have to verify it with the public. They really want to know who the real members are. Nice, I like it. All right, sweet. We got three people done, it looks like so far. When you got it, yeah, I figured it would just be easy thing to just make a little reaction to that message I wrote when you're done. That way I can keep track. And we'll probably sync up and let's give it maybe 10 or five more minutes and then we'll go through, finish this off. You guys are working your way up, becoming real assembly programmers. And as you're doing this, think about how this used to be the only way to program. You could only write assembly, is that insane? No fancy Java or C++ to hold your hand. And you got to actually move those values into registers. And if I can pontificate a little bit more, this is understanding how to program a machine at this kind of low level, right? Moving things in, moving things out. This is what is used for things like programming Rop. I mean, basically the people have described exploitation as programming a weird machine. So basically you're reusing little bits of functionality of the program to do something it wasn't supposed to do. Or like with heap exploitation, you're getting the heap into this crazy configuration that it wasn't meant to do. And that's actually what enables you to, that's like at really at the heart of exploitation is getting a program to do what it wasn't supposed to do. But in order to do that, you need to understand how to make a program do what it's supposed to do, right? So it's these concepts of doing these types of things. Programming these, you know, X86 is a weird machine. It's just the one that most of our systems run right now. And so we're used to reading it and understanding it. But these concepts apply to many different areas. I guess to abuse Yon's cruddy kid kind of metaphor, this is kind of the, you know, you're on the practicing, whatever, what's it called the wax on, wax off, right? You're doing what seems to be mundane programming things, but really there's a deep thing that when you really grasp how to understand and program a machine that applies to other aspects of hacking. All right, we'll sync back up in five minutes. All right, let's see, we're at five people. It's pretty good, we got roughly 50%. All right, shall we continue? All right, we have, yeah, five people done. We'll kind of wrap this up here. See, do I want to give you any homework? I don't know, did anybody actually do the homework last time? Cool. I totally did. Yeah, some reason, hey, that's fine. That's actually 100% fine. It doesn't matter when you do it, right? The only thing I care about is that you actually, you know, because this is, I mean, this is, it's the truth, right? You can, you get out of it what you put into it, right? I mean, Xion, did you get to be leaked by going to meetings and following along in the meetings that we had? No, I guess not. I had leaked from the content from the meetings and I would work every weekend to get better. So I guess it was self-studied more than anything. Yep, playing CTFs. I'll push you the right way. Yeah, exactly, like the purpose of the meetings is to exactly do that, right? Give you an opportunity to guide you, right? That's what we're trying to do here, what I'm trying to do is to guide you through these topics starting from somebody who literally knows nothing, right? To now your writing code to open and write files and learning X86 assembly in the process, which I think is super cool. And so, yeah, that's the goal here. But it's only question and discord. Elite is quantifiable. Would you guys know how you become a hacker? There's kind of a famous way of how you become a hacker. Anybody know? Well, I don't know this one, what's the answer? Oh, you're a hacker when a hacker calls you a hacker. So it's one of those things, you can't call yourself a hacker. You can only have somebody else call you a hacker. And that person themselves, if they are a hacker, of course, you have this chicken and egg problem of, yeah, exactly, in the chat, who was the first hacker? I think it's probably a group of people, but yeah, it's kind of a similar concept in terms of LEET. So I think it's honestly pretty lame to claim that you are LEET. You kind of know if you are. And you especially know it when other people either call you LEET or think of you as LEET. So that's kind of what I think about there. But yeah, that's a good question. Cool, okay, let's speak of LEET. And the only way we get there is by practice. So that's what we're doing here. Cool, all right, let's do this together. If you're stuck and got stuck at a point, and then follow along, see where you got stuck. Feel free to ask questions of why, like this way it didn't work, just like we talked about, right? If you change this load effective address to a move, you're gonna have problems. Okay, so now I wanna call write and I wanna pass in the FD as the second argument, the FD that we got from the open system call. I then pass in foobar and I'm gonna pass in eight. So what's the first thing I need to do? Look at my other code because I'm not an insane person. I'm not gonna just write this again from scratch, but I have my echo function here. I already have a write function. Why the heck would I start over from scratch? Cool, so I know from here, I'm gonna need to move into RAX one. I'm going to need to move into RDI. So what I wanna move into RDI, so let's look at the, I've already lost the thread. RAX was the number, so this was write. RDI was the first argument. RSI was the second argument and RDX is the third argument. So RDX is pretty easy. My third argument here is just eight. So I'm gonna move eight into there. I move one into RAX, but how do I put this file descriptor in the first argument, which is RDI? So again, I don't wanna write, I hard-coded one because I wanted to write to standard output, but now I wanna write to that file that I just opened. Yeah, move RDI, RAX, thank you. But before I overwrite it, yes, thank you, look at that. You're already anticipating all of the questions and stuff that come up, right? So we first move RAX into RDI, setting this file descriptor. I move one into RAX, I load effective address into R, now it's not RDI, it's the second argument, so it's RSI and rip plus foo underscore bar, if I recall correctly, yep. And then what do I need at the end here? Sis call, yes, thank you. All right, so now let's test that. Let's cat out, I have the file foo, so there's nothing in there right now. I can do LSHLA foo, it'll show me there are zero bytes in this file. GCC, I shouldn't need that. Yep, run eight on out. I always like to do S trace eight on out. Oh, look at this, this is beautiful. Open foo, read write, create 777, write to file descriptor three foo bar slash n eight bytes. If I do LSHLA foo, I'll see there are eight bytes in there and if I cat foo, it'll say foo bar and if I hex dump foo, I can see those bytes. Oh, that's all, I hate actually this program. Okay, yeah, this is much better. F-O-O space, remember we were looking at hex 20, it's a good one to remember as space B-A-R new line. Boom, those are those eight bytes in that file, awesome. Now, what I want you to do is pretty easy. Let's take our hello world meeting and I want you to call write, change it to write to to hello world. That purpose of program, write to standard and make sure this compiles so I don't give you some, this is writing the standard output, shouldn't take you too long. All right, look at that, getting better and better. All right, everyone say bye, Twitch, see you all. You know what I gotta say, it was actually fun chatting with people on Twitch. I don't understand any of the emojis that you guys use, but you know, I can learn and yeah, see y'all next week. Whoa, it's a cool one to end on. No.