Home / vulnerabilities Microsoft Unicode Scripts Processor Arbitrary Code Execution
Posted on 18 December 2015
Source : packetstormsecurity.org Link
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
https://technet.microsoft.com/en-us/library/security/ms15-130.aspx
https://secunia.com/community/advisories/66666
https://en.wikipedia.org/wiki/Uniscribe
https://www.microsoft.com/typography/otspec/cmap.htm
Hossein Lotfi
Security Specialist
Secunia Research at Flexera Software