Secunia Research details the Vulnerability in Oracle Outside In

Yesterday, Oracle issued a patch for Outside In, which fixes a vulnerability discovered by Secunia Research. (1,2)

The vulnerability is rated Moderately Critical by Secunia Research.

Product Background(3)

Oracle Outside In Technology provides software developers with a comprehensive solution to access, transform, and control the contents of over 500 unstructured file formats. From the latest Microsoft office suites, to specialty formats and legacy files, Outside In Technology provides software developers with the tools to transform unstructured files into controllable information.

Issue Summary

The fixed vulnerability, assigned CVE-2015-0493, is caused due to a sign extension error when handling PSD files within the ibpsd2.dll component. The vulnerability can be exploited to cause a heap-based buffer overflow and crash an application using the SDK or, potentially, execute arbitrary code within the context of such an application.

The vulnerability has been confirmed in the ibpsd2.dll component version Prior versions may also be affected. Please note that all further details (e.g. offsets or code snippets) are given with respect to this component version.

File Format Introduction

From a very high level perspective, a PSD (Photoshop File Format) file is comprised of five major parts called sections:

  • File Header section, specifying basic image properties e.g. width and height.
  • Color Mode Data section, carrying data specific to certain color modes.
  • Image Resources section, storing non-pixel data. This data is not used by the affected code.
  • Layer and Mask Information section, providing information about layers and masks. This data is not used by the affected code.
  • Image Data section, containing the (possibly compressed) merged image with each color channel stored separately (not interleaved).

It is the last section that will interest us the most.

Further details on the file format can be found here (4).

Technical Details

The function sub_1C001000 is responsible for parsing the file contents. After some start-up code, the function reads the file header section and validates its contents:

.text:1C00115C push 1
.text:1C00115E lea ecx, [ebp+PsdSignature]
.text:1C001161 push ecx
.text:1C001162 push offset a4c1s6c1s2l2s ; "4C1S6C1S2L2S"
.text:1C001167 mov edx, [ebp+fp]
.text:1C00116A push edx
.text:1C00116B call imsReadStruct
.text:1C001170 add esp, 10h
.text:1C001173 cmp eax, 1
.text:1C001176 jz short loc_1C001194
.text:1C001178 mov [ebp+var_4], 0Eh
.text:1C00117F mov eax, [ebp+var_4]
.text:1C001182 push eax
.text:1C001183 mov ecx, [ebp+arg_0]
.text:1C001186 push ecx
.text:1C001187 call sub_1C002B80
.text:1C00118C add esp, 8
.text:1C00118F jmp loc_1C001CF8
.text:1C001194 ; ------------------------------------------------------------------------
.text:1C001194 loc_1C001194: ; CODE XREF: sub_1C001000+176j
.text:1C001194 push 4 ; Size
.text:1C001196 push offset a8bps ; "8BPS"
.text:1C00119B lea edx, [ebp+PsdSignature]
.text:1C00119E push edx ; Buf1
.text:1C00119F call memcmp
.text:1C0011A4 add esp, 0Ch
.text:1C0011A7 test eax, eax
.text:1C0011A9 jz short loc_1C0011BC
.text:1C0011AB mov [ebp+var_4], 0FFFFFFE9h
.text:1C0011B2 jmp loc_1C001D40
.text:1C0011B7 ; ------------------------------------------------------------------------
.text:1C0011B7 jmp loc_1C001CF8
.text:1C0011BC ; ------------------------------------------------------------------------
.text:1C0011BC loc_1C0011BC: ; CODE XREF: sub_1C001000+1A9j
.text:1C0011BC movzx eax, [ebp+PsdVersion]
.text:1C0011C0 cmp eax, 1
.text:1C0011C3 jnz short loc_1C0011E7
.text:1C0011C5 movzx ecx, [ebp+PsdColorMode]
.text:1C0011C9 cmp ecx, 9
.text:1C0011CC jz short loc_1C0011E7
.text:1C0011CE movzx edx, [ebp+PsdColorMode]
.text:1C0011D2 cmp edx, 7
.text:1C0011D5 jz short loc_1C0011E7
.text:1C0011D7 movzx eax, [ebp+PsdChannelCount]
.text:1C0011DB test eax, eax
.text:1C0011DD jz short loc_1C0011E7
.text:1C0011DF movzx ecx, [ebp+PsdBitDepth]
.text:1C0011E3 test ecx, ecx
.text:1C0011E5 jnz short loc_1C0011F8
.text:1C0011E7 loc_1C0011E7: ; CODE XREF: sub_1C001000+1C3j
.text:1C0011E7 ; sub_1C001000+1CCj ...
.text:1C0011E7 mov [ebp+var_4], 0FFFFFFE8h
.text:1C0011EE jmp loc_1C001D40

After initializing certain internal structures, the function reads the lengths of the following sections and handles their contents. The only actual handling is performed for the Color Mode Data section — contents of other sections is simply skipped, e.g.:

.text:1C00165D push 1
.text:1C00165F lea eax, [ebp+SectionLength]
.text:1C001662 push eax
.text:1C001663 mov ecx, [ebp+fp]
.text:1C001666 push ecx
.text:1C001667 call imsReadLong ; Read the length of the Image Resources Section
.text:1C00166C add esp, 0Ch
.text:1C00166F cmp eax, 1
.text:1C001672 jz short loc_1C00168D
.text:1C001674 mov [ebp+var_4], 0Eh
.text:1C00167B mov edx, [ebp+var_4]
.text:1C00167E push edx
.text:1C00167F mov eax, [ebp+arg_0]
.text:1C001682 push eax
.text:1C001683 call sub_1C002B80
.text:1C001688 add esp, 8
.text:1C00168B jmp short loc_1C00169F
.text:1C00168D ; ------------------------------------------------------------------------
.text:1C00168D loc_1C00168D: ; CODE XREF: sub_1C001000+672j
.text:1C00168D push 1
.text:1C00168F mov ecx, [ebp+SectionLength]
.text:1C001692 push ecx
.text:1C001693 mov edx, [ebp+fp]
.text:1C001696 push edx
.text:1C001697 call imsSeek
.text:1C00169C add esp, 0Ch

The routine follows on to handle the Image Data section. First, it reads the compression method marker and prepares some internal structures. Then, the function performs the following quite interesting computations

First, the channel count is clamped to 3.

.text:1C0018FC movzx ecx, [ebp+PsdChannelCount]
.text:1C001900 cmp ecx, 3
.text:1C001903 jge short loc_1C001911
.text:1C001905 movzx edx, [ebp+PsdChannelCount]
.text:1C001909 mov [ebp+ChannelCount], edx
.text:1C00190F jmp short loc_1C00191B
.text:1C001911 ; ------------------------------------------------------------------------
.text:1C001911 loc_1C001911: ; CODE XREF: sub_1C001000+903j
.text:1C001911 mov [ebp+ChannelCount], 3
.text:1C00191B loc_1C00191B: ; CODE XREF: sub_1C001000+90Fj
.text:1C00191B mov ax, word ptr [ebp+ChannelCount]
.text:1C001922 mov [ebp+ChannelCount$1], ax

The bit depth is also manipulated a bit:

.text:1C001929 movzx ecx, [ebp+PsdBitDepth]
.text:1C00192D cmp ecx, 16
.text:1C001930 jnz short loc_1C00193E
.text:1C001932 mov [ebp+BitDepth], 24
.text:1C00193C jmp short loc_1C001948
.text:1C00193E ; ------------------------------------------------------------------------
.text:1C00193E loc_1C00193E: ; CODE XREF: sub_1C001000+930j
.text:1C00193E movsx edx, [ebp+PsdBitDepth]
.text:1C001942 mov [ebp+BitDepth], edx
.text:1C001948 loc_1C001948: ; CODE XREF: sub_1C001000+93Cj
.text:1C001948 mov ax, word ptr [ebp+BitDepth]
.text:1C00194F mov [ebp+BitDepth$1], ax

And then the following, calculating the amount of bytes per one image line:

.text:1C001956 movsx ecx, [ebp+BitDepth$1] ; <<< SIGN EXTENSION
.text:1C00195D imul ecx, [ebp+PsdImageWidth]
.text:1C001961 movzx edx, [ebp+ChannelCount$1]
.text:1C001968 imul ecx, edx
.text:1C00196B mov [ebp+BytesPerLine], ecx
.text:1C001971 mov eax, [ebp+BytesPerLine]
.text:1C001977 add eax, 7
.text:1C00197A shr eax, 3
.text:1C00197D mov [ebp+BytesPerLine], eax
.text:1C001983 mov ecx, [ebp+BytesPerLine]
.text:1C001989 add ecx, 3
.text:1C00198C shr ecx, 2
.text:1C00198F shl ecx, 2
.text:1C001992 mov [ebp+BytesPerLine], ecx

which is roughly equivalent to:

BytesPerLine = ((int16_t)BitDepth) * PsdImageWidth * ChannelCount; BytesPerLine = (BytesPerLine + 7) >> 3; /* Round up to bytes */ BytesPerLine = ((BytesPerLine + 3) >> 2) << 2; /* Round up to DWORD count */

Note the BitDepth value is sign-extended from 16-bit to 32-bit. The function then proceeds to allocate memory:

.text:1C0019C8 mov eax, [ebp+ImageHeight]
.text:1C0019CE mov [ebp+ImageHeight$1], eax
.text:1C0019D4 mov ecx, [ebp+BytesPerLine]
.text:1C0019DA imul ecx, [ebp+ImageHeight$1]
.text:1C0019E1 mov [ebp+var_B0], ecx
.text:1C0019E7 mov edx, [ebp+var_B0]
.text:1C0019ED add edx, [ebp+BytesPerLine]
.text:1C0019F3 push edx ; dwBytes
.text:1C0019F4 push 42h ; uFlags
.text:1C0019F6 call ds:GlobalAlloc
.text:1C0019FC mov [ebp+ImageBufferHandle], eax
.text:1C001A02 cmp [ebp+ImageBufferHandle], 0
.text:1C001A09 jz loc_1C001CD5
.text:1C001A0F mov eax, [ebp+ImageBufferHandle]
.text:1C001A15 push eax ; hMem
.text:1C001A16 call ds:GlobalLock
.text:1C001A1C mov [ebp+ImageBufferPtr], eax

After some further manipulations, we reach a call to the sub_1C001D70 function, which is responsible for image decompression:

.text:1C001B59 lea ecx, [ebp+var_C4]
.text:1C001B5F push ecx
.text:1C001B60 mov dx, [ebp+PsdImageCompressionMethod]
.text:1C001B67 push edx
.text:1C001B68 mov eax, [ebp+ImageHeight$1]
.text:1C001B6E push eax
.text:1C001B6F lea ecx, [ebp+PsdSignature]
.text:1C001B72 push ecx
.text:1C001B73 mov edx, [ebp+ImageBufferPtr]
.text:1C001B79 push edx
.text:1C001B7A mov eax, [ebp+fp]
.text:1C001B7D push eax
.text:1C001B7E call sub_1C001D70

The function repeats the buffer size calculations, with one minor difference:

.text:1C001DCB mov ecx, [ebp+PsdFileHeaderPtr]
.text:1C001DCE movzx edx, [ecx+PsdFileHeader.BitDepth]
.text:1C001DD2 mov eax, [ebp+PsdFileHeaderPtr]
.text:1C001DD5 imul edx, [eax+PsdFileHeader.Width]
.text:1C001DD9 add edx, 7
.text:1C001DDC shr edx, 3
.text:1C001DDF mov [ebp+BytesPerChannelLine], edx
.text:1C001E2D mov ecx, [ebp+BytesPerChannelLine]
.text:1C001E30 push ecx ; dwBytes
.text:1C001E31 call sub_1C003490
.text:1C001E36 add esp, 4
.text:1C001E39 mov [ebp+ChannelLineBufferPtr], eax

The BitDepth value is not sign-extended when calculating the size required for the line buffer allocated via a call to sub_1C003490. Note that, via a specially crafted image, it may be possible to exploit this discrepancy and force the program to allocate two buffers of different size (with the full image buffer being smaller).

The function then enters a loop, processing each color channel and another, inner loop that handles scan line decompression. This is where an overflow actually occurs.

.text:1C001E93 ; Read and decompress a line
.text:1C001E93 mov cx, [ebp+PsdImageCompressionMethod]
.text:1C001E97 push ecx
.text:1C001E98 mov edx, [ebp+BytesPerChannelLine]
.text:1C001E9B push edx
.text:1C001E9C mov eax, [ebp+ChannelLineBufferPtr]
.text:1C001E9F push eax
.text:1C001EA0 mov ecx, [ebp+fp]
.text:1C001EA3 push ecx
.text:1C001EA4 call sub_1C002070
.text:1C001EA9 add esp, 10h
.text:1C001EAC ; Calculate the offset into the image buffer
.text:1C001EAC mov [ebp+var_10], eax
.text:1C001EAF mov edx, [ebp+ChannelLineBufferPtr]
.text:1C001EB2 mov [ebp+var_2C], edx
.text:1C001EB5 mov eax, [ebp+CurrentLine]
.text:1C001EB8 imul eax, [ebp+BytesPerLine]
.text:1C001EBC add eax, [ebp+CurrentChannel]
.text:1C001EBF add eax, [ebp+ImageBufferPtr]
.text:1C001EC2 mov [ebp+ImageBufferLinePtr], eax
.text:1C001EC5 ; Loop and copy the channel data into the image buffer
.text:1C001EC5 mov [ebp+var_28], 0
.text:1C001ECC jmp short loc_1C001ED7
.text:1C001ECE ; ------------------------------------------------------------------------
.text:1C001ECE loc_1C001ECE: ; CODE XREF: sub_1C001D70+18Cj
.text:1C001ECE mov ecx, [ebp+var_28]
.text:1C001ED1 add ecx, 1
.text:1C001ED4 mov [ebp+var_28], ecx
.text:1C001ED7 loc_1C001ED7: ; CODE XREF: sub_1C001D70+15Cj
.text:1C001ED7 mov edx, [ebp+var_28]
.text:1C001EDA cmp edx, [ebp+BytesPerChannelLine]
.text:1C001EDD jge short loc_1C001EFE
.text:1C001EDF mov eax, [ebp+ImageBufferLinePtr]
.text:1C001EE2 mov ecx, [ebp+var_2C]
.text:1C001EE5 mov dl, [ecx]
.text:1C001EE7 mov [eax], dl
.text:1C001EE9 mov eax, [ebp+var_2C]
.text:1C001EEC add eax, 1
.text:1C001EEF mov [ebp+var_2C], eax
.text:1C001EF2 movzx ecx, [ebp+ChannelCount$1]
.text:1C001EF6 add ecx, [ebp+ImageBufferLinePtr]
.text:1C001EF9 mov [ebp+ImageBufferLinePtr], ecx
.text:1C001EFC jmp short loc_1C001ECE

The overflown buffer is located on the heap, which in the context of this product may make exploitation more difficult, but arbitrary code execution cannot be ruled out.



Leave a Reply

Your email address will not be published. Required fields are marked *