TippingPoint Digital Vaccine Laboratories
DID YOU KNOW... Frost and Sullivan announced in their Feb. 2007 report, "Analysis of Vulnerability Discovery and Disclosure", that TippingPoint was the fastest growing discoverer of new vulnerabilities and the leader in the discovery of both high-severity and Microsoft vulnerabilities.

MindshaRE: Hooking ReadFile and MapViewOfFile for Vulnerability Analysis

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:

  1. Hook CreateFile: saving all file handles for files that match our fuzzed file
  2. Hook CloseHandle: removing all saved file handles
  3. 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:

  1. Check file_handle_array to see if ARG_0 is in the array
  2. Get current file offset: call SetFilePointer(handle, 0,0,1) which moves the filepointer 0 from current position.
  3. Get number of bytes read (see below)
  4. Loop through offset array, and for every offset do:
    1. 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)
    2. If we find a matching offset: break into the debugger by issueing a int3
  5. clean up and let the _SEH_epilog run
  6. 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

I have a version of the code that works on W7 as well (only on 32bits processes), but you do need to change a few things since currently all the kernel32 addresses are hardcoded. It is not too hard to do, but for now we leave it at this.

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



Tags:
Published On: 2011-07-26 14:52:45

Comments post a comment

  1. Anonymous commented on 2011-07-26 @ 16:14

    Is there a reason PIN Tool or Detours was not used here to perform the function hooking?

  2. anonymous commented on 2011-07-28 @ 15:57

    to fellow anon: does pintool or detours support instrumentation via python or better-yet idc? :))

  3. Sanjay commented on 2012-02-15 @ 10:20

    No, Pin does not have a python module for assisting in instrumentation as such. however, there is a project called processtap, which has limited capabilities to use Pin via python. I am not sure that Detours has any python interface.


Trackback