diff options
Diffstat (limited to 'batch/patchLoader.py')
-rwxr-xr-x | batch/patchLoader.py | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/batch/patchLoader.py b/batch/patchLoader.py new file mode 100755 index 0000000..8697e0a --- /dev/null +++ b/batch/patchLoader.py @@ -0,0 +1,465 @@ +#!/usr/bin/env python2.7 + +import sys +import struct +import os +import re +import subprocess +import random +from optparse import OptionParser, OptionGroup + + +objdmp_bin = os.path.dirname(sys.argv[0]) + '/../deps/sysroot/i686-w64-mingw32/bin/i686-w64-mingw32-objdump' +pyload_name = 'pyloader' +pyload_so = os.path.dirname(sys.argv[0]) + '/../bin/'+pyload_name +pycrypt_name = 'pycrypt' +pycrypt_so = os.path.dirname(sys.argv[0]) + '/../bin/'+pycrypt_name +objdmp_sargs = '-h' +objdmp_dargs = '-x' +objdmp_retval = None + + +def require_pyso(name, path): + try: + import imp + pymod = imp.load_dynamic(name, path) + except (ImportError, IOError): + return None + return pymod + +def parse_c_array(carr): + m = re.finditer(r'(([0-9a-fA-F]){2})+', carr) + ret = bytearray() + for val in m: + for byte in bytearray.fromhex(val.group()): + ret += struct.pack("B", byte & 0xFF) + return ret + +def objdump_print_err(bname): + if objdmp_retval is not None: + sys.stderr.write(bname + ': objdump ('+objdmp_bin+') returned: ' + str(objdmp_retval) + '\n') + +def objdump_data(path): + p = subprocess.Popen(str(objdmp_bin)+' '+objdmp_dargs+' '+str(path), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + found = 0 + OBJDMP_NEED = [ 'ImageBase', 'SizeOfImage', 'SizeOfHeaders' ] + regexmstr = str().join(['|'+s for s in OBJDMP_NEED])[1:] + matchdict = {key: int(-1) for key in OBJDMP_NEED} + for line in p.stdout.readlines(): + regex = re.match(r'^\s*('+regexmstr+')\s+([0-9a-fA-F]+)', line) + if regex: + found += 1 + matchdict[regex.group(1)] = int(regex.group(2), 16) + retval = p.wait() + global objdmp_retval + objdmp_retval = retval + retlst = list() + retlst += [(retval,found)] + for key in OBJDMP_NEED: + retlst += [matchdict[key]] + return retlst + +def objdump_sections(path, section): + p = subprocess.Popen(str(objdmp_bin)+' '+objdmp_sargs+' '+str(path), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + r = False + for line in p.stdout.readlines(): + regex = re.match(r'^\s+[0-9]+\s+'+section+r'\s+([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+[0-9a-fA-F]+\s+([0-9a-fA-F]+)', line) + if regex: + secPtr = int(regex.group(3), 16) + secVma = int(regex.group(2), 16) + secSiz = int(regex.group(1), 16) + r = True + break + retval = p.wait() + global objdmp_retval + objdmp_retval = retval + if r and retval == 0: + return ( secVma, secPtr, secSiz ) + else: + return ( None, None, None ) + +def file_to_buf(path): + buf = bytearray() + with open(path, "rb") as fin: + for line in fin: + buf += line + return buf + return None + +def buf_to_file(path, buf): + with open(path, "wb") as fout: + fout.write(str(buf)) + fout.flush() + return True + return False + +def find_endmarker_offset(endmarker, bytebuf, ldrPtr, ldrSiz): + if type(bytebuf) is not bytearray: + return -1 + return str(buf).find(endmarker, 0 if ldrPtr is None else ldrPtr, 0 if ldrSiz is None or ldrSiz is None else ldrPtr+ldrSiz) + +def swapByteOrder32(bytebuf, offset): + if type(offset) == int and (type(bytebuf) == int or bytebuf is None): + intval = struct.unpack("<I", struct.pack(">I", offset))[0] + elif type(offset) == int and type(bytebuf) == bytearray: + intval = struct.unpack('<I', bytebuf[offset:offset+0x4])[0] + else: raise TypeError('bytebuf must be either int or bytearray') + return bytearray([(intval >> i & 0xff) for i in (24,16,8,0)]) + +def setInt32(bytebuf, offset, intbuf): + if type(bytebuf) != bytearray or \ + type(intbuf) != int or \ + type(offset) != int: + raise TypeError('Check your arguments: f(%s,%s,%s)' % (type(bytebuf),type(offset),type(intbuf))) + bytebuf[offset:offset+4] = swapByteOrder32(None, intbuf) + +def getInt32(bytebuf, offset): + if type(bytebuf) != bytearray or \ + type(offset) != int: + raise TypeError('Check your arguments: f(%s,%s)' % (type(bytebuf),type(offset))) + return swapByteOrder32(bytebuf, offset) + +def setInt32Buf(bytebuf, offset, buf): + if type(bytebuf) != bytearray or \ + type(buf) != bytearray or \ + type(offset) != int: + raise TypeError('Check your arguments: f(%s,%s,%s)' % (type(bytebuf),type(offset),type(buf))) + if len(buf) % 4 != 0: + raise TypeError('buffer length is not a multiple of 4: %d' % (len(buf))) + for i in range(0, len(buf), 4): + setInt32(bytebuf, offset+i, int(str(buf[i:i+4]).encode('hex'), 16)) + +def getInt32Buf(bytebuf, offset, maxlen=4): + if type(bytebuf) != bytearray or \ + type(offset) != int or \ + type(maxlen) != int: + raise TypeError('Check your arguments: f(%s,%s,%s)' % (type(bytebuf),type(offset),type(buf))) + if maxlen % 4 != 0: + raise TypeError('max length is not a multiple of 4: %d' % (maxlen)) + retbuf = bytearray(maxlen) + for i in range(0, maxlen, 4): + retbuf[i:i+4] = getInt32(bytebuf, offset+i) + return retbuf + +def calcLoaderStructOffset(endmarkerOffset, loaderOffsets): + structsiz = loaderOffsets['structSize'] + endmarkersiz = loaderOffsets['endMarkerSize'] + return endmarkerOffset + endmarkersiz - structsiz + +# patches ptrToDLL, sizOfDLL +def patchLoader(bytebuf, loaderOffsets, endmarkerOffset, (dllVma, dllPtr, dllSiz)): + buf = bytebuf + if buf is None: + return False + structbase = calcLoaderStructOffset(endmarkerOffset, loaderOffsets) + + # loader: uint32_t ptrToDLL, uint32_t sizOfDLL + setInt32(bytebuf, structbase + loaderOffsets['ptrToDLL'], dllVma) + setInt32(bytebuf, structbase + loaderOffsets['sizOfDLL'], dllSiz) + return True + +# get loader iv/key or generate (and patch) it if user want so +def getXorKeyIv(bytebuf, loaderOffsets, endmarkerOffset, gen_func=None): + buf = bytebuf + if buf is None: + return (None,None) + structbase = calcLoaderStructOffset(endmarkerOffset, loaderOffsets) + + ldr_key = loaderOffsets['key[0]'] + ldr_iv = loaderOffsets['iv[0]'] + ldr_ivkeylen = loaderOffsets['ldrIvKeyLen'] + ldr_ivkeysiz = loaderOffsets['ldrIvKeySiz'] + + keybuf = getInt32Buf(buf, structbase + ldr_key, ldr_ivkeylen*ldr_ivkeysiz) + ivbuf = getInt32Buf(buf, structbase + ldr_iv, ldr_ivkeylen*ldr_ivkeysiz) + keypatched = False + ivpatched = False + if keybuf == '\x00'*(ldr_ivkeylen*ldr_ivkeysiz) and gen_func is not None: + setInt32Buf(buf, structbase + ldr_key, gen_func(ldr_ivkeylen)) + keybuf = getInt32Buf(buf, structbase + ldr_key, ldr_ivkeylen*ldr_ivkeysiz) + keypatched = True + if ivbuf == '\x00'*(ldr_ivkeylen*ldr_ivkeysiz) and gen_func is not None: + setInt32Buf(buf, structbase + ldr_iv, gen_func(ldr_ivkeylen)) + ivbuf = getInt32Buf(buf, structbase + ldr_iv, ldr_ivkeylen*ldr_ivkeysiz) + ivpatched = True + return ( (keybuf, keypatched), (ivbuf, ivpatched) ) + +def isLoaderStringsEncrypted(bytebuf, loaderOffsets, endmarkerOffset, xorkey, xoriv, xor_npcbc_func=None): + buf = bytebuf + if buf is None: + return False + structbase = calcLoaderStructOffset(endmarkerOffset, loaderOffsets) + + (ldr_sVALen, ldr_sIBRPLen) = loaderOffsets['ldrStrLen'] # NULL-char included + ldr_strivkeylen = loaderOffsets['ldrStrIvKeyLen'] + ldr_ivkeysiz = loaderOffsets['ldrIvKeySiz'] + + abs_siz = ldr_strivkeylen*ldr_ivkeysiz + key = xorkey[:abs_siz] + iv = xoriv[:abs_siz] + + (ldr_sVALen, ldr_sIBRPLen) = loaderOffsets['ldrStrLen'] # NULL-char included + idxVA = structbase + loaderOffsets['strVirtualAlloc[0]'] + idxIBRP = structbase + loaderOffsets['strIsBadReadPtr[0]'] + strVA = getInt32Buf(buf, idxVA, ldr_sVALen-1) + strIBRP = getInt32Buf(buf, idxIBRP, ldr_sIBRPLen-1) + + retplain = bool(str(strVA).isalpha() is True and str(strIBRP).isalpha() is True) + if retplain is True: + retvalid = True + else: + decVA = getInt32Buf(xor_npcbc_func(strVA, key, iv), 0, ldr_sVALen-1) + decIBRP = getInt32Buf(xor_npcbc_func(strIBRP, key, iv), 0, ldr_sIBRPLen-1) + retvalid = bool(decVA.isalpha()) is True and bool(decIBRP.isalpha()) is True + return (retplain, retvalid) + +# patches (encrypt) loader strings +def patchLoaderStrings(bytebuf, loaderOffsets, endmarkerOffset, xorkey, xoriv, xor_npcbc_func=None): + buf = bytebuf + if buf is None or xor_npcbc_func is None: + return False + structbase = calcLoaderStructOffset(endmarkerOffset, loaderOffsets) + + (ldr_sVALen, ldr_sIBRPLen) = loaderOffsets['ldrStrLen'] # NULL-char included + ldr_strivkeylen = loaderOffsets['ldrStrIvKeyLen'] + ldr_ivkeysiz = loaderOffsets['ldrIvKeySiz'] + + abs_siz = ldr_strivkeylen*ldr_ivkeysiz + key = xorkey[:abs_siz] + iv = xoriv[:abs_siz] + + idxVA = structbase + loaderOffsets['strVirtualAlloc[0]'] + idxIBRP = structbase + loaderOffsets['strIsBadReadPtr[0]'] + strVA = getInt32Buf(buf, idxVA, ldr_sVALen-1) + strIBRP = getInt32Buf(buf, idxIBRP, ldr_sIBRPLen-1) + + (cipherVA, cipherIBRP) = ( xor_npcbc_func(strVA, key, iv), xor_npcbc_func(strIBRP, key, iv) ) + if len(cipherVA) != ldr_sVALen -1 or len(cipherIBRP) != ldr_sIBRPLen -1: + return False + (plainVA, plainIBRP) = ( xor_npcbc_func(cipherVA, key, iv), xor_npcbc_func(cipherIBRP, key, iv) ) + if plainVA != strVA or plainIBRP != strIBRP: + return False + + setInt32Buf(buf, idxVA, cipherVA) + setInt32Buf(buf, idxIBRP, cipherIBRP) + return True + +def isDllHeaderEncrypted(buf, dllPtr): + e_lfanew_OFFSET = 0x3C + e_lfanew = struct.unpack("<L", buf[e_lfanew_OFFSET:e_lfanew_OFFSET+0x4])[0] + if buf[dllPtr:dllPtr+0x2] != '\x4d\x5a' or e_lfanew < 0x40 or e_lfanew > 0x400: + return (False, False) + if len(buf) < e_lfanew+2: + return (True, False) + if buf[dllPtr+e_lfanew:dllPtr+e_lfanew+2] == '\x50\x45': + return (True, False) + return (True, True) + +def patchEncryptDll(buf, dllPtr, dllSiz, xorkey, xoriv, xor_npcbc_func=None): + if dllPtr+dllSiz < len(buf) or dllSiz % 8 != 0: + return (False, False, False) + hdrbuf = getInt32Buf(buf, dllPtr, dllSiz) + cipherHeader = xor_npcbc_func(hdrbuf, xorkey, xoriv) + if len(cipherHeader) != len(hdrbuf): + return (True, False, False) + plainHeader = xor_npcbc_func(cipherHeader, xorkey, xoriv) + if len(cipherHeader) != len(plainHeader): + return (True, True, False) + if hdrbuf != plainHeader: + return (True, True, False) + setInt32Buf(buf, dllPtr, cipherHeader) + return (True, True, True) + + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("-q", "--quiet", action="store_false", dest="verbose", default=True, help="don't print status messages to stdout") + parser.add_option("-o", "--objdump", dest="objdmp_bin", default=objdmp_bin, help="path to mingw objdump binary [default: %default]") + parser.add_option("-f", "--out-file", dest="out_file", help="set output file [default: same as --win32]") + parser.add_option("-l", "--pyload", dest="pyload", default=pyload_so, help="set "+pyload_name+" path [required, default: %default]") + parser.add_option("-c", "--pycrypt", dest="pycrypt", default=pycrypt_so, help="set "+pycrypt_name+" path [required, default: %default]") + bingrp = OptionGroup(parser, "Binary Options", None) + bingrp.add_option("-w", "--win32", dest="win32_pe", help="path to windows pe binary which contains the loader [required]") + bingrp.add_option("-b", "--binary", dest="miller_bin", help="patch loader with sections from miller dll") + parser.add_option_group(bingrp) + ldrgrp = OptionGroup(parser, "WIN32_PE Options", None) + ldrgrp.add_option("-e", "--endmarker", dest="endmarker", help="set the loader endmarker value (4*n bytes)") + ldrgrp.add_option("-s", "--ldr-section", dest="section", help="specify the loader section name [required]") + ldrgrp.add_option("-t", "--dll-section", dest="target_section", help="psecify the dll section name") + ldrgrp.add_option("-r", "--crypt-strings", action="store_true", dest="crypt_strings", help="encrypt loader strings") + ldrgrp.add_option("-H", "--crypt-dll", action="store_true", dest="crypt_dll", help="encrypt dll pe header") + parser.add_option_group(ldrgrp) + actgrp = OptionGroup(parser, "Actions", None) + actgrp.add_option("-a", "--show-address", action="store_true", dest="show_adr", help="shows section offset (if found) from the pe binary") + actgrp.add_option("-z", "--show-size", action="store_true", dest="show_siz", help="shows section size (if found) from the pe binary") + actgrp.add_option("-m", "--show-marker", action="store_true", dest="show_marker", help="shows the endmarker (offset)") + actgrp.add_option("-k", "--show-xorkey", action="store_true", dest="show_xorkey", help="print XOR key to stdout") + actgrp.add_option("-i", "--show-xoriv", action="store_true", dest="show_xoriv", help="print XOR iv to stdout") + actgrp.add_option("-p", "--patch", action="store_true", dest="patch", default=False, help="patch the --section with address and size information from --target-section") + parser.add_option_group(actgrp) + (options, args) = parser.parse_args() + + bname = os.path.basename(sys.argv[0]) + # load *.so's if necessary + pyload = require_pyso(pyload_name, options.pyload) + # some commands need pycrypt module + pycrypt = require_pyso(pycrypt_name, options.pycrypt) + if pycrypt is None: + sys.stderr.write(bname + ': Could not import '+pycrypt_name+': ' + options.pycrypt + '.\n') + sys.exit(1) + + endmarker = None + if pyload is None: + sys.stderr.write(bname + ': WARNING: Could not import '+pyload_name+': ' + options.pyload + '.\n') + if options.patch: + sys.stderr.write(bname + ': Patching requires '+pyload_name+'\n') + sys.exit(1) + else: + endmarker = pyload.getEndmarker() + loaderdict = pyload.getStructOffset() + + # argument checks + # pyloader python lib and endmarker + if not options.endmarker and pyload is None: + sys.stderr.write(bname + ': missing --endmarker and '+pyload_name+' ('+options.pyload+') not imported\n') + sys.exit(1) + elif not options.endmarker: + sys.stderr.write(bname + ': using default endmarker 0x'+str(endmarker).encode('hex')+'\n') + else: + tmp = str(parse_c_array(options.endmarker)) + if endmarker is not None and tmp != endmarker: + sys.stderr.write(bname + ': WARNING: LOADER_ENDMARKER is not equal --endmarker: '+str(endmarker).encode('hex')+' != '+str(tmp).encode('hex')+'\n') + sys.stderr.write(bname + ': using '+str(tmp).encode('hex')+'\n') + endmarker = tmp + if len(endmarker) % 4 != 0: + sys.stderr.write(bname + ': endmarker length MUST be a multiple of 4 and not ' + str(len(endmarker)) + '\n') + sys.exit(1) + if options.verbose: + print bname + ': using 0x' + endmarker.encode('hex') + ' as endmarker' + # win32_pe is required for all operations + if options.win32_pe is None: + sys.stderr.write(bname + ': WIN32_PE is required for all operations\n') + parser.print_help() + sys.exit(1) + # same applies for section (TODO: Maybe discard section and search for endmarker in whole pe file) + if options.section is None: + sys.stderr.write(bname + ': --win32 needs --section\n') + parser.print_help() + sys.exit(1) + # target section is required (specifies the DLL section) + if options.patch and options.target_section is None: + sys.stderr.write(bname + ': --patch needs --target-section\n') + parser.print_help() + sys.exit(1) + # patch win32_pe directly if possible + if options.out_file is None: + options.out_file = options.win32_pe + + for binary in [options.win32_pe, options.miller_bin]: + if binary is not None: + if not os.access(binary, os.R_OK): + sys.stderr.write(bname + ': No read access ' + binary + '\n') + sys.exit(2) + + if not(os.path.isfile(objdmp_bin) or os.access(objdmp_bin, os.X_OK)): + sys.stderr.write(bname + ': objdump ('+objdmp_bin+') does not exist or is not executable\n') + sys.exit(2) + + # read win32pe/miller_bin + buf = None + (ldrVma, ldrPtr, ldrSiz) = objdump_sections(options.win32_pe, options.section) + if (ldrVma or ldrPtr or ldrSiz) is None: + sys.stderr.write(bname + ': Error: Loader section missing or objdump binary does not work.\n') + objdump_print_err(bname) + sys.exit(3) + # print section offset/size + if options.verbose: + print bname + (': found section %s in %s (RVA: 0x%08X | PTR: 0x%08X | SIZ: 0x%08X)' % (options.section, options.win32_pe, ldrVma, ldrPtr, ldrSiz)) + # load file to memory + buf = file_to_buf(options.win32_pe) + if buf is None: + sys.stderr.write(bname + ': could not load file '+options.win32_pe+' into memory\n') + sys.exit(3) + # search loader endmarker + endoff = find_endmarker_offset(endmarker, buf, ldrPtr, ldrSiz) + if endoff == -1: + sys.stderr.write(bname + ': endmarker(`'+endmarker.encode('hex')+'`) not found\n') + sys.exit(3) + if options.verbose: + print bname + ': endmarker(`'+endmarker.encode('hex')+'`) found at '+str(endoff)+' ('+str(hex(endoff))+')' + # -a, -z, -m + if options.show_adr: + print str(ldrPtr) if not options.verbose else str(bname) + ': '+options.section+' offset: '+str(ldrPtr)+' ('+str(hex(ldrPtr))+')' + if options.show_siz: + print str(ldrSiz) if not options.verbose else str(bname) + ': '+options.section+' size: '+str(ldrSiz)+' ('+str(hex(ldrSiz))+')' + if options.show_marker: + print str(endoff) if not options.verbose else str(bname) + ': '+options.section+' endmarker: '+str(endoff)+' ('+str(hex(endoff))+')' + + # parse dll and patch loader + if options.win32_pe is not None: + if options.target_section is None: + sys.stderr.write(bname + ': Dumping data from target section requires --dll-section\n') + sys.exit(3) + (dllVma, dllPtr, dllSiz) = objdump_sections(options.win32_pe, options.target_section) + if (dllVma or dllPTr or dllSiz) is None: + sys.stderr.write(bname + ': Error: DLL (target)section missing or objdump binary does not work.\n') + objdump_print_err(bname) + sys.exit(3) + if options.verbose: + print bname + (': found section %s in %s (RVA: 0x%08X | PTR: 0x%08X | SIZ: 0x%08X)' % (options.target_section, options.win32_pe, dllVma, dllPtr, dllSiz)) + + # let's encrypt + if pycrypt is not None and options.win32_pe is not None and buf is not None: + ((keybuf,keypatched), (ivbuf,ivpatched)) = getXorKeyIv(buf, loaderdict, endoff, pycrypt.xorRandomKeyIv) + if options.verbose: + print bname + ': ' + ('XOR(KEY) patched' if keypatched is True else 'XOR(KEY) !patched') + ', ' + ('XOR(IV) patched' if ivpatched is True else 'XOR(IV) !patched') + print (bname + ': XOR(KEY,LEN): %s (%d bytes)\n' + bname + ': XOR(IV ,LEN): %s (%d bytes)') % (str(keybuf).encode('hex'), len(keybuf), str(ivbuf).encode('hex'), len(ivbuf)) + else: + if options.show_xorkey: + print str(keybuf).encode('hex') + if options.show_xoriv: + print str(ivbuf).encode('hex') + + # Loader string encryption + if options.crypt_strings is True: + (isPlain, isValid) = isLoaderStringsEncrypted(buf, loaderdict, endoff, keybuf, ivbuf, pycrypt.xorCrypt) + if not isValid: + sys.stderr.write(bname + ': XOR Loader Strings are not valid, wrong XOR key/iv?\n') + sys.exit(4) + if not isPlain: + sys.stderr.write(bname + ': XOR Loader Strings already encrypted\n') + elif patchLoaderStrings(buf, loaderdict, endoff, keybuf, ivbuf, pycrypt.xorCrypt) is not True: + sys.stderr.write(bname + ': XOR Crypt Loader Strings failed\n') + sys.exit(4) + elif options.verbose: + print bname + ': String encryption succeeded!' + + # PE binary encryption + if options.crypt_dll is True: + (validDOS, validPE) = isDllHeaderEncrypted(buf, dllPtr) + if validDOS is not True or validPE is not True: + sys.stderr.write(bname + ': Not a valid DOS/PE Header, already encrypted?\n') + else: + ret = patchEncryptDll(buf, dllPtr, dllSiz, keybuf, ivbuf, pycrypt.xorCrypt) + if ret != (True, True, True): + sys.stderr.write(bname + ': PE encryption failed! Returned: %s\n' % (str(ret))) + sys.exit(4) + if options.verbose: + print bname + ': PE encryption done' + + # parse dll and patch loader + if options.patch and pyload is not None and buf is not None: + if options.verbose: + print bname + (': Patching Loader with dll section (RVA: 0x%08X | PTR: 0x%08X | SIZ: 0x%08X)' % (dllVma,dllPtr,dllSiz)) + found = patchLoader(buf, loaderdict, endoff, (dllVma,dllPtr,dllSiz)) + if found: + if not buf_to_file(options.out_file, buf): + sys.stderr.write(bname + ': could not write buffer to disk\n') + sys.exit(4) + if options.verbose: + print bname + ': Patching succeeded!' + else: + sys.stderr.write(bname + ': None found ..\n') + sys.exit(4) + + sys.exit(0) |