For this pwnable we've got a zip with
means we get to experience the wonders of ASLR+DEP+Win8.1
- C++ Object Memory Layout (virtual function tables)
- Windows 64bit ABI / Calling Convention
- ASLR, DEP/NX
- x64dbg obsoletes ollydbg
Running the binary ☻☻
Inputting arbitrary trash tends to crash it. Guessing the input shows the
program provides noticeably different output when given
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.
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...
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.
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
to a place we can store our ropchain. The function pointer is at
rdx is copied into both
rdi, which means we can look for any gadget
rsp=rdi|rcx|rdx. Gadget hunting will come later, because we need to
take care of...
2XXX... command saying
Decompressed is ...? We played with that
for a minute, and noticed it tended to not crash when it was fed numbers...
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!
This is where the message was being printed TO A STRING. It returns without
printing back to stdout.
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.
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!
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)
Let's take a look again at how our buffer needs to be laid out, in C pseudocode.
So, we went looking for a pivot gadget that does
rsp=rdi|rcx|rdx. In ntdll, we found:
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:
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