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

by Luka Treiber, 0patch Team

Based on the vulnerability report from David Landave we developed micropatches for heap buffer overflow CVE-2017-17969 and use-after-free 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 [email protected].



      Article Link: http://0patch.blogspot.com/2018/02/two-interesting-micropatches-for-7-zip.html