04 - DiskSavvy Enterprise 10.4.18 BoF SEH (and how I finally became not so bad at it)

I decided to make a whole new post in regarding DiskSavvy, as the others were all over the place.  This post will focus on the working PoC, now that I have it, and explain some of the tribulations and pitfalls I experienced during it.

0x01 - Locating a Crash

Alright, so in the past we've located a relatively easy crash-- after performing a three way handshake, the application sends a packet requesting some server information.  This packet'ssends the following ASCII string: 'SERVER_GET_INFO', prepended by a few headers, and appended by some more data.  This whole interaction can be seen in previous posts, namely post one and two, where I go as in depth as I was able at the time with my fairly limited knowledge.  Initially, I found a location in a portion of the headers that resulted in a crash that I was pretty psyched on, but after bashing my head against the keyboard for an undetermined (read: unreleased) amount of time, I found I wasn't sure I was able to take advantage of this crash the way I wanted.

Below, I'm including these headers so we can break them down a bit:

0x1A000000  # 26 bytes
0x20000000  # delimiter?  Ehh, who knows.

Alright, the first line (I'm assuming) is an initial header based on 'type'.  As is typical with protocols and files alike, in many cases, the initial entries in files tend to be headers that define the type of data that follows.  DiskSavvy doesn't have a defined RFC, so it does remain to be seen it's exact purpose, but it's fair to assume it's a definition.

Lines two and three, I'm honestly unaware of.  Through some pretty thorough testing, which was a large part of my fuzzing, I found that these two are absolutely necessary to be present and packets not containing them were simply dropped.  Obviously, that leads me to believe that they need to stay.

Lines four and five.  Here is where I hit my first crash (woo) but was unable to take advantage of.  As far as I could tell, line five was paramount and the application would not crash without a space.  The value contained in line four was plugged directly into ECX.  ECX is used as the general register for counting.  Register for counting.  Hmm.  Yeah, as you'll see, looking at my previous posts, I completely overlooked this idea, but I'll get to it.

So yeah, that's the gist of what we have.  As my previous posts (2, 2.5) demonstrate, I had located a crash via fuzzing.  That's well and good, but I was definitely a bit stuck, limited buffer space, no SEH.  Looking at publicly available exploits quite a bit of buffer space is available, but in my crashes, I was only able to shove in a maximum of 32 bytes, nothing afterwards stuck around (side note-- I've likely found a way to take advantage of that other crash, and will be pursuing it in a future post).  Here's an example of the crash I located:

Honestly, it seems pretty slick.  I control ECX, and some of my buffer is right in the stack.  Yeah, well, nope.

0x02 Crashes are cool and all, but

Let's try to take a look at what's happening here.  In the above crash, and every attempt I made to take advantage of it, I consistently hit a wall of limited buffer space, and never seeing that famed SEH overwrite all the other exploits took advantage of.  Why?  WELL LEMME TELL YA.

As I previously mentioned, ECX is used for counting.  Now, to those as noobish as I, that doesn't really mean a whole lot.  From previous BoF's, I learned you could really use each register however you wanted.  That makes sense, as registers just hold data.  My lack of experience caused a lot of grief though, because as it turns out, that particular register (for this app) was tracking data sizes.  In respect to this application, it was expecting 26 bytes of data to be sent (there's a lot more to it-- I'm so over the application I just don't care anymore).  Anything more than that is ignored.  Hmm.  

Okay, well, looking back at the publicly available exploit, clearly something is happening here:

header += pack('<I', len(payload))
header += pack('<I', len(payload))
header += pack('<I', ord(payload[-1]))

So... like, what is happening here?  Writing a quick python script t test these header values and piping the output through xxd leaves us with:

root@kali:/pentest/exploits/diskSaavy# ./test.py | xxd
00000000: e803 0000 e803 0000 4100 0000 0a         ........A....

Note: I added a 'print' to the end of the script for prettiness, hence the line break on the end.

So uhhh yeah, here we go, we should be able to simply write these raw headers out and produce the correct crash:

header =  "\x75\x19\xba\xab"
header += "\x03\x00\x00\x00"
header += "\x00\x00\x00\x00"
header += "\xE8\x03\x00\x00"
header += "\xE8\x03\x00\x00"
header += "\x41\x00\x00\x00"

And... as expected, we get the crash we have been expecting this whole time.  A clean SEH overwrite, output below, as well as a ton of buffer space.

SEH chain of thread 00000604, item 0
 SE handler=libpal.002ADF5B
 SE handler=41414141
 SE handler=*** CORRUPT ENTRY ***

A bit bittersweet perhaps, but a good lesson nonetheless.  This really reinforces the idea of paying attention to what you're looking at and gave me some new, great ideas moving forward.  For one thing, while fuzzing in the future, I'll definitely never forget to attempt defining the buffer size I am sending in various locations of the packet.  It may not work, but it's certainly worth a shot.

SEH overwrites tend to be fairly straightforward.  The SEH pointer, seen at address '0164FF5C' in the SEH output above, will point to the first SEH record, which we can populated with a usable POP,POP,RET instruction.  This instruction will pop two addresses off the stack, and return into the other SE Handler we control, landing us in a location containing '41414141'.

The screenshot below illustrates this crash.

Take note of the SEH chain-- we have two pointers that contain \x41, and can clearly see quite a bit of buffer in our registers.

0x03 Taking Advantage of SEH

We clearly control SEH, which is great, but at this point we don't actually know the offset that the crash occurs at.  This means we can't control what data is put into SEH at the time of crash.  This is where metasploit's pattern_create and pattern_offset ruby scripts come in.  These scripts are infinitely useful, as they will allow us to send a buffer of a given size which contains specific patterns.  We first generate a pattern of specific size (1000 bytes) using the pattern_create.rb script, and send it at the application as our buffer.  When we have a crash and find our SEH overwritten with data, we take the data contained in SEH (or wherever you wish to locate) and pass it back through the pattern_offset.rb script.  This script returns the pattern's offset.

In this case, it looks like our SEH offsets begin at 124 bytes.  Let's experiment and throw in some B's and C's. Below is the start of the payload and the resulting SEH output:

payload = "\x41"*124
payload += "\x42"*4
payload += "\x43"*4

SEH chain of thread 00000A78, item 1
 SE handler=43434343

SEH chain of thread 00000A78, item 2
 SE handler=*** CORRUPT ENTRY ***

Notice the SEH pointers-- where our buffer goes BBBB CCCC, our SEH chain is CCCC BBBB.  That's important to remember, the buffer will get read in and written to the stack 'backwards'.

Now that we know where our SEH offset is at crash time, we can utilize mona to do the hard work for us.  Running '!mona seh' will create a nice list of possible PPR locations.  Mona will likely find quite a few, so choose one that adheres to any restrictions you may have, such as bad chars.

Log data, item 21
 Message=  0x10056da4 : pop ebx # pop ecx # ret 0x20 |  {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files (x86)\Disk Savvy Enterprise\bin\libspp.dll)

I chose the record above for no other reason than seeing it didn't contain a null byte.

We want our SEH chain to start with our POP POP RET instruction, so we return back into the next part of the SEH chain.  Overwriting the bytes starting at offset 128, we can shove in the above PPR address.  Assuming there are no bad characters interfering with our plans, we should end up landing right at a PPR, then back into four B's:

  1.  The SEH chain is triggered, and the application's flow of execution is redirected to the first pointer in the chain.
  2. Since we control the SEH pointer, the application jumps to the beginning of our shellcode, which contains a POP, POP, RET instruction set.
    1. Each POP removes one value from the stack
    2. The third instruction, RET, returns the application back to the location that called the first SEH pointer, which holds the next part of our buffer, BBBB.
  3. The application returns to it's SEH calling address, 01F9FF5C, which holds our shellcode.
We have now successfully redirected the the application's SEH chain, but there's a problem.  Take a look below:

Note: The above was taken with a working PoC, modified for this screenshot.

Since we return to our buffer, BBBB, which if you recall, was prior to the CCCC (which we ended up using as our PPR), we end up in a pickle.  If we continue execution, we're going to hit unknown instructions and the payload will break.  We need to jump over that part of our buffer and land right behind it.  To do so, we'll simply change the BBBB to \x90\x90\xEB\x0A  That should jump us 10 bytes ahead, more than enough to hurdle the first part of our SEH chain.

BUT... we have another problem.  Scrolling to the end of our buffer-- it's fairly... short.  In fact, it's only 117 bytes if we jump exactly to 014AFF67.

That's gonna be an issue.

0x04 Adjusting the Stack

First off, adding an \xEB\x0F to move us 15 bytes into our initial buffer will clear us of SEH chain instructions.  Take a look at the screenshot below:

What's up with the stack?  Well, 34h bytes down from our current location lies the beginning of the bulk of our buffer.  Looking at the memory dump of that location, we see that indeed, the address at 0x01EBF474 points to the exact start of our C's.  If we can move the stack pointer to that location, we should be in a location we can utilize to plant our shellcode.  Creating an initial payload such as the following, should allow us to enter shellcode land:

payload = "\x41"*124
payload += "\xEB\x0F\x90\x90"
payload += "\xA4\x6D\x05\x10"
payload += "\x90"*10
payload += "\x83\xC4\x34" # ADD ESP,34
payload += "\xFF\x24\x24"  # JMP DWORD PTR SS:[ESP]
payload += "\x41"* 112    # Fill / Correct buffer

Lines four to six are the stack adjustment, line four being a small nop sled into our addition and jump.  The last line is simply filling in the remaining buffer space, so when we do jump to [ESP], we end up at the start of the C's.  Line six is especially important, as we are not jumping to ESP, we are jumping to the DWORD value contained at ESP.  This is an extremely important concept.

If we were to jump to ESP, EIP would be 0x01E6F474.  Now, although sure, we controlled execution and got ESP to point to a location we controlled, this is what we end up with:

Yeah, that's not at all what we wanted.  When we jump directly to ESP, we end up attempting to process the instructions contained right on the stack.  Now, in some cases this is totally fine.  For instance, if we find we can write directly to the stack and have a reliable way to get there i.e. no ASLR, no randomization, or have a way to leverage the application to move into the stack where we want and control it, then it's fine.  In this case, the address contained in ESP is the one we want to move to.  This is called a pointer, and can be seen in many instructions written as PTR.

If you've ever seen Inception, the idea of a 'dream within a dream' is kind of similar to this type of instruction.  We're pointing application flow to the address within another address.  Although confusing to read and explain, once you start working with them it becomes much more clear.  When we issue the following command, JMP DWORD PTR SS:[ESP] (written effectively as JMP [ESP]) we will end up with EIP as the DWORD in ESP:

Boom.  At this point, it's just a matter of determining bad characters by filling in the remaining buffer with all hex chars and looking at them in dump or CPU to determine if they have been removed, transformed, padded, etc.  It's absolutely possible that a bad char could have appeared prior to getting this large buffer space, it's just that at this point, shellcode will be added and we have a nice view of the actual bytes in play.

In my case, I didn't find any bad characters beyond the typical null byte.  Just throw some shellcode in and be off.

The full exploit can be found here.

0x05 Takeaways

  • Pay Attention to what the data is doing. 
    • Yeah, it seems simple, but when there are a crap load of hex bytes staring you in the face, it's a bit harder than you may think.
  •  Don't be afraid of trying your ideas. 
    • Thought of something weird?  Try it!  Why not?  It may mean you need to restart the app, but wasn't that the plan, anyway?
  •  Data sizes matter!
    • This one isn't going to be universal, but seriously, the amount of time I spent banging my head against the keyboard is unreal.  It definitely goes hand in hand with the first bullet point, but I do think it's relevant to be it's own subsection.  In retrospect, it makes a lot of sense that the application would need to know how much data to process :]

Good luck, happy hunting.

Popular posts from this blog

06 - How to maybe not be so bad at fuzzing, Part 2

07 - Just Another OSCE Review

02x01 - How to maybe not be as bad at fuzzing unknown binary protocols as you were before reading this