Monday, February 19, 2018

Two Interesting Micropatches For 7-Zip (CVE-2017-17969 and CVE-2018-5996)

by Luka Treiber, 0patch Team

Based on the vulnerability report from Dave we developed micropatches for heap buffer overflow CVE-2017-17969 and insufficient exception handling CVE-2018-5996 in 7-Zip version 16.04.


CVE-2017-17969

By diffing source code of the patched version 18.00 and the vulnerable version 16.04 we found the following changes related to CVE-2017-17969 (highlighted lines mark patched code).


001: // ShrinkDecoder.cpp

163:     lastSym = sym;
164:     unsigned cur = sym;
165:     unsigned i = 0;
166:     
167:     while (cur >= 256)
168:     {
169:       _stack[i++] = _suffixes[cur];
170:       cur = _parents[cur];
171:       // don't change that code:
172:       // Orphan Check and self-linked Orphan check (_stack overflow check);
173:       if (cur == kEmpty || i >= kNumItems)
174:         break;
175:     }
176:     
177:     if (cur == kEmpty || i >= kNumItems)
178:       break;
179:



We disassembled 7z.dll version 16.04 and found the above changes affect two offsets - 0x000a5ab4 and 0x000a5abb (both marked in red):




We then created micropatch code resembling source code lines 173 - 174 and 177 - 178 by using two patchlets, the first one extending (piggybacking on) an existing conditional statement (the end of the while loop - jnb loc_100A5A9C) and the second one introducing an additional conditional statement:

; CVE-2017-17969 patch for 7z.dll 16.04 
MODULE_PATH "C:\0patch\Patches\7zip\16.4\7z.dll"
PATCH_ID 316
PATCH_FORMAT_VER 2
VULN_ID 3295
PLATFORM win32

patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x000a5ab4
JUMPOVERBYTES 5
N_ORIGINALBYTES 5

;piggybacking jnb 
;3D 00 01 00 00                       cmp     eax, 100h
;73 CE                                jnb     short loc_100AAF28
;if (cur == kEmpty || i >= kNumItems)
;    break;
code_start
    cmp eax, 100h
    jne skip1  ;cur != kEmpty?
    STC             ;set piggyback condition for jnb to NOT jump
    call PIT_ExploitBlocked
    jmp end
  skip1:
    cmp esi, 2000h
    jb skip2  ;i < kNumItems?
    STC             ;set piggyback condition for jnb to NOT jump
    call PIT_ExploitBlocked
    jmp end
  skip2:
    cmp eax, 100h ;original code
  end:
code_end
patchlet_end

patchlet_start
PATCHLET_ID 2
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x000a5abb
PIT 7z.dll!0xa5bc4
JUMPOVERBYTES 0
N_ORIGINALBYTES 5

code_start
    cmp eax, 100h
    je block        ; cur == kEmpty?
    cmp esi, 2000h
    jge block       ; i >= kNumItems?
    jmp skip
  block:
    call PIT_ExploitBlocked
    jmp PIT_0xa5bc4 ; break
  skip:
code_end
patchlet_end



CVE-2018-5996

The source code patch for CVE-2018-5996 introduced the following changes (highlighted lines mark patched code):


001: // Rar3Decoder.h
194:   bool m_IsSolid;
195:   bool _errorMode;

001: // Rar3Decoder.cpp

089: CDecoder::CDecoder():
090:   _window(0),
091:   _winPos(0),
092:   _wrPtr(0),
093:   _lzSize(0),
094:   _writtenFileSize(0),
095:   _vmData(0),
096:   _vmCode(0),
097:   m_IsSolid(false),
098:   _errorMode(false)
099: {
100:   Ppmd7_Construct(&_ppmd);
101: }

827: HRESULT CDecoder::CodeReal(ICompressProgressInfo *progress)
828: {
829:   _writtenFileSize = 0;
830:   _unsupportedFilter = false;
831:   
832:   if (!m_IsSolid)
833:   {
834:     _lzSize = 0;
835:     _winPos = 0;
836:     _wrPtr = 0;
837:     for (int i = 0; i < kNumReps; i++)
838:       _reps[i] = 0;
839:     _lastLength = 0;
840:     memset(m_LastLevels, 0, kTablesSizesSum);
841:     TablesRead = false;
842:     PpmEscChar = 2;
843:     PpmError = true;
844:     InitFilters();
845:     _errorMode = false;
846:   }
847: 
848:   if (_errorMode)
849:     return S_FALSE;
850: 
851:   if (!m_IsSolid || !TablesRead)
852:   {
853:     bool keepDecompressing;
854:     RINOK(ReadTables(keepDecompressing));
855:     if (!keepDecompressing)
856:       return S_OK;
857:   }

890:   return S_OK;
891: }

929:   catch(const CInBufferException &e) { _errorMode = true; return e.ErrorCode;}
930:   catch(...) { _errorMode = true; return S_FALSE; }


When developing a micropatch for this vulnerability, we had to patch the CDecoder class that had to be extended by a new member - the _errorMode variable. This was the first time we attempted something like that with 0patch. So we either had to make room for the new variable (increase the allocated object memory block) or find existing unused space within the object's memory layout. By searching for cross references to CDecoder member variables (in offset range around the m_IsSolid variable 1c6d - 1c7c) we found offsets 1C6Fh, 1C79h, 1C7Ah, 1C7Bh to be unused. Based on this we selected and assigned the first one of the available offsets - 1C6Fh - to the new _errorMode variable.

Our micropatch contains five patchlets that correspond to five code offsets in 7z.dll that had to be patched:

  • Patchlet 1: We micropatched offset 0x000a0388 (the CDecoder::CDecoder constructor) with code resembling line 195 of RarDecoder.h and line 98 of Rar3Decoder.cpp.





  • Patchlets 2 and 3: We micropatched offsets 0x000a25bf and 0x000a25de with  code resembling lines 845 and 848-849.



  • Patchlets 4 and 5: We micropatched  offsets 0x000a22ee and 0x000a22f6 with code resembling changes to lines 929-930.


This is the 0pp file implementing these patchlets:


; CVE-2018-5996 patch for 7z.dll 16.04 
MODULE_PATH "C:\0patch\Patches\7zip\16.4\7z.dll"
PATCH_ID 315
PATCH_FORMAT_VER 2
VULN_ID 3296
PLATFORM win32

; added variables:
;  _errorMode (this+1C6Dh)
;
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x000a0388
JUMPOVERBYTES 0
N_ORIGINALBYTES 5

code_start
  mov [esi+1C6Fh], bl ; _errorMode=0 //ebx set to 0 at offset a030a
code_end
patchlet_end

patchlet_start
PATCHLET_ID 2
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x000a22ee
JUMPOVERBYTES 0
N_ORIGINALBYTES 5

code_start
  mov  [esi+1C6Fh], bl ; _errorMode=0 //ebx set to 0 at offset a2278
code_end
patchlet_end

patchlet_start
PATCHLET_ID 3
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x000a22f6
PIT 7z.dll!0x000A2452
JUMPOVERBYTES 0
N_ORIGINALBYTES 5

code_start
  cmp [esi+1C6Fh],bl ; _errorMode=0 //ebx set to 0 at offset a2278
  jz skip
  call PIT_ExploitBlocked
  jmp PIT_0x000A2452
 skip:
code_end
patchlet_end
patchlet_start
PATCHLET_ID 4
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x000a25bf
JUMPOVERBYTES 0
N_ORIGINALBYTES 5

code_start
  mov  eax, [ebp+08h]      ; catch(const CInBufferException &e) {
  mov byte [eax+1C6Fh], 1  ;  _errorMode = true;
                           ;  return e.ErrorCode;}
code_end
patchlet_end

patchlet_start
PATCHLET_ID 5
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x000a25de
JUMPOVERBYTES 0
N_ORIGINALBYTES 5

code_start
  mov  eax, [ebp+08h]      ; catch(...) { 
  mov byte [eax+1C6Fh], 1  ;  _errorMode = true; 
                             ;  return S_FALSE; }
code_end
patchlet_end
 

These two micropatches have already been published and distributed to all installed 0patch Agents. If you're using 32-bit 7-Zip version 16.04, you can download our free 0patch Agent, create a free 0patch account and register the agent to that account to immediately receive these micropatches and have them applied to your 7-Zip executable.

If you're using some other version of 7-Zip and would like to have micropatches for it, please contact us at support@0patch.com.