This post will explain the process of finding and exploiting a previously unknown vulnerability in a real-world piece of software to achieve code execution. The vulnerability was initially found in 2016 and the vendor was contacted however no response was ever received. Now several years later (March 2019 at time of writing), the vulnerability still exists in the latest version.
Please note that this post is supposed to serve as a basic introduction to file format fuzzing, I’m fully aware that the fuzzing methods used in this post are somewhat outdated. Also, if you think I have explained something badly or something is completely incorrect then please let me know.
FastStone Image Viewer 6.9 — http://www.faststone.org/FSViewerDetail.htm
Never heard of it? Me either, however for some reason it has 13.8 million downloads on CNET. Additionally, the application has been hosted on its own website for the last few years, so the total number of downloads is likely much higher.
What you will need
- 32bit Windows Windows 7 Virtual machine
- FastStone Image Viewer 6.9
- .NET Framework 4
- Immunity Debugger
- Peach Community Edition v3
- 010 Editor
- Text Editor of your choice
- fuzz.zip Containing Config/Sample/Crash files for Peach. I have also included a copy of FSViewer in case the FastStone site goes down in the future.
“Fuzz testing or fuzzing is a Black Box software testing technique, which basically consists in finding implementation bugs using malformed/semi-malformed data injection in an automated fashion.” — OWASP
File format fuzzing is relatively simple. You provide your fuzzer with a legitimate file sample, the fuzzer then repeatedly mutates the sample and opens it in the target application. If the target application crashes, something has obviously gone wrong and the mutated file is saved to be reviewed at a later date. As the original file doesn’t crash the application but your mutated file does, it is possible you have some sort of control over this crash. If you can control what is read/written/executed in memory, you may be able to take control over application flow. If you control the application flow you can make the application do things it is not supposed to.
Configuring the Fuzzing VM
The fuzzing framework we will be using is Peach Community Edition. It’s a bit outdated but should be fine for a basic introduction.
Step 1) Install everything
Once you have a Windows 7 VM set up, install all the tools listed above and extract fuzz.zip onto the desktop. Fuzz.zip will contain the following files:
- FSViewerSetup69.exe — A backup copy of FSViewer in case their site goes down in the future;
- Sample.cur — This is your test case, when running, peach will select this file, mutate it, and open it within FSViewer;
- Cur.xml — This is your peach “pit” file. This is used to configure the peach framework;
- Crash.cur — This is the original crash that Peach found;
- ExploitFinal.jpg — The Proof of Concept exploit you should have at the end of this post.
Step 2) Configure Peach
Configuring peach requires a bit more work:
- After downloading, right click the zip folder, select properties, and click “unblock” (Not everyone will have to do this);
- Extract all files and copy them to C:\peach;
- Create a folder called samples_cur (C:\peach\samples_cur);
- Move sample.cur (from fuzz.zip) into C:\Peach\samples_cur\;
- Move cur.xml (from fuzz.zip) into C:\Peach\;
- Edit cur.xml and make any necessary changes.
I have commented the important lines of cur.xml, as such everything should be self-explanatory.
Step 3) Test Peach
You can test your Peach Pit has been configured correctly by opening cmd.exe (as an administrator), and running the following commands:
C:\Windows\System32> cd c:\peachC:\Peach> peach -1 cur.xml
Hopefully you should see FSViewer open our sample.cur file once and then close. Assuming this is successful you may wish to disable your network adaptor for the VM. Some software automatically sends crash reports to the vendor. As vulnerabilities these days have a monetary value and clear legal methods exist to sell them, you may not wish to disclose the vulnerabilities directly to the vendor. 😊
Step 4) Running peach
To start fuzzing, Open cmd.exe as an administrator and execute the following commands:
C:\Windows\System32> cd c:\peachC:\Peach> peach cur.xml
Peach should begin opening mutated files in (relatively) quick succession. There are a number of steps you can take to speed up fuzzing with peach. I usually set the desktop to a blank background, reduce visual effects for the machine
(Right click “My Computer" > Properties > Advance System Settings > Select the "Advanced" tab > Performance Settings > Adjust for best performance), and kill explorer.exe. If you wish to start explorer again you can ctrl+alt+del and start ‘explorer’ as a new task).
Unfortunately the biggest bottleneck with this rig is probably the Peach Framework itself. By design Peach opens the target application, mutates a file, loads the mutated, waits to see if it crashes, and then closes the target application. Obviously, a lot of CPU cycles are wasted opening and closing the application, instead of opening multiple mutated files into the existing process. You’re also probably wondering why I chose to fuzz the cursor (.cur) file type as surely no one would trust such a random file extension? Firstly, I made the assumption that if people have targeted this software before, it was probably JPEG/BMP/GIF/other common image types, so rare image formats may be more fruitful for bugs, additionally most applications don’t use the file extension to identify file types, they use magic numbers, and fortunately for us FastStone image viewer is no different. We can rename our file to a more trustworthy extension such as .jpg and FastStone will still execute the file as a cursor and our exploit will be triggered all the same.
Reviewing our Crash
After a few hours of fuzzing you will probably have quite a few unique crashes. By default peach uses a windbg extension called !exploitable to triage vulnerabilities into unique crashes (You can find this at
C:\Peach\Debuggers\DebugEngine\MSEC). !Exploitable marks crashes as ‘exploitable’, ‘probably exploitable’, ‘unknown’, and ‘probably not exploitable’. Whilst these are useful for separating different unique crashes. The ratings should be completely ignored. !exploitable only looks at the first crash and the events leading up to it, it does not look at what happens after the exception or even later exceptions. For example, the first exception may be a read access violation and !exploitable will mark this as ‘probably not exploitable’ or ‘unknown’, however skipping this exception manually may result in a later exception where the instruction pointer (EIP) is overwritten with user controllable data (Clearly exploitable).
After reviewing the descriptions for each crash I came across the following that !exploitable has rated ‘UNKNOWN’. A sample of this crash (crash.cur) may be found in fuzz.zip:
First chance exceptions are reported before any exception handling.
eax=1101ffff ebx=0048189c ecx=75dc9e17 edx=000005f0 esi=0048189c edi=05e2afb4eip=005ac745 esp=0012f4e0 ebp=0012f988 iopl=0 nv up ei pl nz ac po cycs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210213image00400000+0x1ac745:005ac745 8b00 mov eax,dword ptr [eax] ds:0023:1101ffff=????????
As you can see it’s a read access violation (It’s trying to read from the address
1101ffff, into EAX ). Assuming we have some sort of control over the
1101ffff value, we may be able to get our own address moved into EAX. We then have to follow the execution of the application to see if this is later used for something potentially exploitable. If this were a more worthy target or I didn’t have numerous other crashes to review, I may be willing to look more into this crash, but for FastStone Image Viewer™ I am not. As mentioned before, !exploitable only looks at the first exception and the events leading up to it. So let’s load this into a debugger and open our crash file. We can skip this first exception and see if anything more interesting occurs later on.
FSViewer comes with a file browser to select images, if you have more than one crash file in a directory it may automatically crash on one of them when opening the directory, to avoid this I opted to use the command line to select specific files:
C:\Program Files\Debugging Tools for Windows (x86)>windbg “C:\Program Files\FastStone Image Viewer\FSViewer.exe” “C:\Documents and Settings\Daniel\Desktop\crash.cur”
Once windbg has loaded, type ‘g’ and press enter to run the application (alternatively hit F5). It should first break at the read access violation mentioned above. We wish to skip this so type ‘g’ (or f5) again to continue running.
005ac745 8b00 mov eax,dword ptr [eax] ds:0023:1101ffff=????????0:000> gFirst chance exceptions are reported before any exception handling.eax=00000000 ebx=00000000 ecx=1111110f edx=776d6d1d esi=00000000 edi=00000000eip=1111110f esp=0012ef98 ebp=0012efb8 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=002102461111110f ?? ???
As we can see the value for the EIP register has been overwritten by
1111110f. The EIP register contains the address of the next instruction to be executed. This means a mutation our fuzzer made to sample.cur has resulted in EIP pointing somewhere it should not. Perhaps we have some sort of control of this EIP value? This is definitely more interesting than the original crash. Let’s diff our two files (sample.cur and crash.cur) in 010 hex editor to see what changes the fuzzer actually made.
Peach has made a single change to the line beginning at
0x30. Whilst 010 editor doesn’t have a template for the .cur file format, it does have a template for the .ico format which is very similar (Simply change the the start of the file from
00 00 02 to
00 00 01). Using this we can revert the changes made by peach back to the values of sample.cur. The purpose of this is to find the minimum number of changes that still results in the same crash.
We can see that our changes all occur within the
BITMAPINFOHEADER structure. Which starts at
0x26 and ends at
0x4D. Looking closer at the file format we can see that the changes Peach made first affects
From our file diff above, we can see that the two bytes at
0x30 in sample.cur were
00 00. So let’s revert this change and test the crash again in windbg:
eax=00000000 ebx=00000000 ecx=1111110f edx=776d6d1d esi=00000000 edi=00000000eip=1111110f esp=0012ef98 ebp=0012efb8 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=002102461111110f ?? ???
Still the same crash. This process was repeated until it was found that we could then revert all of the changes except those to biBitCount to hit the same exception. Let’s call this file mincrash.cur:
Interestingly, the value overwriting EIP (
1111110f) can be found in mincrash.cur seven times (due to endianness this will be backwards, e.g.
Let’s try overwriting them with easy to identify values such as
41414141 (hex for AAAA),
42424242 (BBBB) etc, save this file under another name EIP.cur and once again, open it in the debugger (always try and keep backups of your files at each step, it solves you attempting to revert changes later on when everything stops working!). After skipping the first exception you should see the following:
eax=00000000 ebx=00000000 ecx=45454545 edx=776d6d1d esi=00000000 edi=00000000eip=45454545 esp=0012ef98 ebp=0012efb8 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=0021024645454545 ?? ???
It looks like we have been very fortunate and have complete control of the EIP register… Excellent! Revert the other changes back to their original
0f111111 values (try and keep changes to the file minimal). Using the
!exchain command we can see the current exception handler chain:
45454545 ?? ???0:000> !exchain0012efac: ntdll!ExecuteHandler2+3a (776d6d1d)0012f9a0: 45454545Invalid exception stack at 0000f011
It looks like we are overwriting the pointer to the SE handler. If you’re not familiar with SEH based exploits, now would be a great time to read the FuzzySecurity or Corelan Exploit Writing tutorials. General practice for SEH based exploits is to overwrite nSEH with a jump to our shell code and overwrite SEH with a reference to a POP POP RET. So let’s try that.
POP POP RET
First of all, we must find a POP POP RET instruction sequence in either the application itself or a loaded dll. A loaded .dll file is generally preferred as they sit at a high memory address which do not contain null bytes (Depending on the root cause of the vulnerability, null bytes may break your exploit). Fortunately for us this vulnerability doesn’t seem to be affected by null bytes. Using Process Explorer we can see that the FSViewer executable does not make use of ASLR or DEP. So let’s just use a POP POP RET from FSViewer.exe.
To find the POP POP RET sequence we can use mona.py.
0:007> .load pykd.pyd0:007> !py mona seh — — snip — -[+] Results :0x004071e7 | 0x004071e7 : pop ecx # pop ebp # ret 0x04
So let’s overwrite our EIP with
E7714000 (little endian), and save our changes to exploit.cur. From here on I will be using Immunity Debugger as I believe it is much easier to follow the exploit process visually.
Writing our Exploit
First let’s open the FSViewer binary in Immunity Debugger with an argument of our exploit.cur file
click the goto address button (subtly shown below), type the address of our POP POP RET (
004071e7), and press okay. Set a break point on POP ECX (Highlight the instruction and press F2. Alternatively, you can right click > Breakpoint > Toggle).
Now we have our break point set, we can run the program within Immunity Debugger. Due to the address we have chosen we will hit our breakpoint multiple times as FSViewer loads. Keep pressing F9 until we hit our exception (
"Access violation when reading [1101ffff"at the bottom of ImmDbg). To skip the exception press Shift+F9. We should now be back at our breakpoint and see our POP POP RET. Use f8 to step one instruction at a time whilst keeping an eye on the stack (bottom right). Each pop instruction will remove an address from the stack and save it into ECX and EBP respectively. Once we step over (F8) the return instruction we will return to
That ASM doesn’t look good at all… However, notice the opcodes:
As mentioned earlier “General practice for SEH based exploits is to overwrite nSEH with a jump to our shellcode and overwrite SEH with a reference to a POP POP RET.” So, we’ve overwritten SEH with a reference to POP POP RET which lands us at nSEH (or
0x4FE in exploit.cur). Which means we have 4 bytes spare to jump to our shellcode.
Fortunately for us, image formats are very forgiving, so we can put our shell code pretty much wherever we want provided it’s not in the header and doesn’t overwrite our POP POP RET address. I opted to use a small jump to jump over our SEH address at
0x502 , and land on the other side where we have plenty of room for our shellcode. I used a small jump of
0x26 bytes (the opcode for this is
EB 25). So, let’s replace the bytes at
0x4FE with our jump opcode and save our file as exploitJmp.cur:
Now let’s go back to ImmDbg and see this in action (Don’t forget to change the argument name to exploitJmp.cur!). Hopefully the breakpoint should still be set, if not go to
0x004071e7 and set it again. As before repeatedly hit F9 until we get to our Access Violation. Press Shift+F9 to get to our breakpoint, and step three times (f8).
ADC EAX, ESI we now see
JMP SHORT 0012F9C8 (opcode
EB 26). Pressing F8 again you will jump 26 bytes to
0012F9C8. We now have plenty of room for our shellcode. I’ll be using a simple calc.exe shellcode comandeered from FuzzySecurity’s “Writing W32 Shellcode” tutorial. I’ll also add a few NOPs at the beginning for reliability.
After making the changes and saving our file as exploitFinal.cur our file should look like this (Tip: use Ctrl+Shift+V in 010 editor to paste as hex):
Let’s test each step one last time in ImmDbg to make sure we understand what’s going on. Skipping the access violation (Shift+F9) we land at our breakpoint. Stepping three times (F8) we land at our short jump. Stepping again we (hopefully) land in the middle of our NOPsled.
After our nopsled we can see the start of our shellcode. So far so good. Hit F9 to continue execution and you should hopefully see calc.exe spawn as it did in my tl;dr video above. As previously mentioned FastStone Image viewer doesn’t rely on file extensions to identify file types, as a final step let’s rename exploitFinal.cur to a more trustworthy file type such as kitten.jpg. Who would suspect a jpeg (or a kitten)?
Root Cause Analysis
At this point you may be realising that we have managed to create a full working exploit without actually knowing anything about the underlying vulnerability (apart from it having something to do with biBitCount). In normal circumstances this lazy approach to exploit development will not work. Rarely will the EIP value be read directly from your file without some sort of modification. And on the off chance it is, overwriting values in memory (41414141, 42424242, 43434343. etc.) will either cause a different exception or force it down a separate code path that doesn’t hit our original exception. So if you only came here to see calc get popped, now is a great time to stop reading!
After a while of searching I found a call to
0x0040B9F8 that appears to be reading our cursor file.
ReadFile() perform 4 reads from our file before the exception occurs. The sizes of which are 0x6, 0x10, 0x28, and then 0x800 bytes. After looking at the CUR/ICO file format the first three reads make perfect sense:
ICONDIR Structure (0x6 bytes)ICONDIRENTRY Structure (0x10 bytes)BITMAPINFOHEADER Structure (0x28 bytes)
As we know from our original file diffing , the only change is to the
BITMAPINFOHEADER, where we changed the value from
0x3089. So let’s skip to the
0x28 byte read and see what happens to our value.
PUSH EAX ; |pBytesRead = 0012F6F8PUSH EDI ; |BytesToRead = 28 (40.) PUSH ESI ; |Buffer = 0012FB20PUSH EBX ; |hFile = 0000025C (window)CALL <ReadFile>
After the call to
ReadFile() we can clearly see our value has been read into the buffer at
Stepping through the code we see that at
biBitCount value from our fuzzer(
0x3089) is read into EAX and saved at EBP-34:
005AC6FD our value of
0x3089 is then read into ECX. 1 is moved into EAX, and then something interesting happens. A logical shift occurs on EAX to the left by CL bits. As we can directly control CL (currently with a value of
0x89), this results in
shl 1, 89.
This results in a very large value (
0x20000000000000000000000). This number is significantly larger than the maximum positive value that can be stored in a 32-bit signed integer. This obviously cannot be saved into EAX, so what does happen?
Stepping over this instruction we see EAX now has a value of
0x200 which is then stored at ebp-40. Consulting the x86 guide, it can be found that:
“shifts counts of greater than 31 are performed modulo 32”
005AC72E our value of
0x200 is moved into ECX, before another shift left is performed on it, this time with a shift of 2 bits. So ECX now holds our new value of
Stepping into the call at
005AC73F (by pressing F7) and stepping once (F8), you will arrive at a call to
0040B9E4 step into this call too. We can now see the call to ReadFile().
Looking at the parameters we can see that
ReadFile() will be reading
0x800 bytes into a buffer at
0012F720. This means we can directly control the number of bytes being read into the buffer. The process looks like this:
1. ReadFile() reads 0x28 bytes, including our controlled value of 0x3089 , and places it onto the stack;2. Our value is then used to perform a logical shift (SHL 1, 89) resulting in a controlled value of 0x200 being saved onto the stack;3. Another SHL is performed (SHL 200, 2) Resulting in a value of 0x800;4. This value (0x800) is then passed to ReadFile() asthe “BytesToRead” parameter;5. 0x800 bytes are read into the buffer at 0012F720.
If you think back to the start of this post you will also remember that the cause of the crash was due to Peach modifying
0x3089. For our non-mutated sample.cur the process would be the following:
1. ReadFile() reads 0x28 bytes, including the 0x0001 biHeight value;2. SHL 1, 01 resulting in a value of 0x02;3. Another SHL is performed (SHL 02, 2) Resulting in a value of 0x08;4. 0x08 is then passed to ReadFile() as the “BytesToRead” parameter;5. 0x08 bytes are read into the buffer at 0012F720.
As no bound checking appears to be going on, the buffer is likely expecting
0x08 bytes, but instead receives
0x800. So finally, let’s look at the SEH Chain before and after the call to
I like to think this post sits somewhere between the basic “Send AAAAA until EIP=414141” tutorials and the more hardcore memory corruption write ups. Obviously it’s still a basic overflow issue, but it does cover a number of things that authors often skip e.g. how the original issue was found, and the root cause of the vulnerability. I’m purely a hobbyist when it comes to memory corruption issues so I suspect there’s a number of technical errors within this post, if you do spot any issues then please contact me and i’ll try and fix them.
I hope you enjoyed my MSPaint artwork!
If you enjoyed this post and want to read more similar write-ups then please check out the following links:
- https://www.corelan.be/index.php/articles/ (Exploit Writing Tutorials)
- 2016: Issue discovered and reported to vendor
- 2017: *Crickets*
- 2018: *Crickets*
- March 2019: This blog post detailing vulnerability published