Tuesday, September 6, 2016

FLARE On 2015 - Challenge 10


This is the 10th challenge from FireEye's 2015 "FLARE On" challenge (http://flare-on.com/)


To me, challenge #10 was both the most challenging and most interesting of all the challenges yet.

The level starts out with an exe file that doesn't seem to do anything when executed from the terminal:

Rather than go straight to IDA, this time I decided to take a look with a common malware sandbox first. The most interesting take away from the output was that the file (allegedly) dropped a few files to the System32 directory:

The strange thing is, I couldn't find those files on my system after execution....

Breaking out ProcessMonitor quickly showed the problem :)

Ok, so re-running the EXE as an administrator gave a much nicer output:

So now let's take a look at the original EXE and those dropped files (ioctl.exe and challenge.sys) in IDA...

It's immediately clear that the original EXE is an AutoIt-compiled script (or something made to look like an AutoIt-compiled script...), but unfortunately I wasn't able to find a decompiler to use...

Another thing that's quickly clear is that challenge.sys is a kernel driver that starts out simple...

... but ends up being quite complex:

ioctl.exe seems to be a simple piece of code that triggers an event and calls DeviceIoControl with an argument that's passed in via the command line:

As it turns out, ioctl.exe requires this argument or it'll crash (first execution below succeeded, second one failed):

Debugging the execution made it clear that the issue was with the call to _strtoul (below) -- so putting two and two together let's us know that it's using this value as the ioctl code that it sends to the kernel driver:

To confirm all this, I took a look through the ProcessMonitor logs from the original execution, and it looks like after the driver & ioctl.exe files are dropped to disk, ioctl.exe is called with the argument "22E0DC" -- this must be our code!

So now we're in a pretty good place as far as our understanding of what's going on.

We still don't know exactly what we're looking for, but a good place to go next would be to try to analyze exactly what happens when the driver is sent the IOCTL with the correct code.

To do this, the three options that first come to mind are:
  1. try to debug the challenge.sys driver as it runs live on the system
  2. look through the disassembly of challenge.sys in IDA and try to determine the behavior via static analysis
  3. hack the driver code to the point where we can run it directly in user-space and debug it as if it were a traditional .exe/.dll (turns out this was done here: http://www.ghettoforensics.com/2015/09/solving-2015-flare-on-challenges.html -- pretty cool...)
I decided to hope that static analysis would be enough and went with (2) but quickly decided to switch and do the full (1) and live-debug the running driver instead.

I hadn't debugged a kernel driver before, but the StackOverflow answer here was very helpful with getting things set up. (You'll need to use msconfig instead of bootcfg to set the boot params on Windows 7 images, though.)

Once I had everything set up and brought up both virtual machines, I wanted to make sure I could detect the loaded challenge.sys driver, so I ran the following:

Woo hoo! That looks pretty good... We're missing the symbols, but that's expected.

Now, ideally we could find a good place in our kernel module, set a break point for it, and live-debug the module's code.

I decided to set a breakpoint the part of the code where all the jump table entries jump to after they call their included subroutine, hoping that the flag might be in memory somewhere after the driver is called with the correct code:

So now we know our offset within the driver (0x29D468), we just need to know the location our driver's been mapped to in memory.

I found this using Process Hacker, within System's modules:

You can see our final address needs to be the location we see in IDA + the base address from Process Hacker - 0x10000 (the default base address IDA used):

So this is good... we can double check that we have the right address by test disassembling the code in that location in WinDBG, too.

Unfortunately, after hitting the breakpoint and checking out the process's memory, it turns out that the flag is NOT present anywhere.... so it looks like there's still a bit more to do.

Let's trace the code to see what function we call when we go through the jump table this time.

It turns out you hit this rather interesting function:

You can see that it seems to be doing bit checks, which, after reversing the logic, ends up being valid for the string: try this ioctl: 22E068

This looks encouraging again!

So now let's retrace our way through the driver code after it's been triggered by the new ioctl.

It turns out we get sent to another monstrous function. This one seems to do some kind of decoding/decryption, however much of the logic doesn't seem to have any global effect.

Tracing down to the end of the function, we can see a reference to the offset byte_29F210 being passed as an argument to what looks like it could definitely be a decryption function, so that's worth watching.

After reviewing parts of the code above that, it becomes clear that many of the jnz's are being triggered off of registers hard-coded to 0:

 This logic causes our global buffer to always be uninitialized.... However what if we patch the code's memory to take the loops instead?

This can be done many ways, however for our purposes we can use WinDBG's .writemem/.readmem.

Replacing the mov [ebp + var_62], 0 instructions with mov [ebp + var_62], 1 initializes the buffer, and if we trace & watch the memory while it's decrypted, we get the following email address:


No comments:

Post a Comment