# acdsee article ###### tags: `apriorit`, `article`, `acdsee` # Finding vulnerabilities in colsed source software on Windows ## Preambula Fuzzing has prooven to be very effective techique of disovering bugs, I would say, nowadays you should integrate fuzzing into your product's SDL, in order to catch bugs which can't be reached by classic code QA techniques like: code review, source code annotations, unit-testing, etc... In this article I'll show you how we can find vulnerabilities in closed source applications using coverage guided fuzzing on Windows platform. We'll leverage [WinAFL](https://github.com/googleprojectzero/winafl) and consider [ACDSee Photo Studio Standard 2019](https://www.acdsee.com/en/products/photo-studio-standard/) as an example. Which is 64-bit binary and has version number 22.1.0.1159 at the moment of writing this paper. ![](https://i.imgur.com/JPcxtm3.png) In case you don't know what ACDSee is, let's check out [wiki](https://en.wikipedia.org/wiki/ACDSee) >ACDSee is an image organizer, viewer, and image editor program for Windows, macOS and iOS, developed by ACD Systems International Inc. > I'm interested in particular in viewing feature of an app, I think the app should implement image parsing functionality itself which I aim to fuzz. ## The theory First of all, what is [fuzzing](https://en.wikipedia.org/wiki/Fuzzing)? > Fuzzing or fuzz testing is an automated software testing technique that involves providing invalid, unexpected, or random data as inputs to a computer program. The program is then monitored for exceptions such as crashes, failing built-in code assertions, or potential memory leaks. > In our case, the goal of fuzzing is generating a lot of images and opening them via image viewer. Let's assume we can generate a lot of samples, for example placing random data into the image file internal structure or data. If we open them one by one it would be kind of inefficient, because we don't have any information about app behaviour, I mean coverage of an app. If we could get app's coverage we can create more intelligent mumator, hence we can increase overall efficiency of fuzzing. Here is where [AFL](http://lcamtuf.coredump.cx/afl/) is especially good at. It implements mutation of input data based on coverage of an app. To get the coverage, AFL instruments the binary during compile time. In our case, we don't have source code, thankfully there is great branch of AFL called [WinAFL](https://github.com/googleprojectzero/winafl) which relies on coverage gathered by [DynamoRIO](https://www.dynamorio.org/). WinAFL supports others methods of instrumentation as well, such as [IntelPT](https://github.com/googleprojectzero/winafl/blob/master/readme_pt.md) or [Syzygy](https://github.com/googleprojectzero/winafl/blob/master/readme_syzygy.md) but we will use DynamoRIO. ![](https://i.imgur.com/opxWJFF.png) * `ACDSeeStandard2019.exe` -- is target process I hope the whole idea should be clear now and we can start to search entry point for fuzzing i.e. create a harness. ## Reversing, searching for attack surface WinAFL already has a mutator and coverage analyzer, our job is only to deliver his power to an app. Let's pop the hood of the app and see what we can do. What we are looking for is the code which actually parses the input file. As reverse engineer, you can use any approach that you like and familiar with. Here is some of them: * Static analysis with tools like [IDA Pro](https://www.hex-rays.com/products/ida/), [Ghidra](https://www.nsa.gov/resources/everyone/ghidra/), [radare2](https://rada.re/r/), etc. * Debugging with [WinDBG](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools), [x64dbg](https://x64dbg.com/#start). Setting up the breakpoints and analyzing the parameters of functions in runtime is quite helpful. * Use auxiliary tools like: API monitors, process monitors, coverage tools, etc. The good starting point might be analyzing stack call using [ProcMon](https://docs.microsoft.com/en-us/sysinternals/downloads/procmon) on file's read. Just add filter for input file and open in in the viewer. ![](https://i.imgur.com/XnRKf7F.png) You can notice that weird module `IDE_ACDStd.apl`. It actually exists in `Plugins` folder and it's just a regular .dll executable with very interesting exports: ![](https://i.imgur.com/xCKwsPJ.png) Indeed, `ACDSeeStandard2019.exe` levereges some plugin system and it seems `IDE_ACDStd.apl` is just a regular plugin which is responsible for handling most popular image formats. Actually we can even find a manual for it! ![](https://i.imgur.com/9xiloet.png) Of course it's not SDK manual, but at least it describes what is capable of. Having SDK for plugins would mean that we can easily write a harness for fuzzing. But unfortunately it's not the case. ## Writing the harness > The harnees should be able to properly load, process and close the image using exports of `IDE_ACDStd.apl`. Harness will specify entry point for fuzzer, providing function which is called on every fuzzer's iteration. In other words when WinAFL generates new input, it leverage harness to check if that input generates new coverage on the target binary. Let's analyze which exported functions of `IDE_ACDStd.apl` gets executed when image is opened. I've done that by leveraging IDAPython's breakpoints: ```python import idautils condition = """ print("In BP: %s") return False """ def bp_mark_exports(): for exp_i, exp_ord, exp_ea, exp_name in idautils.Entries(): address = get_name_ea(0, exp_name) print("[+] Set BP: 0x%x, %s" % (address, exp_name)) add_bpt(address, 0, BPT_SOFT) enable_bpt(address, True) SetBptCnd(address, condition % exp_name) bp_mark_exports() ``` We can clearly see this pattern: ``` ... In BP: IDP_OpenImageW In BP: IDP_GetImageInfo In BP: IDP_GetImageInfo In BP: IDP_GetPageInfo In BP: IDP_PageDecodeStart In BP: IDP_PageDecodeStep In BP: IDP_PageDecodeStep In BP: IDP_PageDecodeStep ... In BP: IDP_PageDecodeStep In BP: IDP_IsAutoRotated In BP: IDP_IsAutoRotated In BP: IDP_PageDecodeStop In BP: IDP_CloseImage ... ``` What we can see here is next: * The image is opened with `IDP_OpenImageW` * We can see `IDP_PageDecodeStart` at some point * We've got a lot of `IDP_PageDecodeStep` calls, this is the place where "heavy work" is done * In the end we can see `IDP_PageDecodeStop` and `IDP_CloseImage`. The sequence is pretty clear, but there are still at least two issues preventing us from writing the harness. We can see check in every function this check: ![](https://i.imgur.com/SERgNjF.png) I've named that global variable `g_isInit` because if it's not set, then the code isn't working. Using cross-references we can see, that the global variable is set to `1` only once in `IDP_Init`. When `IDP_Init` is called, some randomly looked data passed to as parameter, `IDP_Init` checks that data using some hashing and sets `g_isInit` to `1` if everything is ok. We can simply patch that check. The second issue, we don't know the parametes for all the functions we're interested in. So we need to reverse it. Here is the snippet of what we should get in the end: ![](https://i.imgur.com/cfTyASC.png) `imageClass` is instance of image, we can should use it as parameter for subsequent calls and we should close it with `IDP_CloseImage` in the end. `fc` is the input structure that we must to fill in: ![](https://i.imgur.com/LEG6VtB.png) The crucial fields here: * `imageData` points to raw data of the file * `imageSize` is file's size * `checkSizeCallback` is callback function which is called when plugin's code need to check the size, instead of direct size check using `imageSize` member of this struct, it will call this callback and pass checked value as a parameter for it. * `self` should points to the start of this structure. ![](https://i.imgur.com/3M1UyxG.png) I won't provie the full sources of the harness in order not to cause any damage to the vendor of the product, but if you've read up to this point carefully, you should understand how it works. The harness is doing only this: 1. Loads the `IDE_ACDStd.apl` library, calls `IDP_Init` as we discussed previously. 1. Read image data from the fuzzer using command line path to the file. 3. Call `IDP_OpenImageW` and `IDP_PageDecodeStart` to initialize the iteration. 4. Call `IDP_PageDecodeStep` multiple time, until it returns an error. 5. Call `IDP_PageDecodeStop` and `IDP_CloseImage` to free the resources. ## Fuzzing itself Finally we're moving to the most interesting part of our journey - fuzzing itself. Usually, at this point we need to gather input corpus, minimize it and use it as "seed" for mutator. The good starting point might be AFL's [demo testcases](http://lcamtuf.coredump.cx/afl/demo/). But it turns out, this software is sometimes crashing on it already. So, let's try less efficient but more interesting approach for educative purposes. What if we don't provide any input corpus? Well, literally speaking you can't do it, because WinAFL won't run with empty input folder. I mean, we can provide some fictitious input file, for example file contaiting "123" and wait for mutator to generate files for us that trigger some coverage in target file. Eventually, that new files should look like images. The command line to kick off the fuzzer was like this: ``` afl-fuzz.exe -D z:\s\tools\dr70\bin64\ -i in_none -o out_none -timeout 15000+ -- -target_module harness.exe -target_method parseFile -coverage_module IDE_ACDStd.apl -- z:\s\acdsee\harness.exe "C:\Program Files\ACD Systems\ACDSee\22.0\PlugIns\IDE_ACDStd.apl" @@ ``` I've also used basic block type of instrumentation instead of edge, because I think it is not so crucial in particular case. > Also, you might want to enable [page heap](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags-and-pageheap) > ![](https://i.imgur.com/KROwQWe.png) I found some crashes after ~10-20 minutes of multithreaded fuzzing! It was TGA files: ``` $ file id_000004_00 id_000004_00: Targa image data - Map 8224 x 8224 x 32 +65280 +6104 - top " b \005 " ``` I ran 3 fuzzing sessions overnight on my laptop powered by 8 threads. You can use `winafl-plot.py` to generate nice reports. Here is how fuzzing report looks like (for one instance of fuzzer over one night): ![](https://i.imgur.com/VY8Btwp.png) ![](https://i.imgur.com/4OtzYBN.png) The most interesting metrics are: * *total paths* -- is the number of unique coverage paths detected by DynamoRIO's instrumentation; * *unique crashes* -- is the number of crashes, uniqueness is counted based on coverage, it can boil down to the only a few unique assembler lines which cause a crash in the end; * *execs/sec* -- is the number of iterations per second; I gathered 1812 unique testcases from WinAFL. I've tried to sort them based on linux's `file` tool output: ![](https://i.imgur.com/j5DEr6N.png) ``` total: 1812 unrecognized: 944 PC bitmap: 383 TGA: 355 TIFF: 99 PCX: 25 PIC: 6 ``` ## Summary Looking at those crashes manually isn't feasible. You might use tools like [!explitable](https://archive.codeplex.com/?p=msecdbg) or [BugId](https://github.com/SkyLined/BugId) to triage them. I've used `!explitable` and 1812 testcases narrowed down to 84 reports, 27 of them was marked as "EXPLOITABLE". ![](https://i.imgur.com/f00Np8m.png) --- * *unrecognized* -- means that `file` tool failed to recognize the format or gives false positive. * bugid, !exploitable TODO: "bb vs edge" insert ![](https://i.imgur.com/o1JotwZ.png) ![](https://i.imgur.com/mZKD9tz.png) ``` WinAFL->>DynamoRIO: spawn process DynamoRIO->>ACDSeeStandard2019.exe: instrumenting target binary Note over ACDSeeStandard2019.exe: all basic blocks are instrumented now ACDSeeStandard2019.exe-->>DynamoRIO: process ready loop Note left of WinAFL: AFL's mutator WinAFL->>DynamoRIO: produce new file DynamoRIO->>ACDSeeStandard2019.exe: run ineration Note over ACDSeeStandard2019.exe: collecting coverage data Note over ACDSeeStandard2019.exe: process input data ACDSeeStandard2019.exe-->>WinAFL: coverage trace Note over WinAFL: analyze coverage, create new input WinAFL-->>DynamoRIO: produce new file... Note over DynamoRIO: ... end ``` keywords: * finding vulnerabilities in software ## attack surface ![](https://i.imgur.com/BgYEFPS.png) ![](https://i.imgur.com/wrcG9Tu.png) ![](https://i.imgur.com/biE3fUg.png) ![](https://i.imgur.com/bakYLKk.png) ![](https://i.imgur.com/sCFg2Zx.png) last session: ![](https://i.imgur.com/kNuzRkj.png)