Vulnerability in Microsoft’s Unicode Scripts Processor allows execution of arbitrary code

On the 8th December 2015, Microsoft released Security Bulletin MS15-130 [1] to fix a vulnerability in Unicode Scripts Processor component found by Secunia Research [2]. The Common Vulnerabilities and Exposures (CVE) project has assigned the CVE-2015-6130 identifier for the vulnerability.

The vector for a successful exploitation is a specially crafted “True Type Font” (TTF) file, which typically can be embedded in e.g. Microsoft Office documents or even in emails and web-based content depending on the font type.

The result is the execution of arbitrary code once successfully exploited and thus is rated as “Highly Critical” by Secunia Research.

Introduction:

Uniscribe is the Microsoft Windows set of services for rendering Unicode-encoded text, especially complex text layout. They are implemented in USP10.DLL. USP is an initialism for Unicode Scripts Processor [3].

Reproduction:

Open %systemroot%Fontsariblk.ttf in a hex editor and change content of offset 0x4ED2 from 0x0014 to 0x011B.

Technical Details:

Note: The following analysis is done on Windows 7 SP1 with usp10.dll version 1.626.7601.18454.

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

.text:7603D2E0 mov     edi, edi
.text:7603D2E2 push    ebp
.text:7603D2E3 mov     ebp, esp
.text:7603D2E5 sub     esp, 25Ch
.text:7603D2EB mov     eax, ___security_cookie
.text:7603D2F0 xor     eax, ebp
.text:7603D2F2 mov     [ebp+var_8], eax
.text:7603D2F5 mov     eax, [ebp+arg_0]
.text:7603D2F8 push    esi                             ; struct FACE_CACHE *
.text:7603D2F9 push    edi                             ; HDC
.text:7603D2FA xor     edi, edi
.text:7603D2FC push    220h                            ; Size
.text:7603D301 lea     ecx, [ebp+Dst]
.text:7603D307 push    edi                             ; Val
.text:7603D308 push    ecx                             ; Dst
.text:7603D309 mov     [ebp+hdc], eax
.text:7603D30F mov     [ebp+var_250], edi
.text:7603D315 call    _memset
.text:7603D31A xor     eax, eax
.text:7603D31C mov     dword ptr [ebp+var_28], eax
.text:7603D31F mov     [ebp+var_24], eax
.text:7603D322 mov     [ebp+var_20], eax
.text:7603D325 mov     [ebp+var_1C], eax
.text:7603D328 mov     [ebp+var_18], eax
.text:7603D32B mov     [ebp+var_14], eax
.text:7603D32E mov     [ebp+var_10], eax
.text:7603D331 mov     [ebp+var_C], eax
.text:7603D334 mov     al, [ebx+95h]
.text:7603D33A mov     edx, 0F807h
.text:7603D33F and     [ebx+0A0h], dx
.text:7603D346 and     al, 0Ch
.text:7603D348 add     esp, 0Ch
.text:7603D34B cmp     al, 8
.text:7603D34D jnz     short loc_7603D366
.text:7603D34F cmp     byte ptr [ebx+97h], 0
.text:7603D356 jnz     short loc_7603D366
.text:7603D358 lea     esi, [ebx+98h]
.text:7603D35E mov     dword ptr [esi], 0FFFFFFFDh
.text:7603D364 jmp     short loc_7603D385
.text:7603D366 ; ---------------------------------------------------------------------------
.text:7603D366
.text:7603D366 loc_7603D366:                           ; CODE XREF: LoadFont(HDC__ *,FACE_CACHE *)+6D_j
.text:7603D366                                         ; LoadFont(HDC__ *,FACE_CACHE *)+76_j
.text:7603D366 mov     eax, [ebp+hdc]
.text:7603D36C lea     ecx, [ebp+var_250]
.text:7603D372 push    ecx                             ; int *
.text:7603D373 lea     esi, [ebx+98h]
.text:7603D379 push    esi                             ; HDC
.text:7603D37A 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:7603AF70 ; __int32 __stdcall GetFontDesc(HDC, int *, struct FONTCMAPDESC **)
.text:7603AF70 ?GetFontDesc@@YGJPAUHDC__@@PAHPAPAUFONTCMAPDESC@@@Z proc near
.text:7603AF70                                           ; CODE XREF: LoadFont(HDC__ *,FACE_CACHE *)+9A_p
.text:7603AF70
.text:7603AF70 var_20= dword ptr -20h
.text:7603AF70 var_1C= dword ptr -1Ch
.text:7603AF70 var_18= dword ptr -18h
.text:7603AF70 pvBuffer= byte ptr -14h
.text:7603AF70 var_12= byte ptr -12h
.text:7603AF70 var_10= dword ptr -10h
.text:7603AF70 var_C= dword ptr -0Ch
.text:7603AF70 var_8= dword ptr -8
.text:7603AF70 var_4= dword ptr -4
.text:7603AF70 arg_0= dword ptr  8
.text:7603AF70 arg_4= dword ptr  0Ch
.text:7603AF70
.text:7603AF70 mov     edi, edi
.text:7603AF72 push    ebp
.text:7603AF73 mov     ebp, esp
.text:7603AF75 sub     esp, 20h
.text:7603AF78 push    ebx
.text:7603AF79 mov     ebx, ds:__imp__GetFontData@20   ; GetFontData(x,x,x,x,x)
.text:7603AF7F push    esi                             ; int
.text:7603AF80 push    edi                             ; unsigned __int16 *
.text:7603AF81 mov     edi, [ebp+arg_4]
.text:7603AF84 push    4                               ; cjBuffer
.text:7603AF86 mov     esi, eax
.text:7603AF88 lea     eax, [ebp+pvBuffer]
.text:7603AF8B push    eax                             ; pvBuffer
.text:7603AF8C push    3Eh                             ; dwOffset
.text:7603AF8E push    '2/SO'                          ; dwTable
.text:7603AF93 push    esi                             ; hdc
.text:7603AF94 mov     dword ptr [edi], 0
.text:7603AF9A call    ebx ; GetFontData(x,x,x,x,x)    ; GetFontData(x,x,x,x,x)
.text:7603AF9C cmp     eax, 4
.text:7603AF9F jz      short loc_7603AFB5
.text:7603AFA1 mov     ecx, [ebp+arg_0]
.text:7603AFA4 pop     edi
.text:7603AFA5 pop     esi
.text:7603AFA6 mov     dword ptr [ecx], 0FFFFFFFEh
.text:7603AFAC xor     eax, eax
.text:7603AFAE pop     ebx
.text:7603AFAF mov     esp, ebp
.text:7603AFB1 pop     ebp
.text:7603AFB2 retn    8
.text:7603AFB5 ; ---------------------------------------------------------------------------
.text:7603AFB5
.text:7603AFB5 loc_7603AFB5:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+2F_j
.text:7603AFB5 mov     al, [ebp+var_12]                ; usFirstCharIndex
.text:7603AFB8 cmp     al, 0F0h
.text:7603AFBA jnb     short loc_7603AFC0
.text:7603AFBC test    al, al
.text:7603AFBE jnz     short loc_7603AFD1
.text:7603AFC0
.text:7603AFC0 loc_7603AFC0:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+4A_j
.text:7603AFC0 mov     al, [ebp+pvBuffer]              ; fsSelection
.text:7603AFC3 test    al, al
.text:7603AFC5 jz      short loc_7603AFD1
.text:7603AFC7 movzx   edx, al
.text:7603AFCA mov     eax, [ebp+arg_0]
.text:7603AFCD mov     [eax], edx
.text:7603AFCF jmp     short loc_7603AFDA
.text:7603AFD1 ; ---------------------------------------------------------------------------
.text:7603AFD1
.text:7603AFD1 loc_7603AFD1:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+4E_j
.text:7603AFD1                                         ; GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+55_j
.text:7603AFD1 mov     ecx, [ebp+arg_0]
.text:7603AFD4 mov     dword ptr [ecx], 0FFFFFFFFh
.text:7603AFDA
.text:7603AFDA loc_7603AFDA:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+5F_j
.text:7603AFDA push    0                               ; cjBuffer
.text:7603AFDC push    0                               ; pvBuffer
.text:7603AFDE push    0                               ; dwOffset
.text:7603AFE0 push    'pamc'                          ; dwTable
.text:7603AFE5 push    esi                             ; hdc
.text:7603AFE6 call    ebx ; GetFontData(x,x,x,x,x)    ; GetFontData(x,x,x,x,x)
.text:7603AFE8 mov     ebx, eax
.text:7603AFEA mov     [ebp+var_4], ebx
.text:7603AFED cmp     ebx, 0FFFFFFFFh
.text:7603AFF0 jz      loc_7603B188
.text:7603AFF6 cmp     ebx, 4
.text:7603AFF9 jl      loc_7603B188
.text:7603AFFF push    edi                             ; int
.text:7603B000 lea     edx, [ebx+34h]
.text:7603B003 push    edx                             ; dwBytes
.text:7603B004 call    _UspAllocCache@8                ; UspAllocCache(x,x)
.text:7603B009 test    eax, eax
.text:7603B00B jl      short loc_7603B061
.text:7603B00D mov     eax, [edi]
.text:7603B00F lea     ecx, [eax+34h]
.text:7603B012 mov     [eax+4], ecx
.text:7603B015 push    ebx                             ; cjBuffer
.text:7603B016 mov     [eax+8], ebx
.text:7603B019 mov     edx, [eax+4]
.text:7603B01C push    edx                             ; pvBuffer
.text:7603B01D push    0                               ; dwOffset
.text:7603B01F push    'pamc'                          ; dwTable
.text:7603B024 push    esi                             ; hdc
.text:7603B025 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:7603B035 mov     ecx, [eax+4]
.text:7603B038 mov     dx, [ecx+2]                     ; numTables
.text:7603B03C add     ecx, 2
.text:7603B03F rol     dx, 8                           ; change endianness
.text:7603B043 mov     [ecx], dx
.text:7603B046 mov     esi, [eax+4]
.text:7603B049 movzx   ecx, word ptr [esi+2]
.text:7603B04D lea     edx, ds:4[ecx*8]
.text:7603B054 cmp     ebx, edx                        ; check if enough data is available
.text:7603B056 mov     [ebp+var_20], ecx
.text:7603B059 jge     short proceed1
.text:7603B05B push    eax
.text:7603B05C call    _UspFreeMem@4                   ; UspFreeMem(x)
.text:7603B061
.text:7603B061 return_error:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+9B_j
.text:7603B061 mov     eax, [ebp+arg_0]
.text:7603B064 pop     edi
.text:7603B065 pop     esi
.text:7603B066 mov     dword ptr [eax], 0FFFFFFFDh
.text:7603B06C xor     eax, eax
.text:7603B06E pop     ebx
.text:7603B06F mov     esp, ebp
.text:7603B071 pop     ebp
.text:7603B072 retn    8
.text:7603B075 ; ---------------------------------------------------------------------------
.text:7603B075
.text:7603B075 proceed1:                                ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+E9_j
.text:7603B075 xor     edx, edx
.text:7603B077 add     esi, 4
.text:7603B07A cmp     ecx, edx                         ; check if numTables is zero
.text:7603B07C mov     [eax+2Ch], edx
.text:7603B07F mov     [ebp+var_C], edx
.text:7603B082 mov     [ebp+var_10], edx
.text:7603B085 mov     [eax+30h], edx
.text:7603B088 mov     [ebp+var_8], edx
.text:7603B08B jle     clean_and_return

Afterwards, a loop is entered to process available EncodingRecords. If platform ID is 3 and encoding ID is either 0 (Symbol) or 1 (Unicode BMP (UCS-2)) [4], then the offset and format of a table are saved in respective local variables.

.text:7603B094 loop:                                   ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+200_j
.text:7603B094 mov     cx, [esi]                       ; platformID
.text:7603B097 mov     dx, [esi+2]                     ; encodingID
.text:7603B09B rol     cx, 8
.text:7603B09F mov     [esi], cx
.text:7603B0A2 rol     dx, 8
.text:7603B0A6 lea     edi, [esi+4]                    ; offset
.text:7603B0A9 mov     ecx, 1
.text:7603B0AE mov     eax, edi
.text:7603B0B0 mov     [esi+2], dx
.text:7603B0B4 call    ?FlipDWords@@YGXPAKH@Z          ; FlipDWords(ulong *,int)
.text:7603B0B9 mov     edi, [edi]                      ; offset (little endian)
.text:7603B0BB movzx   ecx, word ptr [esi]             ; platformID
.text:7603B0BE test    edi, edi                        ; is_offset_zero?
.text:7603B0C0 jz      continue_loop2
.text:7603B0C6 lea     eax, [ebx-4]                    
.text:7603B0C9 cmp     eax, edi                        ; enough_data_available?
.text:7603B0CB jbe     continue_loop2
.text:7603B0D1 mov     edx, [ebp+arg_4]
.text:7603B0D4 mov     edx, [edx]
.text:7603B0D6 mov     eax, [edx+4]
.text:7603B0D9 mov     bx, [eax+edi]                   ; format
.text:7603B0DD add     eax, edi
.text:7603B0DF rol     bx, 8
.text:7603B0E3 movzx   ebx, bx
.text:7603B0E6 test    cx, cx
.text:7603B0E9 jnz     short loc_7603B115
.text:7603B0EB movzx   ecx, word ptr [esi+2]
.text:7603B0EF cmp     cx, 5
.text:7603B0F3 jnz     short continue_loop1
.text:7603B0F5 cmp     bx, 0Eh
.text:7603B0F9 jnz     short continue_loop1
.text:7603B0FB cmp     dword ptr [edx+2Ch], 0
.text:7603B0FF jnz     short continue_loop1
.text:7603B101 mov     ebx, [ebp+var_4]
.text:7603B104 mov     ecx, ebx
.text:7603B106 sub     ecx, edi
.text:7603B108 cmp     ecx, 0Ah
.text:7603B10B jl      short continue_loop2
.text:7603B10D mov     [edx+2Ch], eax
.text:7603B110 mov     [edx+30h], ecx
.text:7603B113 jmp     short continue_loop2
.text:7603B115 ; ---------------------------------------------------------------------------
.text:7603B115
.text:7603B115 loc_7603B115:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+179_j
.text:7603B115 mov     edx, 3
.text:7603B11A cmp     cx, dx
.text:7603B11D jnz     short continue_loop1
.text:7603B11F movzx   ecx, word ptr [esi+2]
.text:7603B123 test    cx, cx
.text:7603B126 jnz     short loc_7603B137              ; Unicode BMP encodings?
.text:7603B128 mov     ecx, 1
.text:7603B12D cmp     [ebp+var_8], ecx
.text:7603B130 jge     short continue_loop1
.text:7603B132 mov     [ebp+var_8], ecx
.text:7603B135 jmp     short loc_7603B15A
.text:7603B137 ; ---------------------------------------------------------------------------
.text:7603B137
.text:7603B137 loc_7603B137:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+1B6_j
.text:7603B137 cmp     cx, 1                           ; Unicode BMP encodings?
.text:7603B13B jnz     short loc_7603B14C
.text:7603B13D mov     ecx, 2
.text:7603B142 cmp     [ebp+var_8], ecx
.text:7603B145 jge     short continue_loop1
.text:7603B147 mov     [ebp+var_8], ecx
.text:7603B14A jmp     short loc_7603B15A
.text:7603B14C ; ---------------------------------------------------------------------------
.text:7603B14C
.text:7603B14C loc_7603B14C:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+1CB_j
.text:7603B14C cmp     cx, 0Ah
.text:7603B150 jnz     short continue_loop1
.text:7603B152 cmp     [ebp+var_8], edx
.text:7603B155 jge     short continue_loop1
.text:7603B157 mov     [ebp+var_8], edx
.text:7603B15A
.text:7603B15A loc_7603B15A:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+1C5_j
.text:7603B15A                                         ; GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+1DA_j
.text:7603B15A mov     [ebp+table_pointer], eax
.text:7603B15D movzx   eax, bx
.text:7603B160 mov     [ebp+format4_offset], edi
.text:7603B163 mov     [ebp+format], eax
.text:7603B166
.text:7603B166 continue_loop1:                         ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+183_j
.text:7603B166                                         ; GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+189_j ...
.text:7603B166 mov     ebx, [ebp+var_4]
.text:7603B169
.text:7603B169 continue_loop2:                         ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+150_j
.text:7603B169                                         ; GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+15B_j ...
.text:7603B169 add     esi, 8
.text:7603B16C sub     [ebp+var_18], 1
.text:7603B170 jnz     loop

Immediately after finishing the loop, a check is done to see if a local variable for a possible encountered offset of EncodingRecord is set and then another check is done to see if saved format is a format 4 (segment mapping to delta values).

.text:7603B176 mov     ecx, [ebp+format4_offset]
.text:7603B179 test    ecx, ecx
.text:7603B17B jnz     short loc_7603B19C              ; is Segment_mapping_to_delta_values?
.text:7603B17D mov     edi, [ebp+arg_4]
.text:7603B180
.text:7603B180 clean_and_return:                       ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+11B_j
.text:7603B180 mov     ecx, [edi]
.text:7603B182
.text:7603B182 loc_7603B182:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+314_j
.text:7603B182 push    ecx
.text:7603B183 call    _UspFreeMem@4                   ; UspFreeMem(x)
.text:7603B188
.text:7603B188 loc_7603B188:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+80_j
.text:7603B188                                         ; GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+89_j
.text:7603B188 mov     edx, [ebp+arg_0]
.text:7603B18B pop     edi
.text:7603B18C pop     esi
.text:7603B18D mov     dword ptr [edx], 0FFFFFFFDh
.text:7603B193 xor     eax, eax
.text:7603B195 pop     ebx
.text:7603B196 mov     esp, ebp
.text:7603B198 pop     ebp
.text:7603B199 retn    8
.text:7603B19C ; ---------------------------------------------------------------------------
.text:7603B19C
.text:7603B19C loc_7603B19C:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+20B_j
.text:7603B19C movzx   eax, word ptr [ebp+format]      ; is Segment_mapping_to_delta_values?
.text:7603B1A0 cmp     eax, 4
.text:7603B1A3 jz      format4
.text:7603B1A9 cmp     eax, 0Ch
.text:7603B1AC jnz     short loc_7603B210

After that, a loop is entered to check if there is an EncodingRecord offset larger than saved format 4 offset. If it is also smaller than cmap table data size, it is considered valid and will be saved.

.text:7603B242 next_record:                            ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+2E5_j
.text:7603B242 mov     ecx, [eax]                      ; loading EncodingRecord offset
.text:7603B244 cmp     ecx, [ebp+offset]
.text:7603B247 jbe     short loc_7603B24F
.text:7603B249 cmp     edx, ecx                        ; smaller than cmap table data size?
.text:7603B24B jbe     short loc_7603B24F
.text:7603B24D mov     edx, ecx                        ; saving in EDX
.text:7603B24F
.text:7603B24F loc_7603B24F:                           ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+2D7_j
.text:7603B24F                                         ; GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+2DB_j
.text:7603B24F add     eax, 8
.text:7603B252 sub     esi, 1
.text:7603B255 jnz     short next_record

Then, a subroutine is entered to change endianness of format 4 subtable. In order to calculate the length of operation, the saved offset from the last loop is subtracted from the original format 4 offset and to skip format and length fields (two short values), a subtraction by 4 is performed. Note that there is no check here for an integer underflow.

.text:7603B257 mov     edi, [ebp+format4_offset]
.text:7603B25A mov     esi, [ebp+table_pointer]
.text:7603B25D sub     edx, edi
.text:7603B25F sub     edx, 4          ; *** Integer Underflow ***
.text:7603B262 shr     edx, 1          ; would not be a signed value any more.
.text:7603B264 lea     ecx, [esi+4]    ; skip ushort_format and ushort_length
.text:7603B267 call    FlipWords(ushort *,int)

Within the “FlipWords()” function, the underflowed value is used to change endianness of the content of subtable, resulting in a heap-based buffer overflow.

.text:7603AF00 ; void __cdecl FlipWords(unsigned __int16 *, int)
.text:7603AF00 ?FlipWords@@YGXPAGH@Z proc near         ; CODE XREF: GetFontDesc(HDC__ *,int *,FONTCMAPDESC * *)+2F7_p
.text:7603AF00 xor     eax, eax
.text:7603AF02 test    edx, edx                        ; huge size due to underflow
.text:7603AF04 jle     short locret_7603AF22
.text:7603AF06 push    esi
.text:7603AF07 jmp     short loc_7603AF10
.text:7603AF07 ; ---------------------------------------------------------------------------
.text:7603AF09 align 10h
.text:7603AF10
.text:7603AF10 loc_7603AF10:                           ; CODE XREF: FlipWords(ushort *,int)+7_j
.text:7603AF10                                         ; FlipWords(ushort *,int)+1F_j
.text:7603AF10 mov     si, [ecx+eax*2]
.text:7603AF14 rol     si, 8
.text:7603AF18 mov     [ecx+eax*2], si                 ; Heap-based buffer overflow
.text:7603AF1C inc     eax
.text:7603AF1D cmp     eax, edx
.text:7603AF1F jl      short loc_7603AF10
.text:7603AF21 pop     esi
.text:7603AF22
.text:7603AF22 locret_7603AF22:                        ; CODE XREF: FlipWords(ushort *,int)+4_j
.text:7603AF22 retn
References
  1. https://technet.microsoft.com/en-us/library/security/ms15-130.aspx
  2. https://secunia.com/community/advisories/66666
  3. https://en.wikipedia.org/wiki/Uniscribe
  4. https://www.microsoft.com/typography/otspec/cmap.htm

Leave a Reply

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