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 fire

point_right challenge download point_left tl;dr ruby solution

Prerequisite Knowledge

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 bangbang

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-brujin sequence of the same length.
We see that rdx is set to values starting at character 200 in our input by triggering the crash again. Inspecting the code at the crash, the solver should notice that this is a typical virtual function call.

1
2
3
4
5
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 the address of 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" has the length of
the first number we pass it, and the contents are the ASCII conversions of decimal
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!

1
2
3
4
5
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 noticeable 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! (most of the time) leaves

ROP

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

1
2
3
4
5
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:

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

Which is good enough! We know rcx points to our input. 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:

1
2
[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