By Jure Skofic, 0patch team.
Working on a 0patch for Foxit Reader takes me back. One of the first patches we made was for Foxit and 0patch has come a long way since. We have a snappy agent which works reliably and we have the 0patch Factory which helps us develop and debug the patches. A lot of work has gone into making this happen and we'd like to share our know-how on writing patches.
Working on a 0patch for Foxit Reader takes me back. One of the first patches we made was for Foxit and 0patch has come a long way since. We have a snappy agent which works reliably and we have the 0patch Factory which helps us develop and debug the patches. A lot of work has gone into making this happen and we'd like to share our know-how on writing patches.
In this blog post I'm going to describe in detail how to create a 0patch for a heap buffer overflow in Foxit Reader 7.3.4.311 (CVE-2016-3740) using the official patch as a guideline. The vulnerability was reported to Foxit by Source Incite's Steven Seeley working with Trend Micro's Zero Day Initiative and was fixed with the release of Foxit reader 8.0. A proof-of-concept exploit was posted on Source Incite's GitHub.
The following tools were used to analyze the vulnerability and write the patch:
- WinDbg
- IDA Pro 6.9
- Zynamics BinDiff 4.2
- 010 Editor
- 0patch Factory
- 0patch Agent
The first step was reproducing the crash. The PoC is a TIFF image which Foxit reader converts into a PDF when opened. Here's WinDbg's output:
(2468.285c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
ConvertToPDF_x86!CreateFXPDFConvertor+0x2da244:
5cf04d34 f3ab rep stos dword ptr es:[edi]
The crash occurs in the ConvertToPdf_x86.dll library and is caused when rep stos tries to write to an unmapped memory section pointed to by edi. This causes the access violation.
Next I disassembled the function where the crash occurred. It appeared to be an elaborate memset which tries to fill a buffer of a specified length, with a supplied character in 4 byte chunks. The pointer to the buffer, the fill character and the buffer length are passed to this function as arguments.
At this point I decided to make my life easier and performed a diff between the vulnerable and the patched version of the library. Since the library was heavily modified by the update, I couldn't rely on the diff to find the official patch. What I could do was follow the call stack and see if I end up in one of the modified functions.
Figure 1: Call stack at the time of the crash |
The function the crash occurred in wasn't modified by the update, so I moved to the next one up the call stack. It appeared to be a wrapper for the previous function and wasn't modified either. The next function as seen in Figure 2, was the first one BinDiff marked as modified. What piqued my interest was the added branch resulting from a compare instruction:
Figure 2: Diff between the unpatched and the patched code |
You'll notice that an additional check is performed where eax and ecx are compared. As marked by the green box on Figure 3, if ecx is larger or equal than eax, the execution continues on the left branch. If that's not the case, the execution follows the right branch and we enter the new code introduced by the official patch.
Figure 3: Official patch code |
If you follow the right branch you'll notice something interesting. Amongst other stuff, a pointer to a string buffer is pushed onto the stack. Notice the //aNotEnoughMem_0 marked by the orange box on Figure 3? Disassembling the function gave me more reason to believe I was on the right track. After determining ecx is smaller than eax, a log function is called. One of the arguments passed to the function is a pointer to the string "Not enough memory at buf %lu (short %llu pixels)". After the log function call, the return value is set to 0 (xor eax, eax) and the patched function exits. This was solid evidence that a buffer length check was added in the patched version of the library.
I needed to figure out if the added check was actually connected to the PoC. I used the 010 Editor to open the PoC. 010 Editor provides templates for a wide variety of binaries including TIFFs. The PoC description in Source Incite's advisory states that a large SamplesPerPixel value is used to cause the crash. Once I examined the PoC there was indeed a large value assigned to SamplesPerPixel - specifically 51201 as shown in Figure 5.
Figure 5: PoC analysis with 010 Editor |
In order to determine if there was any connection between the PoC and the presumed patch code, I installed the patched version of Foxit Reader. I placed a breakpoint at the cmp ecx, eax instruction and ran the PoC to get the value of both registers. ecx had the value of 0xa68 (2664 decimal) and eax had the value 0x104294d (17049933 decimal). At first glance none of those values had anything to do with the large SamplesPerPixel value. I used 010 Editor to decrement the SamplesPerPixel value by 1 and ran the PoC again to see if any of the registers would change. I noticed the ecx value changed to 0x1042800. I decremented the value again and eax changed to 0x10426B3. For every decrement the value of eax was 0x14d less the the previous time. Next I divided the initial SamplesPerPixel value by 0x14d. The result was 0xC801 or 51201 decimal which is the exact SamplesPerPixel value of the PoC. At this point I was certain I had found the official patch for this vulnerability.
The next step was analyzing and applying the same patch logic to the vulnerable library. If we take a look at Figure 6 we can see that in both versions of the function, eax holds the same value - SamplesPerPixel value multiplied by 0x14d. As indicated by the orange box on the patched execution graph in Figure 6, edi holds the pointer to an object passed as one of the function's arguments. The vulnerable buffer is a member of this object, and the pointer to it is retrieved in ebx (mov ebx, [edi+0x240]). Next the buffer length is moved to ecx as indicated by the red box in Figure 6.
Figure 6: Diff between the vulnerable and the patched code |
So to write the 0patch I needed to store the buffer length into ecx, compare ecx and eax and if eax is larger exit the function. Here's the patch pseudo code:
ecx = [ebx+10h]; //Store the allocated buffer length in ecx
if(ecx < eax) //if ecx is smaller than SamplesPerPixel
{ //value multiplied by 0x14d, exit the
return 0; //function
}
else continue
if(ecx < eax) //if ecx is smaller than SamplesPerPixel
{ //value multiplied by 0x14d, exit the
return 0; //function
}
else continue
The optimal spot for patching would be 0x5ce994ce, right after we get the object pointer in ebx, so the correct value can be copied to ecx. Here's the 0patch code:
mov ecx, [ebx+10h] ;store the allocated buffer length in ecx
cmp ecx, eax ;compare to the length of input data
jge end_patch ;if ecx is greater or equal jump to
;end_patch tag and continue
xor eax, eax ;else if ecx is smaller
pop edi
pop ebx
leave
retn ;return 0
end_patch:
Now that I had the patch code and offset, it was time to create a 0patch using 0patch Factory. It's an awesome tool built by my colleague Luka Treiber which makes building 0patches much easier. It takes the 0patch source code and builds it into a 0patch ready for deployment with our agent. It also provides nifty tools for debugging the patches. I used one of 0patchFactory's templates and created the 0patch source code.
Mitja already explained in detail how to build and deploy 0patches (see Writing a 0patch for Acrobat Reader's Use-After-Free Vulnerability CVE-2016-1077), so I won't go into that here.
MODULE_PATH "C:\Program Files (x86)\Foxit Software\Foxit Reader
\plugins\Creator\x86\ConvertToPDF_x86.dll" PATCH_ID 249 PATCH_FORMAT_VER 2 VULN_ID 1441 PLATFORM win32 patchlet_start PATCHLET_ID 1 PATCHLET_TYPE 2 PATCHLET_OFFSET 0x002794CE JUMPOVERBYTES 0 N_ORIGINALBYTES 5 code_start mov ecx, [ebx+10h] cmp ecx, eax jge end_patch call PIT_ExploitBlocked xor eax, eax pop edi pop ebx leave retn end_patch: code_end patchlet_end
\plugins\Creator\x86\ConvertToPDF_x86.dll" PATCH_ID 249 PATCH_FORMAT_VER 2 VULN_ID 1441 PLATFORM win32 patchlet_start PATCHLET_ID 1 PATCHLET_TYPE 2 PATCHLET_OFFSET 0x002794CE JUMPOVERBYTES 0 N_ORIGINALBYTES 5 code_start mov ecx, [ebx+10h] cmp ecx, eax jge end_patch call PIT_ExploitBlocked xor eax, eax pop edi pop ebx leave retn end_patch: code_end patchlet_end
Mitja already explained in detail how to build and deploy 0patches (see Writing a 0patch for Acrobat Reader's Use-After-Free Vulnerability CVE-2016-1077), so I won't go into that here.
When I tested the 0patch, Foxit Reader displayed an error message. This behaviour is exactly the same as with the official patch. We added the Exploit Attempt Blocked dialogue for added measure, to alert the user about the attack.
That's about it. The 0patch contains 8 instructions and took me a day to analyze and develop it.
If you already have 0patch Agent installed, the patch (ZP-249) should already be deployed on your machine - all you need to do is install the vulnerable version of Foxit Reader and open the PoC with it. In case you're new to 0patch, simply create a free 0patch account and install 0patch agent.
This is a great example how 0patch could be used in bridging the security update gap. Foxit's official update contains a lot of new code. According to BinDiff about 10% of ConvertToPdf_x86.dll's code was added or modified. The update addressed multiple vulnerabilities and possibly added some functionality. That's a lot of new code to test compared to the few instructions in a 0patch. Less code, less testing.
For anyone interested in writing their own 0patches, please contact us at crowdpatching@0patch.com.
Jure Skofic
@0patch