QuickZip 4.60.019 SEH Buffer Overflow

Introduction

Hey everyone! How’s it going? Today, we’re going to have some fun and discuss the QuickZip 4.60.019 structured exception handler buffer overflow, which is a very interesting multi-stage exploit. This article is inspired by the original articles and those which others have written, but the goal is to dig deeper into the details and provide a more in-depth explanation about what’s happening, what we’re doing, and why. For me, when following the work of others, I felt that at times the articles glossed over or sped through details which were critical to a full understanding of the exploit. As such, my goal is to explain those parts, and ensure that the exploit process is clear to everyone.

VMWare Fusion Shared Folders (Installed on both machines, used to transfer the ZIP file between OS’s)

Black python-formatter (Attacker machine) for consistent Python formatting, no matter what changes we make

Flake8 python-linter (Attacker machine) to detect issues as soon as they occur

Flake8 Configuration

As Flake8 has specific configuration rules that don’t match Black out of the box, we’ll use the following .flake8 file in the exploit development directory to avoid line length warnings.

The Exploit

Proof of Concept

Thanks to the wonderful work of corelanc0d3r (Peter Van Eeckhoutte), we started this exploit off with the proof-of-concept exploit which was available on the Corelan and Offensive-Security resource pages. This looks like so:

Running this on Kali 2018.3 though, we see that this doesn’t work as we hope.

It seems like escape sequences that were once correct in this perl code no longer work with the current version of Perl. This results in the provided proof-of-concept exploit creating an ASCII file which is not recognized as a ZIP file. We verify this using the file command, to understand how the operating system will view the file, as even if there are issues with the file, the magic number at the start of the file should cause it to be properly identified as a ZIP.

Since this proof of concept, while a correct starting point, doesn’t seem to be generating a ZIP file, and perl isn’t my language of choice, let’s take this opportunity to convert this code to Python and fix the escape sequences. To do this, you need to know a few things:

Bytes in Python can be represented using \xFF style definitions. Where the \x is the prefix designating this as a byte, and the FF being the specific byte that should be represented.

Concatenation of information in Perl is done using the . operator, while concatenation of information in Python is done using the + operator

Variables in perl are declared (generally) using the my $variable syntax, while in python we can simply name the variable and assign it without prefixing keywords.

Strings in Python which are within a set of parenthesis are automatically concatenated together, removing the need for explicit + operators

Perl requires semicolons at the end of statements, while Python uses whitespace to denote the beginning and ends of a statement.

With this piece of information, it’s clear that this should be relatively easy to update. We’ll need to take the x00 sequences and convert them to \x00 sequences, update the shebang so that it’s pointed to Python instead of Perl, fix the syntax of string concatenation, and update the file writing to use Python’s style of writing to a file. In our case, we’ll use a context manager rather than strict open and close functions, to simplify the exploit.

With these changes, we’re left with:

If we verify that this is creating a ZIP properly, we see that it is:

Nice! You’ll notice though that line 40 references a 4064 byte pattern though, so we’ll need to generate that and add it in. Also, since this file payload (not including the headers) is explicitly 4068 bytes (the filename length of 4064 bytes plus the 4 byte extension, .txt), as the file size in the headers says (\xe4\x0f in the headers is the little-endian representation of 0FE4 which is the decimal value 4068), we’ll also use this opportunity to add a check to our file contents before writing the file, so that we can warn ourselves if the file size no longer is 4068. This can help us catch errors that we make in our math early, saving time during debugging.

This will give a cyclical unique pattern which we can paste into the variable. This unique pattern will allow us to identify any individual location in the pattern, simplifying calculating offsets and locating our exploit overwrites.

With our changes, we end up with:

The key changes to note are the pattern on lines 42–99, the target_len variable (which holds the file length we’ve configured in the headers) on line 15, and the payload length check on lines 103–109.

If we generate the ZIP file now, open Immunity Debugger, attach the debugger to Quick Zip, and open the ZIP file, attempting to extract the file (the act of opening the ZIP doesn’t trigger the exploit by itself), we see:

The first image shows the debugger itself at the time of the crash. None of the registers appear to be overwritten with our pattern, though along the bottom status line we notice that there is an exception which we can pass to the program. If we look at the structured exception handler chain, you’ll notice the bottom-most entry is kernel32. If we pass this exception to the program using Shift+F9, we can see in the last error:

ERROR_FILENAME_EXCED_RANGE (000000CE)

This means that the last exception we saw was due to the filename of 4068 characters is too long for a Windows machine and triggered an error within the Kernel. Once we pass it to the program though, we now see (annoyingly with the Quick Zip crash window obscuring part of the debugger):

This exception looks much more interesting to us. We now see parts of our pattern in some of the registers. We see this is also an exception, and take a look at the structured exception handler chain:

Identifying The Overflow

This looks much better for us to consider exploiting. We now have a corrupted structured exception handler. Let’s take a look at the value in the structured exception handler (SE handler column of the line above the corrupt one) and the next structured exception handler (address column of the corrupt line) to see if these values are within our pattern:

Nice! It looks like we have control over both the next structured exception handler (nSEH) and the structured exception handler (SEH) values. As is common for a SEH exploit, SEH will be the value that gets put into EIP, and commonly points to a POP POP RET sequence, and nSEH is going to be the four bytes we have to jump forward or backwards.

Verifying the Overflow Offsets

First things first, though, let’s verify this and make sure we actually do have control over these. We’ll also add another verification check to ensure that our padding before the nSEH value and before the SEH value are correct, so that if we mess up math related to the offset later, we want to know that. Honestly, this may be overkill, but I’ve found it helps me catch errors if I work on a specific exploit for too long.

With our changes, we now have:

The changes that you should notice here:

On lines 16 and 17 we now have the offset to nSEH and SEH

On lines 44, 45, and 46 we now have the four byte values for the nSEH value (the value that overwrites the nSEH value), SEH value, and the file extension

Lines 48–66 build the payload, and include checks of the total payload size to both nSEH and SEH, to ensure that the offsets are correct.

The payload is now built with A’s until our nSEH value, nSEH should be filled with B’s, SEH should be filled with C’s, and then the remaining payload should be all D’s.

If we now restart the debugger and Quick Zip, and attempt to extract our file, passing the maximum file size exception to the program, we now see:

Perfect, we now have the ability to overwrite the nSEH value and the SEH value. This means we can control EIP via the SEH value, and then should be able to execute code from our nSEH value.

Finding a POP, POP, RET

Let’s use mona to look for a value which we can jump to which does not have ASLR, SafeSEH, or other protections. Also, we want this to be part of the application, not the OS, so that our exploit will be as portable and reliable as possible. To find a list of memory addresses which we can use, we’ll use Mona’s seh command like so:

!mona seh -o

This will look for POP, POP, RET sequences that we can use for SEH exploits. The -o flag will tell Mona to ignore OS modules. With this, we get back almost 8,000 addresses:

Sadly, it looks like all of these start with null bytes, let’s re-run mona and see if she can find something without a null byte (-n to ignore modules with null bytes):

!mona seh -o -n

Sadly no results though. So it looks like we’re going to be stuck trying to make this work with the null byte. Luckily it’s the first byte, so when reversed for little-endian format, it should still give us a full return address.

We’ll choose this one due to the attributes associated with it. First, it’s printable ascii, which since we’re putting this value in a filename seems like it will be beneficial. There are other asciiprint return addresses, but this one looked good to me, as it’s also alphanum.

So we’ll replace our seh variable value with the address 0x00435133 in little-endian format. We’ll also include the information about what’s at that address, so that we have it for reference later. In this case, we could enter the following for the SEH variable:

seh = '\x33\x51\x43\x00'

But I’ve found that it’s prone to typos. Instead, we’ll use Python’s standard library to convert the hex for us to little endian.

You’ll notice that we’re now importing the pack function from the struct module, and packing the value into a little-endian value. You may be wonder how does <L mean little-endian. Essentially, Python says that < represents little-endian order, > represents big-endian order, and L represents a 4-byte integer. Thus, as we want to store a four byte address, the size of our SEH value, we end up using:

seh = pack("<L", 0x00435133)

With our return address in the SEH variable, let’s execute our exploit and see what happens.

Comparing the address, we see that this overwrote the return value successfully. The QuickZip. in front of the address is simply a debugger helper which tells us which module the address is located within, this can sometimes cause people new to debuggers to be a bit confused.

If we then use F2 to set a breakpoint on this return address (which in the SEH chain window will highlight that line red), and pass the exception to the program using Shift+F9, we correctly end up at a POP, POP, RET sequence:

Looking at the stack in the bottom right pane, we see that two DWORD’s below our current location in the stack is our B values, which we put in nSEH, so if we follow the POP, POP, RET using F7 three times, we end up landing at our B’s:

What do we do now though?

At this point though, the exploit starts to become more difficult for a few reasons:

We don’t no longer have data after our SEH value

It looks like we may be restricted to ASCII / filename compatible characters, which will impact our ability to JMP (EB)

If we have to jump backwards, rather than forwards over SEH, in four byte’s we can probably only go 128 bytes back (80 is a -128 byte jump). But the backwards jumps are all going to be above the ASCII character range

Some of these we can solve without a doubt, others are going to depend, and we’ll need to do a bit of testing. There are conditional jumps the in \x70–\x7F range which we should be able to use. A jump net may work. If you aren’t familiar with a jump net, take a look at this article by OJ Reeves. It’s a fantastic article. For my purposes, I’ll be using the example he gives for the jump directly. But we still need to figure out a jump offset.

Figuring out the negative jump offset

Now, if you’re like me you may wish for an easier way, but honestly I haven’t found one. Essentially though, this step is a lot of trial and error, and a bit of hope. For this to work, we’re going to try jump sequences with known bad characters, and see if any of the modifications the application makes to them are going to allow us to jump back. The way I approached this was:

nseh = '\x71\xBAD_CHAR_1\x70\xBAD_CHAR_2'

This allows us to see how the jump get’s altered, two characters at a time. Then, I repeated this, working from \x80 upwards till I found a value that was good. To give you an idea of how the values got mangled, take a look at the table below:

Original Byte

Mangled Byte

\x81

\xFC

\x82

\xE9

\x83

\xE2

\x84

\xE4

\x85

\xE0

\x86

\xE5

\x87

\xE7

\x88

\xEA

\x89

\xEB

\x8A

\xE8

\x8B

\xEF

\x8C

\xEE

\x8D

\xEC

\x8E

\xC4

\x8F

\xC5

\x90

\xC9

\x91

\xE6

\x92

\xC6

\x93

\xF4

\x94

\xF6

\x95

\xF2

\x96

\xFB

\x97

\xF9

\x98

\xFF

\x99

\xD6

\x9A

\xDC

\x9B

\xA2

\x9C

\xA3

\x9D

\xA5

\x9E

\x50

\x9F

\x83

This is by NO MEANS an exhaustive list of all of the byte mangling that occurs, but we do see in the last line that \x9F is converted into \x83. A jump of \x83 is a -125 byte jump, which will give us a fair amount of room to work. Not enough room for a reverse shell, but it may give us enough room for a decoder, another jump, or something else.

So if we can jump back 125 bytes, and our nSEH offset is at 294, that means that we have 171 bytes of unused space before our jump landing and 123 bytes of space including our jump landing. You may be looking at that and saying shouldn’t that be 169 and 125? Nope! Because we have already executed the two bytes for our jump, we also have to jump over them, meaning we only actually have 123 bytes of space after we jump back.

As a result, let’s modify our exploit so that we can test the jump back, and ensure we’re landing where we expect. As we know how far back we’re jumping, we’ll update our pre-nSEH payload with A’s in our above jump landing zone, and B’s in our jump landing zone, which is where we’ll be able to execute code. This shouldn’t cause any confusion for us as we’re going to replace the B’s in nSEH with our actual jump.

With our updates, we now have:

Note how our payload now is being built with a section of A’s and a section of B’s on lines 53 and 54 and our jump net, as described in OJ’s article, is on line 46. Let’s generate a new ZIP file and test this. We’ll set a breakpoint at the POP, POP, RET return address, pass the exception to the program with Shift+F9, and step through with F7 until we land at our conditional jumps.

When we get there, we see our expected conditional jumps:

We’ll then take the jump, landing either at the very first B or the third B, depending on if we’re taking the first or second jump, as if we’re using the second jump, it has to also jump over the previous jump (which is two bytes long).

In this case, we’re hitting our first jump, so we land at the first B:

What do we do with our new space?

Now that we have this additional space, we have to figure out what we want to do. If you remember the problems I listed before, we solved some, but not all. Specifically, we still don’t actually know if this is the only space we have or if there is other space available to us. The question to be asking yourself here is what happened to the D’s at the end of our payload, now that they got cut off by the null byte in SEH.

To figure out what happened, we’ll try to locate these D’s in memory. If we click on the M in the top toolbar to view the memory window, right click and select search, we can then search for our D’s. Make sure you put enough D’s in here though so that it’s not accidentally finding other areas of normal instructions, but so that it’s definitely your payload:

Nice! It looks like our D’s are actually still in memory. We should be able to use this to store a larger payload. This means we have a potential way forward, if we can figure out how to fit an egghunter into the limited space that we have.

What’s an egghunter?

An egg hunter is a small payload designed to search a system’s virtual address space, commonly abbreviated VAS, for an egg denoting the beginning of another, larger payload. This egg is usually two instances of four bytes (e.g. we may use the egg “eggs” as “\x73\x67\x67\x65” (note this is in reverse for little-endian architectures), or “w00t” as “\x74\x30\x30\x77”) which are used to first identify that we’ve found the larger payload, and then validate that the entry we’ve found is in fact our payload.

If like me, one of the first things you’ll think is can we just use four bytes, rather than eight? At first, this would seem to make sense as we want to keep space of both payloads (most likely) as small as possible. If we use only four bytes though, the egg hunter may inadvertently detect itself, as it has to store the egg in memory itself. Thus, by using the key twice, totalling eight bytes, we bypass this risk and ensure that we have actually found our payload. In addition, using a single four byte key, repeated twice, allows us to reduce the size of our egghunter by four bytes, as we do not need to store two unique keys in memory.

The other thing to consider is what bytes are you choosing and are then something which would not be unique in memory. If you are not careful, it is possible for the chosen key to already exist in memory. An example of a bad key would be “\x90\x90\x90\x90” which constitutes four no operation (NOP) operation codes. These can exist in memory anyway, and as such should not be used.

Can we fit an egghunter

Normally an egghunter is approximately 34 bytes long but contains non-ascii characters making it a problem for us to use in our exploit. Directly encoding it via Mona is going to give us too long of an egghunter, at about 200 bytes. If we encode it using Metasploit’s alpha_mixed encoder, we actually get a good sized payload, but it has some characters which are not printable.

This is due to the opcodes (“\x89\xe2\xdb\xdb\xd9\x72”) at the beginning of the payload, which are needed in order to find the payloads absolute location in memory and obtain a fully position-independent shellcode.

Once our start of shellcode address is obtained through the first two instructions, it is pushed onto the stack and stored in the ECX register, which will then be used to calculate relative offsets. However, if we are somehow able to obtain the absolute position of the shellcode on our own and save that address in a register before running the shellcode, we can use a not-well-documented option (in my opinion at least, took my awhile to figure out how to use this option), BufferRegister to tell the payload what register contains the address of the first byte of our encoded shellcode.

With that in mind, can we potentially take our egghunter and put it in the 171 bytes of space at the beginning of the exploit, and then use the space after our jump to craft this address, store it in a register and jump to it?

Figuring out if we have the space

So now let’s take the time to actually figure out how much space we need. We want to prepare a register for our shellcode, and then jump to it. Looking at the instructions to add and subtract from the primary general purpose registers, we see that EAX seems to be the only one that we can probably add or subtract to without a bad character, as the others appear to use \x81, which as we saw earlier will get mangled into \xFC by Quick Zip.

So if we can manipulate EAX to build information, it may not be the one we want to store the data in directly, as it’s the only one we can safely change. Instead, let’s use EDX, so that we can store the data we want in EDX, and then do instruction calculations via EAX. This will make more sense in a minute.

So it we are going to store the location of our shellcode in EDX, let’s see how much space this is going to use. First, we need an egghunter to encode. We’ll generate this using Mona’s egghunter command, which uses the tag (or egg) w00t by default:

!mona egghunter

This gives us the following output:

We’ll save the hex, as output, to a file named egghunter.txt on our attacking machine.

To encode a custom payload using Metasploit, it has to be in it’s raw binary form, so we’ll need to convert this hex into the raw form. To do this we can use a little bit of command line kung-fu:

This will cat the hex file, remove the quotation marks, newline characters and \x byte indicators, then pipe it to xxd which will reverse (-r) the hexdump into binary and we’ll tell xxd that the hexdump is in continuous hexdump format, meaning it’s the hex in a single line without any line breaks or other characters. We’ll then use the > operator to redirect the raw output into a file, egghunter.bin which will store our binary egghunter data.

With the binary data ready, let’s see if we can encode the egghunter using the EDX register as our buffer register (meaning that when we hit the shellcode, in EDX we will have the address of the first instruction of the shellcode):

So that used 117 bytes of space. So that means if we started with 171 bytes of open space before our jump, 171 – 117 = 54 /2 = 27. So we’ll put 27 NOP equivalent instructions (instructions which affect registers that are not the ones we’re working with, such as EBX or ECX). This will give us a bit of wiggle room if we need it.

This gives us:

OK, so we have the space for the egghunter, and that looks ok. Let’s take care of getting to our egghunter next.

Getting to the Egghunter Payload

With our egghunter safe in memory, we need to use our remaining 123 bytes to get to it. So let’s see what needs to happen. We’re going to use an addition / subtraction encoder, as shown by Muts in his NNM 7.5.1 exploit. Personally, this was something which confused me more than I care to admit. So we’re going to discuss it in a bit more depth. You can find source code for this encoder in Mona, Metasploit, and other locations. Using one of these, while more efficient than doing it by hand, hides the magic behind this idea.

So first, what do we want to do:

Zero out EAX

Calculate the value of the address of our shellcode, in little-endian order

Push the address in EAX onto the stack

Pop the value into EDX

Zero out EAX (not strictly necessary, but easier math)

Push ESP onto the stack

Pop the value into EAX

Adjust the stack address to point to an address below our decoder, so that once we’ve decoded it, we automatically execute it

Push EAX onto the stack

Pop the value into ESP, making it so if we push a value, we’ll be writing below our current execution

Push JMP EDX instruction

If we do everything right, this will give us the location of our shellcode in EDX and will decode it below our current EIP location. There are a few dangers to this though:

When adjusting the ESP location, we have to make sure it stays on a 4 byte boundary. If it’s not, when we execute our shellcode things will break

We only have 123 bytes of space, so we need to make sure we only do the minimum necessary, to ensure we have enough space.

Encoding Our Opcodes

So we need a few things:

The address of our shellcode in memory

POP EDX opcode

PUSH ESP opcode

POP EAX opcode

PUSH EAX opcode

POP ESP opcode

JMP EDX opcode

We can get some of these easily via the nasm_shell.rb tool within metasploit:

This gives us:

The address of our shellcode in memory

POP EDX — \x5A

PUSH ESP opcode — \x54

POP EAX opcode — \x58

PUSH EAX opcode — \x50

POP ESP opcode — \x5C

JMP EDX opcode — \xFF\xE2

Wonderful! This is progress. Let’s re-generate our zip file with our egghunter, and see where our egghunter shellcode begins. Since it has A’s above and below it, we should be able to find it pretty easily. Sadly, not the best photo (if anyone knows how to prevent the crashed program from hanging in front of the debugger, please comment!), but we can see if better in the dump in the bottom left:

This location is one away from a nice multiple of four, which I’m a bit hesitant of, and we have the space to fix it, so we’ll move our egghunter up by one by adjusting our NOPs.

By up in this case, we mean that we’re going to put one less NOP equivalent before our payload. This gives us:

So we now have our egghunter address, giving us the following information:

The address of our shellcode in memory — 0012FAF0

POP EDX — \x5A

PUSH ESP opcode — \x54

POP EAX opcode — \x58

PUSH EAX opcode — \x50

POP ESP opcode — \x5C

JMP EDX opcode — \xFF\xE2

So let’s zero out EAX. We’ll do this using AND EAX instructions, which are denoated by the \x25 instruction. The key to this instruction and the reason it zeroes out EAX is the binary representation of the hex number. The output below is grabbed from Mona, but there are multiple values that can use here, whether that’s manually calculated or not.

So this is going to be 10 bytes to zero out EAX. Let’s take care of encoding this now. The process is going to be start with a zero value, then subtract three times from that value until we end up at the two’s compliment of the reversed desired value. Don’t worry if it doesn’t make sense yet.

Encoding Our Value

So let’s manually encode one value, and then we’ll leverage a tool to help us out. So we want the following address in memory: 0012FAF0. In little endian, this is going to be \xF0\xFA\x12\x00.

So we need the two’s compliment of the reverse of this value. Reversed, we get the original \x00 \x12 \xFA \xF0.

Now, let’s get the value for the two’s compliment. Easy way to do this:

hexOfDecimal(4294967296-decimalOfHex(0012FAF0))

With that, we know the decimal value of 0012FAF0:
0012FAF0 = 1243888

So:

4294967296 - 1243888 = 4293723408

Now, 4293723408 in hex is FFED0510.

So now, we can divide by three, and end up with our desired value. The headers include the original byte, then the three rows below show values that if added together will reach the desired value. Note that the byte three column is 104, thus byte two column we’re only calculating \xEC as the 1 carrying over will give us \xED:

So this gets us zero’d out EAX, and the value of our shellcode, we now need to PUSH EAX, POP EDX.

If we update our exploit, we now have the following. Note how we have four B’s, as NOP’s, in front of our decoder. Because we may not land either at B number one or B number three, we use this small NOP sled to allow both jumps to function:

As a result, we use 27 to get our shellcode location into EDX. Next, we need to align the stack below us, by first pushing ESP then POP’ing it into EAX:

PUSH ESP opcode — \x54
POP EAX opcode — \x58

So where should we align the stack to and decode our JMP EDX to? Well, we want it to be a multiple of four, and we have a bit of space below our decoder still. Because we have a fair bit still to decode, and there is a lot of overhead, we’ll want to start at the bottom, perhaps even overwriting our conditional jumps. Looking at it though, we’ll try a bit above our conditional, and hope it works. In our case, we’ll use:

0012FBF8 42 INC EDX

If we need to, we can always adjust by four. So if we want to be at 0012FBF8, where is ESP.

ESP 0012F5C0

That doesn’t look too bad, that’s a difference of (h at the end to note this a hex value):

0012F5C0 (ESP) - 0012FBF8 (Target) = FFFFF9C8

Thus, let’s calculate a subtraction value for this:

FF FF F9 C8
/ 03
=============
55 55 53 42
55 55 53 42
55 55 53 44

We need to then subtract these in reverse order, to get our correct value. Verifying that things are working so far in Immunity, we first see if we get our shellcode location in EAX:

Nice! That worked. Now let’s see if we get our decoding location:

Nice! We got to our location. You’ll notice that I got the math wrong a few times, which is why there is a difference between the instructions in the top and bottom image. That’s why I’m writing this, to document the correct way to do the math!

This gives us the following code:

Nice, one last piece, let’s encode our JMP EDX, which is how we’ll jump to our egghunter. JMP EDX is \xFF\xE2, but we want it to be a DWORD (4 bytes), so we’ll use \xFF\xE2\x90\x90.

Now that we’ve covered encoding manually, let’s use Mona. Note that FF and E2 occur in the correct order, not reversed. This is because they are a singular command

Nice! Now we run everything and see that we decode our jump properly. Problem though, B’s are an INC EDX instruction. We need to change B’s to something else. Let’s use C’s. If we don’t then by the time we take our JMP, it’s going to be the wrong address. After changing to C’s, which are INC EBX, we take our jump and decode our egghunter.

With it correctly decoding, we now have the following code:

With our jump in place and properly decoding, we are missing the most crucial part of this exploit — our egg and our payload. As a result, let’s build a reverse shell, and add our egg in front of it.

Author Kevin Kirsche

Kevin is a Principal Security Architect with Verizon. He holds the OSCP, OSWP, OSCE, and SLAE certifications. He is interested in learning more about building exploits and advanced penetration testing concepts.