Monday, February 6, 2017

0patch Agent on Linux - Micropatching CVE-2016-9445

By Jure Skofic, 0patch Team.

In December we finally got our hands on a working prototype of our Linux Agent. It's been in the making for quite some time and now that it's in alpha stage, we can finally start 0patching Linux user space code. The Linux agent still lacks some of the fancier functionality our Windows agent supports, but it's ready to be taken for a spin.

Now I can't stress enough how big of a deal this is. With over 65% of web servers running some flavor of Unix, patching security vulns in high availability Linux environments could become much less of a problem. If we take a zero day like Heartbleed for example, the time it takes to deploy an official patch isn't only comprised of the time it takes the vendor to release the fix, but also of the time it takes DevOps to test and deploy it. We aim to shorten this considerably by making patches tiny, easy to review and even easier to deploy, avoiding the need to restart the server. 

When developing and testing the Linux agent on Ubuntu we already created a patch for Heartbleed and while there's still a lot of vulnerable servers out there, the hype train is nearing its last stop. So we needed something new for the grand unveiling and Chris Evans (@scarybeasts) was kind enough to provide us with just the thing. It also gave us an excuse to port the agent to Fedora and increase our Linux flavor support.

This bug was published last November on Chris's blog. The flaw is located in gstreamer's VMnc decoder that handles the VMware screen capture format. It's a pretty straight forward integer overflow and Chris provided a PoC.

I started off by creating a Fedora 25 virtual machine, then tried playing the PoC with totem, a video player native to Gnome which uses the vulnerable version of gstreamer on an unpatched Fedora 25. Playing the PoC with totem resulted in a crash. Chris was kind enough to do a detailed analysis of the bug which helped a lot. He figured out that the integer overflow occurs in vmnc_handle_wmvi_rectangle method in vmncdec.c, which is a part of the gstreamer-plugins-bad package. You can view its source on gstreamers github. As Chris explains the overflow occurs because of user-supplied rect->width and rect->height that are multiplied with bytes_per_pixel to allocate the image data buffer. Both variables are signed 32-bit integers. If high enough values are supplied the result is an integer overflow. The subsequent memory allocation (marked in red on the image below) produces a buffer that is smaller than the image data, which results in a buffer overflow.


I disassembled libgstvmnc.so and located the point in the function vmnc_handle_wmvi_rectangle where the height and width are multiplied and the buffer is allocated (marked in red on the image below).




The imul instruction sets the carry and overflow flags when the significant bit is carried into the upper half of the result, so my first thought was that we could return an error if these flags are set. The patch location is marked in green on the image above. This was my first version of the patch, which is applied at the location of the original imul (marked in green on the image above):

imul 0x30c(%rbx),%edi //Multiply width and height
jc _bad //If CF is set jump to return error
imul 0x314(%rbx),%edi //Multiply with bytes_per_pixel jc _bad //If CF is set jump to return error jmp _good //If no CF is set continue execution _bad: pop %rdi //Return error code: add $0x28,%rsp mov $0xffffffff,%eax //Set %eax to -1 pop %rbx pop %rbp pop %r12 pop %r13 pop %r14 pop %r15 retq //and return _good:

As you can see, we check if the carry flag is set after each imul and return an error if set. After applying the patch, however, totem still crashed. After some debugging I figured out that returning an error in vmnc_handle_wmvi_rectangle wasn't enough. Instead of looking for the cause I took a look at the official patch below.



The official patch can be found in function vmnc_handle_packet. You can see that both width and height are checked if they're larger then 16384 or 0x4000 and an error is returned if they are. Our patch should do the same. I disassembled both the vulnerable and the patched version of vmnc_handle_packet and used Bindiff to look for an appropriate patch location. The image below represents the combined view of both codes. The green code was added by the official patch.




The official patch code is executed only if the packet type equals TYPE_WMVi or 0x574D5669. At my chosen patch location (marked in red - click the image above for a larger view) the %edi register holds the packet type value, while %r8w and %cx hold the width and height. This is the resulting patch:


cmp $0x574d5669,%edi //If the packet type doesn't equal TYPE_WMVi,
jnz _end //continue execution cmp $0x4000,%r8w //If width is greater than 0x4000, ja _bad //jump to return error cmp $0x4000,%cx //If height is smaller than 0x4000, jbe _end //continue execution, else return error _bad: mov $0xffffffff,%eax //Set %eax to -1 add $0x48,%rsp pop %rbx pop %rbp pop %r12 pop %r13 pop %r14 pop %r15 retq //and return _end:

The patch successfully stops the integer overflow and subsequent memory allocation, actually making the file playable again, just as the official patch does. Here's a short video of our Linux 0patch agent and the patch we just developed.



As you can see, totem crashes when trying to open the PoC. After enabling the patch by copying the patch file to the patch store folder, totem no longer crashes and the video is actually playable.

Effort wise, developing this patch took about 8 hours. That includes reversing libgstvmnc.so, trying out a faulty patch candidate, analyzing the official patch and implementing an adequate patch candidate. This was a relatively simple patch but I wanted to showcase our Linux agent and its capabilities which are almost on par with what we're already doing on Windows.

We're continuously working on expanding the OS platform support so we can one day bring 0patch to everyone who needs it.


@0patch

Wednesday, January 25, 2017

Micropatching Remote Code Execution in WebEx Browser Extension (CVE-2017-3823)

This is a short story of writing a micropatch for a widely-used web browser extension.

[Updated on 1/27/17 after Cisco has, again commendably quickly, issued a new WebEx browser extension version to fix a bypass in version 1.0.5.]

Two days ago, Tavis Ormandy of Google's Project Zero published details of a remote code execution bug he had found in Cisco WebEx Browser Extension that allowed any web page to launch arbitrary executable on visitor's computer. A thing like this is a big deal: WebEx has ~20 million users and many of them are enterprise and government users, or in other words, interesting targets for professional criminals and spies.

According to Tavis, Cisco was impressively quick to issue an updated version 1.0.5 after receiving his report. The automatically installed update restricts the affected functionality so that it can only be used by web sites originating via HTTPS from domains webex.com and webex.com.cn. For all other web sites, an attempt to exploit the vulnerability would result in a warning dialog like this:




I tried to get Tavis's exploit to work with the updated extension, but clicking OK on the above dialog didn't work. Delivering the exploit from a fake local www.webex.com server didn't - as expected - show the above warning, but also didn't result in the exploit working. My theory is that Cisco also added some quick check to break this particular exploit, which was a smart thing to do, since cross-site scripting issues are surely about to start springing up on webex.com servers (one is already there). If my theory is correct, someone will find (and possibly publish) a way to get around this quick check and combine it with a cross-site scripting issue somewhere in the webex.com domain. By the way, Pentest-Tools.com finds 544 hosts in the webex.com domain, so the attack surface seems sizeable.

The show is not over either, it seems, as Tavis has apparently found and reported some residual remote code execution issues with the latest WebEx update.

[Update 1/27/17: Tavis's report is now public. Apparently WebEx developers have implemented the DLL white-listing strategy in version 1.0.5 that we mentioned in the original blog post, but Tavis has found a way around it by misusing the installation functionality for replacing one of the white-listed DLLs with Cisco-signed MSVCR100.DLL. Cisco has since published WebEx update 1.0.7 that fixes this bypass.]

So we have a publicly known remote code execution bug with a working POC on millions of computers, an unknown number of not-yet-updated WebEx extensions, an update that seems to be exploitable with cross-site scripting, and a still-secret residual remote code execution bug in the latest update. One could say there's room for improvement.


Writing a Micropatch

The way this WebEx vulnerability is currently fixed doesn't seem good enough to me. (To be fair, it probably also doesn't look good enough to Cisco but they had to do something quick and are probably working on a better long-term solution.) The risk of resurrecting Tavis's exploit with some modification and cross-site scripting, or of simply tricking users to click OK, made me wonder if we could do a better fix with 0patch.

My initial thought was that we couldn't, as browser extensions are essentially JavaScript code and we currently can't patch that. But then my man Edwin van Andel of Zerocopter kind of dared me to 0patch this bug so I dropped everything else and fired up my 0patch virtual machines. Surprisingly, it turned out that most of WebEx is actually native code - the browser code is merely in charge of launching ciscowebexstart.exe and passing the meeting parameters to it. It is this executable that then does the heavy lifting.

Taviso's exploit shows that a WebEx message, which is used for launching a meeting, can also be used for executing any exported function with arbitrary arguments from any DLL in the WebEx folder. We don't know, perhaps even from anywhere on the computer or even from a remote network share.

So what if we patched WebEx so that it would only be possible to call a limited set of functions from a limited set of DLLs, namely those that are needed for WebEx's normal operations. Looking at what happens when a meeting is launched, my debugger and I could only find calls to functions from atmccli.DLL.Well, atmccli.DLL only exports four functions, and they all seem to be in use so why don't we simply limit loading of DLLs to atmccli.DLL?

The challenge at this point was to find the place in the code where the DLL name provided in the (potentially malicious) WebEx message is used for actually loading a DLL, likely using LoadLibrary/Ex. If we found that place, we could perhaps replace the DLL name with "atmccli.DLL".

I started by monitoring ciscowebexstart.exe with Process Monitor to see the call stack where Tavis's calc.exe gets launched. This is what I found.





Apparently, it is atgpcext.dll that calls wsystem from MSVCR100.DLL. So I opened it up in IDA and looked around the addresses in the above call stack. Fortunately, a lot of logging strings are sprinkled around the DLL, making it easier to understand what the code is supposed to be doing.

Subsequent hours comprised of typical connect-the-dots reversing steps, switching from IDA to WinDbg to Process Monitor and back in attempting to understand the process from point A (web site sending WebEx a message) to point B (the chosen DLL function gets invoked).

Eventually the dots were connected and I ended up with a decent patch candidate:


MODULE_PATH "C:\ProgramData\WebEx\WebEx\T30_MC\atgpcext.dll"
PATCH_ID 10000
PATCH_FORMAT_VER 2
VULN_ID 2099
PLATFORM win32

patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2

PATCHLET_OFFSET 0x5da9f
JUMPOVERBYTES 5 ; We jump over the original "call sub_21CBA"
N_ORIGINALBYTES 5

code_start

    call over
    dw __utf16__("atmccli.dll"),0
over:
    pop eax

code_end
patchlet_end


Let's see what this patch does. It gets applied to WebEx's atgpcext.dll at offset 0x5da9f, and replaces the call to function which returns a pointer to the DLL name from the message with a pointer to our own fixed string, "atmccli.dll". This replacing is caused by JUMPOVERBYTES 5, which tells 0patch agent not to continue execution of the original code after returning from the patch code, but to jump over (well, obviously) 5 bytes of that code, effectively landing just behind the "call sub_21CBA" 5-byte instruction.

But wait, what is this trickery in the patch code? Do we really make a call to some label, then pop eax from the stack? Yes we do. This is an efficient, and very hacker-worthy solution for getting a pointer to a string if you don't have some extra space to store said string. The call namely pushes the address of the "following instruction" to stack, but what follows is not an instruction but the string. The pop then takes the address of the string from stack, and that's all the patch needs to do. (Note that this is an old trick; if you have any references to its past use, do let me know so I can credit the author.)

Now, the result of this patch getting applied is that whichever DLL one instructs WebEx to use, WebEx will always use its own atmccli.DLL. This obviously breaks Tavis's exploit, as he is trying to load MSVCR100.DLL, and we replace that with atmccli.DLL; there is no _wsystem exported function in atmccli.DLL, so the operation silently fails.

Let's take a look at a video showing how this micropatch blocks the exploit while keeping WebEx functional.




So How Good Is This Micropatch?

Generally speaking, there can be two problems with a security patch: it can fail to adequately remove the security issue, and/or it can cause functional problems.

  1. Adequate removal of the security issue: Frankly, it's hard to say without digging deep into what the four exported functions of atmccli.DLL are capable of. Perhaps one could find that InitControl provides a way to launch arbitrary executables, which would make the patch ineffective. At this point, only WebEx developers know the answer to this, which just underlines my belief that 0patches like these should best be written by original developers - and someday hopefully will be.

    In comparison to Cisco's official fix (update 1.0.5), this patch prevents the exploit even if it originated from one of the webex.com servers, e.g., via cross-site scripting.

    [Update 1/27/17: After the report about exploiting the WebEx version 1.0.5 was made public, we reproduced the second exploit and discovered that our original micropatch - the one for version 1.0.1 - also blocked said exploit without any modification, which we think is kind of cool. This video shows it best.]

  2. Functional problems: In an attempt to simulate a vendor's quick response to a published vulnerability in their product, we simply tested the main WebEx functionality: launching and running a meeting. This seemed to work, but again, WebEx developers would quickly notice if this patch broke some functionality as they are deeply familiar with its working. If they said, for instance, that a few other DLLs are also invoked under normal WebEx operations, we could improve the patch to white-list DLL-function pairs, and still keep things tiny.


The entire process of creating this patch took about 8 hours, including finding and setting up the vulnerable extension, getting the exploit to work, reversing WebEx, trying out a couple of inadequate patch candidates, and wrestling with our not-quite-yet-production-quality 0patch Builder that we use for compiling patches.

It is my assessment that with some help of WebEx developers this micropatch - or likely a much better one - could be made in two hours tops. Their knowledge would significantly shorten the reversing time and would allow for finding the optimal patching strategy that would reliably close the hole without impacting functionality.

And this is how we see the future of vulnerability patching.


@mkolsek
@0patch

Friday, September 2, 2016

Patch to Self

The Birth of the World's First Self-Healing Micropatch


By Luka Treiber, 0patch team

And now for something completely different: we just published our first patch for 0patch Agent itself. A self-healing patch, so to speak.

It's been almost three months since 0patch open beta has been released and users gave it a warm reception. Among the feedback given there were not only bug reports, improvement requests and thank-yous, but also patches you would like to see in our 0patch database. Being a patch developer I added those to our already oversized wish list and figured it would be hard to set priorities. But then - allow me to add a little drama here - as the 0patch Agent development team, with one foot on vacation, already rescheduled tasks to fix reported bugs in the agent and started another rush to release a new version, I got a brainwave:  

Let's 0patch it! 

Not only did the dev team heave a sigh of relief (and switched back to the original schedule) - I also finally got my priority bug to patch. 

This bug that I'll share with you raised concerns that in rare circumstances some 0patches may become ineffective. Although hardly noticeable the bug was kind of a big deal to us as we want to make 0patch as reliable as possible.

The flaw, a logical error, is located in function updatePatches which is in charge of applying patches to modules in a process

Here's the relevant source:

3564: void updatePatches(bool forceSync, 
                         bool firstCall, 
                         HMODULE hTriggerModule) {
  //...
3664: // Get a list of all the modules in this process.
3665: if(EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
  //...
3670: if (hTriggerModule != NULL && hTriggerModule == INVALID_HANDLE_VALUE)
3671:    hMods[nMods++]=hTriggerModule;

What the developer tried to do here was not to add an invalid handle to the hMods list. However, instead, the code adds only invalid handles to the hMods list. Obviously the second condition check at line 3670 is mistyped. Instead of != the coder used == , consequently processing only modules with invalid handles. Because of this, a newly loaded module might get ignored by our agent under certain circumstances.  

The function updatePatches synchronizes the list of patched modules kept by 0patch Loader with the actual list of modules returned by EnumProcessModules (line 3665). Whenever a new module load event is detected, updatePatches is called in order to apply any available patches to the new module. However, the list of modules provided by  EnumProcessModules does not always contain the latest module that triggered updatePatches, so we pass along the module's handle and append it to the list after checking that the handle is not invalid - which apparently got miscoded. 

Now to the patch. We have to change the operator == to !=. Should be simple so let's start. How does this source code translate to assembly? 

5c1a5428 mov eax,dword ptr [ebp-3Ch] ; eax = hTriggerModule
5c1a542b cmp eax,ebx ; hTriggerModule != NULL ?
5c1a542d je 0PatchLoader!updatePatches+0x2d6 (5c1a5446
5c1a542f cmp eax,0FFFFFFFFh ; hTriggerModule == INVALID_HANDLE_VALUE ?
5c1a5432 jne 0PatchLoader!updatePatches+0x2d6 (5c1a5446)
5c1a5434 mov eax,dword ptr [ebp-1Ch] 
5c1a5437 mov dword ptr [ebp+eax*4-1054h],0FFFFFFFFh ; hMods[nMods] 
         =INVALID_HANDLE_VALUE
5c1a5442 inc eax 
5c1a5443 mov dword ptr [ebp-1Ch],eax ; nMods++
5c1a5446 cmp byte ptr [ebp+214h],0

In the code snippet above we see that at 5c1a5428 hTriggerModule is stored in eax. Then the two conditions of the flawed if statement from line 3670 are checked: first hTriggerModule against NULL (at 5c1a542b) and then against INVALID_HANDLE_VALUE (at 5c1a542f). We want to patch that second condition check so we place our patchlet at address 5c1a5432. As a first patchlet instruction we will use a je which effectively replaces the erroneous jne marked in red above. This way we microsurgically cut out a flawed instruction and replace it with a correct one and - Voila! We should have a patch for our Agent before you could say JMP! Or so I thought...

Looking at the code snippet above again (only this time more closely) I noticed something unexpected: at 5c1a5437 the compiler apparently optimized the code to always add INVALID_HANDLE_VALUE to hMods list or in c++ terms: 

  hMods[nMods++]=INVALID_HANDLE_VALUE; 
 
Because honestly, it is hard to see any other intent in that buggy if statement. So what I had to do was copy the body of the if statement at line 3671 (code between 5c1a5434 and 5c1a5443) to the patch and modify that copied code to again resemble 

  hMods[nMods++]=hTriggerModule; 
 
I packed these modifications into a 0patch Factory source file:

MODULE_PATH "C:\Progra~2\0patch\Agent\0patchLoader.dll" 
PATCH_ID 247 
PATCH_FORMAT_VER 2 
VULN_ID 1630 
PLATFORM win32

patchlet_start 
PATCHLET_ID 1 
PATCHLET_TYPE 2 
PATCHLET_OFFSET 0x5432 
JUMPOVERBYTES 20
N_ORIGINALBYTES 5 

code_start

 je end_patch ; replace jne with je
 mov ebx,eax ; temporarily store hTriggerModule to ebx
 mov eax,dword [ebp-1Ch] 
 mov dword [ebp+eax*4-1054h],ebx ; store ebx to hMods array
 inc eax
 mov dword [ebp-1Ch],eax ; nMods++
 xor ebx,ebx ; restore ebx to NULL as it was before
end_patch

code_end 
patchlet_end

I hit 0patch Factory's Build Patch button and Presto! The patch is born. After it gets applied the disassembly looks as follows:

5c1a5428 mov eax,dword ptr [ebp-3Ch]
5c1a542b cmp eax,ebx
5c1a542d je 0PatchLoader!updatePatches+0x2d6 (61725446)
5c1a542f cmp eax,0FFFFFFFFh
004c007c je 5c1a5446
004c007e mov ebx,eax
004c0080 mov eax,dword ptr [ebp-1Ch]
004c0083 mov dword ptr [ebp+eax*4-1054h],ebx
004c008a inc eax
004c008b mov dword ptr [ebp-1Ch],eax
004c008e xor ebx,ebx

5c1a5446 cmp byte ptr [ebp+214h],0

Note that to improve readability I inlined the patch code (marked in blue) in the original code.

Still pretty simple. The 0patch contains 7 instructions and it took me an hour to analyze and develop for both 32 and 64 bit Agent versions. No restarts and no reinstalling, that's how we operate. If you already have 0patch Agent installed, patches ZP-247 and ZP-248 should already be deployed on your machine.

This is a great example of how 0patch could be used by software vendors to quickly release emergency updates without affecting regular release (and vacation) schedules.



Luka Treiber

@0patch 

P.S.: It just struck me that 0patch Agent may actually be the first Runtime Application Self-Protection (RASP) product to patch itself at run-time.

Tuesday, July 26, 2016

0patching Foxit Reader's Heap Buffer Overflow Vulnerability CVE-2016-3740

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.

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

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.

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


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

  翻译: