Microsoft Windows DirectX MJPEG Decoder Remote Heap Corruption by Piotr Bania http://www.piotrbania.com All rights reserved. Original url: http://www.piotrbania.com/all/adv/ms-directx-mjpeg-adv.txt Severity: Critical/Important Impact: Potential Remote Code Execution Tested on: Microsoft Windows XP SP2 with quartz.dll (6.5.2600.3367). Affected software: - DirectX 8.1 on Microsoft Windows 2000 Service Pack 4 - DirectX 9.0 on Microsoft Windows 2000 Service Pack 4 - DirectX 9.0 on Windows XP Service Pack 2 and Windows XP Service Pack 3 - DirectX 9.0 on Windows XP Professional x64 Edition and Windows XP Professional x64 Edition Service Pack 2 - DirectX 9.0 on Windows Server 2003 Service Pack 1 and Windows Server 2003 Service Pack 2 - DirectX 9.0 on Windows Server 2003 x64 Edition and Windows Server 2003 x64 Edition Service Pack 2 - DirectX 9.0 on Windows Server 2003 with SP1 for Itanium-based Systems and Windows Server 2003 with SP2 for Itanium-based Systems Timeline: 2008-08-08 - Initial contact with vendor 2009-04-14 - Security bulletin released 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. DESCRIPTION The problem exists when a special crafted MJPEG video file is being parsed. Typically each MJPEG video file consists of frames, which are stored inside of the ##dc chunk. Each of this chunk contains a single video frame. It appears that every frame of this entry seems to be a normal jpeg compressed data. The problem starts when parsing an video frame with malformed Huffman table (malformed DHT Class). This is described by the following code: --//- snip ----//----------------------------------------------------- .text:74886A6C loc_74886A6C: ; CODE XREF: VULNERABLE+103j .text:74886A6C mov dl, [ebx] .text:74886A6E dec [ebp+var_11C] .text:74886A74 lea eax, [ebp+edi+var_18] .text:74886A78 mov [eax], dl .text:74886A7A movzx eax, dl .text:74886A7D add [ebp+huff_val], eax .text:74886A83 inc ebx .text:74886A84 inc edi .text:74886A85 cmp edi, 10h .text:74886A88 jle short loc_74886A41 .text:74886A8A sub [ebp+my_value], 11h .text:74886A91 cmp [ebp+huff_val], 100h .text:74886A9B jg throw_exception --//- snip ----//----------------------------------------------------- As you can see the instruction at 74886A7Dh computes a sum from values written in the Huffman Table, when the computed sum is larger then 100h the execution is transfered to the throw_exception label and then the _throw_exception@4 function is called. --//- snip ----//----------------------------------------------------- .text:748846FA ; __stdcall throw_exception(x) .text:748846FA _throw_exception@4 proc near ; CODE XREF: jpeg_start_compress(x,x)+10p .text:748846FA ; jpeg_write_scanlines(x,x,x)+10p ... .text:748846FA .text:748846FA memory_struct = dword ptr 8 .text:748846FA .text:748846FA mov edi, edi .text:748846FC push ebp .text:748846FD mov ebp, esp .text:748846FF cmp [ebp+memory_struct], 0 .text:74884703 jz short loc_74884715 .text:74884705 push [ebp+memory_struct] .text:74884708 call _jpeg_abort@4 ; jpeg_abort(x) .text:7488470D push [ebp+memory_struct] .text:74884710 call _jpeg_destroy@4 ; jpeg_destroy(x) .text:74884715 .text:74884715 loc_74884715: ; CODE XREF: throw_exception(x)+9j .text:74884715 push 0 ; lpArguments .text:74884717 push 0 ; nNumberOfArguments .text:74884719 push 0 ; dwExceptionFlags .text:7488471B push 0FF00FFh ; dwExceptionCode .text:74884720 call ds:__imp__RaiseException@16 ; RaiseException(x,x,x,x) .text:74884726 pop ebp .text:74884727 retn 4 .text:74884727 _throw_exception@4 endp --//- snip ----//----------------------------------------------------- The _throw_exception@4 function is generaly used for cleaning the allocated heap chunks, and finally it raises an custom exception with code equal to FF00FFh (note the heap chunk allocation is done in _jpeg_get_small@8 subroutine). At this point everything seems to be ok, however in next stages of the decoding process it appears that the destroyed heap memory chunk (*) is being written AFTER the memory is deallocated (**): --//- snip ----//----------------------------------------------------- [*] KT:199017 HeapAlloc(0xA0000,0x8,0x6B0)=0x2F20008, last=0x02f206b8 [*] BadWrite()#350 at 0x02f20078 [*] HeapFree(0xA0000,0x0,0x2F20008)=0x2F20008 ; * [*] BadWrite()#351 at 0x02f20078 ; ** --//- snip ----//----------------------------------------------------- --//- snip ----//----------------------------------------------------- Debugge output: Exception 00FF00FF Debug string: HEAP[wmplayer.exe]: Debug string: Free Heap block 02F20000 modified at 02F20070 after it was freed --//- snip ----//----------------------------------------------------- The BadWrite (jpeg_decompress_src) procedure code: --//- snip ----//----------------------------------------------------- .text:74884690 ; __stdcall jpeg_decompress_src(x, x, x) .text:74884690 _jpeg_decompress_src@12 proc near ; CODE XREF: Decompress(x,x,x)+67p .text:74884690 .text:74884690 arg_0 = dword ptr 8 .text:74884690 arg_4 = dword ptr 0Ch .text:74884690 arg_8 = dword ptr 10h .text:74884690 .text:74884690 mov edi, edi .text:74884692 push ebp .text:74884693 mov ebp, esp .text:74884695 push esi .text:74884696 mov esi, [ebp+arg_0] .text:74884699 cmp dword ptr [esi+14h], 0 .text:7488469D jnz short bez_alokacji ; use the old pointer .text:7488469F mov eax, [esi+4] .text:748846A2 push 28h .text:748846A4 push 0 .text:748846A6 push esi .text:748846A7 call dword ptr [eax] ; give us some mem .text:748846A7 .text:748846A7 .text:748846A9 mov [esi+14h], eax ; save the mem pointer .text:748846AC .text:748846AC bez_alokacji: ; CODE XREF: jpeg_decompress_src(x,x,x)+Dj .text:748846AC mov eax, [esi+14h] ; :-) [...] .text:748846AF mov ecx, [ebp+arg_4] .text:748846B2 and dword ptr [eax+4], 0 .text:748846B6 and dword ptr [eax], 0 .text:748846B9 mov [eax+1Ch], ecx .text:748846BC mov ecx, [ebp+arg_8] .text:748846BF mov [eax+20h], ecx .text:748846C2 mov dword ptr [eax+8], offset loc_748845FC .text:748846C9 mov dword ptr [eax+0Ch], offset sub_74884614 .text:748846D0 mov dword ptr [eax+10h], offset loc_74884658 .text:748846D7 mov dword ptr [eax+14h], offset _jpeg_resync_to_restart@4 ; jpeg_resync_to_restart(x) .text:748846DE mov dword ptr [eax+18h], offset ?OnRenderEnd@CBaseRenderer@@UAEXPAUIMediaSample@@@Z .text:748846E5 pop esi .text:748846E6 pop ebp .text:748846E7 retn 0Ch .text:748846E7 _jpeg_decompress_src@12 endp --//- snip ----//----------------------------------------------------- As you can see unfortunately the overwrite almost completly relies on the const values, however arg_4 value is a pointer to our JPEG data. Due to that fact the sucessful exploitation maybe hard. The heap corruption process described here may bring future problems when the target process will be allocating next heap memory regions. Also note when decoding valid MJPEG video, no custom FF00FFh exeception is thrown. II. IMPACT Successful exploitation of this vulnerability may lead to remote code execution. III. POC CODES POC codes will not be provided.