Microsoft Windows “LoadUvsTable()” Heap-based Buffer Overflow Vulnerability (Update 2016-12-14)

Update December 14, 2016: During the analysis of the fix of Microsoft we confirmed a related error remains unpatched. Therefore an additional Secunia Advisory SA74000 [5] has been issued to account for that.

On December 13, 2016, Microsoft released updates that fix two vulnerabilities reported by Secunia Research. Both can be exploited through a specially crafted font file. One vulnerability results in a Denial of Service (DoS) or a privilege escalation and the other allows a compromise of a vulnerable system even.

The purpose of the write up is to provide details on the second vulnerability, which is a heap-based buffer overflow vulnerability resulting from an integer overflow error within the Microsoft Unicode Scripts Processor, that is part of the Microsoft Windows operating system. This vulnerability has been assigned the Common Vulnerabilities and Exposures (CVE) identifier CVE-2016-7274 and is outlined in the Microsoft Security Bulletin MS16-147 [1] and Secunia Advisory SA70000 [2]. As the vulnerability ultimately results in a compromise of an affected system through a remote supplied True Type Font (TTF) file, the Secunia Advisory SA70000 is rated “Highly Critical” by Secunia Research.

Typically such a vulnerability can be exploited within a web-based scenario through a specially crafted web page, whereas the link to such a web page may get send to an unsuspecting victim through email for example. Furthermore, an attacker can also create a document with a specially crafted font file embedded, which is then send to a victim. Upon opening of such a specially crafted web page or document, the exploitation may then succeed on an affected system.

Introduction:

Uniscribe is the Microsoft Windows set of services for rendering Unicode-encoded text, especially complex text layout [3]. In Windows 8 and 8.1, the implementation is moved from USP10.dll (DLL, Dynamic Linking Library) towards GDI32.dll. In Windows 10 the functionality is implemented in GDI32full.dll.

Please note that the following analysis is performed on a Windows 10 Enterprise with GDI32full.dll version 10.0.14393.206.

Reproduction:

A default installation of Windows 10 Enterprise contains 119 TTF files overall. Two of them can be used to easily trigger this issue with only slight modification: Microsoft PhagsPa (phagspa.ttf) and Microsoft PhagsPa Bold (phagspab.ttf).

For reproduction, open the file “C:WindowsFontsphagspa.ttf” in a hex editor and change the value at offset 0x2051 from 0x00000006 to 0x33333334.

Technical Details:

During the processing of scripts in a font file, the code flow reaches the “LoadFont()” function within GDI32full.dll. Shortly after, this function calls the “GetFontDesc()” function to load the mapping of character codes within the font.

.text:750F42CA ; __int32 __stdcall LoadFont(HDC, struct FACE_CACHE *)
.text:750F42CA var_260         = dword ptr -260h
.text:750F42CA var_25C         = dword ptr -25Ch
.text:750F42CA var_258         = dword ptr -258h
.text:750F42CA var_254         = dword ptr -254h
.text:750F42CA var_250         = dword ptr -250h
.text:750F42CA lpMem           = dword ptr -24Ch
.text:750F42CA Dst             = byte ptr -248h
.text:750F42CA var_24          = HDC__ ptr -24h
.text:750F42CA var_4           = dword ptr -4
.text:750F42CA
.text:750F42CA ; FUNCTION CHUNK AT .text:7512EDD3 SIZE 000001F7 BYTES
.text:750F42CA
.text:750F42CA          mov     edi, edi
.text:750F42CC          push    ebp
.text:750F42CD          mov     ebp, esp
.text:750F42CF          sub     esp, 264h
.text:750F42D5          mov     eax, ___security_cookie
.text:750F42DA          xor     eax, ebp
.text:750F42DC          mov     [ebp+var_4], eax
.text:750F42DF          push    ebx
.text:750F42E0          push    esi             ; unsigned __int32 *
.text:750F42E1          push    edi             ; union FONTUVSENTRY *
.text:750F42E2          xor     ebx, ebx
.text:750F42E4          mov     [ebp+var_250], ecx
.text:750F42EA          and     [ebp+var_254], ebx
.text:750F42F0          lea     eax, [ebp+Dst]
.text:750F42F6          push    220h            ; Size
.text:750F42FB          push    ebx             ; Val
.text:750F42FC          push    eax             ; Dst
.text:750F42FD          mov     esi, edx
.text:750F42FF          mov     [ebp+lpMem], ebx
.text:750F4305          call    _memset
.text:750F430A          add     esp, 0Ch
.text:750F430D          lea     edi, [ebp+var_24]
.text:750F4310          push    8
.text:750F4312          pop     eax
.text:750F4313          mov     ecx, eax
.text:750F4315          xor     eax, eax
.text:750F4317          rep stosd
.text:750F4319          mov     eax, 0F807h
.text:750F431E          and     [esi+0A0h], ax
.text:750F4325          mov     al, [esi+95h]
.text:750F432B          and     al, 0Ch
.text:750F432D          cmp     al, 8
.text:750F432F          jz      loc_7512EDD3
.text:750F4335
.text:750F4335 loc_750F4335:        ; CODE XREF: LoadFont(HDC__ *,FACE_CACHE *)+3AB0F_j
.text:750F4335          mov     ecx, [ebp+var_250]
.text:750F433B          lea     eax, [ebp+lpMem]
.text:750F4341          lea     edi, [esi+98h]
.text:750F4347          push    eax             ; HDC
.text:750F4348          mov     edx, edi
.text:750F434A          call    GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)

The “GetFontDesc()” function first checks for certain values within the “OS/2” table and then loads data from the cmap table.

.text:750F6618 ; __int32 __stdcall GetFontDesc(HDC, int *, struct FONTCMAPDESC **)
.text:750F6618 hdc             = dword ptr -30h
.text:750F6618 var_2C          = dword ptr -2Ch
.text:750F6618 var_28          = dword ptr -28h
.text:750F6618 var_24          = dword ptr -24h
.text:750F6618 pvBuffer        = dword ptr -20h
.text:750F6618 var_1C          = dword ptr -1Ch
.text:750F6618 var_18          = dword ptr -18h
.text:750F6618 var_14          = dword ptr -14h
.text:750F6618 var_10          = dword ptr -10h
.text:750F6618 var_C           = dword ptr -0Ch
.text:750F6618 var_8           = dword ptr -8
.text:750F6618 var_4           = dword ptr -4
.text:750F6618 arg_0           = dword ptr  8
.text:750F6618
.text:750F6618 ; FUNCTION CHUNK AT .text:7512F8E4 SIZE 0000007C BYTES
.text:750F6618
.text:750F6618          mov     edi, edi
.text:750F661A          push    ebp
.text:750F661B          mov     ebp, esp
.text:750F661D          sub     esp, 30h
.text:750F6620          and     [ebp+var_C], 0
.text:750F6624          lea     eax, [ebp+pvBuffer]
.text:750F6627          push    ebx
.text:750F6628          push    esi             ; int
.text:750F6629          push    edi             ; unsigned __int32 *
.text:750F662A          mov     edi, [ebp+arg_0]
.text:750F662D          mov     ebx, ecx
.text:750F662F          push    4               ; cjBuffer
.text:750F6631          push    eax             ; pvBuffer
.text:750F6632          push    3Eh             ; dwOffset
.text:750F6634          and     dword ptr [edi], 0
.text:750F6637          mov     esi, edx
.text:750F6639          push    '2/SO'          ; dwTable
.text:750F663E          push    ebx             ; hdc
.text:750F663F          mov     [ebp+var_18], esi
.text:750F6642          mov     [ebp+hdc], ebx
.text:750F6645           call    ds:__imp__GetFontData@20 ; GetFontData(x,x,x,x,x)
.text:750F664B          cmp     eax, 4
.text:750F664E          jnz     loc_750F68B2
.text:750F6654          mov     al, byte ptr [ebp+pvBuffer+2]
.text:750F6657          cmp     al, 0F0h
.text:750F6659          jnb     short loc_750F665F
.text:750F665B          test    al, al
.text:750F665D          jnz     short loc_750F666A
.text:750F665F
.text:750F665F loc_750F665F:     ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+41_j
.text:750F665F          mov     eax, [ebp+pvBuffer]
.text:750F6662          test    al, al
.text:750F6664          jnz     loc_7512F8E4
.text:750F666A
.text:750F666A loc_750F666A:    ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+45_j
.text:750F666A          or      dword ptr [esi], 0FFFFFFFFh
.text:750F666D
.text:750F666D loc_750F666D: ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+392D1_j
.text:750F666D          xor     eax, eax
.text:750F666F          push    eax             ; cjBuffer
.text:750F6670          push    eax             ; pvBuffer
.text:750F6671          push    eax             ; dwOffset
.text:750F6672          push    'pamc'          ; dwTable
.text:750F6677          push    ebx             ; hdc
.text:750F6678          call    ds:__imp__GetFontData@20 ; GetFontData(x,x,x,x,x)
.text:750F667E          mov     ebx, eax
.text:750F6680          cmp     ebx, 0FFFFFFFFh
.text:750F6683          jz      loc_7512F955
.text:750F6689          cmp     ebx, 4
.text:750F668C          jl      loc_7512F955
.text:750F6692          push    edi             ; int
.text:750F6693          lea     ecx, [ebx+34h]
.text:750F6696          push    ecx             ; dwBytes
.text:750F6697          call    _UspAllocCache@8 ; UspAllocCache(x,x)
.text:750F669C          test    eax, eax
.text:750F669E          js      loc_7512F955
.text:750F66A4          mov     ecx, [edi]
.text:750F66A6          push    ebx             ; cjBuffer
.text:750F66A7          lea     eax, [ecx+34h]
.text:750F66AA          mov     [ecx+4], eax
.text:750F66AD          mov     eax, [edi]
.text:750F66AF          mov     [eax+8], ebx
.text:750F66B2          mov     eax, [edi]
.text:750F66B4          push    dword ptr [eax+4] ; pvBuffer
.text:750F66B7          push    0              ; dwOffset
.text:750F66B9          push    'pamc'          ; dwTable
.text:750F66BE          push    [ebp+hdc]       ; hdc
.text:750F66C1          call    ds:__imp__GetFontData@20 ; GetFontData(x,x,x,x,x)

Based on the loaded information, a check is done to make sure enough data is available and that there is at least one EncodingRecord table.

.text:750F66CF          mov     eax, [edi]
.text:750F66D1          mov     eax, [eax+4]
.text:750F66D4          rol     word ptr [eax+2], 8             ;  numTables
.text:750F66D9          mov     ecx, [edi]
.text:750F66DB          mov     edx, [ecx+4]
.text:750F66DE          movzx   eax, word ptr [edx+2]
.text:750F66E2          mov     [ebp+numTables], eax
.text:750F66E5          lea     eax, ds:4[eax*8               ;  needed data
.text:750F66EC          cmp     ebx, eax                    ; check if enough data is available.
.text:750F66EE          jl      loc_7512F8EE
.text:750F66F4          lea     eax, [edx+4]
.text:750F66F7          xor     edx, edx
.text:750F66F9          and     [ecx+2Ch], edx
.text:750F66FC          and     [ebp+var_8], edx
.text:750F66FF          and     [ebp+var_10], edx
.text:750F6702          mov     [ebp+var_14], eax
.text:750F6705          mov     eax, [edi]
.text:750F6707          mov     [ebp+var_4], edx
.text:750F670A          and     [eax+30h], edx
.text:750F670D          mov     eax, [ebp+numTables]            ; check if numTables is zero
.text:750F6710          test    eax, eax
.text:750F6712          jle     cleanup_and_return

Afterwards, a loop is entered to process available EncodingRecords. If platform ID is 0, encoding ID is 5, and subtable format is 14 [4], then the record is treated as a “Unicode Variation Sequences” (UVS) record and the UVS subtable offset and size are saved in the processing object for later processing.

loc_750F671E:           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+1B0_j
.text:750F671E          xor     edx, edx
.text:750F6720          inc     edx
.text:750F6721          xor     eax, eax
.text:750F6723
.text:750F6723 loc_750F6723:   ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+114_j
.text:750F6723          rol     word ptr [ecx+eax*2], 8
.text:750F6728          inc     eax
.text:750F6729          cmp     eax, 2
.text:750F672C          jl      short loc_750F6723
.text:750F672E          lea     esi, [ecx+4]
.text:750F6731          mov     ecx, esi
.text:750F6733          call    ?FlipDWords@@YGXPAKH@Z          ; FlipDWords(ulong *,int)
.text:750F6738          movzx   ecx, word ptr [esi-2]           ; encodingID
.text:750F673C          mov     esi, [esi]
.text:750F673E          mov     [ebp+var_28], esi               ; offset
.text:750F6741          test    esi, esi
.text:750F6743          jz      continue_loop
.text:750F6749          lea     eax, [ebx-4]
.text:750F674C          cmp     eax, esi
.text:750F674E          jbe     continue_loop
.text:750F6754          mov     edx, [edi]
.text:750F6756          mov     eax, [edx+4]
.text:750F6759          add     eax, esi
.text:750F675B          mov     esi, [ebp+var_14]
.text:750F675E          mov     [ebp+encoding_subtable_offset], eax ; encoding_subtable_offset
.text:750F6761          mov     ax, [eax]
.text:750F6764          rol     ax, 8
.text:750F6768          movzx   eax, ax
.text:750F676B          mov     [ebp+var_2C], eax
.text:750F676E          movzx   eax, word ptr [esi]             ; platformID
.text:750F6771          test    ax, ax
.text:750F6774          jz      loc_750F689B
.text:750F677A          push    3
.text:750F677C          pop     edx
.text:750F677D          cmp     ax, dx
.text:750F6780          jnz     continue_loop
.text:750F6786          xor     eax, eax
.text:750F6788          inc     eax
.text:750F6789          test    cx, cx
.text:750F678C          jz      short loc_750F679A
.text:750F678E          cmp     cx, ax
.text:750F6791          jnz     loc_750F68BA
.text:750F6797          push    2
.text:750F6799          pop     eax
.text:750F679A
.text:750F679A loc_750F679A:   ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+174_j
.text:750F679A          cmp     [ebp+var_10], eax
.text:750F679D          jge     continue_loop
.text:750F67A3          mov     [ebp+var_10], eax
.text:750F67A6
.text:750F67A6 loc_750F67A6:   ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+2B2_j
.text:750F67A6          mov     eax, [ebp+var_2C]
.text:750F67A9          mov     esi, [ebp+encoding_subtable_offset]
.text:750F67AC          mov     edx, [ebp+var_28]
.text:750F67AF          movzx   eax, ax
.text:750F67B2          mov     [ebp+var_8], esi
.text:750F67B5          mov     [ebp+var_4], edx
.text:750F67B8          mov     [ebp+var_C], eax
.text:750F67BB
.text:750F67BB loc_750F67BB:  ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+295_j
.text:750F67BB          mov     ecx, [ebp+var_14]
.text:750F67BE          add     ecx, 8
.text:750F67C1          sub     [ebp+num_Tables], 1
.text:750F67C5          mov     [ebp+var_14], ecx
.text:750F67C8          jnz     loc_750F671E

.text:750F689B loc_750F689B:  ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+15C_j
.text:750F689B          cmp     ecx, 5     ;encoding ID = 5?
.text:750F689E          jz      loc_7512F8F1

.text:7512F8F1 loc_7512F8F1:    ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+286_j
.text:7512F8F1          mov     eax, [ebp+var_2C]
.text:7512F8F4          cmp     ax, 0Eh          ; is subtable format 14?
.text:7512F8F8          jnz     continue_loop
.text:7512F8FE          cmp     dword ptr [edx+2Ch], 0          ; just one record allowed.
.text:7512F902          jnz     continue_loop
.text:7512F908          mov     ecx, ebx
.text:7512F90A          sub     ecx, [ebp+var_28]
.text:7512F90D          push    0Ah
.text:7512F90F          pop     eax
.text:7512F910          cmp     ecx, eax
.text:7512F912          jl      continue_loop
.text:7512F918          mov     esi, [ebp+encoding_subtable_offset]
.text:7512F91B          mov     [edx+2Ch], esi                  ; UVS_subtable_offset
.text:7512F91E          mov     eax, [edi]
.text:7512F920          mov     [eax+30h], ecx                  ; UVS_subtable_size
.text:7512F923          jmp     continue_loop

After returning from this function, the code finally calls “LoadUvsTable()” function to process a possibly encountered UVS table.

.text:750F4446          xor     edx, edx                ; passing zero. Just return space needed.
.text:750F4448          push    edi                    ; struct FONTCMAPDESC *
.text:750F4449          mov     ecx, ebx
.text:750F444B          mov     [ebp+var_258], edi
.text:750F4451          call    LoadUvsTable (FONTCMAPDESC *,FONTUVSENTRY *,ulong *)

At first, the function checks if a UVS record is found by verifying if UVS_subtable_offset is set.

.text:750F5DB2 ; __int32 __stdcall LoadUvsTable(struct FONTCMAPDESC *, union FONTUVSENTRY *, unsigned __int32 *)
.text:750F5DB2 var_2C= dword ptr -2Ch
.text:750F5DB2 var_28= dword ptr -28h
.text:750F5DB2 var_24= dword ptr -24h
.text:750F5DB2 var_20= dword ptr -20h
.text:750F5DB2 var_1C= dword ptr -1Ch
.text:750F5DB2 var_18= dword ptr -18h
.text:750F5DB2 var_14= dword ptr -14h
.text:750F5DB2 var_10= dword ptr -10h
.text:750F5DB2 var_C= dword ptr -0Ch
.text:750F5DB2 var_8= dword ptr -8
.text:750F5DB2 var_4= dword ptr -4
.text:750F5DB2 arg_0= dword ptr  8
.text:750F5DB2
.text:750F5DB2 ; FUNCTION CHUNK AT .text:7512F4CE SIZE 000001D4 BYTES
.text:750F5DB2
.text:750F5DB2          mov     edi, edi
.text:750F5DB4          push    ebp
.text:750F5DB5          mov     ebp, esp
.text:750F5DB7          sub     esp, 2Ch
.text:750F5DBA          mov     eax, [ebp+arg_0]
.text:750F5DBD          push    ebx
.text:750F5DBE          mov     [ebp+var_C], edx
.text:750F5DC1          xor     edx, edx
.text:750F5DC3          mov     [ebp+var_10], edx
.text:750F5DC6          mov     ebx, [eax]
.text:750F5DC8          mov     [ebp+var_2C], ebx
.text:750F5DCB          push    esi
.text:750F5DCC          push    edi
.text:750F5DCD          test    ecx, ecx
.text:750F5DCF          jz      short loc_750F5DDC
.text:750F5DD1          mov     ebx, [ecx+2Ch]                  ; is UVS_subtable_offset set?
.text:750F5DD4          test    ebx, ebx
.text:750F5DD6          jnz     loc_7512F4CE                    ; is UVS_subtable_size set?

After some sanity checks, the number of variation selector records is extracted.

.text:7512F4CE          cmp     dword ptr [ecx+30h], 0Ah ; at least one variation Selector Record data available?
.text:7512F4D2          jb      loc_750F5DDC
.text:7512F4D8          movzx   esi, byte ptr [ebx+2]
.text:7512F4DC          movzx   eax, byte ptr [ebx+3]
.text:7512F4E0          shl     esi, 8
.text:7512F4E3          or      esi, eax
.text:7512F4E5          movzx   eax, byte ptr [ebx+4]
.text:7512F4E9          shl     esi, 8
.text:7512F4EC          or      esi, eax
.text:7512F4EE          movzx   eax, byte ptr [ebx+5]
.text:7512F4F2          shl     esi, 8
.text:7512F4F5          or      esi, eax                        ; extracted_size_from_subtable
.text:7512F4F7          cmp     esi, [ecx+30h]
.text:7512F4FA          ja      loc_750F5DDE
.text:7512F500          movzx   edi, byte ptr [ebx+6]
.text:7512F504          movzx   eax, byte ptr [ebx+7]
.text:7512F508          shl     edi, 8
.text:7512F50B          or      edi, eax
.text:7512F50D          movzx   eax, byte ptr [ebx+8]
.text:7512F511          shl     edi, 8
.text:7512F514          or      edi, eax
.text:7512F516          movzx   eax, byte ptr [ebx+9]
.text:7512F51A          shl     edi, 8
.text:7512F51D          or      edi, eax                 ; Number of variation selector records
.text:7512F51F          imul    eax, edi, 0Bh
.text:7512F522          add     eax, 0Ah
.text:7512F525          cmp     esi, eax                 ; enough data available in subtable?
.text:7512F527          jb      loc_750F5DDE
.text:7512F52D          and     [ebp+var_18], edx
.text:7512F530          lea     eax, [ebx+0Ah]
.text:7512F533          and     [ebp+var_24], edx
.text:7512F536          mov     [ebp+var_28], eax
.text:7512F539          test    edi, edi                  ; No variation selector records?
.text:7512F53B          jz      return

Then a loop is entered to see how much space is needed to process the variation selector records. This is done by calculating the total number of ranges within all records. For each extracted record range number, a check is done to see if enough data is available. Note that there is an integer overflow error here which can be used to bypass this check. As no buffer is passed when calling this function, just the calculated buffer size is returned.

.text:7512F547          movzx   eax, byte ptr [eax]
.text:7512F54A          movzx   ecx, byte ptr [edx-8]
.text:7512F54E          shl     eax, 8
.text:7512F551          or      ecx, eax
.text:7512F553          movzx   eax, byte ptr [edx-7]
.text:7512F557          shl     ecx, 8
.text:7512F55A          or      ecx, eax
.text:7512F55C          mov     [ebp+var_20], ecx
.text:7512F55F          cmp     [ebp+var_18], ecx
.text:7512F562          jnb     loc_750F5DDE
.text:7512F568          movzx   eax, byte ptr [edx-1]
.text:7512F56C          mov     [ebp+var_18], ecx
.text:7512F56F          movzx   ecx, byte ptr [edx-2]
.text:7512F573          shl     ecx, 8
.text:7512F576          or      ecx, eax
.text:7512F578          movzx   eax, byte ptr [edx]
.text:7512F57B          shl     ecx, 8
.text:7512F57E          or      ecx, eax
.text:7512F580          movzx   eax, byte ptr [edx+1]
.text:7512F584          shl     ecx, 8
.text:7512F587          or      ecx, eax
.text:7512F589          jz      loc_7512F66B
.text:7512F58F          lea     eax, [ecx+4]
.text:7512F592          cmp     eax, ecx
.text:7512F594          jb      loc_750F5DDE
.text:7512F59A          cmp     eax, esi
.text:7512F59C          ja      loc_750F5DDE
.text:7512F5A2          lea     edx, [ecx+ebx]
.text:7512F5A5          movzx   eax, byte ptr [edx]
.text:7512F5A8          shl     eax, 8
.text:7512F5AB          mov     [ebp+var_4], eax
.text:7512F5AE          movzx   eax, byte ptr [edx+1]
.text:7512F5B2          or      [ebp+var_4], eax
.text:7512F5B5          movzx   eax, byte ptr [edx+2]
.text:7512F5B9          shl     [ebp+var_4], 8
.text:7512F5BD          or      [ebp+var_4], eax
.text:7512F5C0          movzx   eax, byte ptr [edx+3]
.text:7512F5C4          shl     [ebp+var_4], 8
.text:7512F5C8          mov     [ebp+var_1C], edx
.text:7512F5CB          mov     edx, [ebp+var_4]
.text:7512F5CE          or      edx, eax
.text:7512F5D0          mov     [ebp+var_4], edx                ; numUnicodeValueRanges
.text:7512F5D3          imul    edx, 5                          ; *** Integer Overflow ***
.text:7512F5D6          push    esi
.text:7512F5D7          add     edx, 4
.text:7512F5DA          call    CheckBuffer
.text:7512F5DF          test    eax, eax
.text:7512F5E1          jz      loc_750F5DDE
.text:7512F5E7          mov     edx, [ebp+var_1C]
.text:7512F5EA          add     edx, 4
.text:7512F5ED          cmp     [ebp+var_C], 0                  ; is a buffer passed?
.text:7512F5F1          jz      short loc_7512F65F
.text:7512F5F3          and     [ebp+var_1C], 0
.text:7512F5F7          cmp     [ebp+var_4], 0
.text:7512F5FB          jbe     short loc_7512F668
.text:7512F5FD          mov     ecx, [ebp+var_20]
.text:7512F600
.text:7512F600 ; CODE XREF: LoadUvsTable(FONTCMAPDESC *,FONTUVSENTRY *,ulong *)+398A9_j
.text:7512F600          movzx   eax, byte ptr [edx]
.text:7512F603          shl     eax, 8
.text:7512F606          mov     [ebp+var_8], eax
.text:7512F609          movzx   eax, byte ptr [edx+1]
.text:7512F60D          or      [ebp+var_8], eax
.text:7512F610          shl     [ebp+var_8], 8
.text:7512F614          movzx   eax, byte ptr [edx+2]
.text:7512F618          or      [ebp+var_8], eax
.text:7512F61B          jbe     loc_750F5DDE
.text:7512F621          mov     eax, [ebp+var_10]
.text:7512F624          inc     eax
.text:7512F625          mov     [ebp+var_10], eax
.text:7512F628          cmp     [ebp+var_2C], eax
.text:7512F62B          jb      short loc_7512F698
.text:7512F62D          mov     eax, [ebp+var_C]
.text:7512F630          mov     [eax+4], ecx
.text:7512F633          mov     ecx, [ebp+var_C]
.text:7512F636          mov     eax, [ebp+var_8]
.text:7512F639          add     [ebp+var_C], 0Ah
.text:7512F63D          mov     [ecx], eax
.text:7512F63F          mov     ax, [edx+3]
.text:7512F643          add     edx, 5
.text:7512F646          rol     ax, 8
.text:7512F64A          mov     [ecx+8], ax
.text:7512F64E          mov     eax, [ebp+var_1C]
.text:7512F651          mov     ecx, [ebp+var_20]
.text:7512F654          inc     eax
.text:7512F655          mov     [ebp+var_1C], eax
.text:7512F658          cmp     eax, [ebp+var_4]
.text:7512F65B          jb      short loc_7512F600
.text:7512F65D          jmp     short loc_7512F668
.text:7512F65F ; ---------------------------------------------------------------------------
.text:7512F65F
.text:7512F65F; CODE XREF: LoadUvsTable(FONTCMAPDESC *,FONTUVSENTRY *,ulong *)+3983F_j
.text:7512F65F          mov     eax, [ebp+var_10]
.text:7512F662          add     eax, [ebp+var_4]          ; *** Integer Overflow Possibility***
.text:7512F665          mov     [ebp+var_10], eax
.text:7512F668
.text:7512F668; CODE XREF: LoadUvsTable(FONTCMAPDESC *,FONTUVSENTRY *,ulong *)+39849_j
.text:7512F668; LoadUvsTable(FONTCMAPDESC *,FONTUVSENTRY *,ulong *)+398AB_j
.text:7512F668          mov     edx, [ebp+var_14]
.text:7512F66B
.text:7512F66B; CODE XREF: LoadUvsTable(FONTCMAPDESC *,FONTUVSENTRY *,ulong *)+397D7_j
.text:7512F66B          mov     ecx, [ebp+var_24]
.text:7512F66E          add     edx, 0Bh
.text:7512F671          mov     eax, [ebp+var_28]
.text:7512F674          inc     ecx
.text:7512F675          add     eax, 0Bh
.text:7512F678          mov     [ebp+var_24], ecx
.text:7512F67B          mov     [ebp+var_28], eax
.text:7512F67E          mov     [ebp+var_14], edx
.text:7512F681          cmp     ecx, edi
.text:7512F683          jb      loc_7512F547

Upon return, the calculated buffer size is multiplied by 10 where an integer overflow error happens. This will lead to allocation of an undersized buffer.

.text:7512EEB9 loc_7512EEB9:             ; CODE XREF: LoadFont(HDC__ *,FACE_CACHE *)+19A_j
.text:7512EEB9          mov     ecx, [edi]
.text:7512EEBB          test    ecx, ecx                        ; calculated buffer size
.text:7512EEBD          jz      loc_750F446A
.text:7512EEC3          lea     eax, [esi+410h]
.text:7512EEC9          push    eax                             ; int
.text:7512EECA          mov     [ebp+var_25C], eax
.text:7512EED0          imul    eax, ecx, 0Ah                   ; *** Integer Overflow ***
.text:7512EED3          push    eax                          ; dwBytes
.text:7512EED4          call    _UspAllocCache@8               ; UspAllocCache(x,x)

Another call to “LoadUvsTable()” function is done where the undersized buffer is passed to perform actual loading of the UVS table.

.text:7512EEE3          mov     edx, [esi+410h]
.text:7512EEE9          mov     ecx, ebx        ; allocated undersized buffer
.text:7512EEEB          push    edi             ; struct FONTCMAPDESC *
.text:7512EEEC          call   LoadUvsTable(FONTCMAPDESC *,FONTUVSENTRY *,ulong *)

A loop is used to copy data to the provided undersized buffer leading to a memory corruption. It is possible to exit the loop when desired by setting a startUnicodeValue to zero.

.text:7512F600          movzx   eax, byte ptr [edx]
.text:7512F603          shl     eax, 8
.text:7512F606          mov     [ebp+var_8], eax
.text:7512F609          movzx   eax, byte ptr [edx+1]
.text:7512F60D          or      [ebp+var_8], eax
.text:7512F610          shl     [ebp+var_8], 8
.text:7512F614          movzx   eax, byte ptr [edx+2]
.text:7512F618          or      [ebp+var_8], eax
.text:7512F61B          jbe     loc_750F5DDE             ; early exit of loop whenever needed! ;)
.text:7512F621          mov     eax, [ebp+var_10]
.text:7512F624          inc     eax
.text:7512F625          mov     [ebp+var_10], eax
.text:7512F628          cmp     [ebp+var_2C], eax
.text:7512F62B          jb      short loc_7512F698
.text:7512F62D          mov     eax, [ebp+var_C]
.text:7512F630          mov     [eax+4], ecx                    ; *** Corruption ***
.text:7512F633          mov     ecx, [ebp+var_C]
.text:7512F636          mov     eax, [ebp+var_8]
.text:7512F639          add     [ebp+var_C], 0Ah
.text:7512F63D          mov     [ecx], eax                      ; *** Corruption ***
.text:7512F63F          mov     ax, [edx+3]
.text:7512F643          add     edx, 5
.text:7512F646          rol     ax, 8
.text:7512F64A          mov     [ecx+8], ax                     ; *** Corruption ***
.text:7512F64E          mov     eax, [ebp+var_1C]
.text:7512F651          mov     ecx, [ebp+var_20]
.text:7512F654          inc     eax
.text:7512F655          mov     [ebp+var_1C], eax
.text:7512F658          cmp     eax, [ebp+var_4]                ; big value
.text:7512F65B          jb      short loc_7512F600

The memory corruption and its control ultimately puts an attacker in the position to potentially compromise a system running an affected version of the Microsoft Windows operating system.

References:

[1] https://technet.microsoft.com/library/security/MS16-147
[2] https://secunia.com/advisories/70000
[3] https://en.wikipedia.org/wiki/Uniscribe
[4] https://www.microsoft.com/typography/otspec/cmap.htm
[5] https://secunia.com/advisories/74000

Leave a Reply

Your email address will not be published. Required fields are marked *