Thursday, July 24

Atredis BlackHat 2014 Contest After Action Report

<Trigger warning: spoilers ahoy! If you’re still playing and don’t want to read it, STOP NOW!>

We had an extra BlackHat pass lying around this year so decided to play a little game to give it away. We figured we’d shoot for a pretty easy challenge, given that we’re so close to the actual conference, so intentionally did a few things to make it easier on you. This is my explanation of how I’d have gone about the challenge, with a few notes along the way about what’s going on under the hood.

Next year, we’ll make it a lot harder, and release it in ~April so people can convince their bosses to buy them cheap airfare to Vegas.

The first three correct answers came in at 1h28m, 1h51m, and 2h00m. Congrats to Cloudchaser, steponequit, and anon!

So, on to the binaries.

When you first download the file at the link, you receive a simple PDF, Contest.pdf.

But wait, there’s more!

The thing with the PDF file format is that it’s extremely forgiving. Its contents go between a header in the first few bytes until it reaches a %%EOF, and will happily allow garbage to be appended to the end.

Conversely, ZIP keeps its “header” at the end of the file, and the standard unzip utility will ignore garbage in front of the real one, so you can simply combine the two files together and readers for both PDF and ZIP file formats will be (mostly) happy.

[Aside: you can stuff the ZIP file in a real PDF object trailing after the EOF marker and gain full PDF compliance, like the PoC||GTFO’s PDFs which are simultaneously ZIPs, PDFs, Truecrypt binaries, Angelic loot fairies, and will even make you a sandwich. Likewise, I’m pretty sure you can tailor the ZIP headerfooter to tell it at what offset the actual ZIP file starts, but #lazy. We didn’t mess with modifying the PDF or ZIP files because they worked fine, other than the warning message shown above in unzip.]

On another note, some folks ran binwalk on the PDF and found the .zip appended to it that way. Not a bad idea.

Yeah yeah, a very simple stage 0, so simple I hesitate to call it anything other than our marketing guy’s attempt to make you look at our logo. (But what a pretty logo it is!) Before I digress too much further, let’s move on.

[stage 1]
Ok, so now we have this new file, bouncilicious. What is it?

Since we’re pretty sure we’re working with a Linux binary, let’s go ahead and switch over to Linux and run it to see what’s up.

So it echoes your favorite song in the world, takes some input, and ends. Is this a super easy challenge that has a ridiculous buffer overflow in it?

Why yes, yes it is! Let’s load it into GDB and take a look.

OK, so we immediately have control of EIP (and EBP). When we ran file, above, it told us the binary still had symbols in it, so what are those?

So either this binary only has three functions in it or the symbols for all the rest were stripped out. The three functions with symbols are blackhat, goto_blackhat, and main.

Huh, I want to goto_blackhat. Do you want to goto blackhat? What happens if we just try to go to it, via gdb? (Or look up its address and stuff it in our input string.)

OK, that na├»ve idea didn’t work. Let’s back up and take a look at those functions whose symbols weren’t stripped.


Wow, the assembly in these things is so clean. It’s almost like someone disabled all gcc optimization to make it easily readable. That someone must be a nice guy, or a guy I hate, because he made this too easy.

Here, we have main loading some variables on the stack and making a call to mmap. IDA helpfully told us its args, so we know it’s mmap’ing a 0x1000 byte region near 0x24242424 (we’re having mmap automatically page align for us) with some flags.

Assuming mmap doesn’t fail, it goes here:

Which copies 0x13 bytes from goto_blackhat to the value stored in EAX, which was 0x24242424 (“$$$$”). OK, so we know that goto_blackhat is copied to 0x24242424. Let’s save that knowledge for later.

Looking at sub_0804A9FA we can see _gets is called with absolutely no checking, and an overflow occurs.
.text:0804A9FA                 push    ebp
.text:0804A9FB                 mov     ebp, esp
.text:0804A9FD                 sub     esp, 28h
.text:0804AA00                 mov     dword ptr [esp], offset a31myoILlTellYo ; "\x1B[31mYo, I'll tell you what I want, "...
.text:0804AA07                 call    _printf
.text:0804AA0C                 lea     eax, [ebp+s]
.text:0804AA0F                 mov     [esp], eax      ; s
.text:0804AA12                 call    _gets
.text:0804AA17                 leave
.text:0804AA18                 retn
[Note: I think one solver didn’t notice the BOF and just patched the binary to bounce everywhere needed in the binary to solve this challenge. Word.]

So let’s take a look at the function that was copied to 0x24242424.


Here, we can see it simply loads the address for blackhat and jumps to it. Moving onwards…


And so now we land at blackhat, which has this check in the first chunk of code:
<snipped: prologue, set up the stack>
<snipped: call a couple of functions>
<snipped: load some strings into variables>

.text:0804A8F1                 mov     eax, ebp
.text:0804A8F3                 add     eax, 4
.text:0804A8F6                 mov     eax, [eax]
.text:0804A8F8                 cmp     eax, 24242424h
.text:0804A8FD                 jz      short loc_804A917
.text:0804A8FF                 mov     dword ptr [esp], offset s ; "Oops, that's not the money shot! You ne"...
.text:0804A906                 call    _puts
.text:0804A90B                 mov     dword ptr [esp], 1 ; status
.text:0804A912                 call    _exit
This is the part of the code that implements the error message shown above. Here, don’t scroll back up, I’ll paste it again:

To understand what’s being compared against 0x24242424 at 0x0804A8F8, you have to backtrack from the value stored at [EAX] at 0x0804A8F6, which is the value stored at [EBP+4]. Right after the overflow, leave/retn pops 0x24242424 into both EBP and EIP, gaining execution flow. In goto_blackhat's prologue, EBP is pushed to the stack, fixed with a valid EBP (ESP was not under user control via this path) and then the code jmp's to blackhat.

In blackhat, then, the address from the time of the overflow that's now on the stack is dereferenced and compared against 0x24242424. As such, if you didn't stuff this value on the stack yourself, or modify EBP prior to hitting goto_blackhat, Do Not Pass Go and the application quits.

Regardless, from talking to folks who completed the challenge, many (most?) people didn’t know what was actually being compared here, and just made an educated (or uneducated) guess to bypass this check or nop’d the check or patched the jump or swapped the eflag, which is basically what was intended at the outset anyway. (Note to self: if you’re trying to be sneaky, don’t implement your sneakiness in a fashion that gives people the info they need to just guess and bypass the whole damn check without understanding what it’s doing.)

Moving on, if your EBP was correct at the time goto_blackhat was called, it outputs those strings that were loaded at the beginning of the function:

… and then falls into these two loops down here:

For now, you can ignore what this does, other than that it calls _write with some data. (And if you’re really perceptive, you’ll notice it wrote to FD 2, which is STDERR.)
.text:0804A9A4                 mov     [esp+4], eax       ; buf
.text:0804A9A8                 mov     dword ptr [esp], 2 ; fd
.text:0804A9AF                 call    _write
[Note: we considered several ways to hide the goto_blackhat mmap/memcpy, stripping everything, inlining everything, forcing you to ROP or change memory permissions yourself, dynamically generating the two echoed values, seeding the decryption routine with EBP, etc. Marketing put the kibosh on that, as there was concern no one would spend the time to solve it before BH actually arrived. Plus, only malware reversers (and warez scenesters) would figure it out, and there are too many of you at BH already. ☺ We hope you enjoyed this easier version.] [Just kidding, we love you all.]

[Another Note: This big loop does some relatively simple decryption of the embedded buffer from a relatively nasty key computed with two modified SHA1 hashes with a non-standard TEA and other ridiculousness. We had one solver who RE’d these functions and manually decrypted the buffer. Basically, the key generation part was fixed in code with no seeds from process state, so grabbing that key and then manually decrypting was not hard once you saw what it was doing.]

Ok, so we need our EBP to be 0x24242424, and we saw in main, earlier, that the bounce function goto_blackhat was copied to 0x24242424, so our input string is really easy. Dolla dolla billz, yo:

When you press enter, it dumps a bunch of binary data to STDERR, stage 2.

[stage 2]

Great, another PDF.

This brilliant haiku is a hat tip to our wonderful neighbors over at PoC||GTFO, specifically their 3rd edition that was released a couple of months ago. When you track it down, you’ll find a fantastic article on Angecryption, which is a way to create a binary that, when encrypted with a specific key and IV, generates a new file.

[Several people got stuck here. They briefly skimmed the angecryption article and just tried to decrypt the PDF (or the garbage at the end of the PDF) with the key/IV values given at the end of [stage 1]. Angecryption is madness because you don’t decrypt to get the plaintext, but encrypt it. I heartily recommend you go read that (and all other) PoC||GTFO articles over at any of the mirrors, such as]

Googling for Angecryption, you’ll find the googlecode site hosting it (which contains an example encryption python script), or you could have just read the paper and done the encryption by hand:

This dumps yet another PDF:

…and this time it’s simply a wrapper for a zip file containing an mp3. (As currently written, Angecryption only supports a few filetypes, and we didn’t mess with implementing a zip or mp3 file; we simply reused the same PDF+ZIP file trick we used at the beginning.)

This mp3 instructed you to email us at a supersecret address, but I won’t tell you, just in case anyone else still wants to play along.