Original text by Nadav Grossman
In this article, we tell the story of how we found a logical bug using the WinAFL fuzzer and exploited it in WinRAR to gain full control over a victim’s computer. The exploit works by just extracting an archive, and puts over 500 million users at risk. This vulnerability has existed for over 19 years(!) and forced WinRAR to completely drop support for the vulnerable format.
A few months ago, our team built a multi-processor fuzzing lab and started to fuzz binaries for Windows environments using the WinAFL fuzzer. After the good results we got from our Adobe Research, we decided to expand our fuzzing efforts and started to fuzz WinRAR too.
One of the crashes produced by the fuzzer led us to an old, dated dynamic link library (dll) that was compiled back in 2006 without a protection mechanism (like ASLR, DEP, etc.) and is used by WinRAR.
We turned our focus and fuzzer to this “low hanging fruit” dll, and looked for a memory corruption bug that would hopefully lead to Remote Code Execution.
However, the fuzzer produced a test case with “weird” behavior. After researching this behavior, we found a logical bug: Absolute Path Traversal. From this point on it was simple to leverage this vulnerability to a remote code execution.
Perhaps it’s also worth mentioning that a substantial amount of money in various bug bounty programs is offered for these types of vulnerabilities.
Figure 1: Zerodium tweet on purchasing WinRAR vulnerability.
What is WinRAR?
WinRAR is a trialware file archiver utility for Windows which can create and view archives in RAR or ZIP file formats and unpack numerous archive file formats.
According to the WinRAR website, over 500 million users worldwide make WinRAR the world’s most popular compression tool today.
This is what the GUI looks like:
Figure 2: WinRAR GUI.
The Fuzzing Process Background
These are the steps taken to start fuzzing WinRAR:
- Creation of an internal harness inside the WinRAR main function which enables us to fuzz any archive type, without stitching a specific harness for each format. This is done by patching the WinRAR executable.
- Eliminate GUI elements such as message boxes and dialogs which require user interaction. This is also done by patching the WinRAR executable.
There are some message boxes that pop up even in CLI mode of WinRAR.
- Use a giant corpus from an interesting piece of research conducted around 2005 by the University of Oulu.
- Fuzz the program with WinAFL using WinRAR command line switches. These force WinRAR to parse the “broken archive” and also set default passwords (“-p” for password and “-kb” for keep broken extracted files). We found those options in a WinRAR manual/help file.
After a short time of fuzzing, we found several crashes in the extraction of several archive formats such as RAR, LZH and ACE that were caused by a memory corruption vulnerability such as Out-of-Bounds Write. The exploitation of these vulnerabilities, though, is not trivial because the primitives supplied limited control over the overwritten buffer.
However, a crash related to the parsing of the ACE format caught our eye. We found that WinRAR uses a dll named
Build a Specific Harness
We decided to focus on this dll because it looked like it would be quick and easy to exploit.
Also, as far as WinRAR is concerned, as long as the archive file has a .rar extension, it would handle it according to the file’s magic bytes, in our case – the ACE format.
To improve the fuzzer performance, and to increase the coverage only on the relevant dll, we created a specific harness for
To do that, we need to understand how
- An initialization function named
, with the following signature:ACEInitDll
INT __stdcall ACEInitDll(unknown_struct_1 *struct_1);
• struct_1: pointer to an unknown struct
- An extraction function named
, with the following signature:ACEExtract
INT __stdcall ACEExtract(LPSTR ArchiveName, unknown_struct_2 *struct_2);•<br>: string pointer to the path to the ace file to be extractedArchiveName
•: pointer to an unknown structstruct_2
Both of these functions required structs that are unknown to us. We had two options to try to understand the unknown struct: reversing and debugging WinRAR, or trying to find an open source project that uses those structs.
The first option is more time consuming, so we opted to try the second one. We searched github.com for the exported function
and found a project named FarManager that uses this dll and includes a detailed header file for the unknown structs.
Note: The creator of this project is also the creator of WinRAR.
After loading the header files to IDA, it was much easier to understand the previously “unknown structs” to both functions (
From the headers we found in the FarManager project, we came up with the following signature:
To mimic the way that WinRAR uses
We started to fuzz this specific harness, but we didn’t find new crashes and the coverage did not expand in the first few hours of the fuzzing. We tried to understand the reason for this limitation.
We started by looking for information about the ACE archive format.
Understanding the ACE Format
We didn’t find a RFC for that format, but we did find vital information over the internet.
1. Creating an ACE archive is protected by a patent. The only software that is allowed to create an ACE archive is
- It can extract an ACE archive.
- It contains a brief explanation about the ACE file format.
- It has a very helpful feature that prints the file format header with an explanation.
To understand the ACE file format, let’s create a simple
Figure 3: File before compression.
These are the options we selected in
Figure 4: WinACE compression GUI.
This option creates the subdirectories
Figure 5: The simple_file.ace produced using WinACE’s “store” compression option for visibility.
Figure 6: Parsing ACE file header using acefile.py.
This results in:
Figure 7: acefile.py header parsing output.
- Consider each “\\” from the
field in the image above as a single slash “\”, this is just python escaping.filename
- For clarity, the same fields are marked with the same color in the hex dump and in the output from
Summary of the important fields:
- hdr_crc (marked in pink):
Two CRC fields are present in 2 headers. If the CRC doesn’t match the data, the extraction
is interrupted. This is the reason why the fuzzer didn’t find more paths (expand its coverage).To “solve” this issue we patched all the CRC* checks in.*Note – The CRC is a modified implementation of the regular CRC-32.unacev2.dll
- filename (marked in green):
It contains the relative path to the file. All the directories specified in the relative path are created during the extracting process (including the file). The size of theis defined by 2 bytes (little endian) marked by a black frame in the hex dump.filename
- advert (marked in yellow)
The advert field is automatically added by, during the creation of an ACE archive, if the archive is created using an unregistered version ofWinACE.WinACE
- file content:
” – The content’s size. The content itself is positioned after the header that defines the file (“hdr_type” field == 1).origsize
” – The header size. Marked by a gray frame in the hex dump.hdr_size
- At offset 70 (0x46) from the second header, we can find our file content: “Hello From Check Point!”
Because the filename field contains the relative path to the file, we did some manual modification attempts to the field to see if it is vulnerable to “Path Traversal.”
For example, we added the trivial path traversal gadget “\..\” to the filename field and more complex “Path Traversal” tricks as well, but without success.
After patching all the structure checks, such as the CRC validation, we once again activated our fuzzer. After a short time of fuzzing, we entered the main fuzzing directory and found something odd. But let’s first describe our fuzzing machine for some necessary background.
The Fuzzing Machine
To increase the fuzzer performance and to prevent an I\O bottleneck, we used a RAM disk drive that uses the ImDisk toolkit on the fuzzing machine.
The Ram disk is mapped to drive R:\, and the folder tree looks like this:
Figure 8: Fuzzer’s folders hierarchy
Detecting the Path Traversal Bug
A short time after starting the fuzzer, we found a new folder named sourbe in a surprising location, in the root of drive R:\
Figure 9: ”sourbe”, the unexpected folder which created during fuzzing.
The harness is instructed to extract the fuzzed archive to sub-directories under “output_folders”. For example,
Figure 10: Content of the file that produced by the fuzzer in the unexpected path “R:\sourbe\RED VERSION_¶”.
This is the hex dump of the test case that triggers the vulnerability:
Figure 11: A hex dump of the file that produced by the fuzzer in the unexpected path “R:\sourbe\RED VERSION_¶”.
- We made some minor changes to this test case, (such as adjusting the CRC) to make it parsable by
- For convenience, fields are marked with the same color in the hex dump and in
the output from.acefile
Figure 12: Header parsing output from acefile.py for the file that produced by the fuzzer in the unexpected path.
These are the first three things that we noticed when we looked at the hex dump and the output from
- The fuzzer copied parts of the “advert” field to other fields:
- The content of the compressed file is “SIO”, marked in an orange frame in the hex dump. It’s part of the advert string “*UNREGISTERED VERSION*”.
- The filename field contain the string “RED VERSION*” which is part of the advert string “*UNREGISTERED VERSION*”.
- The path in the
field was used in the extraction process as an “absolute path” instead of a relative path to the destination folder (the backslash is the root of the drive).filename
- The extract file name is “RED VERSION_¶”. It seems that the asterisk from the
field was converted to an underscore and the \x14\ (0x14) value represented as “¶” in the extract file name. The other content of thefilenamefield is ignored because there is a null char which terminates the string, after the \x14\ (0x14) value.filename
To find the constraints that caused it to ignore the destination folder and use the
Our first assumption was the first character of the
- The first char should be a ‘/’ or a ‘\’.
- ‘*’ should be included in the
at least once; the location doesn’t matter.filename
Example of a
Now that it worked on our fuzzing harness, it is time to test our crafted archive (e.g. exploit file) file on WinRAR.
Trying the exploit on WinRAR
At first glance, it looked like the exploit worked as expected on WinRAR, because the
These behaviors raised two questions:
- Why did the harness and WinRAR behave differently?
- Why were the directories that were specified in the exploit file created, and the extracted file was not created?
Why did the harness and WinRAR behave differently?
We expected that the exploit file would behave the same on WinRAR as it behaved in our harness, for the following reasons:
- The dll (
) extracts the files to the destination folder, and not the outer executable (WinRAR or our harness).unacev2.dll
- Our harness mimics WinRAR perfectly when passing parameters / struct members to the dll.
A deeper look showed that we had a false assumption in our second point. Our harness defines 4 callbacks pointers, and our implemented callbacks differ from WinRAR’s callbacks. Let’s return to our harness implementation.
We mentioned this signature when calling the exported function named
These callbacks are called by the dll (
The callbacks are used as external validators for operations that about to happen, such as the creation of a file, creation of a directory, overwriting a file, etc.
The external callback/validators get information about the operation that’s about to occur, for example, file extraction, and returns its decision to the dll.
If the operation is allowed, the following constant is returned to the dll:
Our harness returned
It turns out, WinRAR does validation for the extracted
WinRAR Validators / Callbacks
This is part of the WinRAR callback’s validator pseudo-code that prevents the file creation:
Figure 13: WinRAR validator/callback pseudo-code.
The function does the following checks:
- The first char does not equal “
” or “\”./
- The File Name doesn’t start with the following strings “
” or “..\” which are gadgets for “Path Traversal”.../
- The following “Path Traversal” gadgets does not exist in the string:
The extraction function in
The relative path is checked by the WinRAR callback’s validator. The validators return
The following string passes to the WinRAR callback’s validator:
Note: This is the original filename with fields “\sourbe\RED VERSION*¶”. “
Why were the folders that were specified in the exploit file created and the extracted file was not created?
Because of a bug in the dll (“
The reason for this is that
It actually creates the extracted file, without writing content to it. It calls to CreateFile API
and then checks the return code from the callback function. If the return code is
- We found a way to bypass the deletion of the file, but it allows us to create empty files only. We can bypass the file deletion by adding “:” to the end of the file, which is treated as Alternate Data Streams. If the callback returns
, dll tries to delete the Alternate Data Stream of the file instead of the file itself.ACE_CALLBACK_RETURN_CANCEL
- There is another filter function in the dll code that aborts the extraction operation if the relative path string starts with “\” (slash). This happens in the first extraction stages, before the calls to any other filter function.
However, by adding “*”or “?” characters (wildcard characters) to the relative path (filename field) of the compressed file, this check is skipped and the code flow can continue and (partially) trigger the Path Traversal vulnerability. This is why the exploit file which was produced by the fuzzer triggered the bug in our harness. It doesn’t trigger the bug in WinRAR because of the callback validator in the WinRAR code.
Summary of Intermediate Findings
- We found a Path Traversal vulnerability in
. It enables our harness to extract the file to an arbitrary path, and completely ignore the destination folder, and treats the extracted file relative path as the full path.unacev2.dll
- Two constraints lead to the Path Traversal vulnerability (summarized in previous sections):
1. The first char should be a ‘/’ or a ‘\’.
2. ‘*’ should be included in theat least once. The location does not matter.filename
- WinRAR is partially vulnerable to the Path Traversal:
doesn’t abort the operation after getting the abort code from the WinRAR callback (unacev2.dll). Due to this delayed check of the return code from WinRAR callback, the directories specified in the exploit file are created.ACE_CALLBACK_RETURN_CANCEL
- The extracted file is created as well, on the full path specified in the exploit file (without content), but it is deleted right after checking the returned code from the callback (before the call to WriteFile API).
- We found a way to bypass the deletion of the file, but it allows us to create empty files only.
Finding the Root Cause
At this point, we wanted to figure out why the destination folder is ignored, and the relative path of the archive files (
To achieve this goal, we could use static analysis and debugging, but we decided on a much quicker method. We used DynamoRio to record the code coverage in
These are the results we got:
Figure 14: Lighthouse’s coverage overview window. You can see the coverage subtraction in the “Composer” form, and one result highlighted in purple.
In the “Coverage Overview” window we can see a single result. This means there is only one basic block that was executed in the first attempt (marked in A) and wasn’t reached on the second attempt (marked in B).
The Lighthouse plugin marked the background of the diffed basic block in blue, as you can see in the image below.
Figure 15: IDA graph view of the main bug in unacev2.dll. Lighthouse marked the background of the diffed basic block in blue.
From the code coverage results, you can understand that the exploit file is not going through the diffed basic block (marked in blue), but it takes the opposite basic block (the false condition, marked with a red arrow).
If the code flow goes through the false condition (red arrow) the line that is inside the green frame replaces the destination folder with
The code flow to the true and false conditions, marked with green and red arrows respectively,
is influenced by the call to the function named
If the result from the call to
This means that the relative path will actually be treated as a fullpath to the file/directory that should be written/created.
Let’s look at GetDevicePathLen function to get a better understanding of the root cause:
Figure 16: GetDevicePathLen code.
The relative path of the extracted file is passed to
It checks if the device or drive name prefix appears in the Path parameter, and returns the length of that string, like this:
- The function returns 3 for this path:
- The function returns 1 for this path:
- The function returns 15 for this path:
- The function returns 21 for this path:
- The function returns 0 for this path:
If the return value from
However, there is a function that “cleans” the relative path of the extract file, by omitting any sequences that are not allowed before the call to
This is a pseudo-code that cleans the path “
Figure 17: Pseudo-code of CleanPath.
The function omits trivial Path Traversal sequences like “
Note that it doesn’t care about the first letter; the following sequence will be omitted as well: “
Putting It All Together
To create an exploit file, which causes WinRAR to extract an archived file to an arbitrary path (Path Traversal), extract to the Startup Folder (which gains code execution after reboot) instead of to the destination folder.
We should bypass two filter functions to trigger the bug.
To trigger the concatenation of an empty string to the relative path of the compressed file, instead of the destination folder:
The result from
It depends on the content of the relative path (“file_relative_path”). If the relative path starts the device path this way:
- option 1:
- option 2:
(The first slash represents the current drive.)<strong>\</strong>some_folder\some_file.ext
The return value from
However, there is a filter function in
It omits the “
To overcome this limitation, we can add to option 1 another “
- option 1’:
However, there is a callback function in WinRAR code (Figure 13), that is used as a validator/filter function. During the extraction process,
The callback function validates the relative path of the compressed file. If the blacklist sequence is found, the extraction operation will be aborted.
One of the checks that is made by the callback function is for the relative path that starts with “\” (slash).
But it doesn’t check for the “
We also found an SMB attack vector, which enables it to connect to an arbitrary IP address and create files and folders in arbitrary paths on the SMB server.
Example of a Simple Exploit File
We change the .ace extension to .rar extension, because WinRAR detects the format by the content of the file and not by the extension.
This is the output from
Figure 18: Header output by acefile.py of the simple exploit file.
We trigger the vulnerability by the crafted string of the
This archive will be extracted to
Creating a Real Exploit
We can gain code execution, by extracting a compressed executable file from the ACE archive to one of the Startup Folders. Any files that reside in the Startup folders will be executed at boot time.
To craft an ACE archive that extracts its compressed files to the Startup folder seems to be trivial, but it’s not.
There are at least 2 Startup folders at the following paths:
C:\Users<em>\<strong><user name></strong></em>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
The first path of the Startup folder demands high privileges / high integrity level (in case the UAC is on). However, WinRAR runs by default with a medium integrity level.
The second path of the Startup folder demands to know the name of the user.
We can try to overcome it by creating an ACE archive with thousands of crafted compressed files, any one of which contains the path to the Startup folder but with different
The Most Powerful Vector
We have found a vector which allows us to extract a file to the Startup folder without caring about the
By using the following
It is translated to the following path by the
Moreover, this destination folder will be ignored because the
Let’s analyze the last path:
The sequence “
If WinRAR is executed from its folder, the “current directory” will be this WinRAR folder:
However, if WinRAR is executed by double clicking on an archive file or by right clicking on “extract” in the archive file, the “current directory” of WinRAR will be the path to the folder that the archive resides in.
Figure 19: WinRAR’s extract options (WinRAR’s shell extension added to write click)
For example, if the archive resides in the user’s Downloads folder, the “current directory” of WinRAR will be:
If the archive resides in the Desktop folder, the “current directory” path will be:
To get from the Desktop or Downloads folder to the Startup folder, we should go back one folder “
This is the end result:
Remember that there are 2 checks against path traversal sequences:
- In the
function which skips such sequences.CleanPath
- In WinRAR’s callback function which aborts the extraction operation.
The WinRAR’s callback function checks for the following patterns:
Because the first slash or backslash are not part of our sequence “
Note: If we want to go back more than one folder, we should concatenate the following sequence “
Toward the end of our research, we discovered that
The source code for Windows (which
However, Figure 16 was taken from the source code.
We also found the Path Traversal bug in the source code. It looks like this:
Figure 20: The path traversal bug in the source code of unace-nonfree
CVE-2018-20250, CVE-2018-20251, CVE-2018-20252, CVE-2018-20253.
WinRAR decided to drop UNACEV2.dll from their package, and WinRAR doesn’t support ACE format from version number: “5.70 beta 1”.
Quote from WinRAR website:
“Nadav Grossman from Check Point Software Technologies informed us about a security vulnerability in UNACEV2.DLL library. Aforementioned vulnerability makes possible to create files in arbitrary folders inside or outside of destination folder when unpacking ACE archives.
WinRAR used this third party library to unpack ACE archives. UNACEV2.DLL had not been updated since 2005 and we do not have access to its source code. So we decided to drop ACE archive format support to protect security of WinRAR users. We are thankful to Check Point Software Technologies for reporting this issue.“
Check Point’s SandBlast Agent Behavioral Guard protect against these threats.
Check Point’s IPS blade provides protections against this threat: “RARLAB WinRAR ACE Format Input Validation Remote Code Execution (CVE-2018-20250)”