The Problem
As Aaron mentioned in another MindshaRE here at ZDI we often get submissions containing only a fuzzed file without any analysis. When analysing those cases it is often useful to know exactly when our vulnerable program reads the bytes that have been changed in the file. This can be done using the hooking technique Aaron described earlier.
The Solution
Most read function available in Windows will eventually end up in kernel32!ReadFile. A quick look at the msdn page for this function show us that this function has the following syntax:BOOL WINAPI ReadFile( __in HANDLE hFile, __out LPVOID lpBuffer, __in DWORD nNumberOfBytesToRead, __out_opt LPDWORD lpNumberOfBytesRead, __inout_opt LPOVERLAPPED lpOverlapped );
When we hook the ReadFile function we only want to break when it is reading from our fuzzed file, and even better only when it is reading the exact offset in the file that we care about. Unfortunately the ReadFile function uses a HANDLE hFile instead of a filename so we cannot easily see which file we are reading from. There are options to retrieve the file name from an handle, but that involve quite a few Windows API calls so I have decided to take another route. The most used options to obtain a file handle is a call to kernel32!CreateFile. This function takes a filename and returns a file handle.
We will hook at the CreateFile function, check all the incoming filenames and when the filename matches our fuzzed file we will save the filehandle in an array for later use. Then we can check this array on very call to ReadFile to see if we are reading from a file handle that we are interested in. When we find a matching file handle we will check an array of file offsets that we are interested in and try to determine if one of those offsets was read from the file. We can do this by calling SetFilePointer to find the current file position and combine that with the amount of bytes that we read in ReadFile.
Just to be nice we will also hook CloseHandle and remove any handle that is in our array when it is being closed.
On paper, this idea looks prety simple and straight forward:
- Hook CreateFile: saving all file handles for files that match our fuzzed file
- Hook CloseHandle: removing all saved file handles
- Hook ReadFile: Check filehandle and then check our offsets.
Lets start with CreateFile. We will need to hook the end of the function since that is where we have access to the return value (the actual file handle) of the function. Normally this is not a problem, however, with CreateFile we run into the following situation:
kernel32!CreateFileW: 7c810800 8bff mov edi,edi 0:000> dc esp L8 0013ec2c 7c814e05 0013ec84 80000000 00000005 .N.|............ 0013ec3c 00000000 00000003 00000000 00000000 ................ 0:000> .printf "%mu", poi(esp+4);.echo C:\WINDOWS\system32\msctfime.ime 0:000> pt kernel32!CreateFileW+0x421: 7c810a10 c21c00 ret 1Ch 0:000> dc esp L8 0013ec2c 7c814e05 00000668 80000000 00000005 .N.|h........... 0013ec3c 00000000 00000003 00000000 00000000 ................
As you can see, upon function return our arg_0 no longer exists, so we cannot check the filename to see if it matches our fuzzed file. We will have to patch both the start and the return of the function to accomplish our goal. To prevent multithreading from messing things up for us we will store our currrent ThreadID for any calls to CreateFile that matches our filename and then check that again when we hook the return of CreateFile. This is not a 100% airtight solution, but it will do enough.
CreateFile
If you hadnt noticed yet, we are actually hooking CreateFileW since CreateFileA will internally simply call CreateFileW.
Our python script that adds the hooks to CreateFileW will also create a memory region that hold some of our data. The memory region will look like this:
[Dword:slot to save our ThreadID] : our assembly references
this address as 0x41414141
[Dword:number_file_handle_slots]
[DWORD:file handle slot] * number_file_handle_slots
[DWord:number_of_offsets] : our assembly references
this as 0x43434343
[DWord:offset] * number_of_offsets
[char*:filename] : our assembly references
this as 0x42424242
The 0x41414141 etc addresses will be replaces by the actual addresses once our python code has allocated the memory in the target process
with all this in mind our assembly code that we use to patch CreateFileW (0x7c810800) looks like this:
[BITS 32] pushad mov ecx, [esp+0x24] ; arg_0 (after pushad) ; place holder for file string mov ebx, 0x42424242 push ebx push ecx ; call wcsstr, check if we found a matching filename mov eax, 0x7c829f14 call eax pop ebx pop ebx test eax, eax jz .done ; No match? done ; We need our ThreadID mov eax, 0x7c8097d0 ; GetCurrentThreadId call eax ; eax is now our CurrentThreadID ; place holder for filehandles memory, ; first slot is reserved to store our ThreadID mov edx, 0x41414141 mov [edx], eax TEXT .done popad
Then we patch the retn of the CreateFileW function (0x7c810a10), luckily there are some nop's after the retn so we can patch the exact retn instruction with:
[BITS 32] pushad mov edx, 0x41414141 ;Place holder for our mem location cmp dword [edx], 0x0 ; If there is nothing in ThreadID slot ... jz .done ; Then we're done ; There is something in there.... check if its ours ; GetCurrentThreadId location. mov eax, 0x7c8097d0 call eax cmp eax, [edx] jnz .done ; Nope, not our ThreadID, ignore the rest ; Its ours!..... lets remove it and save file handle. mov dword [edx], 0 mov ebx, [esp+0x1c] ; We did a pushad the original eax is now here. xor eax, eax ; what we are looking for lea edi, [edx+8] ; start of our file handles array mov ecx, [edx+4] ; number of slots in our array repne scasd ; repeat search for eax in [edi] jnz .done ; we have no more room to save file handles.. oops mov [edi-4], ebx ; save the file handle into the empty slot TEXT .done popad
We checked to see if the return matched the ThreadID we save on the CreateFileW call, then we found the first empty slot in our file handles array and in the slot we wrote the return value from the CreateFileW function (eax). So now we have saved a file handle that we are interested in.
CloseHandle
Let patch the CloseHandle (0x7c809be7) function first to keep things somewhat tidy:
[BITS 32]
; need to remove handle if its in the table
pushad
mov edx, 0x41414141 ;Place holder for our mem location
mov eax, [esp+0x24] ; This should be arg_0 to the CloseHandle function
lea edi, [edx+8] ; points to start of FileHandle Array.
mov ecx, [edx+4] ; size
repne scasd ; search for dword eax in [edi] size ecx
jnz .done ; we didnt find it
mov dword [edi-4], 0 ; Clear out the slot containing the
; filehandle we are about to close
TEXT
.done
popad
ReadFile
And now for the more interesting function, ReadFile. For this function we want to patch the retn of the function so our buffer is filled with data from the file, and we can calculate to see if our offset was read into the memory. Unfortunately, there is not enough room to patch the retn of ReadFile:
7c80188f 8b4d14 mov ecx,dword ptr [ebp+14h] 7c801892 8901 mov dword ptr [ecx],eax 7c801894 33c0 xor eax,eax 7c801896 40 inc eax 7c801897 e8750c0000 call kernel32!_SEH_epilog (7c802511) 7c80189c c21400 ret 14h 7c80189f 8b4010 mov eax,dword ptr [eax+10h] 7c8018a2 8b7818 mov edi,dword ptr [eax+18h]
As you can see at 0x7c80189c we have the return, followed immediately by 'mov eax,dword ptr [eax+10h]'. Patching the exact 'retn' instruction means that wel will also alter the following instruction and since we do not know how it is being used we better find another place to insert our jump. The best candidate is the call kernel32!_SEH_epilog (7c802511) just above the retn. The only extra thing we have to do it to make sure we fix the call instruction to point to the correct address again since it is an relative call (e8750c0000 means relative call to eip + 0xc75, eip after this instruction), but that is not a big issue.
So we know where we want to patch our ReadFile function, now we still need to know what we need to do there. The short pseudo version goes like this:
- Check file_handle_array to see if ARG_0 is in the array
- Get current file offset: call SetFilePointer(handle, 0,0,1) which moves the filepointer 0 from current position.
- Get number of bytes read (see below)
- Loop through offset array, and for every offset do:
- check if the offset falls within the boundary of (file_offset - bytes_read) and file_offset (remember, we are done with the ReadFile function so the file pointer has moved)
- If we find a matching offset: break into the debugger by issueing a int3
- clean up and let the _SEH_epilog run
- retn
The most correct way to get the number of bytes_read is by using the ReadFile local variable : mov eax, [ebp+lpNumberOfCharsRead] This is the internal variable that the function uses to keep track of the number of bytes is actually read, and it might differ from the 'nNumberOfBytesToRead' function argument. Also, in step 3b) we will set some of the registers to useful values before we issue the int3 so we know immediately where out data is in memory. The final assembly code looks like this:
[BITS 32]
pushad
; First, are we interested in this filehandle at all?
mov edx, 0x41414141 ;Place holder for our mem location
; Unfortunately, we couldnt hook on the exact end of the function
; since there was nog enough room. We will use ebp to get the args
mov eax, [ebp+0x8] ; This should be arg_0
mov ecx, [edx+4] ; size
lea edi, [edx+8] ; file_handles
repne scasd
jnz .done ; done if we couldnt find the file handle in the array
; We are interested in this file handle! Let get current file offset
push 1 ; move from current
push 0
push 0
push eax ; Filehandle
mov dword eax, 0x7c810c2e
call eax ; SetFilePointer
mov ebx, [ebp-0x24] ; We fetch how much we read from a variable
; since that is the most accurate way
; EBX = how much did we read.
; EAX = FileOffset
mov edi, eax ; edi == file position AFTER we read our bytes
sub edi, ebx ; edi is now file position BEFORE we started reading
mov edx, 0x43434343 ; Offsets table
mov ecx, [edx] ; Number of Offsets
lea esi, [edx+4] ; points to start of Offsets Array
xor edx, edx ; Loop counter
; Special case, 0xffffffff means we wanne break at every read
; This one is only gonna be in the first slot...
cmp dword [esi], 0xFFFFFFFF
jz .break
TEXT
.loop
cmp [esi], eax ;cmp offset with current file position
jae .next ; if offset >= current file pos -> next check
cmp [esi], edi
jb .next ; if offset < current offset - how much we read
;FOUND ONE!
.break
; we will do:
; eax = exact point in buffer where our offset is
; ebx = start of buffer
; ecx = offset we found
; edx = current file offset
; edi points to file offset at start of read
mov ecx, [esi] ; done
mov edx, eax ; done
mov eax, [ebp+0xC] ; arg_1 == buffer to read into
mov ebx, [ebp+0xC]
cmp ecx, 0xFFFFFFFF ; do we have to point to a specific point
; in the buffer, or just the buffer itself?
jz .show
; lets calc the exact buffer point.
sub ecx, edi ; offset - FilePointer before we started reading
add eax, ecx ; Add base of the buffer
mov ecx, [esi]
.show
mov esi, [ebp-0x24] ; how much did we read
int3
jmp .done
.next ; offset loop, next item
add esi, 0x4
inc edx
cmp edx, ecx ; are we done yet?
jbe .loop
.done
popad
As you might have picked up from the assembly we set eax to the exact point in memory where the bytes we are interested in are located. So when we are running our program and we receive an INT3 from the patched functions, we can use 'ba r1 eax' to set a breakpoint on the memory address we are interested in. As soon as the program now uses the fuzzed byte we are alerted. Dont forget that a memory breakpoint breaks halts your debugger on the instruction AFTER it has read (or written) to the address you have your breakpoint on. (at least, that is how windbg does it and thats my debugger of choice)
Bonus
After writing all the assembly, creating a python script to patch any processes I wanted I was pretty satisfied with myself. Everything worked great, I did some Quicktime cases where this code was pretty useful (despite quicktime reading the first 0x200 bytes of a file at least 8 times). And so I was confident a RealPlayer case that I had would be a walk in the park. I fired up RealPlayer, ran my patch code and .... nothing happened. As it turned out, RealPlayer does not always use ReadFile. Instead it uses MapViewOfFile to just map the file into memory and then read from it.
So while we're at it, lets also patch this function. MapViewOfFile takes a HANDLE hFileMappingObject as first argument. This handle is obtained from a call to CreateFileMapping or OpenFileMapping. CreateFileMapping takes a file handle from a call to CreateFile and returns something that MapViewOfFile can use again. So in order to track MapViewOfFile calls to see if we are mapping files we are interested in we need to keep track of calls to CreateFileMapping (and OpenFileMapping, but I didnt do that yet).So lets just keep patching.
CreateFileMapping
CreateFileMapping, we patch the code at the retn of the function, look through our array of file handles to see if we created a file map for any file handles we are interested in and then just store the newly created handle in the same array.
[BITS 32]
; CreateFileMappingW: we need to check arg_0.
; if its in our FH table, then we are interested in the result (eax)
; unless eax = 0x0 anyways....
pushad
test eax, eax ; eax = return value. we are not
; interested in 0x00 results
jz .done
mov edx, 0x41414141 ;Place holder for our mem location
mov ebx, eax ; save for later
mov eax, [esp+0x24] ; CHECK THIS, this is arg_0
test eax, eax ; not supporting INVALID_HANDLE_VALUE calls
jz .done
lea edi, [edx+0x8] ; points to start of FileHandle Array.
mov ecx, [edx+4] ; number of file handles slots
repne scasd ; search for eax in edi
jnz .done ; couldnt find it
; Search for empty slot to save our new created file mapping object.
xor eax, eax
lea edi, [edx+0x8] ; points to start of FileHandle Array.
repne scasd ; look for dword 0x00 in slot
jnz .done ; oops, no more free slots... just silently fail.
mov [edi-0x4], ebx ; save our object
TEXT
.done
popad
MapViewOfFile
And now we can add the code to patch MapViewOfFile. This code looks a lot like ReadFile with a few exception since there is no FilePointer, but I leave it to the reader to discover the differences and the reasons for it.
[BITS 32]
pushad
; First, are we interested in this filehandle at all?
mov eax, [esp+0x24]
mov edx, 0x41414141 ;Place holder for our mem location
mov ecx, [edx+4] ; number of file handle slots
lea edi, [edx+8] ; edi points to first slot
repne scasd
jnz .done ; not interested
; we need to check:
; 1) offset LOW
; 2) bytes to read,
mov edi, [esp+0x50] ; We take the args from the MapViewOFFile
; function args, even tho we are inside the
; MapViewOfFileEx function
; edi holds 'OffsetLow'
mov ebx, [esp+0x34] ; how much did we read?
add ebx, edi ; ebx now points to the actual end of
; the file we reached (start + how much did we read)
mov edx, 0x43434343 ; Offsets table
mov ecx, [edx] ; ecx = number of offset slots
lea esi, [edx+4] ; first slot
xor eax, eax ; loop counter
; Special case, 0xffffffff means we wanne break at every read
; This one is only gonna be in the first slot...
cmp dword [esi], 0xFFFFFFFF
jz .break
TEXT
.loop
cmp [esi], ebx ;cmp offset with 'end' of read
jae .next ; if offset >= current file pos -> next check
cmp [esi], edi
jb .next ; if offset < current offset - how much we read
;FOUND ONE!
.break
; we will do: eax = exact point in buffer where our offset is
; ebx = start of buffer
; ecx = offset we found
; edx = current file offset
mov ecx, [esi] ; done
mov edx, edi ; done (lowOffset)
mov eax, [esp+0x1C] ; This holds our original eax
mov ebx, eax ; done. beginninng of the buffer
mov esi, [esp+0x34] ; how much did we read
cmp ecx, 0xFFFFFFFF
jz .show ; eax points to beginning of the buffer. this is enough
lea eax, [eax+ecx]
sub eax, edi ; Done (I think)
.show
int3
jmp .done
.next
add esi, 0x4
inc eax
cmp eax, ecx
jbe .loop
.done
popad
The above assembly is a little bit sloppy, but I leave it to the readers to improve on it and maybe improve the location of the patch if they want to.
Now we are all done with our patch codes! Lets write some python to tie it all together. I used Aarons code as a base and added a 'SearchProcesses' that takes a part of an process name (executable name actually) and tries to find a matching pid. Currently it is limiting itself to only patch 1 matching process, which of course is annoying if you want to patch for example IE or Adobe Reader, but you can always still use a real pid instead of the name.
Other than that, the code should be rather self-explanatory and I will just provide you the .py script as-is. You can run it with the following parameter:
The Final Product
python.exe ReadFileHook.py[Offset, [Offsets....]]
- ProcName: does not have to be complete, I usually use 'quick' when I want to patch quicktime.
- Filename: it will match using wcssrt so a partial match is enough.
- Offset: if no offset is provided it will break everytime it reads from your file, but usually you want to provide at least 1 offset. You can provide as many offsets as you want, but the code will break on the first offset that is read with a single call to ReadFile, so if your program swallows to of your offsets in one call you will not notice it. You can of course fix this by altering the code within the ReadFile and MapViewOfFile assemblies to keep looping even after is has interupted your program, and if you do, please send me the updated code :) Also, all offset will be read using int(offset,16) in python, and are considered to be base_16
The zip file includes all the assembly code in case you do not trust the opcodes in the python file ;)
You can download it all HERE
Example Use
Lets try the code to help with some reversing on Quicktime. More specifically, lets try to find the code within QuickTime that parses PICT files.
I downloaded a bunch of random .pct files from the intertubes, saved them with their md5 hash as name and then picked a random one. I made some small changes to ExifTool so it prints the exact location of all the opcodes in the pct file:
C:\ExifTool\Image-ExifTool-8.47>perl exiftool -v c:\Reversing\Quicktime\0a996324b31cc4147277c766d0cf0c90.pct ExifToolVersion = 8.47 FileName = 0a996324b31cc4147277c766d0cf0c90.pct Directory = c:/Reversing/Quicktime FileSize = 137522 FileModifyDate = 1304401206 FilePermissions = 33206 FileType = PICT MIMEType = image/pict ImageWidth = 720 ImageHeight = 480 XResolution = 72 YResolution = 72 PICT version 2 extended 22a: DefHilite Tag 0x001e, start 00000228 22c: ClipRgn Tag 0x0001, start 0000022a 238: OpColor Tag 0x001f, start 00000236 240: CompressedQuickTime Tag 0x8200, start 0000023e PreviewImage = SCALAR(0x330d794) 218b0: PnSize Tag 0x0007, start 000218ae 218b6: PnSize Tag 0x0007, start 000218b4 218bc: TxFont Tag 0x0003, start 000218ba 218c0: TxFace Tag 0x0004, start 000218be 218c4: TxSize Tag 0x000d, start 000218c2 218c8: TxRatio Tag 0x0010, start 000218c6 218d2: LongText Tag 0x0028, start 000218d0 218ea: LongText Tag 0x0028, start 000218e8 2190a: LongText Tag 0x0028, start 00021908 21930: Nop Tag 0x0000, start 0002192e 21932: OpEndPic End of picture
I then picked a rather random pict opcode: 0x001F located at offset 00000236 in the file. I know Quicktime reads the first 0x200 bytes of a file a few times to determine what the file type is so I choose an offset bigger then 0x200 (also, the first 0x200 bytes of most pict images are empty). Then I loaded the file into quicktime with a debugger attached:
C:\Program Files\Debugging Tools for Windows (x86)>windbg.exe -G "c:\Program Files\QuickTime\QuickTimePlayer.exe" c:\Reversing\Quicktime\0a996324b31cc4147277c766d0cf0c90.pct
Ran the ReadFileHooker:
C:\Python26>python.exe c:\ReadFile\ReadFileHook.py quick 0a99 236 ReadFileHook by Peter Vreugdenhil (@WTFuzz) - Zero Day Initiative found 1 matching pids Getting handle to process with PID 5552 Allocated memory for handle 1956 at 0x01690000 Patching CreateFile - start Asked to make a jump from 0x7c810800 to 0x0169009a Patching jump from 0x7c810800 to 0x0169009a Asked to make a jump from 0x016900c7 to 0x7c810805 Patching CreateFile - end Asked to make a jump from 0x7c810a10 to 0x016900cc Patching jump from 0x7c810a10 to 0x016900cc Patching CloseHandle Asked to make a jump from 0x7c809be7 to 0x01690102 Patching jump from 0x7c809be7 to 0x01690102 Asked to make a jump from 0x01690123 to 0x7c809bec Patching ReadFile Asked to make a jump from 0x7c801897 to 0x01690128 Patching jump from 0x7c801897 to 0x01690128 Asked to make a jump from 0x016901a2 to 0x7c802511 Patching CreateFileMapping Asked to make a jump from 0x7c809502 to 0x016901aa Patching jump from 0x7c809502 to 0x016901aa Patching ViewMapOfFile Asked to make a jump from 0x7c80b99d to 0x016901d8 Patching jump from 0x7c80b99d to 0x016901d8 Done!
Now lets see what happens, I did remove some useless messages from windbg but other then that its straight forward how I did it.
Microsoft (R) Windows Debugger Version 6.11.0001.402 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: "c:\Program Files\QuickTime\QuickTimePlayer.exe" c:\Reversing\Quicktime\0a996324b31cc4147277c766d0cf0c90.pct Symbol search path is: srv*c:\mss*http://msdl.microsoft.com/download/symbols Executable search path is: (15b0.1874): Break instruction exception - code 80000003 (first chance) eax=01536fec ebx=7ffd5000 ecx=00000004 edx=00000010 esi=0153afb0 edi=01536fec eip=7c90120e esp=0013fb20 ebp=0013fc94 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 ntdll!DbgBreakPoint: 7c90120e cc int 3 0:000> g
That was the initial breakpoint, I used that to run the ReadFileHook.py
(15b0.1874): Break instruction exception - code 80000003 (first chance) eax=11cde02a ebx=11cdddf4 ecx=00000236 edx=00001000 esi=00001000 edi=00000000 eip=01690190 esp=0013ed80 ebp=0013eddc iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202+0x169018f: 01690190 cc int 3
Program gave me an int3, this means the data we want is located in the memory where eax points to. I will set a breakpoint here
0:000> ba r1 eax 0:000> g (15b0.1874): Break instruction exception - code 80000003 (first chance) eax=14aa0236 ebx=14aa0000 ecx=00000236 edx=00002000 esi=00002000 edi=00000000 eip=01690190 esp=0013ba28 ebp=0013ba84 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206+0x169018f: 01690190 cc int 3 0:000> ba r1 eax 0:000> bl 0 e 11cde02a r 1 0001 (0001) 0:**** +0x11cde029 1 e 14aa0236 r 1 0001 (0001) 0:**** +0x14aa0235 0:000> db 11cde02a 11cde02a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 11cde03a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 11cde04a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 11cde05a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 11cde06a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 11cde07a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 11cde08a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 11cde09a ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 0:000> bc 0 0:000> br 1 0 Breakpoint 1 renamed to 0
Again, int3 hit. Time to set another breakpoint and see if the previous breakpoint still has the data we care about. I might become useless because the memory got freed or something else. And indeed, the first breakpoint we found is now empty. Removing it and renaming breakpoint 1 to 0 to keep them in order.
0:000> g (15b0.1874): Break instruction exception - code 80000003 (first chance) eax=14ab0236 ebx=14ab0000 ecx=00000236 edx=00002000 esi=00002000 edi=00000000 eip=01690190 esp=0013b060 ebp=0013b0bc iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206+0x169018f: 01690190 cc int 3 0:000> bl 0 e 14aa0236 r 1 0001 (0001) 0:**** +0x14aa0235 0:000> db 14aa0236 14aa0236 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14aa0246 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14aa0256 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14aa0266 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14aa0276 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14aa0286 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14aa0296 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14aa02a6 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0:000> bc 0 0:000> ba r1 eax 0:000> db eax 14ab0236 00 1f 80 00 80 00 80 00-82 00 00 02 16 6a 00 00 .............j.. 14ab0246 00 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14ab0256 00 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14ab0266 40 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 @............... 14ab0276 00 40 00 00 00 00 01 e0-02 d0 00 00 03 00 00 00 .@.............. 14ab0286 00 00 00 00 00 56 6a 70-65 67 00 00 00 00 00 00 .....Vjpeg...... 14ab0296 00 00 00 01 00 01 61 70-70 6c 00 00 00 00 00 00 ......appl...... 14ab02a6 03 ff 02 d0 01 e0 00 48-00 00 00 48 00 00 00 02 .......H...H.... 0:000> g (15b0.1874): Break instruction exception - code 80000003 (first chance) eax=14ab0236 ebx=14ab0000 ecx=00000236 edx=00002000 esi=00002000 edi=00000000 eip=01690190 esp=0013b500 ebp=0013b55c iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206 +0x169018f: 01690190 cc int 3 0:000> bl 0 e 14ab0236 r 1 0001 (0001) 0:**** +0x14ab0235 0:000> db 14ab0236 14ab0236 00 1f 80 00 80 00 80 00-82 00 00 02 16 6a 00 00 .............j.. 14ab0246 00 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14ab0256 00 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 14ab0266 40 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 @............... 14ab0276 00 40 00 00 00 00 01 e0-02 d0 00 00 03 00 00 00 .@.............. 14ab0286 00 00 00 00 00 56 6a 70-65 67 00 00 00 00 00 00 .....Vjpeg...... 14ab0296 00 00 00 01 00 01 61 70-70 6c 00 00 00 00 00 00 ......appl...... 14ab02a6 03 ff 02 d0 01 e0 00 48-00 00 00 48 00 00 00 02 .......H...H.... 0:000> ba r1 eax breakpoint 0 redefined 0:000> g Breakpoint 0 hit eax=14ab2000 ebx=00001e00 ecx=00000764 edx=00000000 esi=14ab0270 edi=11600938 eip=668e238a esp=0013b4f0 ebp=0013b4f8 iopl=0 nv up ei pl nz ac pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210216 QuickTime!CallComponentFunctionWithStorage+0x3f2ca: 668e238a f3a5 rep movs dword ptr es:[edi],dword ptr [esi] 0:000> db edi - 4 11600934 00 00 00 00 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 11600944 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 11600954 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 11600964 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 11600974 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 11600984 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 11600994 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 116009a4 c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 0:000> bl 0 e 14ab0236 r 1 0001 (0001) 0:**** +0x14ab0235 0:000> db edi - 3a 116008fe 00 1f 80 00 80 00 80 00-82 00 00 02 16 6a 00 00 .............j.. 1160090e 00 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 1160091e 00 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 1160092e 40 00 00 00 00 00 00 00-00 00 c0 c0 c0 c0 c0 c0 @............... 1160093e c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 1160094e c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 1160095e c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 1160096e c0 c0 c0 c0 c0 c0 c0 c0-c0 c0 c0 c0 c0 c0 c0 c0 ................ 0:000> ba r1 116008fe
So after another unused breakpoint we now hit a memcopy routine. On a rep movs call it can depend where our data in in [edi], usually it will be in [edi-4] and the debugger broke right after it read from [esi-4] but sometimes it is further behind. In this case we had to backup from [edi] by 0x3a bytes. This is pretty easy to find, just do: esi - [breakpoint_location], and that will give you the offset. In this case: 14ab0270 - 14ab0236 = 0x3A. We will set a new breakpoint on that address
0:000> g Breakpoint 1 hit eax=11600900 ebx=00130002 ecx=00000000 edx=00000002 esi=116008fe edi=0013b9c4 eip=668e24ca esp=0013b8e4 ebp=0013b8ec iopl=0 nv up ei ng nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200293 QuickTime!CallComponentFunctionWithStorage+0x3f40a: 668e24ca 8807 mov byte ptr [edi],al ds:0023:0013b9c4=73 0:000> ub QuickTime!CallComponentFunctionWithStorage+0x3f3fe: 668e24be 8807 mov byte ptr [edi],al 668e24c0 8b4508 mov eax,dword ptr [ebp+8] 668e24c3 5e pop esi 668e24c4 5f pop edi 668e24c5 c9 leave 668e24c6 c3 ret 668e24c7 90 nop 668e24c8 8a06 mov al,byte ptr [esi] 0:000> t eax=11600900 ebx=00130002 ecx=00000000 edx=00000002 esi=116008fe edi=0013b9c4 eip=668e24cc esp=0013b8e4 ebp=0013b8ec iopl=0 nv up ei ng nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200293 QuickTime!CallComponentFunctionWithStorage+0x3f40c: 668e24cc 8a4601 mov al,byte ptr [esi+1] ds:0023:116008ff=1f 0:000> t eax=1160091f ebx=00130002 ecx=00000000 edx=00000002 esi=116008fe edi=0013b9c4 eip=668e24cf esp=0013b8e4 ebp=0013b8ec iopl=0 nv up ei ng nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200293 QuickTime!CallComponentFunctionWithStorage+0x3f40f: 668e24cf 884701 mov byte ptr [edi+1],al ds:0023:0013b9c5=ee 0:000> eax=1160091f ebx=00130002 ecx=00000000 edx=00000002 esi=116008fe edi=0013b9c4 eip=668e24d2 esp=0013b8e4 ebp=0013b8ec iopl=0 nv up ei ng nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200293 QuickTime!CallComponentFunctionWithStorage+0x3f412: 668e24d2 8b4508 mov eax,dword ptr [ebp+8] ss:0023:0013b8f4=0013b9c4 0:000> ba r1 edi
We hit another copy routine, where our bytes are copied into [edi] so we set another breakpoint on that address
0:000> g Breakpoint 2 hit eax=00000000 ebx=67346c00 ecx=00001f00 edx=00000002 esi=0013ba1c edi=0e6bc5f6 eip=66919fe0 esp=0013b9c0 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 QuickTime!CallComponentFunctionWithStorage+0x76f20: 66919fe0 66c1c108 rol cx,8 0:000> ub QuickTime!CallComponentFunctionWithStorage+0x76f00: 66919fc0 8d4c2403 lea ecx,[esp+3] 66919fc4 e847fdffff call QuickTime!CallComponentFunctionWithStorage+0x76c50 (66919d10) 66919fc9 85c0 test eax,eax 66919fcb 7531 jne QuickTime!CallComponentFunctionWithStorage+0x76f3e (66919ffe) 66919fcd b802000000 mov eax,offset+0x1 (00000002) 66919fd2 8d4c2404 lea ecx,[esp+4] 66919fd6 e835fdffff call QuickTime!CallComponentFunctionWithStorage+0x76c50 (66919d10) 66919fdb 668b4c2404 mov cx,word ptr [esp+4] 0:000> r ecx ecx=00001f00 0:000> t eax=00000000 ebx=67346c00 ecx=0000001f edx=00000002 esi=0013ba1c edi=0e6bc5f6 eip=66919fe4 esp=0013b9c0 ebp=00000000 iopl=0 nv up ei pl zr na pe cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200247 QuickTime!CallComponentFunctionWithStorage+0x76f24: 66919fe4 85c0 test eax,eax 0:000> t eax=00000000 ebx=67346c00 ecx=0000001f edx=00000002 esi=0013ba1c edi=0e6bc5f6 eip=66919fe6 esp=0013b9c0 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 QuickTime!CallComponentFunctionWithStorage+0x76f26: 66919fe6 0fb7d1 movzx edx,cx 0:000> eax=00000000 ebx=67346c00 ecx=0000001f edx=0000001f esi=0013ba1c edi=0e6bc5f6 eip=66919fe9 esp=0013b9c0 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 QuickTime!CallComponentFunctionWithStorage+0x76f29: 66919fe9 89542404 mov dword ptr [esp+4],edx ss:0023:0013b9c4=66911f00 0:000> t eax=00000000 ebx=67346c00 ecx=0000001f edx=0000001f esi=0013ba1c edi=0e6bc5f6 eip=66919fed esp=0013b9c0 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 QuickTime!CallComponentFunctionWithStorage+0x76f2d: 66919fed 750f jne QuickTime!CallComponentFunctionWithStorage+0x76f3e (66919ffe) [br=0] 0:000> db 0013b9c4 0013b9c4 1f 00 00 00 8b ee 91 66-f6 c5 6b 0e 00 00 00 00 .......f..k..... 0013b9d4 5c bd 13 00 f8 6c 34 67-f8 8b a4 10 00 00 00 00 ....l4g........ 0013b9e4 00 94 00 00 00 00 00 00-7a f3 91 66 00 00 00 00 ........z..f.... 0013b9f4 20 bb 13 00 70 bb 13 00-60 bb 13 00 00 00 00 00 ...p...`....... 0013ba04 00 00 84 66 92 c6 6b 0e-78 c6 6b 0e 5e c6 6b 0e ...f..k.x.k.^.k. 0013ba14 ac c6 6b 0e 00 00 00 00-00 00 00 00 00 00 00 00 ..k............. 0013ba24 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0013ba34 e0 01 d0 02 00 00 00 00-64 00 64 00 64 00 64 00 ........d.d.d.d. 0:000> ba r1 0013b9c4 breakpoint 2 redefined
Here all that happens is a endian flip, and then it is saved back on the same address ([esp+4])
0:000> g Breakpoint 2 hit eax=02ba001f ebx=67346c00 ecx=03ab09c9 edx=00000000 esi=0013ba1c edi=0e6bc5f6 eip=66919ffc esp=0013b9c0 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 QuickTime!CallComponentFunctionWithStorage+0x76f3c: 66919ffc 7404 je QuickTime!CallComponentFunctionWithStorage+0x76f42 (6691a002) [br=1] 0:000> ub QuickTime!CallComponentFunctionWithStorage+0x76f20: 66919fe0 66c1c108 rol cx,8 66919fe4 85c0 test eax,eax 66919fe6 0fb7d1 movzx edx,cx 66919fe9 89542404 mov dword ptr [esp+4],edx 66919fed 750f jne QuickTime!CallComponentFunctionWithStorage+0x76f3e (66919ffe) 66919fef e8dc45feff call QuickTime!CallComponentFunctionWithStorage+0x5b510 (668fe5d0) 66919ff4 6685c0 test ax,ax 66919ff7 668b442404 mov ax,word ptr [esp+4] 0:000> t eax=02ba001f ebx=67346c00 ecx=03ab09c9 edx=00000000 esi=0013ba1c edi=0e6bc5f6 eip=6691a002 esp=0013b9c0 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246 QuickTime!CallComponentFunctionWithStorage+0x76f42: 6691a002 83c408 add esp,8 0:000> eax=02ba001f ebx=67346c00 ecx=03ab09c9 edx=00000000 esi=0013ba1c edi=0e6bc5f6 eip=6691a005 esp=0013b9c8 ebp=00000000 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202 QuickTime!CallComponentFunctionWithStorage+0x76f45: 6691a005 c3 ret 0:000> eax=02ba001f ebx=67346c00 ecx=03ab09c9 edx=00000000 esi=0013ba1c edi=0e6bc5f6 eip=6691ee8b esp=0013b9cc ebp=00000000 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202 QuickTime!CallComponentFunctionWithStorage+0x7bdcb: 6691ee8b 0fb7f8 movzx edi,ax
What we see here is that we were in a function that returned the 0x001F opcode in eax. You can view this function here, with some comments. Lets see where we end up when we return from this function.
call sub_66919F90 ; Fetches OpCodes movzx edi, ax cmp di, 0FFh ; End of picture? mov dword_67346CD4, ebp jz loc_6691EF2A
If you take a good look at this function you see it splits the opcodes in 3 ranges, 0x00 - 0x1F, 0x20 - 0xFF, and everything bigger then 0xFF. It also has a special case for 0x00FF since that is the opcode for 'OpEndPic'. If you were to follow the call to 'PictOpcodes00_1F_6691E200' or '6691ED30' you would find two big jump tables switching between the different opcodes. From this point on you could try and make some sense out of all the different opcodes and perhaps find some vulnerabilities
The link again with the code, for those we simply scroll down to the bottom of the blog to grab it
- Peter
