MIT course "Computer Systems Security". Lecture 3: "Buffer overflow: exploits and protection", part 3
Massachusetts Institute of Technology. Lecture course # 6.858. "Security of computer systems". Nikolai Zeldovich, James Mykens. year 2014
Computer Systems Security is a course on the development and implementation of secure computer systems. Lectures cover threat models, attacks that compromise security, and security methods based on the latest scientific work. Topics include operating system (OS) security, capabilities, information flow control, language security, network protocols, hardware protection and security in web applications.
Lecture 1: “Introduction: threat models” Part 1 / Part 2 / Part 3
Lecture 2: “Controlling hacker attacks” Part 1 / Part 2 / Part 3
Lecture 3: “Buffer overflow: exploits and protection” Part 1 /Part 2 / Part 3
You can use the method of guessing the "canary" for your own purposes in order to determine the presence of "weak" bits in terms of selection. That is, if you guessed correctly, the server will reboot, and this will serve as a signal to you that the given value is fairly easy to guess. Thus, it is possible to defeat randomized "canaries", assuming that after rebooting the server, their value will not change. You can also use gadgets to implement a connected sequence of multiple attacks.
Next, we will look at a more productive way in which you can use all these methods to defeat data execution prevention, random address spaces, and canaries.
Let's turn our attention to 64-bit architectures instead of 32-bit architectures. The former are better suited for randomization, so they give you a lot more “randomness” to protect against a hacker. And these systems look much more interesting in terms of the formation of attacks.
This type of 64-bit architecture was also considered from the point of view of BROP , “blind” reverse-oriented programming. For simplicity, we assume that the only difference between 64-bit and 32-bit machines is that on a 64-bit machine, arguments are passed to the registers, and 32-bit machines to the stack.
When a function starts execution, it is taken to “look in” at certain registers to find where the arguments are.
And now we come to the essence of today's lecture - what is blind, reciprocation-oriented programming, or BROP . The first thing we do is find a stop gadget. Remember that when we say "gadget", we essentially mean return addresses. The gadget is identified with the return address, the starting address of the sequence of instructions to which we want to go. So what is a stop gadget?
Essentially, it is the return address to some place in the code, and if you jump there, then just suspend the execution of the program, but not cause the program to crash. That is why it is called a stop gadget.
You could jump to some place in the code, which then initiates a sleeping system call, or pauses, or something like that. It is possible that the program will somehow “get stuck” in an infinite loop if you jump into this place. It really doesn't matter why the shutdown happens, but you can imagine several scenarios that would lead to it.
What is the use of a stop gadget?
Once the attacker has managed to defeat the canary using the interactive bit guessing technique, he can start rewriting this return address ret addressand taken to "grope" stop gadget. Note that most of the random addresses that you can put on the stack are likely to just cause the server to crash. Again, this message for you, the attacker, is an indication that what you find is not a stop gadget. Because when the server fails, your socket is closed, and you, as an attacker, understand that you did not hit the stop gadget. But if you guessed something and the socket remains open for some time after that, you think: “Yeah, I found this stop gadget”! So the main idea of the first step is to find this stop gadget.
Step two is that you want to find gadgets that remove stack entries using the pop command. Therefore, you must use this sequence of carefully designed instructions to figure out when you grab one of these stack gadgets. This sequence will consist of the probe address of the probe address , the stop address of the stop address and the address of the system failure of the crash address .
Thus, the probe address is what we are going to push onto the stack. This will be the address of the potential stack cleanup gadget, stop address - this is what we considered in the first step, this is the address of the stop gadget. Then the crash address will simply be the address of the non-executable code. Here you can simply put the zero address (0 x 0), and if you use the ret functionTo this address and try to execute the code there, it will lead to a program crash.
So we can use these types of addresses to find out where these stack popping stack cleaning gadgets are located .
I will give a simple example. Suppose we have two different examples of probe , the trap trap and the stop trap . Suppose using the probe we are going to “feel” some address, suppose it starts with 4, and ends with eight: 0x4 ... 8, and behind it is the following address of the form 0x4 ... C. Hypothetically, we can assume that one of these two addresses is the address of the gadget stack popping .
Trap trapwill receive a zero addresses 0x0 and 0x0, and stop gadget stop even if the address type is arbitrary 0hS 0hS ... ... it does not matter. This stop gadget points to the sleep code (10), causing the program to pause.
Let's start with the probe operation , which clears some register and returns in the following sequence: pop rax; ret . What happens? When the system jumps to this address, the stack pointer moves to the middle of our gadget. And what is the gadget going to do here? Right, perform the pop rax operation .
And then follows ret , which will move the function to the top line of the gadget, that is, to stop, and the function will stop without crashing the entire program. So, using this gadget, an attacker can say that the address of the probe belongs to one of these functions, which clears the stack, because the client's server connection is open.
Now let's assume that the second probe address pointed to something like xor rax, rax, ret for some register.
So what happens if we try to jump to this gadget? Note that it does not clear anything in the stack; it simply changes the contents of the registers. So, we are going to make a return to the above zero address 0x0. And this will lead to the system failing. The client connection to the server will break, and the hacker will understand that this is not a stack cleaning gadget.stack popping .
Thus, you can use a fancy series of traps and stop gadgets, for example, you can clear two items on the stack. To do this, you just need to place another trap instruction here , and if this gadget does not erase two elements, you will find yourself in one of these traps, and the execution of the code will stop. The materials for the lecture described the thing called BROP gadget, which is useful if you do not like to return to programming. But today I will tell you how you can use these simple pop gadgets to launch a similar attack. Once you understand this, it will be much easier to deal with the BROP gadget .
But do all of you understand how we can use the probe function for these gadgets? Suppose you find the location of the code fragments that allow you to clear the stack using the pop function , remove one element from it, but in fact you do not know in which register this pop function will work . You just know that it is already ready for execution. But you need to know in which register these pop gadgets work, because in the 64-bit architecture the registers control where the arguments of the function you want to call are located.
Thus, our goal is to be able to create gadgets that will remove values that we put in certain registers of the stack, and ultimately we initiate a system call, a system call , which allows us to do something bad.
So now we need to determine which registers are used by pop gadgets.
To do this, we can take advantage of the system call pause. If the system call is paused, the execution of the program is suspended, and it does not accept any arguments. And this means that the system ignores everything that is in the registers.
In fact, to find the pause instruction that we execute, you can link all these pop-gadgets so that we can put them all on the stack, and between each of them we insert a syscall number for a pause. Then we will see if we can “hang” the program this way. Let me give you a concrete example.
Here, in the return address line, we put a gadget that clears the RDI registers and applies the ret function to them . Above it, we put a syscall number for a pause.
Suppose that we have another gadget located above, which performs the pop function in another register, say RSI , and then executes ret . And then we put the syscall number again.for a pause. And we do this for all the gadgets we have found, and then we end up at the top of the stack with the suggested address for syscall .
Remember again how you use these system calls. You must put the syscall number in the RAX register , then call the syscall libc function , which is about to make the requested system call.
So what happens when we execute this code? We will come here, in the ret address string , we will jump to the address of this gadget, while it should be noted that the attacker knows that this gadget, located on the right, removes something from the stack, but does not yet know in which register it is located.
So, if we jump onret address , what happens? It will use the pop function to pause syscall , in some register that the attacker is not aware of, and then we will continue to climb this stack of operations up the stack.
At the same time, we hope that one of these gadgets will perform the pop function for the syscall number in the corresponding RAX register . So by the time we get here, to the top of the stack, “spoiling” all the registers that have a syscall number for a pause along the way , we will hope that we still have one register, which must be correct. Because if one of our gadgets does this, then when ret, after some time we will return here, to the top of the stack, then we will get a pause. Once again, the pause acts as a signal to the attacker. Because if this supposed address for syscall was wrong, then the program would crash.
So what does this phase of attack allow us to do? We still don’t know in which registers which pop- gadgets are located, but we know that one of them will release RAX , which we want to control. And we probably know the address of syscall , because we managed to pause the system.
As soon as we do this, we will be able to check these gadgets one by one and find out which of them puts the system on pause. In other words, we will cut out everything that is located between the line.ret address and top of the stack to go directly to syscall . We will check if there is a pause or system crash. If a failure occurs, we identify the gadget that caused it, for example, it is in the bottom line to the right, this is pop rdi . Get rid of him and try the next one. Put here, in the line above the ret address, the real address for syscall . We were able to pause the program? Yeah, so we learned that this pop gadget should free RAX . It's clear?
Audience: So, a way to guess the address for a system call is just a blind transfer of gadgets?
Professor:Yes, it is, and there are ways to optimize this process in the lecture materials when you use the PLT file extension and similar things. With a simple attack that I described, you really just put in the address here, and see if it caused a pause or not. As a result of the test, we find out the location of the syscall . We find out where the instruction that performs pop RAX is located . We also need gadgets that perform pop and in some other registers too. You can do similar tests for them. Therefore, instead of causing a syscall number to pause , use push for some other command that, for example, uses RAX argumentsand RDI .
Thus, you can use the fact that for any particular set of registers that you want to control, there is some kind of syscall that will give you, the attacker, a signal to find out if you broke it successfully or not. So, at the end of this phase you will have the address syscall and the address of the heap of gadgets that allow you to perform pop in arbitrary registers.
Now let's move on to the 4th step, which will be called the write - record. The fourth step is recording the system call. To call write , we need to have the following gadgets:
pop rdi, ret;
pop rsi, ret;
pop rdx, ret;
pop rax, ret;
How are these registers used by the system call? The first is a socket, or, more generally, a file descriptor that you are going to pass to write. The second is the buffer, the third is the length of this buffer, the fourth is the system call number and the fifth is syscall itself .
So, if we find all these gadgets, then we can control the values that are embedded in the arguments, which, in turn, are placed in these registers, because we simply “pushed” them onto the stack.
What should be the socket? Here we will have to do a bit of popping. You can take advantage of the fact that Linux limits the number of simultaneous open connections for a file that reaches a value of 2024. And also that it should be the smallest of all available.
I wonder what we are going to insert into the buffer pointer? That's right, we intend to use a text fragment of the program here, we are going to put it in a pointer somewhere in the program code. This allows us to read a binary file out of memory using the correct client call from a socket. Then the attacker can take this binary file, analyze it offline offline using GDBor another tool to find out where it all is. The attacker knows that now, every time the server “falls,” the same random set of things will be stored in it. So now that the attacker can find out the addresses and offsets for the contents of the stack, he can attack these gadgets directly. It can directly attack other vulnerabilities, figure out how to open the shell, and the like. In other words, in the place where you provided the hacker a binary file, you were defeated.
This is how the BROP attack works.. As I have already said, the materials of the lecture describe many ways to optimize these processes, but first you need to understand the basic material, otherwise the optimization loses its meaning. So you can talk to me about optimizing individually or after class.
For now, suffice it to say that these are the basics of how you can launch a BROP attack . You have to find the stop- gadget, find those gadgets that perform the pop function of the stack entries, find out what registers they are in, where the syscall is located , and then initiate write , based on the information retrieved .
So, quickly go over the topic, how do you defend against BROP? So the most obvious thing you have is re-randomization. Since the fact that “fallen” servers do not respawn, do not create re-randomized versions of themselves, acts as a signal that gives an attacker the opportunity to test various hypotheses about how programs work.
One easy way to protect is to make sure that you do exec instead of fork when you revive your process. Because when you run a process, you create a completely new randomly located space, at least that's what happens in Linux. On Linux, when you compile using a PIE , Position Independent Execution , location-independent executable file using execyou only get a new random address space.
The second way is simply to use Windows, because this OS basically does not have the equivalent of the fork function . This means that when you revive a server on Windows, it will always have a new random address space.
Someone here asked what would happen if, after a server crash, it does not break the connection? So, if, when the server crashes, we somehow “catch” this error and keep the connection open for some time, we will be able to confuse the attacker so that he does not receive a signal of failure and thinks that he has found the correct address.
In this case, your BROP attack will turn into DOS-attack Because you just got all the potential zombie processes that are around. They are useless, but you cannot let them go further, otherwise you will have to delete this information.
Another thing you might think about is the execution of the border check that we talked about before. The lecture materials say that this method is unproductive, since it is 2 times more expensive, but you could still use it.
This is how BROP works .. As for homework, a more delicate question is asked: what if you use the current time hash? That is, the length of time during which you restart the program. Is this enough to prevent this type of attack? Note that hashing does not magically provide you with bits of entropy, if the data entered into the hash is easily guessed. If your hash contains billions of bits, it doesn't matter. But if you only have a couple of values there, the attacker will simply guess them. Of course, using a random time hash is better than using nothing to protect against a hacker, but this does not provide you with the security you should count on.
Full version of the course is available here .
Thank you for staying with us. Do you like our articles? Want to see more interesting materials? Support us by placing an order or recommending to friends, 30% discount for Habr users on a unique analogue of the entry-level servers that we invented for you: The whole truth about VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps from $ 20 or how to share the server? (Options are available with RAID1 and RAID10, up to 24 cores and up to 40GB DDR4).
Dell R730xd 2 times cheaper? Only we have 2 x Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100 TV from $ 249 in the Netherlands and the USA! Read aboutКак построить инфраструктуру корп. класса c применением серверов Dell R730xd Е5-2650 v4 стоимостью 9000 евро за копейки?