Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:10968
HistoryJan 11, 2006 - 12:00 a.m.

Microsoft Embedded OpenType Font Engine "t2embed" Remote Heap Overflow

2006-01-1100:00:00
vulners.com
74
/* oh my, bad luck, eEye released the advisory few minutes ago, and I've been 
       researching this bug since about a week, sorry, it's cancelled
    */

NOTE: this is super initial raport, if you expect some more info mail
      me for the bank account number...


Microsoft Embedded OpenType Font Engine "t2embed" Remote Heap Overflow
by Piotr Bania <[email protected]>
http://www.piotrbania.com
All rights reserved.


Severity:  		Critical - potencial remote code execution.


    0.   DISCLAIMER

    Author takes no responsibility for any actions with provided informations or 
codes. The copyright for any material created by the author is reserved. Any 
duplication of codes or texts provided here in electronic or printed 
publications is not permitted without the author's agreement. 

I.   BACKGROUND

Microsoft Embedded OpenType Font Engine "t2embed" is designed to create the
ability of using specific fonts that might not be available on your local 
    system. The Embeded OpenType font file contains compressed, subsetted font 
    data that is converted to a TrueType font and installed by T2embed.dll 
library.


II.  DESCRIPTION

Due to the fact EOT (Embeded OpenType font file) file structure seems not to
be disclosed to public, I had to do little reversing game, following informations
can be wrong, they were documented only for the clearification of the 
vulnerability. 

When Microsoft Embedded OpenType Font Engine is parsing a .EOT file, it allocates
the heap memory block with size calculated from the .EOT file.

Note: all the "values" came from special case of my POC code

// --- SNIP ------------------------------------------------------------------
.text:73C993EA                 call    _MTX_BITIO_ReadValue@8 ; MTX_BITIO_ReadValue(x,x)
.text:73C993EF                 mov     [esi+FONT_STRUCT.mem_size], eax
// --- SNIP ------------------------------------------------------------------

The _MTX_BITIO_ReadValue@8 (in this case) loopes 18h times the following code:

// --- SNIP ------------------------------------------------------------------
.text:73C9BBF8 give_input_bit:                         ; CODE XREF: MTX_BITIO_ReadValue(x,x)+24j
.text:73C9BBF8                 push    [esp+8+arg_0]   
.text:73C9BBFC                 shl     esi, 1		      ; (*)
.text:73C9BBFE                 call    _MTX_BITIO_input_bit@4 ; MTX_BITIO_input_bit(x)
.text:73C9BC03                 test    ax, ax          
.text:73C9BC06                 jz      short max_io_ret_0
.text:73C9BC08                 or      esi, 1		      ; (*)
// --- SNIP ------------------------------------------------------------------

Note the bit operations on ESI register marked as (*), first one simply multiples the 
value stored in ESI register with 2^1. The second one powers on the LSB (Less
Significant Bit).

The _MTX_BITIO_input_bit@4 (in this case) does the following:
// --- SNIP ------------------------------------------------------------------
.text:73C9BA53                 mov     eax, [esp+read_io]      ; some struct ptr
.text:73C9BA57                 xor     ecx, ecx                ; ECX = 0
.text:73C9BA59                 mov     cx, [eax+read_io.size?] ; CX = 7 (counter)
.text:73C9BA5D                 test    cx, cx		       ; is it 0?
.text:73C9BA60                 lea     edx, [ecx-1]            ; EDX = 6
.text:73C9BA63                 mov     [eax+read_io.size?], dx ; read_io.size? - 1
.text:73C9BA67                 jnz     short _MTX_BITIO_end    ; until != 0 => take the jump
.text:73C9BA69                 mov     ecx, [eax+read_io.licznik_do_fdata] ; ecx = 1	
.text:73C9BA6C                 mov     edx, [eax]              ; EDX = ptr to font data
.text:73C9BA6E                 movzx   dx, byte ptr [ecx+edx]  ; give on byte from [ecx+edx]
.text:73C9BA73                 inc     ecx                     ; ECX++
.text:73C9BA74                 cmp     ecx, [eax+8]            ; is ECX == 0x3B8 ?
.text:73C9BA77                 mov     [eax+read_io.zwracana], dx ; set the return value
.text:73C9BA7B                 mov     [eax+read_io.licznik_do_fdata], ecx ; store the counter
.text:73C9BA7E                 jle     short loc_73C9BA91
			       ...

.text:73C9BA91 loc_73C9BA91:                           ; CODE XREF: MTX_BITIO_input_bit(x)+2Bj
.text:73C9BA91                 inc     dword ptr [eax+10h]
.text:73C9BA94                 mov     [eax+read_io.size?], 7 ; reset the io size to 7
.text:73C9BA9A
.text:73C9BA9A _MTX_BITIO_end:                         ; CODE XREF: MTX_BITIO_input_bit(x)+14j
.text:73C9BA9A                 shl     [eax+read_io.zwracana], 1 ; multiple via 2 -> {100,200,...}
.text:73C9BA9E                 movzx   eax, [eax+read_io.zwracana] ; eax = what we will return
.text:73C9BAA2                 and     ax, 100h        ; and the bits, the return values are {0,100h}
.text:73C9BAA6                 retn    4	
// --- SNIP ------------------------------------------------------------------

The read bytes (read from .EOT data file), of course the .EOT data was firstly 
    xored by the engine, like the code here shows:

// --- SNIP ------------------------------------------------------------------
.text:73C770AD loc_73C770AD:                           ; CODE XREF: sub_73C770A1+16j
.text:73C770AD                 xor     dword ptr [edx+ecx*4], 50505050h
.text:73C770B4                 inc     ecx
.text:73C770B5                 cmp     ecx, eax
.text:73C770B7                 jb      short loc_73C770AD
// --- SNIP ------------------------------------------------------------------	

The end of _MTX_BITIO_ReadValue@8 comes with following code:

// --- SNIP ------------------------------------------------------------------
.text:73C9BC0B max_io_ret_0:                           ; CODE XREF: MTX_BITIO_ReadValue(x,x)+1Ej
.text:73C9BC0B                 dec     edi             ; if AX was 0
.text:73C9BC0C                 jnz     short give_input_bit ; until != 0, repeat
.text:73C9BC0E                 pop     edi
.text:73C9BC0F
.text:73C9BC0F loc_73C9BC0F:                           ; CODE XREF: MTX_BITIO_ReadValue(x,x)+Aj
.text:73C9BC0F                 mov     eax, esi        ; return value is computed from ESI!!!
.text:73C9BC11                 pop     esi             ; 
.text:73C9BC12                 retn    8
// --- SNIP ------------------------------------------------------------------	


The returned mem_size is then used with creating the heap memory block,

// --- SNIP ------------------------------------------------------------------	
.text:73C993FB                 mov     eax, [esi+FONT_STRUCT.mem_size]
.text:73C99401                 add     eax, 1C00h
			       ...
.text:73C99410                 push    eax ; *our mem_size value + 1c00h*
.text:73C99411                 push    dword ptr [esi+5Ch]
.text:73C99414                 call    _MTX_mem_malloc@8 ; MTX_mem_malloc(x,x)
// --- SNIP ------------------------------------------------------------------	


By looking the Microsoft Embedded OpenType Font Engine heap allocations we have:
// --- SNIP ------------------------------------------------------------------	
K: 23 -> [*] HeapAlloc(0x150000,0x8,0x2139(8505))=0x154820 end at: 0x156959   (*)
K: 24 -> [*] HeapAlloc(0x150000,0x8,0x2C(44))=0x156978 end at: 0x1569A4
K: 25 -> [*] HeapAlloc(0x150000,0x8,0x246(582))=0x1569C0 end at: 0x156C06     (O)
K: 26 -> [*] HeapAlloc(0x150000,0x8,0x1B48(6984))=0x156C28 end at: 0x158770   (C)
K: 27 -> [*] HeapAlloc(0x150000,0x8,0x539(1337))=0x158790 end at: 0x158CC9    (*)
// --- SNIP ------------------------------------------------------------------	

First one is computed together with adding 1c00h value, the last one is plain
mem_size (1337). Such "faking" mem_size value has bad it influence of future
EOT file decompression. While the data unpacking in the routine stored at
73C98E9Fh, the memory block marked as "O" is overwritten, and the block
marked as "C" is corrupted. The block corruption is done here:

// --- SNIP ------------------------------------------------------------------	
.text:73C98FBB                 mov     al, [eax+esi]	; read byte from other heap block
.text:73C98FBE                 inc     [ebp+var_14]
.text:73C98FC1                 cmp     [ebp+var_1C], 0
.text:73C98FC5                 mov     byte ptr [ebp+var_C], al
.text:73C98FC8                 mov     [ebx+ecx], al   ; STORE !!!
// --- SNIP ------------------------------------------------------------------	


This gives an heap overflow condition, illustrated here:

// --- SNIP ------------------------------------------------------------------		
77F83905   8901             MOV DWORD PTR DS:[ECX],EAX		; EAX = 0D100C10h
77F83907   8948 04          MOV DWORD PTR DS:[EAX+4],ECX	; EBX = 0F100E10h
// --- SNIP ------------------------------------------------------------------	

The vulnerability may lead to remote code execution when specially crafted file
is being parsed, however the exploitation is _hard_ due to the fact attacker
doesn't control directly the data which will overwrite the heap block.
However, it doesn't mean it can't be done :)


NOTE:
All values here, were written appending to the attached POC code. Values may be 
different on other PC boxes...


III. POC CODE

I will not disclose the poc code.