Note! In the end of this article there is a terminology section that briefly explains some of the concepts mentioned in the article. The concepts included in the terminology section are marked with a " * ".
At some point Microsoft took the buffer overflow vulnerabilities bothering Windows seriously and developed a method for preventing them. Nowadays all modern versions of Windows support Data Execution Prevention (DEP) and it is also used on Linux and Mac OS X. It was developed to prevent illegitimate code from being executed in the call stack*. Return oriented programming (ROP) is tightly related to DEP, because it is a way to bypass DEP. Let's first discuss DEP briefly before we delve into the theory of ROP.
There are also other ways to protect the stack from code injection. For example stack protection, which adds a random integer after the return pointer. The integer will always be checked when returning from a function. If an attacker overwrites the return address, he will also overwrite the random integer and cause the check to fail. However, more thorough discussion of stack protection is outside of the scope of this article.
The goal of DEP is to prevent malicious code from being injected into another program. In other words, DEP was created to stop stack-based exploits. However, it can sometimes cause legit programs to behave in erroneous ways because it can also restrict the operation of normal processes. Therefore, it was made possible to disable DEP on a per-process basis. For this purpose, there are Windows API calls that can be used to mark specific ranges of memory as executable. That is, override DEP on those memory ranges.
There seems to be an opening there. However, if code injection is not possible, how can it be possible for an attacker to execute any code and create a call to Windows API functions? Enter Return Oriented Programming.
ROP works, because even when DEP is enabled, we are allowed to execute one kind of instruction - a return (ret). Hence the name, return oriented programming. What ret instruction does, is redirect execution of the code to the next pointer on the stack. ROP is used to force the execution of a program to return to ROP gadgets instead of returning to the normal address of its function in libc*. That is the first stage of the actual exploit. The normal return point is replaced and the execution is moved to a ROP gadget.
Gadgets are small instruction sequences that always end with a return instruction. Before one can start jumping to gadgets, however, the useful gadgets must be enumerated and chained together to craft the API call. The idea is that the execution jumps from one part of the gadget chain to another part of the chain without ever executing a single bit from any DEP protected regions. That is, each gadget will always return to the address of the next gadget on the stack.
One can find the return gadgets on a computer by doing a search for a binary with “ret” bytes ("c3" in hexadecimal). All valid instruction sequences found in the binary or linked libraries should be listed. Out of that list, the most suitable ones will be chosen to be used in the ROP chain. There are many tools available that can help you find the gadgets, so not everything need to be done manually.
After listing the gadgets in different application modules in the memory, the stack is filled with these gadgets in proper order to craft the API call. This way the instruction sequences are chained together. Note that some of the gadgets manipulate several registers when setting the values. Therefore, the gadgets need to be placed in such order that they don't overwrite the registers set by previous gadgets in the ROP chain. In practice, one way to build the first stage ROP payload is to load all API parameters into various registers and then use PUSHAD instruction to push them to the stack in the proper order.
The API call will then be used to mark certain memory areas in the stack as executable, which will allow the execution of second stage payload (ie. the shell code). In other words, with the API call you will mark the part of memory where you have your shell code as executable. This technique relies on the attackers ability to predict where certain instruction will be located within the program memory. Therefore, only gadgets from modules that don't implement ASLR* can be used. There are techniques for bypassing ASLR too, but I will not go there in this article.
There are many different API calls available across Windows Builds and Service Packs. These calls are documented and can be found on MSDN (Microsoft Developer Network). The attacker will always build his ROP chain based on what API calls are available on the system. However, VirtualAlloc and VirtualProtect are the most common functions used to change the protection on a region of the virtual address space* of the calling process. The API calls for these are universal across all Windows builds.
In the end, the return instructions of the gadgets will take execution to the function pointer for VirtualProtect/-Alloc, which will mark the part of the stack where your shell code is as executable. Then you just need to jump the execution to that shell code and profit.
At some point Microsoft took the buffer overflow vulnerabilities bothering Windows seriously and developed a method for preventing them. Nowadays all modern versions of Windows support Data Execution Prevention (DEP) and it is also used on Linux and Mac OS X. It was developed to prevent illegitimate code from being executed in the call stack*. Return oriented programming (ROP) is tightly related to DEP, because it is a way to bypass DEP. Let's first discuss DEP briefly before we delve into the theory of ROP.
Data Execution Prevention
Before DEP, buffer overflow was harnessed to inject code into the program stack. Then different methods were used to jump to that code and execute it. With DEP enabled, you cannot execute a single instruction in the stack outside the executable memory area. DEP marks the parts of memory that should only contain data with non-executable bit. If DEP is used, the CPU will refuse to execute code from memory ranges that have the NX-bit set. In practice, such attempts will cause an access violation error to occur and the process gets terminated.There are also other ways to protect the stack from code injection. For example stack protection, which adds a random integer after the return pointer. The integer will always be checked when returning from a function. If an attacker overwrites the return address, he will also overwrite the random integer and cause the check to fail. However, more thorough discussion of stack protection is outside of the scope of this article.
The goal of DEP is to prevent malicious code from being injected into another program. In other words, DEP was created to stop stack-based exploits. However, it can sometimes cause legit programs to behave in erroneous ways because it can also restrict the operation of normal processes. Therefore, it was made possible to disable DEP on a per-process basis. For this purpose, there are Windows API calls that can be used to mark specific ranges of memory as executable. That is, override DEP on those memory ranges.
There seems to be an opening there. However, if code injection is not possible, how can it be possible for an attacker to execute any code and create a call to Windows API functions? Enter Return Oriented Programming.
Return Oriented Programming
Since custom code cannot be executed on the stack, the only thing one can do is execute existing instructions or call existing functions from loaded modules. That being said, ROP exploitation borrows pre-existing chunks of code from modules that a running process has already loaded into memory. This borrowed code is then used to create parameters for certain Windows API calls. This seems like a hard way to do stuff, but it is the way to go, because DEP prevents the execution of code from outside the executable memory areas of the program stack.
ROP works, because even when DEP is enabled, we are allowed to execute one kind of instruction - a return (ret). Hence the name, return oriented programming. What ret instruction does, is redirect execution of the code to the next pointer on the stack. ROP is used to force the execution of a program to return to ROP gadgets instead of returning to the normal address of its function in libc*. That is the first stage of the actual exploit. The normal return point is replaced and the execution is moved to a ROP gadget.
Gadgets are small instruction sequences that always end with a return instruction. Before one can start jumping to gadgets, however, the useful gadgets must be enumerated and chained together to craft the API call. The idea is that the execution jumps from one part of the gadget chain to another part of the chain without ever executing a single bit from any DEP protected regions. That is, each gadget will always return to the address of the next gadget on the stack.
One can find the return gadgets on a computer by doing a search for a binary with “ret” bytes ("c3" in hexadecimal). All valid instruction sequences found in the binary or linked libraries should be listed. Out of that list, the most suitable ones will be chosen to be used in the ROP chain. There are many tools available that can help you find the gadgets, so not everything need to be done manually.
After listing the gadgets in different application modules in the memory, the stack is filled with these gadgets in proper order to craft the API call. This way the instruction sequences are chained together. Note that some of the gadgets manipulate several registers when setting the values. Therefore, the gadgets need to be placed in such order that they don't overwrite the registers set by previous gadgets in the ROP chain. In practice, one way to build the first stage ROP payload is to load all API parameters into various registers and then use PUSHAD instruction to push them to the stack in the proper order.
The API call will then be used to mark certain memory areas in the stack as executable, which will allow the execution of second stage payload (ie. the shell code). In other words, with the API call you will mark the part of memory where you have your shell code as executable. This technique relies on the attackers ability to predict where certain instruction will be located within the program memory. Therefore, only gadgets from modules that don't implement ASLR* can be used. There are techniques for bypassing ASLR too, but I will not go there in this article.
There are many different API calls available across Windows Builds and Service Packs. These calls are documented and can be found on MSDN (Microsoft Developer Network). The attacker will always build his ROP chain based on what API calls are available on the system. However, VirtualAlloc and VirtualProtect are the most common functions used to change the protection on a region of the virtual address space* of the calling process. The API calls for these are universal across all Windows builds.
In the end, the return instructions of the gadgets will take execution to the function pointer for VirtualProtect/-Alloc, which will mark the part of the stack where your shell code is as executable. Then you just need to jump the execution to that shell code and profit.
Terminology
- Address space layout randomization (ASLR) - Computer security technique that randomly arranges the address space positions of key data areas of a process. Was created to prevent an attacker from reliably jumping to an exploited function in memory.
- Call stack - a stack data structure that stores information about the active subroutines of a computer program (aka. execution stack, program stack and control stack).
- Libc - the standard library for the C programming language. Standard libraries are made available across implementations of a programming language. Standard libraries are described in the programming language specifications.
- Library - a collection of non-volatile resources used by computer programs. These resources may include configuration data, documentation, pre-written subroutines, classes, etc.
- Virtual address space - the set of ranges of virtual addresses that an operating system makes available to a process.
Sources and further reading
http://resources.infosecinstitute.com/return-oriented-programming-r
https://www.corelan.be/index.php/2010/06/16/exploit-writing-tutorial-part-10-chaining-dep-with-rop-the-rubikstm-cube/
https://www.rapid7.com/resources/rop-exploit-explained/
https://www.exploit-db.com/docs/28479.pdf
http://www.fuzzysecurity.com/tutorials/expDev/7.html
https://www.rapid7.com/resources/rop-exploit-explained/
https://www.exploit-db.com/docs/28479.pdf
http://www.fuzzysecurity.com/tutorials/expDev/7.html
https://www.offensive-security.com/vulndev/return-oriented-exploitation-rop/
No comments :
Post a Comment