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%\Fonts\ariblk.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