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

Monday, June 20, 2016

New Release: 0patch Agent 2016.06.14.850

We released a new build of 0patch Agent for Windows today. There are two reasons for this: we'd like to test the updating mechanism with our beta users; and we have fixed a couple of issues reported by our beta users, plus a few minor issues we had on our own hit list.

The two issues reported by our beta users were:

  1. Gjoko Krstic of Zero Science Lab reported a security issue in the way 0patch Service was registered: we forgot to enclose the path to the service in quotes when registering it, which allowed a local attacker with ability to create an executable (program.exe) in the root of the drive where 0patch Agent was installed to have this executable launched as user SYSTEM whenever 0patch Service was launched (e.g., after restarting the computer or updating the agent). You can find more information about this type of vulnerability here and here. After being alerted of this issue, we reviewed the entire code for similar issues and indeed found two in the way 0patch Tray was executed, both from the installer and upon login.
    It may be tempting to conclude with the impression that default Windows permissions do not allow regular users (those that don't already have admin privileges in some way or another) to create files in C:\ root, which would effectively make this a non-issue. But that would not cover all cases: one could still install 0patch Agent on a non-default location, e.g., D:\Apps\My apps\, and there is no guarantee that a non-admin user couldn't create D:\Apps\My.exe there.
  2. Ryan O'Horo alerted us about a number of silly terms in our beta license agreement, which we forgot to update before going public. While we responded to his alert with an updated agreement on our web site, we have now also put this updated agreement in the installer.

Big thanks to Gjoko and Ryan for providing valuable feedback and helping us improve. And they're not the only ones: we're getting great feedback from many other beta users as well, including some issues related to user interface, which we'll address at a later time. Let us reiterate: You guys are the real value of this beta! Thank you!

When your 0patch Agent syncs (which it does once an hour, but you can also force it through 0patch Console), it will detect a new version of itself being available and will inform you about it. All you need to do is open the 0patch Console and click "Get latest version" as shown on the image below. The console will disappear and relaunch after a while, showing the new agent version (2016.06.14.850).




We'll appreciate your telling us about any problems encountered during this update process or elsewhere. Just shoot us a quick email to support@0patch.com (or encrypted with our PGP key to security@0patch.com). Thanks!

Friday, June 17, 2016

Writing a 0patch for Acrobat Reader's Use-After-Free Vulnerability CVE-2016-1077

This blog post will describe how we created a 0patch for CVE-2016-1077, a use-after-free vulnerability in Adobe's Acrobat Reader DC version 15.010.20060 for Windows.

According to Adobe Security Bulletin APSB16-14, this vulnerability was reported to Adobe by Pier-Luc Maltais of COSIG, and shortly after the release of this bulletin, a proof-of-concept exploit was published on Packet Storm. This PoC is in the form of a PDF document that crashes the Reader when opened. (By the way, it was apparently created with single-byte raw file fuzzing; we were able to compare it to the original PDF file we found on the Internet.)

This was enough for us to start analyzing the vulnerability. To keep this blog post reasonably short, we will omit the vulnerability analysis process, which is nicely described in this detailed post by Fortinet's Kai Lu and Kushal Arvind Shah for another identical vulnerability in the very same function as ours.

So let's fast-forward to the point where we know exactly what the flaw is. We have a use-after-free issue, as shown in the vulnerable code below.


Vulnerable code: after freeing an object, reference to this object is not removed


The above image shows three relevant code blocks somewhere in Acrobat Reader's implementation of deflate operation on an XObject (an element in a PDF document), where the code is obviously doing some clean-up by freeing an object it no longer needs. The pointer to this object is retrieved in register ecx, then the test ecx, ecx instruction effectively checks if the pointer is NULL, and this determines whether the execution will continue on the red branch (ecx not NULL) or the green branch (ecx is NULL). In the red branch, pointer to object's free function is copied from object's vtable (free is the first function in vtable in this case) to register eax, then a call to free is made.

The relevant code can be represented by this simple pseudo-code:


ObjectA* a; // stored in edi register
ObjectB* b; // stored in eax register
ObjectC* c; // stored in ecx register
...
b = a->some_objectB;
c = b->some_objectC;
if (c) // the red branch
  delete c;
...


Note that after c is deleted, its reference (pointer to it) in b->some_objectC is not removed. And sure enough, at some point later in the execution, b->some_objectC  is accessed again. But this time c points to an already deallocated memory address, and access violation occurs.

Now let's see how we would fix the above pseudo-code. It's trivial, we only need to write NULL to b->some_objectC after deleting c:


...
b = a->some_objectB;
c = b->some_objectC;
if (c)
  delete c;
b->some_objectC = NULL; // destroy reference to deleted object
...


So we have a pseudo-code patch, now let's translate it to machine code. First look at the vulnerable code in a "text" view:


6056E274 8B 47 38           mov   eax, [edi+38h]
6056E277 C6 45 FC 0B        mov   byte ptr [ebp+var_4], 0Bh
6056E27B 89 88 C8 00 00 00  mov   [eax+0C8h], ecx
6056E281 8B 4F 3C           mov   ecx, [edi+3Ch]
6056E284 E8 46 07 00 00     call  sub_6056E9CF
6056E289 8B 47 3C           mov   eax, [edi+3Ch]  ; edi points to some object, eax is pointer to

                                                    a member object
6056E28C 8B 88 C8 00 00 00  mov   ecx, [eax+0C8h] ; ecx is pointer to an object member from the

                                                    "eax" object
6056E292 85 C9              test  ecx, ecx        ; Is this pointer NULL?
6056E294 74 05              jz    short loc_6056E29B ; If it's NULL, we don't have to free it
 


6056E296 8B 01              mov   eax, [ecx]      ; Get the address of function "free" from
                                                    object's vtable
6056E298 56                 push  esi             ; esi = 1
6056E299 FF 10              call  dword ptr [eax] ; call "free" for this object
 


6056E29B    loc_6056E29B:                           ; CODE XREF: sub_6056DC50+644 j
6056E29B 56                 push  esi             ; the reference to freed object is not removed

                                                    (CVE-2016-1077)
6056E29C 6A 24              push  24h
6056E29E E8 71 B6 AD FF     call  sub_60049914

6056E2A3 59                 pop   ecx
6056E2A4 59                 pop   ecx


The patch code must come after call dword ptr [eax], which is a call to object's free. We need the address where the pointer to the object is stored, so we can put a NULL there.This address was [edi+3Ch]+C8h just a few instructions earlier, and we know edi is not corrupted by the call to free as it is still being used later in the code without being assigned a new value. (This is easy to check with IDA as it highlights all uses of a register when you click on one.)

When creating a micropatch, we need to be careful about a few things:

  1. Injecting a micropatch works by overwriting one or more existing instructions with a 5-byte JMP instruction to some nearby-allocated memory block, where the overwritten instructions are added at the end of our patch code, followed by a JMP back to the place in the original code right after them. Of the overwritten instructions, the first one may be a destination point of some CALL or JMP, but others may not be, as that would certainly lead to an error in execution. Fortunately, IDA makes it really easy to verify this with its graphical representation of code.  
  2. We should not cause any unwanted side-effects, like corrupting values of registers or stack-based variables that subsequent code may still be using. When in doubt, we should PUSH/POP the registers we use or otherwise preserve them.
  3. We should not merely assume that a register value will survive a function call (e.g., that edi will survive a call to free in our case) - we must prove it by either reviewing this function (and all functions it calls etc.) and showing that the value is reliably preserved, or showing that the original code after the function call relies on the fact that the register hasn't changed.
  4. Our code should be as easy as possible to review. On one hand, this means using as few instructions as possible, and on the other hand it means making it easy for a reviewer to verify our claims upon which the patch code is constructed (e.g., that "patch code can safely change eax because...", or "at this point, edx will always point to..."). Sometimes, these goals are in contradiction: for example, in our patch code below, we could simply use eax instead of edi and avoid having to PUSH/POP edi, but it would make code review harder.

First, let's find a suitable location for our patch. There's an obvious candidate immediately after  call dword ptr [eax] at location 6056E29B (AcroRd32.dll + 0x56E29B). The instructions there are:


6056E29B 56                 push  esi
6056E29C 6A 24              push  24h
6056E29E E8 71 B6 AD FF     call  sub_60049914


Remember, we need 5 bytes for our "JMP to patch", so we'll have to relocate all these three instructions to the space right after our patch code. Note that the third instruction is a relative CALL, which means that its offset has to be recalculated when it is moved. (Don't worry, 0patch agent does this automatically.)

As for the patch code, let's try this:


push edi                   ; store edi as subsequent code still needs it
mov edi, dword[edi+03ch]   ; edi = pointer to object b in pseudo code
mov dword[edi+0c8h], 0h    ; set pointer to object c to NULL
pop edi                    ; restore edi


This code just uses a single register (edi), making it trivial for a reviewer to see that it has no side effects while writing a NULL where the original code forgot to do it. As already noted, we could make it even shorter if we used eax instead of edi, as we wouldn't have to preserve eax's value. However, proving that we don't have to preserve eax's value would require reviewing quite some additional code after our patch location, including a function that is called there. This would consume patch developer's as well as patch reviewer's time, and increase the risk of error for both of them.

One last check: Can we be absolutely sure that edi will point to the same object a at address 6056E29B as it did a couple of instructions back at address 6056E289? There are two execution paths between these two addresses (as IDA nicely shows). One goes through the aforementioned green branch, and the other goes through the red branch. The "green branch" path is easy to prove: there is simply no instruction there that would change edi, so it must still have the same value at the end. The "red branch" path, however, includes a call to free from object's vtable. Proving that this function does not alter edi could be a daunting task, especially as IDA can't show you where the code behind this dynamically-set free is. (In fact, this function could be different for various types of objects being processed here, and only the original developers of this code know what objects that could possibly be.) So we'll use another approach: we look at the code after our patch location to see whether it relies on edi being the same as before. And in fact, we can see that it does (but that's beyond the scope of this article).

We have all we need, but how do we turn this into an actual 0patch? We use 0patch Factory. 0patch Factory is a simple tool we wrote, which takes a 0patch source file as input, and turns it into a 0patch that can be immediately applied on any computer with our 0patch Agent. Let's take a look at a 0patch source file for this micropatch:


MODULE_PATH "C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.dll"
PATCH_ID 245
PATCH_FORMAT_VER 2
VULN_ID 1272
PLATFORM win32

patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2

PATCHLET_OFFSET 0x56e29b
N_ORIGINALBYTES 5

code_start

    push edi
    mov edi, dword[edi+03ch]
    mov dword[edi+0c8h], 0h
    pop edi

code_end
patchlet_end


Some explanation is probably in order:

  • Each 0patch consists of one or more patchlets; a patchlet is a set of machine code instructions that are to be injected at a specific offset from the module's base.
  • MODULE_PATH is full path to the module (binary file) we're patching. 0patch Factory needs this for two reasons: (1) calculating the module's hash, as the patch should only be applied to this exact binary, and (2) extracting the original bytes from all locations where we're injecting our patchlets, as any patchlet should only be applied when the bytes found in the "live" module on its injection location exactly match these original bytes.
  • PATCH_ID is a unique ID of the patch in the 0patch database. It can be arbitrary during patch development, but a patch will get a suitable unique ID before it gets signed and uploaded to the server.
  • PATCH_FORMAT_VER is the version of the format in which the patch is written. 2 is the only supported value at this time.  
  • VULN_ID is the ID of the vulnerability (in the 0patch database) fixed by this patch.
  • PLATFORM is either Win32 or Win64 at this time, specifying the format of machine code in this patch's patchlets. Acrobat Reader DC we're patching here is a 32-bit application.
  • patchlet_start and patchlet_end mark beginning and end of an individual patchlet.
  • PATCHLET_ID is a unique ID of a patchlet inside a patch, usually iterative from 1 upwards.
  • PATCHLET_TYPE is the type of the patchlet, currently the only supported type is type 2 (which means "code injection patchlet"). In the future, we'll support other types of patchlets.
  • PATCHLET_OFFSET is the offset from the module's base where the patchlet code should be injected. In our case, this offset is 0x56e29b.
  • N_ORIGINALBYTES is the number of original bytes that we should check when applying the patchlet. The default is 5, which means all 5 bytes that we will overwrite with the "JMP to patch" instruction.
  • code_start and code_end mark beginning and end of patchlet's code, which will be compiled to binary code by 0patch Factory.

We store this 0patch source file in ZP-245.0pp and build the patch using 0patch Factory on a computer with 0patch Agent installed:


0patch Factory makes it easy to build a micropatch: just
right-click a 0pp file and select "Build Patch"


This both compiles and installs the new 0patch, making it ready for immediate application to a running vulnerable Acrobat Reader. Without further ado, let's open POC_ZP-245.pdf to test our patch.


0patch Agent notifies you that patch 245 just
got applied to the running Acrobat Reader

Acrobat Reader gets launched, the "Patch Applied" pop-up is shown notifying us that patch 245 was applied to  AcroRd32.exe, and... no crash. The document seems to be displayed correctly. We scroll down and get the "A drawing error occurred." error message from the Reader, but this is expected, as there is still a corrupt graphical element in the PDF document, and Reader correctly notifies us about that. (Latest version of Reader also shows this message.) We can continue to use Acrobat Reader as if nothing has happened.

There you go, we've just created a simple micropatch for a remotely executable memory corruption vulnerability. It consists of just four machine code instructions and can be instantaneously applied to a running Acrobat Reader while a user is reading some document with it.

Should you like to experiment with this micropatch on your own, create a free 0patch account and download 0patch Agent for Windows if you haven't already. (If you have, than patch ZP-245 has probably already been downloaded by your agent and is waiting for you to run the vulnerable Acrobat Reader.)


Patch 245 is now in the "Patches" list in the 0patch Console, where you can enable or disable it


Once you have 0patch Agent installed and registered, install Acrobat Reader DC 15.010.20060 and use it to open POC_ZP-245.pdf, first with patch 245 enabled, and then disabled. Notice the difference? ;)

If you'd like to write your own 0patches, and we sure hope many of you would, give us some time to polish our 0patch Factory for you. It's nothing fancy, but you deserve a decently tested tool with useful instructions. In the mean time, please send us an email at crowdpatching@0patch.com to express your interest. We look forward to our collaboration in changing the way vulnerabilities are getting fixed.

@mkolsek
@0patch