Talos Vulnerability Report

TALOS-2016-0093

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

May 10, 2016
CVE Number

CVE-2016-2334

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
---------

TIMELINE

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

Credit

Discovered by Marcin ‘Icewall’ Noga of Cisco Talos