Talos Vulnerability Report

TALOS-2016-0259

Adobe Acrobat Reader DC jpeg decoder Remote Code Execution Vulnerability

January 20, 2017

Report ID

CVE-2017-2971

Summary

A use of uninitialized memory vulnerability exists in JPEG image file format decoding code of Adobe Acrobat Reader which ultimately leads to a heap-based buffer overflow which can be abused to achieve remote code execution. A specially crafted PDF file with an embedded JPEG can trigger this vulnerability when opened on a victim computer.

Tested Versions

Adobe Acrobat Reader DC 2015.020.20039

Product URLs

https://get.adobe.com/reader/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-457 - Use of Uninitialized Variable

Details

Adobe Acrobat Reader is the most popular and most feature-rich PDF reader. It has a big user base, is usually a default PDF reader on systems and integrates into web browsers as a plugin for rendering PDFs. As such, tricking a user into visiting a malicious web page or sending a specially crafted email attachment can be enough to trigger this vulnerability.

There exists a vulnerability in the JPEG decoder and parser which can result in the use of two 4 byte integer values which are previously uninitialized. The use of these two uninitialized variables leads to further process corruptions as can be seen in the following crash context:

(760.11d8): C++ EH exception - code e06d7363 (first chance)
(760.11d8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Adobe\Acrobat Reader DC\Reader\AcroRd32.dll -
eax=25489fdc ebx=254df000 ecx=00001fff edx=00001fff esi=25487fdd edi=254df000
eip=03e9f26d esp=0012d7b4 ebp=0012d7dc iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010217
MSVCR120!memcpy+0x2a:
03e9f26d f3a4            rep movs byte ptr es:[edi],byte ptr [esi]

The above crash occurs with Page Heap enabled. The crash is due to and out of bounds write in a memcpy call. This particular memcopy call is made inside a function starting at 600D25A3. The following disassembly shows the relevant parts:

.text:600D25EA
.text:600D25EA loc_600D25EA:
.text:600D25EA mov     ecx, [edi]                                   [1]
.text:600D25EC cmp     ecx, esi
.text:600D25EE jge     short loc_60
...
.text:600D260F loc_600D260F:           ; Size
.text:600D260F push    ecx
.text:600D2610 push    edx             ; Src
.text:600D2611 push    ebx             ; Dst
.text:600D2612 call    memcpy                                       [2]
...
.text:600D2617
.text:600D2617 loc_600D2617:
.text:600D2617 mov     eax, [edi]
.text:600D2619 add     esp, 0Ch
.text:600D261C add     [edi+4], eax
.text:600D261F add     ebx, eax                                     [3]
.text:600D2621 sub     esi, eax                                     [4]
.text:600D2623 and     dword ptr [edi], 0
.text:600D2626 jmp     short loc_600D2637
...
.text:600D2637
.text:600D2637 loc_600D2637:
.text:600D2637 test    esi, esi                                     [5]
.text:600D2639 jnz     short loc_600D25EA

At [1] we enter a loop. At [2] a memcpy call occurs, size being 0x1fff in case of our testcase. At [3] destination pointer is increased and at [4] esi, which serves as a boundary condition for this loop, is decreased. At [5] it is tested for 0 and the code jumps back if that’s not the case.

If we examine the starting value of esi we can see that it is unusually big:

1:009> g
Breakpoint 0 hit
eax=17619024 ebx=1786e001 ecx=00000044 edx=17618fe0 esi=00000045 edi=17618f54
eip=600d2612 esp=0012d908 ebp=0012d924 iopl=0         nv up ei ng nz na po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283
AcroRd32_60000000!CTJPEGWriter::CTJPEGWriter+0x24aa1:
600d2612 e8592bf7ff      call    AcroRd32_60000000+0x45170 (60045170)
1:009> g
(9d0.518): C++ EH exception - code e06d7363 (first chance)
Breakpoint 0 hit
eax=25267fdc ebx=25289f01 ecx=00001ffe edx=25265fde esi=5a5a57ff edi=25265f94
eip=600d2612 esp=0012d7c0 ebp=0012d7dc iopl=0         nv up ei ng nz na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000287
AcroRd32_60000000!CTJPEGWriter::CTJPEGWriter+0x24aa1:
600d2612 e8592bf7ff      call    AcroRd32_60000000+0x45170 (60045170)
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for c:\Program Files\Adobe\Acrobat Reader DC\Reader\AGM.dll -
1:009> r esi
esi=5a5a57ff

Since in each round of the loop, esi gets decreased by only 0x1fff, the destination buffer for the memcpy call will get increased outside of its allocated space causing a heap-based buffer overflow.

Tracing back the origin of the particular value is esi through the call stack gets us to the following code in the function starting at 605AC8C2 :

.text:605AC9F0 mov     eax, [ebx+4] 					[1]
.text:605AC9F3 mov     ecx, [eax+0C8h]
.text:605AC9F9 lea     edx, [eax+0BCh]					[2]
.text:605AC9FF lea     edi, [eax+0B4h]
.text:605ACA05 push    edx
.text:605ACA06 push    edi
.text:605ACA07 push    dword ptr [edx]					[3]
.text:605ACA09 mov     eax, [ecx]
.text:605ACA0B push    dword ptr [edi]
.text:605ACA0D call    dword ptr [eax+4]

At [1], a pointer is read into eax, at [2] an address is copied into edx and contents from it get pushed to the stack at [3]. This can be observed in the following debugging output:

605ac9ff 8db8b4000000    lea     edi,[eax+0B4h]
1:009> dd edx
24a86f74  81818180 c0c0c0c0 00000190 24a2ffd8
24a86f84  00000000 00000000 00000000 c0c0c0c0
24a86f94  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
24a86fa4  c0c0c0c0 00000001 00800000 00000000
24a86fb4  01000000 c0c0c000 60e21a34 00000000
24a86fc4  00000000 c0c00000 c0c0c0c0 00000000
24a86fd4  00000000 c0c0c0c0 c0c0c0c0 c0c0c0c0
24a86fe4  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
1:009> t
eax=24a86eb8 ebx=25d89ff0 ecx=24a2ffd8 edx=24a86f74 esi=0012d8e0 edi=24a86f6c
eip=605aca05 esp=0012d8c0 ebp=0012d920 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
AcroRd32_60000000!AX_PDXlateToHostEx+0x214130:
605aca05 52              push    edx
1:009> t
eax=24a86eb8 ebx=25d89ff0 ecx=24a2ffd8 edx=24a86f74 esi=0012d8e0 edi=24a86f6c
eip=605aca06 esp=0012d8bc ebp=0012d920 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
AcroRd32_60000000!AX_PDXlateToHostEx+0x214131:
605aca06 57              push    edi
1:009> t
eax=24a86eb8 ebx=25d89ff0 ecx=24a2ffd8 edx=24a86f74 esi=0012d8e0 edi=24a86f6c
eip=605aca07 esp=0012d8b8 ebp=0012d920 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
AcroRd32_60000000!AX_PDXlateToHostEx+0x214132:
605aca07 ff32            push    dword ptr [edx]      ds:0023:24a86f74=81818180
1:009> t
eax=24a86eb8 ebx=25d89ff0 ecx=24a2ffd8 edx=24a86f74 esi=0012d8e0 edi=24a86f6c
eip=605aca09 esp=0012d8b4 ebp=0012d920 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
AcroRd32_60000000!AX_PDXlateToHostEx+0x214134:
605aca09 8b01            mov     eax,dword ptr [ecx]  ds:0023:24a2ffd8=60e33a70
1:009> t
eax=60e33a70 ebx=25d89ff0 ecx=24a2ffd8 edx=24a86f74 esi=0012d8e0 edi=24a86f6c
eip=605aca0b esp=0012d8b4 ebp=0012d920 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
AcroRd32_60000000!AX_PDXlateToHostEx+0x214136:
605aca0b ff37            push    dword ptr [edi]      ds:0023:24a86f6c=c0c0c0c0
1:009> t
eax=60e33a70 ebx=25d89ff0 ecx=24a2ffd8 edx=24a86f74 esi=0012d8e0 edi=24a86f6c
eip=605aca0d esp=0012d8b0 ebp=0012d920 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
AcroRd32_60000000!AX_PDXlateToHostEx+0x214138:
605aca0d ff5004          call    dword ptr [eax+4]    ds:0023:60e33a74=60207647

The value from edx is a result of previous arithmetic operations on an uninitialized memory and after further arithmetic gets its final value as esi at the time of the crashing memcpy call.

If we examine the allocation from which the uninitialized variable use stems from we can see that it was allocated with a call to malloc:

1:009> !heap -p -a 24a86eb8
    address 24a86eb8 found in
    _DPH_HEAP_ROOT @ 1b61000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                248936b4:         24a86eb8              148 -         24a86000             2000
          ? AcroRd32_60000000!CTJPEGThrowException+249d88
    11248e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77f8628e ntdll!RtlDebugAllocateHeap+0x00000030
    77f4a6cb ntdll!RtlpAllocateHeap+0x000000c4
    77f15d20 ntdll!RtlAllocateHeap+0x0000023a
    043bed43 MSVCR120!malloc+0x00000049 [f:\dd\vctools\crt\crtw32\heap\malloc.c @ 92]
    601eee39 AcroRd32_60000000!CTJPEGWriter::CTJPEGWriter+0x001412c8
    601ee3c2 AcroRd32_60000000!CTJPEGWriter::CTJPEGWriter+0x00140851
    601ee2ab AcroRd32_60000000!CTJPEGWriter::CTJPEGWriter+0x0014073a
    601ed1df AcroRd32_60000000!CTJPEGWriter::CTJPEGWriter+0x0013f66e
    601ea98a AcroRd32_60000000!CTJPEGWriter::CTJPEGWriter+0x0013ce19

A possible solution for this issue could be to change the call to calloc instead.

When Page Heap is disabled, we can observe that the same memory area contains zeros which leads to different results of arithmetic operations (all being zeros) which wouldn’t cause a crash.

As has been seen with previous Reader exploits, the heap can be groomed in a specific way so that the uninitialized memory falls under attackers control which could then end up controlling the heap buffer overflow size directly. With further heap layout control this can lead to successful exploitation and remote code execution.

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.

Timeline

2016-12-14 - Vendor Disclosure
2017-01-20 - Public Release