Skip to content

Instantly share code, notes, and snippets.

@dwendt
Created July 16, 2015 07:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dwendt/00f82c62ae1318dbc424 to your computer and use it in GitHub Desktop.
Save dwendt/00f82c62ae1318dbc424 to your computer and use it in GitHub Desktop.
defcon15_quals_thing2.md

LegitBS Quals 2015 - thing2 (4pts)

For this pwnable we've got a zip with AppJailLauncher.exe and thing2.exe. This means we get to experience the wonders of ASLR+DEP+Win8.1 🔥 tl;dr ruby solution

Prerequisite Knowledge

  • C++ Object Memory Layout (Virtual Function Tables)
  • Windows 64bit ABI / Calling Convention
  • ASLR, DEP/NX
  • ROP

Running the binary ☻☻

Inputting arbitrary trash tends to crash it. Guessing the input shows the program provides noticeably different output when given 1, 2, 1XXX..., or 2XXX... where X is anything.

We noticed the author for this challenge is someone who has written many previous challenges, which all take input in this number-based command format.

input example

Inspecting the easy crash ‼️

An access violation reading from an address we control in rdx was simple to trigger, and it seems like a lot of different inputs will trigger it...

crash screenshot

So we modify our input to be a de-bruijn sequence of the same length. We see that rdx is set to values starting at character 200 in our input.

mov rax, qword ptr ds:[rdx]
mov rsi, rcx
mov rcx, rdx
mov rdi, rdx
call qword ptr ds:[rax+8]

We want rdx to point to a memory location 8 bytes before a pointer to code we want called. This should be a pivot gadget that will set rsp to a place we can store our ropchain. The function pointer is at [rdx]+8 and rdx is copied into both rcx and rdi, which means we can look for any gadget that does rsp=rdi|rcx|rdx. Gadget hunting will come later, because we need to take care of...

Defeating ASLR

Remember the 2XXX... command saying Decompressed is ...? We played with that for a minute, and noticed it tended to not crash when it was fed numbers...

showing predictability

The astute reader will notice the text after "Decompressed is" is the length of the first number we pass it, and the contents are the decimal ASCII values of other numbers we pass it.

At this point, I went and wasted a ton of time reversing how this was done figuring there was just some string-based infoleak... but it was easier than that!

safe printf to string

This is where the message was being printed TO A STRING. It returns without printing back to stdout.

then a wild printf appears

The Decompressed is <data> string is passed as the format argument to printf. It's a simple format bug from there on out, that we can then use to leak pointers off the stack by encoding %p over and over.

example dump

In this, we can note some pointers to various module executable code sections and use them to calculate base addresses of the modules. This is a very naive solution that makes our exploit very version-dependent!

ntdll_base =   leaks[48] - 0x15444
k32_base =     leaks[42] - 0x13d2
thing2_base =  leaks[19] - 0x9200
msvcp_base =   leaks[7]  - 0x4fd00
msvcr_base  =  leaks[30] - 0x209eb

It's also noticable that when a previous 1XXX... command has been run, a pointer to the heap where it was allocated will be leaked here. This was found entirely coincidentally by inspecting the stack here in a debugger, as we were trying to see if anything pointed to our input. It should be noted that this heap memory is free at this point.

"free heap memory of a controlled size" is important, as when we trigger the crash we can allocate this same size -- causing our input to exist at a location we know! 🍃

ROP

Let's take a look again at how our buffer needs to be laid out, in C pseudocode.

struct input {
    void* ropChainStart; // we pivot RSP to here. this gadget needs to clean the next pointer off the stack
    void* ropChainPivot; // first thing that gets called
    void* carryOnAsNormal; // rest of the chain
};

So, we went looking for a pivot gadget that does rsp=rdi|rcx|rdx. In ntdll, we found:

mov rsp, [rcx + 0x98];
mov rcx, [rcx + 0xf8];
jmp rcx;

Which is good enough! Note that the offset to this is at ntdll_base + 0x93ab6, which means it is likely to change across win8.1 patch levels. So let's take note of our buffer structure:

[ropChainStart][ropChainPivot][A*136][ptr to set RSP to][A*88][first gadget after pivot][rest of chain]
               ^ 8            ^ 16   ^ 152 (0x98)             ^ 248 (0xf8)

That's pretty much the gist of it. We know the location of our buffer in memory by allocating a large buffer using the 1 command, running the 2 leak, and then sending our buffer of the same size. The rest of the chain uses msvcr's: fopen, fread printf, and flushall to get us back the key.

I noticed that things were slightly janky, and this exploit may have only worked when I decided to switch to using \x00 as filler for my unimportant buffer data. I wasted a lot of time reversing some hash table scheme that I believe was the actual point of the challenge.

Here's the full code: https://gist.github.com/dwendt/f0fcec6f8f48ad53bedb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment