CVE-2017-2971
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.
Adobe Acrobat Reader DC 2015.020.20039
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-457 - Use of Uninitialized Variable
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.
2016-12-14 - Vendor Disclosure
2017-01-20 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.