Fuzzing ACDSee Free 1.1.21 with WinAfl

In this tutorial we are going to learn how to use winafl since there is very little information on the Internet and it has its things. To start, let’s see what winafl is:

What is WinAFL?

AFL is a coverage-guided genetic fuzzer, which has a rock-solid implementation and intelligent heuristics that have proven to be very successful in finding real bugs in real software.

WinAFL is a fork of AFL for Windows, created and maintained by Ivan Fratric (Google Project Zero).

The Windows version uses a different style of instrumentation that allows us to fuzz closed source binaries, the instrumentators that can be used are: DynamoRio, Syzygy, Intel PT.

WinAFL is extremely effective at finding file format errors, especially in compressed binary formats (images/videos/files).

For more information and download https://github.com/googleprojectzero/winafl

Attacking ACDsee free

We are going to attack this well-known image viewer, searching I found the file where it contains the functions that parse the different image formats, it is called IDE_ACDStd.apl

Configuracion de memoria

Well now let’s see what are the functions that are used in that plugin to parse the formats for that we are going to windbg.

We use the sxe ld IDE_ACDStd command to stop it when the IDE_ACDStd plugin loads

Then we touch g and we run the program, let’s see how the plugin loads

Configuracion de memoria

We put the following command:

bm /a IDE_ACDStd!* “.echo callstack; k
L5; .echo parameters:; dc esp L8; .echo return value: ; pt; ”

This command will make it stop in each function that the plugin executes, we touch g and load the image.

Configuracion de memoria

As we see, there I stop at the first function that parses the image, which is OpenImageW. If we continue running the program, we see all the images it uses.

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

Well, we now have the functions that the program uses to parse the different image formats.

Now with these functions we are going to create the harness.

A harness is a program written by us that triggers the functionality we want to fuzz. The harness includes a function that will be used as our objective function.

And our harness the job it has to do is:

  • Open the image
  • Execute all targets functions
  • Close

  • Our harness is going to do the following job:
  • Load with LoadLibrary ACDSee.exe and IDE_ACDStd.
  • Initialize with the IDP_Init function.
  • Open the file.
  • Execute IDP_GetImageInfo, IDP_PageDecodeStart.
  • Execute IDP_PageDecodeStep, which is the one that will do the most heavy work, we are going to execute it in a loop until it returns 0xffffe.
  • Then we execute IDP_PageDecodeStop IDP_CloseImage.

Well let’s start in IDP_init we need this function to be executed to initialize global variables and other things, but the most important thing is to set the global variable flag_ini to 1 that will be used in each function of the plugin and that will check that it is set to 1 to be able to continue executing as we see in the image.

Configuracion de memoria

Well, to set the global flag_init variable to 1 we are going to go to the IDP_init function.

Configuracion de memoria

As you can see, the value returned by sub_3c6f1e0 is going to be passed to the global variable, let’s go inside that function

Configuracion de memoria

As you can see where it is highlighted in red, those two instructions are responsible for generating the value that the function is going to return. This function would seem to be a hash verification, we have control so that it returns 1.

Instruction setz cl sets the cl register to 1 if the comparison esi, eax are equal, for that in the call var_24 in the harness I created a simple function that returns 0 and saves the return in esi, then for the second function we have control it stops an argument so that it returns zero as well and returns the value zero to EAX and below, as we see, it compares ESI with EAX, as it sees that it is the same, the flag z is set to 1 and it is set to 1 to cl, then it passes that 1 to eax and what will be the return value that will be assigned to flag_init

Then that function is ready, I’m going to show a little what open_imagew also does, it takes two arguments, the first one

Configuracion de memoria

This image, as we see, first checks that the flag_init is set to 1 then passes it 1 argument that I call an object that initializes the instance that is going to be used to decode Let’s see what that argument contains.

Configuracion de memoria

We see that it contains the path of the image, the variable called p_imagen contains the opcodes of the image or whatever you say hehe, below the size of the image, then an ACDsee free function and an object that will be used to check the size of the image.

This will be used in the next function to create the instance and all that information will be passed to it.

When it enters the function, it passes argument 1 and checks what format the image is. When it finds what format it is, the instance of that image is created and then used to decode

Configuracion de memoria

The instance would look like this:

Configuracion de memoria

It will contain the Vtable that contains the functions that will be used from now on to parse the image, and as we see the first argument I will pass the pointers that I named above

Now argument 2 is where the pointer to the Object will be stored Decoder to later use it in the other functions.

The function returns the pointer to the object as we see.

Configuracion de memoria

Then we have GetImage_info which is not very relevant, it takes two arguments the pointer to the Decoder Instance and a buffer set to zero that will return the information values

Configuracion de memoria

Buf[0] is where the pointer to the decoder object is, in that if I just saw that I got the variable name wrong haha.

Then we execute the IDP_PageDecodeStart function that initializes the decoder process, loading what is necessary.

Configuracion de memoria

Then execute the IDP_PageDecodeStep function, which is the heaviest function responsible for making the decoder. This function will be executed until it returns FFFFFFFE

Configuracion de memoria

I put those PUSH ECX because if not, every time I executed the loop inside the DecodeStep function there would come a time when it would overwrite the other variables and crash, with those push I solved them.

I clarify the argument that is seen there Buf[0] that is in this and the other functions is the pointer to the decoder instance that is created in the OpenImageW function.

Then we execute the IDP_PageDecodeStop and IDP_CloseImage functions.

Well, we already have the harness, now we have to fuzz.

The first thing we need to fuzz are the test cases that afl provides us with a corpus of images https://lcamtuf.coredump.cx/afl/demo/

Then with these images we must use the tool that winafl provides us, which is afl-cmin, which is used to minimize the corpus.

Minimizing the image corpus

Why is the corpus minimized and what difference is there in sending the corpus of images without minimizing?

I’m going to put an explanation from Boken that is quite well explained:

Testcases are used to make the binary go through certain basic blocks, that is, to execute certain code. That base testcase is mutated to see if an exception is caused by those portions of code.
A fuzzer aims to cover as much code as possible, so you are interested in testcases that cause as many pieces of code to be executed.

For example...

If you are fuzzing a file parser, as is your case with acdsee, you do put a file with:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa

I probably don't get past the first basic block, which checks the magic of the file.

will see that AAAA does not match GIF, JPG, or any other magic that is used as an identifier in the first bytes of the files, and will throw an error, that testcase as code coverage (make it go through as much code as possible ) is just as good/bad as another testcase:

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBb,
another testcase, starting with GIF89a........

It will cause more code and more basic blocks to be executed, since it will pass the first check, reach the area that identifies that it is a GIF and jump to the GIF file header parser part. This has gotten more coverage and is a better testcase for fuzzing

**afl-minimize**,  what it does is eliminate all the testcases that do not increase the number of basic blocks traversed, so with the AAAAAAAAAAAAAAA ,BBBBBBBBBBBBBB, it will take the first one and eliminate the rest, THIS WAY you avoid fuzzing hundreds of thousands of times a testcase, which has already been fuzzed and saving you from wasting N times more time.

in case you have 3 testcase like these:
AAAAAAAAAAAAAAAAAAAA
BBBBBBBBBBBBBBBBBBBB
CCCCCCCCCCCCCCCCCCCCC

Well, if you do afl-minimize, it will leave you with only one, and you will have saved 3 times more time on your fuzzing, time that will be spent on 2 other testcases that have different code coverage and increases the chances of causing a crash.

We are going to minimize the corpus of images with this command

python winafl-cmin.py --skip-dry-run -D C:\DynamoRIO-Windows-7.1.0-1\
bin32 -t 100000 -i corpus -o C:\Users\nex\Desktop\result -covtype edge -
coverage_module IDE_ACDStd.apl -target_module HarnessOpimizado.exe -
target_offset 0x1529 -nargs 2 – HarnessOpimizado.exe @@

in -i we put the address of the folder where our corpus is in -o the address and name of the folder that will be created and stored the minimized test cases in -D the dynamorio path where drrun.exe is located .

Fuzzing with WinAFL

Once we have the cases minimized we are going to fuzz with win-afl with the following command:

afl-fuzz.exe -i in -o out -D C:\DynamoRIO-Windows-7.1.0-1\bin32 -t 20000 -- -
coverage_module IDE_ACDStd.apl -fuzz_iterations 5000 -target_module
HarnessOpimizado.exe -target_offset 0x1529 -nargs 2 --
HarnessOpimizado.exe @@

Well here let’s explain a little -i is the folder where we have our minimized corpus -o the folder where the campaign will be stored and our crashes will be saved in -D the path where dynamorio’s ddrun.exe is located -target_module the name of our harness and -target_offset is the offset where the function to be fuzzed is located.

Important: the function to be fuzzed has to send the path of the test case as an argument for it to work.

Well, we run the command and leave it for a few hours to see what comes out:

Configuracion de memoria

As we see, the fuzzer discovered a total of 706 new routes, as we see in total paths, and a total of 233 unique crashes, as we see in total crashes.

Let’s see some because there are too many hehe To see the crashes we are going to use windbg and a tool called msec

Configuracion de memoria

As we see, apparently this is a BOF that is reported.

Configuracion de memoria

Well, this is a little bit about using Winafl. I just started learning now. The more I learn, I will share with you more tutorials.

I want to thank Boken and all CLS who helped me with winafl, the truth is that for newbies it is a bit complicated hehe.

Written on March 10, 2020