By Mitja Kolsek, the 0patch team
We at 0patch like a challenge as much as the next guy. While we're usually patching memory corruption bugs (most critical remotely exploitable vulns are of that sort), we're happy to demonstrate that in-memory micrpatching can just as well be used for fixing logical bugs - at least temporarily, until the official vendor fix is applied.
So we set upon creating a micropatch for CVE-2017-10952, allowing a script inside a PDF document to use the saveAs function to save itself to an arbitrarily chosen location on user's computer, using an arbitrarily chosen file extension. For example, a PDF document containing a <html> block with some script anywhere in it could simply save itself as an HTA file (locally executable HTML file) in user's StartUp folder like this:
this.saveAs("/c/Users/”+ identity.loginName + ”/AppData/Roaming/Microsoft/Windows/STARTM~1/Programs/Startup/si.hta");
As a result, when the user logged in to Windows the next time, this HTA file would get executed.
Reproducing the issue
Reproducing the issue was simple: we downloaded and installed the latest version of Foxit Reader (18.104.22.16855) and put the above code into a sample PDF file to get our POC.
Opening the POC in Foxit Reader with default/recommended configuration resulted in the Safe Reading Mode warning. Pressing "OK" was enough to disable Safe Reading Mode and get our code executed. Indeed, si.hta got created in the StartUp folder.
However, CreateFile also got called constantly with some BMP file that Foxit Reader seems to be loading all the time for some reason. Making the breakpoint conditional did the trick by simply checking two letters of the file path and not breaking if they matched the said BMP file path:
bp kernel32!CreateFileW "j poi(poi(esp+4)+6)!=0x00730055 ''; 'gc'"
Bingo! The first break occurred right in our saveAs call, confirmed by our HTA path being passed to CreateFile. The call stack tail looked like this:
0030eba8 01703c5e kernel32!CreateFileW
0030ebdc 016f7d4f FOXITREADER+0x823c5e
0030ebfc 011c5a22 FOXITREADER+0x817d4f
0030efac 010bd155 FOXITREADER+0x2e5a22
0030f068 0238ad23 FOXITREADER+0x1dd155
0030f230 02377492 FOXITREADER+0x14aad23
0030f2e4 012f17c7 FOXITREADER+0x1497492
0030f31c 0217568e FOXITREADER+0x4117c7
Time for IDA. As FOXITREADER.EXE is a 54MB beast, it took IDA quite a while to sort it all out. After that, looking at the functions in the call stack revealed that the one containing address FOXITREADER+0x14aad23 holds the bulk of saveAs implementation. There are references to all saveAs arguments there (cPath, cConvID, cFS, bCopy and bPromptToOverwrite), and one can see the logic of bPromptToOverwrite value, which, if true, calls PathFileExistsW and sets a local variable we called allowed_to_save_file to 0 if the user declines the overwrite.
The image below shows the place where allowed_to_save_file is set to 0, and a bit further down where allowed_to_save_file is checked, whereby a bunch of code is bypassed if it is 0. The bypassed code includes the green block where the execution proceeds towards the CreateFile call.
It would be very difficult for us to do exactly what Foxit is about to do to address this issue (only allowing properly signed documents to use dangerous functions like saveAs) because digging down deeper to find a way to the data on document signatures could easily take us days. So we decided to simplify the fix in a way to still allow saveAs, but not in a RCE-style dangerous way.
We noticed (and tested) that in contrast to Adobe's products, Foxit Reader's saveAs function doesn't seem to support document conversion. Consequently, it makes no sense to allow a document to save itself with any other extension than ".pdf". So our logic would be to check the file name passed to saveAs, and only allow files with a ".pdf" extension to proceed, while silently blocking all other extensions.
Having this patching logic in place, we already knew how to get the file path in the function shown above, so we only needed a way to sabotage the file creation for disallowed extensions without causing any unwanted side effects.
The implementation of bPromptToOverwrite logic came in handy as we saw that when the user doesn't allow overwriting, the execution simply bypasses a code block that otherwise leads to CreateFile, and that surely has no unwanted side effects.
So we did a similar thing: We decided to inject our patch code at the beginning of the green block and do the following there:
1) Get the address of the last "." in cPath_string (if any).
2) If no dot, jump out of the block
3) Make a case-insensitive _wcsicmp with ".pdf"
4) If we don't have a match, jump out of the block
5) Otherwise continue
IDA was really helpful in identifying implementations of _wcsrchr and _wcsicmp inside FOXITREADER.EXE for us so we didn't have to implement them in the patch.
Furthermore, we decided to identify any attempt to save a file with some other file extension as an exploit attempt, which results in an "Exploit Attempt Blocked" popup.
This is the micropatch that came out of this:
Micropatch in Action
We made a video to quickly show you how 0patch Agent applies this micropatch to a running Foxit Reader, effectively patching it without disturbing the user.
We made this micropatch to show how logical vulnerabilities can also be patched, and to demonstrate the amount of effort required for creating a micropatch. It took us 6 man-hours from installing the vulnerable Foxit Reader to having a working micropatch. Mind you, the majority of time was spent on analyzing the vulnerability and Reader's code, and deciding on a good way to patch. The original product developers already know the product inside out and would skip most of this process.
We believe that with Foxit's intimate knowledge of their product, and our knowledge of micropatching, a high-quality micropatch could be made in less than an hour. With our distribution system, it would be on everyone's computer within another hour, and applied automatically. Even users using Foxit Reader at that time would not notice anything - Reader would instantly get from "vulnerable" to "fixed" while they would scroll the pages of some document. And that's how we believe most software vulnerabilities should (and could) be fixed.
If you have 0patch Agent installed (it's free!), this micropatch is already on your computer and is getting automatically applied when you launch the vulnerable Foxit Reader. You can use this POC to play with it. Have fun, write some micropatches and if you're a software vendor interested in equipping your products with self-micropatching ability, ping us at firstname.lastname@example.org.
Update 8/29/2017: The latest Foxit Reader (22.214.171.12413) fixes CVE-2017-10951, CVE-2017-10952, and both vulnerabilities reported by