c0w5lip@wired:~$

🏷️ vr pwn re tutorial

Root-causing N-days with Patch Diffing

Introduction

This article is meant to be a quick introduction to “Patch Diffing”, with a practical study case on vulnerability CVE-2023-38831 that affected WinRAR back then.

What is Patch Diffing?

Patch Diffing is simply the process of diffing (comparing to spot differences) two versions of a binary: the one before and after a particular patch was applied.

Why is it done?

(non-exhaustive):

  1. 1-day exploits: Vendors often patch bugs without giving any detail or even mentioning the existence of a vulnerability. Diffing allows researchers to reconstruct the exploit, which is still valid
  2. Patch bypass: Patch applied to vulnerabilities can be flawed. You can therefore try and bypass the patch to exploit the same vulnerability, or even find new vulnerabilities that emerged as a byproduct of the patch.

How is it done?

  1. Export binaries to databases using tools like Diaphora or BinDiff.
  2. Diff the databases
  3. Filter and go through the results
  4. Find interesting changes
  5. Potentially find exploitable material from changes

Personal note

I’ve mostly been a Reverse Engineering guy so far, so Binary Diffing is a comfortable skill to exercise, as it mostly consists of RE. I think it’s nice to train yourself to spot vulnerabilities without drowning in a big mess of code.

Study case: WinRAR

We will be working with two successive versions of the famous tool WinRAR.

For the sake of this article, let’s pretend that the latest version is 6.23, following the 6.22 version from a few months ago.

Getting the binaries

If you want to practice patch diffing, you might want to search for curated and malware-free repositories to download old versions of software from. In our case, we will be using Filepuma.

Analysis

Let’s first open the patched version of WinRAR.exe in IDA Pro 9.2, and export it to an sqlite databse using Diaphora. If you follow along, make sure to unchecked the option related to microcode generation, as it’s not needed and significantly hinders the process.

Once the process is done, we can open the unpatched version of the same binary, and proceed to the export and diffing with the patched binary database.

Eventually, several new views appear, showing information about the diff. We will be focusing on one in particular: Partial Matches.

Because there are quite a few functions, we can narrow our candidates down based on 2 filters: The number of basic blocks added, and the “Ratio” (1). Adding a simple if statement as a patch for a vulnerability will translate to a couple more basic blocks being added to the CFG, so this gives us a rough idea about the number of additional basic blocks.

(1) “Ratio” being simply the degree of difference between the two functions ($1.0$ for identical functions), we can for example eliminate ratio that are way too high ($0.99…$) as these are more likely to be insignificant changes in the assembly rather than a patch.

Filtering tricks like these are useful not to waste time, but the whole process still is tedious and requires you to use your intuition, common sense, and do a lot of digging.

After looking at the diffed pseudo code for a few of our functions, one in particular catches our eye (Line 31).

This code is actually run whenever you’re double-clicking on a file that’s within an archive that you just opened in the WinRAR GUI.

The bug

That code actually leads to CVE-2023-38831.

Without going too much into the details, here’s what happens:

The problem is that ShellExecuteExW() has a particular behaviour where it encounters an ambiguous path.

One of the field of the struct is lpVerb, which “specifies the action to be performed”. WinRAR retries execution with lpVerb = 0 after a failed call, regardless of the failure cause:

v12 = ShellExecuteExW(&pExecInfo);

if (!v12) # ShellExecuteExW fails
{
    ...

    if (pExecInfo.lpVerb) # lpVerb set?
    {
        pExecInfo.lpVerb = 0; # lpVerb gets nulled
        v12 = ShellExecuteExW(&pExecInfo); # we try to execute the function again, with a nulled lpVerb 
    }
}

With pExecInfo.lpVerb = 0, observed shell behavior shows broader path resolution heuristics being applied.

If we name a file evil.pdf (with a trailing space) and a folder of the same name containing a script named evil.pdf .cmd, the trailing will stealthily invalidate the PDF extension (Windows can’t find an application to open the file). This will result in ShellExecuteExW() failing.

However, this we just assessed that this fail will cause lpVerb to be nulled, and ShellExecuteExW() to be called again with the new parameter (Even though the fail had nothing to do with lpVerb!). In that case, the following behaviour is observed from the shell: it looks for a folder of the same name (evil.pdf /), and automatically execute any script file present within the said directory (i.e evil.pdf .cmd).

The patch

A check has been added (if (GetLastError() != 1155)) to prevent ShellExecuteExW() from being executed again if it returned $1155$ (ERROR_NO_ASSOCIATION) during the first attempt.

Therefore, the patch revolves around fixing the logic rather than sanitizing.

Exploit?

I wrote my own exploit but thought I’d redact that part due to legal concerns. All it takes is 4 lines of Python to pop a calc, try it yourself with my explanations.

CVE-2023-38831.zip
├── evil .pdf  (File with trailing space)
└── evil .pdf  (Folder)
    └── evil.pdf .cmd (Payload)

Note on dynamic analysis vs static theory

We just assessed that the payload is running during the 2nd call to ShellExecuteExW() (retry). However, if you debug WinRAR, you’ll notice that the payload is executed during the first call. This reveals that the Windows Shell API is even more “aggressive” than previously thought: it directly notices an ambiguous path, and searches into our directory before the function even returned an error.

Conclusion

Opening

My friend nasm sent me this article about AI-assisted Patch Diffing, which you will most definitely enjoy if you found mine interesting.

Additional resources

Disclamer

This article is for educational and defensive research only. Do not attempt to reproduce exploit code against systems you do not own or without explicit authorization. Users should update WinRAR to 6.23 or later to mitigate the issue described here.