Software is never made perfect and developers can never account for everything. There will always be errors and misses, some of them more prominent than others. Malicious perpetrators often exploit such vulnerabilities to get control over the software in question. And while there is no way to eliminate every possible error, it is possible to protect your software from zero day atatcks and exploits by focusing your efforts and attention on the parts that matter.
If you know how perpetrators are finding and using vulnerabilities, you can account for it and protect your software contributing to true proactive security.
In this tutorial, we will focus on ever prominent stack overflow exploits, describe the basics of ROP chains, and give some examples of how they work and how to code executable to defend from ROP attacks.
We hope that this will provide you some food for thought and become a stepping stone on your journey to writing more secure software in the future. And if you need a team of engineers with extensive experience in creating secure software, you can always contact Apriorit.
ROP is an acronym that stands for return-oriented programming. This is a specific technique that uses exploits to overcome such defenses as code signing and W xor X technique (non-executable memory) in order to allow a perpetrator to execute their own malicious code.
Hackers can easily load your shellcode in the stack or the heap but they can’t start executing it at once. Exploiting memory corruption created by shellcode injection, the hacker gets the control over EIP and jumps to his code using a chain of carefully chosen ROP gadgets.
ROP gadget is a set of assembler instructions ending with ret instruction or analogs. ROP gadgets may look like this:
An attack using the ROP chain is possible if there is a vulnerability in the target application. Windows has two main ways to safeguard software: Data Execution Prevention or DEP, as well as Address Space Layout Randomization or ASLR.
Address Space Layout Randomization makes it difficult to hardcode addresses/memory locations by making predicting them very difficult. This, in turn, makes it very difficult to create a solid exploit. It is achieved by randomizing heap, stack, and module base addresses. Data Execution Prevention works by preventing code from being executed on the stack.
These mechanisms also can be bypassed with more advanced technics. DEP can be bypassed by calling memory allocation/protection functions from the application import address table (IAT). Some examples of such functions:
Some of such calls are:
- VirtualAlloc(MEM_COMMIT + PAGE_READWRITE_EXECUTE) + copy memory. This function provides the ability to make a new memory region where the hacker can then copy the shellcode and run it. In order to do this, hackers most often will need to chain two APIs together.
- SetProcessDEPPolicy(). With this function, a perpetrator can change the DEP policy for the process, which ultimately allows for the shellcode to be executed from the stack. It works only on Windows XP SP3, Vista SP1, and Server 2008 and requires DEP Policy to be set to OptOut or OptIn.
- VirtualProtect(PAGE_READ_WRITE_EXECUTE). It allows hackers to mark the location with the shellcode as an executable for the memory page in question. It is made possible by changing the access protection level.
- NtSetInformationProcess(). DEP policy for the current process can be changed using this function. It allows perpetrators to execute shellcode from the stack.
- WriteProcessMemory(). This function allows the perpetrator to copy the shellcode to another location, allowing them to jump there and run it. This means, however, that the target location needs to be writable and executable.
- HeapCreate(HEAP_CREATE_ENABLE_EXECUTE) + HeapAlloc() + copy memory. Very similar to the first function mentioned (VirtualAlloc), it requires the perpetrator to chain three APIs into each other to work.
Bypass of ASLR is possible by determining the load address of desired modules (for example, kernel32.dll) and generating proper addresses for the whole ROP chain.
Let’s consider an example of an application with a stack overflow vulnerability. This program allows an attacker to overwrite the return address in the stack frame and set EIP to the desired value, thus executing code from the stack. For the sake of simplicity, in this article the application supports only DEP protection and does not support ASLR protection – we disable this option via Visual Studio project properties:
The simplest application with stack overflow issue may look like this:
As you may see – the stack overflow issue can be easily achieved. However, in order to build this code in Visual Studio 2015 which is used in this article, we need to add
This app can be built using any other build environment without that option.
File test.txt which is read by the application contains an ROP chain. ROP chain is specifically designed to bypass DEP protection and call our code.
In the hex editor test.txt looks like this:
In this file ROP chain begins with ba e3 83 6a (0x6a83e3ba). This is a place in a file after a crash where EIP points to. ROPgadget.py is a utility to gather possible ROP gadgets for a given module. It was used to get ROP gadgets for msvcp140.dll. In order to prepare proper addresses for the ROP chain, we need to determine a load address of msvcp140.dll and a base address of msvcp140.dll. This load address is constant during the Windows sessions since we disabled ASLR support before.
If we run our application on the testing environment we can see that the load address of msvcp140.dll in this Windows session will be 6a880000:
Using IDA-Pro we can determine that the base address for msvcp140.dll is 1000000
So the proper address for ROP gadgets will be calculated the next way:
6a880000 - 1000000 + gadgetAddress = address to place in the file.
ROP chain is specifically designed to bypass DEP by calling VirtualProtect() function and then call our code in protected memory. The first thing that we need in an ROP chain is to prepare a stack for the execution of Virtual Protect with flNewProtect parameter == PAGE_EXECUTE_READWRITE. It can be achieved in few steps:
1) charge registers with useful parameters. Particularly we want to fill edi with gadget address 0x1002d588 to acquire a stack
0x1001e3ba : pop eax; pop edi; pop esi; pop ebp; ret
2) acquire a stack
0x1002d588 : and edi, esp; add byte ptr[eax], al; ret 0x18
3) configure a stack for calling VirtualProtect. We need to place VirtualProtect call address from application IAT, return address and parameters continuously into the stack
4) restore ESP to the position where VirtualProtect call starts
0x100322fb : xchg eax, esp; ret
5) when VirtualProtect function returns the next chain of gadgets executed in order to move ESP to the code payload that was placed in the test.txt right after our ROP chain:
After execution of VirtualProtect, we have 0x1000 of writeable and executable memory to execute anything we want. In this article, MessageBox will be called. Code of payload for calling MessageBox function looks like this:
Let’s test our ROP chain:
This is an example of the stack overflow ROP exploit, which we used to call our code (which also can be harmful). Let’s consider how we can create a functional defense against such attacks.
It is clear that in order to call some code on a stack of the application ROP chain MUST bypass DEP protection. ROP chain in this article bypasses it by calling the VirtualProtect function. So if we want to protect our software against ROP attacks we could consider protection against calls that can alter memory attributes.
Let’s consider an example of protection against the ROP chain implemented in the previous paragraph. The main feature of such ROP – it calls VirtualProtect(). We want to somehow prevent the call of the VirtualProtect() function. For such purposes, we can consider the API hooking mechanism and its injection into processes that we want to protect.
In this article, for API hooking we use mhook library. With this library the code for VirtualProtect() hooking may look like:
We check if VirtualProtect() tries to patch memory with PAGE_EXECUTE_READWRITE attributes and return 0 (function fails) if it does.
To set such hook we call Mhook_SetHook() function from DllMain():
The last thing that we need is to inject this protective code into our process. This can be accomplished in the following way:
1) Create a suspended process of the vulnerable application:
2) Inject a call of LoadLibraryA into the vulnerable application using CreateRemoteThread() function call (Actually CreateRemoteThread() is not the best way to inject code into processes because it can easily be prevented by special software and was chosen as the simplest way to demonstrate protection):
3) Resume the main thread of the vulnerable application:
Let’s test our security code injection into the vulnerable application.
Protection against ROP attacks works! And this is only one way of protection – there are many other approaches. If you're interested in writing secure applications, you can also check out our article on typical web application security issues and ways to solve them.