QuickZip 4.60 SEH Based Buffer Overflow w/ Egghunter

Structured Exception Handling (SEH)Based Buffer Overflow Vulnerability

Kali Linux
Windows Vista
QuickZip 4.60

Bug found by: corelancod3r (http://corelan.be:8800)
Software Link: http://www.quickzip.org/downloads.html



This is another SEH based buffer overflow with an egghunter implementation. A more detailed explanation that I wrote about an egghunter implementation can be found here: https://www.pyt3ra.com/2020/03/slae-assignment-3.html

You can find numerous write-ups about this vulnerability and exploit it. This buffer overflow vulnerability gives us a good mix of shellcode encoding due to restricted characters as well as how to implement an egghunter. 

I'm doing this as part of my preparation of Offensive Security Certified Expert (OSCE) certification

0x0 - Setup

Our Proof-of-Concept creates a .zip file that we will access using QuickZip.

0x1 - Fuzzing

As usual, step one in exploit development is fuzzing. This allows us to examine how the program responds when we introduce a buffer (oversized) to it.

We will be using the fuzzer from corelan as shown below.

We examine the crash using immunity debugger and we can see that have successfully overwritten the SEH handler with 41s.

0x2 - Calculating the offset

We will utilize Metasploit's pattern_create.rb and pattern_offset.rb

First, we create unique characters of 4068 bytes and update our POC

We check the SEH chain after the crash to get the offset values

SEH: 6B41396A
nSEH: 41386A41

Second, we calculate these values using pattern_offset.rb

Finally, we update our POC and verify if these values are correct.

If everything is correct, we should see Bs at byte 294 and Cs at byte 298.

Our values are correct as we see the 43s and 42s overwrite both SEH and nSEH respectively.

0x3 - Verifying Restricted Characters

At this point, we are now ready to find an address to redirect the SEH handler. However, before we waste our time looking for an address, we will need to verify first if there are restricted characters. 

First, I worked on the first 128 bytes (x01 to x7F)

I sent the characters to the program and carefully watching how the program responds to the characters. If it gets mangled/truncated/updated, then it's a bad character.

After numerous tries, I was able to narrow down the restricted characters to the following:


Updated POC below:

Second, I worked with the other 128 bytes (x80 to xff)

Interestingly, hex characters 80 and above are changed to an arbitrary hex character.

I sent the following characters...

...and we see the following conversion in Immunity Debugger

The table below shows the conversion (not a complete list).

0x4 - POP-PO-RET address!

Now that we know which characters are not allowed, we are ready to find a POP-POP-RET address.

Lucky for us, Immunity Debugger's mona.py module enables an easier way to find a POP-POP-RET address. 

There were multiple addresses found.

Although not ideal since it has null bytes, we will be using address 00435133

We can verify that this is indeed a POP-POP-RET address by searching for it.

We can update our POC with this address and see if our SEH handler gets redirected to it. 

Note that the address has to be in little-endian.

We fire up the POC and we have successfully redirected the SEH handler as shown below:

…once we take the pop-pop-ret, we then get redirected to our nseh values (EBs)

Note that our  Ds are nowhere to be found now, however, our As are easily accessible which is just right above our nSEH. This means our initial jump will have to be a reverse jump to the beginning of our As that should get us at least 127 bytes of address space to further jump to a bigger space (Ds location)

0x5 - First Jump

Normally, we would be able to easily to a reverse jump short 80 (EB 80). However, note that any hex character of 80 or over is restricted and converts to a different hex character.

More info about jump shorts (forward and reverse) can be found here: https://thestarman.pcministry.com/asm/2bytejumps.htm

If we go back to our bad characters table…we can see that hex 89 and 9F translates to EB and 83 respectively.


Here's how the EB 83 instructions would work as shown in Immunity. 

We can now update our POC with our first jump

We see our EB 83 first jump in SEH chain

We take the pop-pop-ret and we successfully hit our first jump…from 0012FBB0 to 0012FB35

0x6 - Second Jump - Egghunter

Remember that the address space for our Ds is missing now (still in memory). This is where ideally where reverse shellcode would be. 

Note above that our dump doesn't show the address space where are Ds are close to our As.

Just because we can't find it doesn't mean it is missing…this is where an egghunter shellcode comes in to play.

Here's a write-up that I did about egghunter: https://www.pyt3ra.com/2020/03/slae-assignment-3.html

Mona.py also has a module to create an egghunter.

Note: I will be using a different egghunter

Due to restricted characters, we will need to carve out our egghunter shellcode so I will be using the egghunter shellcode that I used for OSCE prep (I spent way too much time carving them).

Also, note that I did another EB 83 at the beginning of our second jump to allow another 120+ bytes of address space as I noticed that the first jump wasn't enough space
Before we can start carving out the egghunter shellcode, we will need to align the ESP to point to the space below our first EB 08  at address 0012FB8D--this is where our egghunter shellcode will be decoded. 

We can see that ESP is currently pointed at 012F574 and we want to point to 0012FB8D

We will need to add 1561 to ESP

To align ESP, we need the following instructions:

PUSH ESP                   
ADD AX, 619

Keep in mind that we still need to avoid restricted characters...fortunately, the opcodes that correspond  to these instructions are not restricted

We execute the following instructions and we can see that our ESP now points to 0012FB8D

Again, we update our POC with our initial ESP realignment which will be the beginning of our egghunter shellcode

With our ESP aligned to where we want it to point....we can start carving out our egghunter shellcode.

Here's the original egghunter shellcode broken down into 4 bytes

#original egg hunter shellcode

We will be using the EAX register to carve out the egghunter. Note that we need to zero out EAX first before we execute SUB instructions. We start from the last 4 bytes and work our way up.

zero_out_eax = "\x25\x4a\x4d\x4e\x55\x25\x35\x32\x31\x2a"

This whole carving thing is straight black magic...it never ceases to amaze me.

#using hex/dword in windows calc
#0 - E7FFE775 = 1800 188B
#7950 5109 + 7950 5109 + 255F7679 = 1800 188B
#0 - 07950 5109 - 79505109 - 255F7679 = E7FFE775

These 4 bytes are encoded by doing some SUB instructions on EAX, then it gets pushed to the stack, and then they get decoded at the ESP address.

We will be doing this for the other 28 bytes

#using hex/dword in windows calc
#0 - AFEA75AF = 5015 8A51
#71094404 + 71094404  + 6E03 0249‬ =  5015 8A51
#0 - 71094404 - 71094404  - 6E03 0249 = AFEA75AF

#using hex/dword in windows calc
#0 - FA8B 5730 = 0574 A8D0‬
#55093131 + 55093131  + 5B62 466E = 0574 A8D0‬
#0 - 55093131 - 55093131  - 5B62 466E = FA8B 5730

#using hex/dword in windows calc
#0 - 3054 B8EF = CFAB 4711
#56316666 +56316666  + 2348 7A45 = CFAB 4711
#0 - 56316666  - 56316666  - 2348 7A45 = 3054 B8EF

At this point, we have successfully carved out the first 16 bytes of our egghunter shellcode.

Note that we are getting close  our second EB 83 jmp…we will need to jump over this to get to the next 127 bytes of address space

I made a mistake on the ESP realignment that resulted in wasted address space. ESP should have been pointed to the end of our 41s as the stack grows bigger, the address becomes smaller.

This means we need ESP to be pointing at address 0012FBAC

Adjusted ESP alignment

This gives us about extra 32 bytes of address space….every byte counts!

We finish up the first 128 bytes by adding the first half of our zero_out_eax instructions and adding a jump short 10 bytes to go over the second EB 83 (I think we could have looped around too and just overwrite the first 16 bytes of our egghunter shellcode).

Our jump short brings us from address 0012FB2F to 0012FB3B and we successfully get over the second EB 83

Now we continue encoding the rest of our egghunter shellcode

#using hex/dword in windows calc
#0 - 745A053C = 8BA5 FAC4
#41214433 + 41214433 + 0963 725E = 8BA5 FAC4
#0 - 41214433 - 41214433 - 0963 725E = 745A053C

#using hex/dword in windows calc
#0 - 2ECD5802 = D132 A7FE
#657F3165 + 657F3165 + 06344534 =  D132 A7FE
#0 - 657F3165 - 657F3165 - 06344534 = 2ECD5802

Finally, we hit our last 4 bytes

That completes our egghunter shellcode…with literally 6 bytes left to spare with our address space

To test if our egghunter can find our egg, we add our egg "T00W" to the beginning of our Ds…and see if we can find it

...and we are successful. We found our Ds address space.

0x7 - Shellcode time!

I created a calc shellcode to see if we are able to pop a shellcode.

We go through our egghunter and add a breakpoint right before the jmp edi instruction and we can see that edi points to the beginning of our calc shellcode

Everything looks good, however, our shellcode just keeps on crashing the program and not spawn a calc.exe

After spending research, it looks like the ESP has to be realigned.

We align ESP with the address of EDX....then make it divisible by 4

AND, ESP FFFFFFF0             ----> make ESP divisible by 4

I update the shellcode to a reverse shell…pop the exploit one more time and we get a reverse shell

Final POC in my GitHub.

Thanks for reading!