#!/usr/bin/env python2.7 """ U{Corelan} Copyright (c) 2011-2024, Peter Van Eeckhoutte - Corelan Consulting bv All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Corelan nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PETER VAN EECKHOUTTE OR CORELAN CONSULTING BV BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. $Revision: 636 $ $Id: mona.py 636 2024-03-26 14:49:00Z corelanc0d3r $ """ __VERSION__ = '2.0' __REV__ = filter(str.isdigit, '$Revision: 636 $') __IMM__ = '1.8' __DEBUGGERAPP__ = '' arch = 32 win7mode = False # try: # import debugger # except: # pass try: import immlib as dbglib from immlib import LogBpHook __DEBUGGERAPP__ = "Immunity Debugger" except: try: import pykd import windbglib as dbglib from windbglib import LogBpHook dbglib.checkVersion() arch = dbglib.getArchitecture() __DEBUGGERAPP__ = "WinDBG" except SystemExit: print("-Exit.") import sys sys.exit(1) except Exception: #import traceback print("Do not run this script outside of a debugger !") #print traceback.format_exc() import sys sys.exit(1) import getopt try: #import debugtypes #import libdatatype from immutils import * except: pass import os import re import sys import types import random import shutil import struct import string import types import urllib import inspect import datetime import binascii import itertools import traceback import pickle import json from operator import itemgetter from collections import defaultdict, namedtuple import cProfile import pstats import copy DESC = "Corelan Consulting bv exploit development swiss army knife" #---------------------------------------# # Global stuff # #---------------------------------------# TOP_USERLAND = 0x7fffffff if arch == 32 else 0x7FFFFFFFFFFF STACK_POINTER = "ESP" if arch == 32 else "RSP" PTR_SIZE_DIRECTIVE = "DWORD PTR" if arch == 32 else "QWORD PTR" g_modules={} MemoryPageACL={} global CritCache global vtableCache global stacklistCache global segmentlistCache global VACache global IATCache global NtGlobalFlag global FreeListBitmap global memProtConstants global currentArgs global disasmUpperChecked global disasmIsUpper global configFileCache global configwarningshown NtGlobalFlag = -1 FreeListBitmap = {} memProtConstants = {} CritCache={} IATCache={} vtableCache={} stacklistCache={} segmentlistCache={} configFileCache={} VACache={} ptr_counter = 0 ptr_to_get = -1 silent = False ignoremodules = False noheader = False dbg = dbglib.Debugger() disasmUpperChecked = False disasmIsUpper = False configwarningshown = False if __DEBUGGERAPP__ == "WinDBG": if pykd.getSymbolPath().replace(" ","") == "": dbg.log("") dbg.log("** Warning, no symbol path set ! ** ",highlight=1) sympath = "srv*c:\symbols*http://msdl.microsoft.com/download/symbols" dbg.log(" I'll set the symbol path to %s" % sympath) pykd.setSymbolPath(sympath) dbg.log(" Symbol path set, now reloading symbols...") dbg.nativeCommand(".reload") dbg.log(" All set. Please restart WinDBG.") dbg.log("") osver = dbg.getOsVersion() if osver in ["6", "7", "8", "vista", "win7", "2008server", "win8", "win8.1", "win10", "win11"]: win7mode = True heapgranularity = 8 if arch == 64: heapgranularity = 16 offset_categories = ["xp", "vista", "win7", "win8", "win10", "win11"] # offset = [x86,x64] offsets = { "FrontEndHeap" : { "xp" : [0x580,0xad8], "vista" : [0x0d4,0x178], "win8" : [0x0d0,0x170], "win10" : { 14393 : [0x0d4,0x178] }, "win11" : { 14393 : [0x0d4,0x178] } }, "FrontEndHeapType" : { "xp" : [0x586,0xae2], "vista" : [0x0da,0x182], "win8" : [0x0d6,0x17a], "win10" : { 14393 : [0x0da,0x182] }, "win11" : { 14393 : [0x0da,0x182] } }, "VirtualAllocdBlocks" : { "xp" : [0x050,0x090], "vista" : [0x0a0,0x118], "win8" : [0x09c,0x110] }, "SegmentList" : { "vista" : [0x0a8,0x128], "win8" : [0x0a4,0x120] } } #---------------------------------------# # Populate constants # #---------------------------------------# memProtConstants["X"] = ["PAGE_EXECUTE",0x10] memProtConstants["RX"] = ["PAGE_EXECUTE_READ",0x20] memProtConstants["RWX"] = ["PAGE_EXECUTE_READWRITE",0x40] memProtConstants["N"] = ["PAGE_NOACCESS",0x1] memProtConstants["R"] = ["PAGE_READONLY",0x2] memProtConstants["RW"] = ["PAGE_READWRITE",0x4] memProtConstants["GUARD"] = ["PAGE_GUARD",0x100] memProtConstants["NOCACHE"] = ["PAGE_NOCACHE",0x200] memProtConstants["WC"] = ["PAGE_WRITECOMBINE",0x400] #---------------------------------------# # Utility functions # #---------------------------------------# def resetGlobals(): """ Clears all global variables """ global CritCache global vtableCache global stacklistCache global segmentlistCache global VACache global NtGlobalFlag global FreeListBitmap global memProtConstants global currentArgs CritCache = None vtableCache = None stacklistCache = None segmentlistCache = None VACache = None NtGlobalFlag = None FreeListBitmap = None memProtConstants = None currentArgs = None disasmUpperChecked = False return def getPythonVersion(): versioninfo = sys.version versioninfolines = versioninfo.split('\n') return versioninfolines[0] def toHex(n): """ Converts a numeric value to hex (pointer to hex) Arguments: n - the value to convert Return: A string, representing the value in hex (8 characters long) """ if arch == 32: return "%08x" % n if arch == 64: return "%016x" % n def sanitize_module_name(modname): """ Sanitizes a module name so it can be used as a variable """ return modname.replace(".", "_") def DwordToBits(srcDword): """ Converts a dword into an array of 32 bits """ bit_array = [] h_str = "%08x" % srcDword h_size = len(h_str) * 4 bits = (bin(int(h_str,16))[2:]).zfill(h_size)[::-1] for bit in bits: bit_array.append(int(bit)) return bit_array def getDisasmInstruction(disasmentry): """ returns instruction string, checks if ASM is uppercase and converts to upper if needed """ instrline = disasmentry.getDisasm() global disasmUpperChecked global disasmIsUpper if disasmUpperChecked: if not disasmIsUpper: instrline = instrline.upper() else: disasmUpperChecked = True interim_instr = instrline.upper() if interim_instr == instrline: disasmIsUpper = True else: disasmIsUpper = False dbg.log("** It looks like you've configured the debugger to produce lowercase disassembly. Got it, all good **", highlight=1) instrline = instrline.upper() return instrline def multiSplit(thisarg,delimchars): """ splits a string into an array, based on provided delimeters""" splitparts = [] thispart = "" for c in str(thisarg): if c in delimchars: thispart = thispart.replace(" ","") if thispart != "": splitparts.append(thispart) splitparts.append(c) thispart = "" else: thispart += c if thispart != "": splitparts.append(thispart) return splitparts def getAddyArg(argaddy): """ Tries to extract an address from a specified argument addresses and values will be considered hex (unless you specify 0n before a value) registers are allowed too """ findaddy = 0 addyok = True addyparts = [] addypartsint = [] delimchars = ["-","+","*","/","(",")","&","|",">","<"] regs = dbg.getRegs() thispart = "" for c in str(argaddy): if c in delimchars: thispart = thispart.replace(" ","") if thispart != "": addyparts.append(thispart) addyparts.append(c) thispart = "" else: thispart += c if thispart != "": addyparts.append(thispart) partok = False for part in addyparts: cleaned = part if not part in delimchars: for x in delimchars: cleaned = cleaned.replace(x,"") if cleaned.startswith("[") and cleaned.endswith("]"): partval,partok = getIntForPart(cleaned.replace("[","").replace("]","")) if partok: try: partval = struct.unpack(' 0: partval = m addyok = True return partval,addyok def getHeapAllocSize(requested_size, granularity = 8): """ Returns the expected allocated size for a request of X bytes of heap memory taking a certain granularity into account """ requested_size_int = to_int(requested_size) interimval = (requested_size_int / granularity) * granularity interimtimes = (requested_size_int / granularity) if (interimval < requested_size_int): interimtimes += 1 allocated_size = granularity * interimtimes return allocated_size def getFunctionAddress(modname,funcname): """ Returns the addres of the function inside a given module Relies on EAT data Returns 0 if nothing found """ funcaddy = 0 m = getModuleObj(modname) if not m == None: eatlist = m.getEAT() for f in eatlist: if funcname == eatlist[f]: return f for f in eatlist: if funcname.lower() == eatlist[f].lower(): return f return funcaddy def getFunctionName(addy): """ Returns symbol name closest to the specified address Only works in WinDBG Returns function name and optional offset """ fname = "" foffset = "" cmd2run = "ln 0x%08x" % addy output = dbg.nativeCommand(cmd2run) for line in output.split("\n"): if "|" in line: lineparts = line.split(" ") partcnt = 0 for p in lineparts: if not p == "": if partcnt == 1: fname = p break partcnt += 1 if "+" in fname: fnameparts = fname.split("+") if len(fnameparts) > 1: return fnameparts[0],fnameparts[1] return fname,foffset def printDataArray(data,charsperline=16,prefix=""): maxlen = len(data) charcnt = 0 charlinecnt = 0 linecnt = 0 thisline = prefix lineprefix = "%04d - %04d " % (charcnt,charcnt+charsperline-1) thisline += lineprefix while charcnt < maxlen: thisline += data[charcnt:charcnt+1] charlinecnt += 1 charcnt += 1 if charlinecnt == charsperline or charlinecnt == maxlen: dbg.log(thisline) thisline = prefix lineprefix = "%04d - %04d " % (charcnt,charcnt+charsperline-1) thisline += lineprefix charlinecnt = 0 return None def find_all_copies(tofind,data): """ Finds all occurences of a string in a longer string Arguments: tofind - the string to find data - contains the data to look for all occurences of 'tofind' Return: An array with all locations """ position = 0 positions = [] searchstringlen = len(tofind) maxlen = len(data) while position < maxlen: position = data.find(tofind,position) if position == -1: break positions.append(position) position += searchstringlen return positions def getAllStringOffsets(data,minlen,offsetstart = 0): asciistrings = {} for match in re.finditer("(([\x20-\x7e]){%d,})" % minlen,data): thisloc = match.start() + offsetstart thisend = match.end() + offsetstart asciistrings[thisloc] = thisend return asciistrings def getAllUnicodeStringOffsets(data,minlen,offsetstart = 0): unicodestrings = {} for match in re.finditer("((\x00[\x20-\x7e]){%d,})" % (minlen*2),data): unicodestrings[offsetstart + match.start()] = (offsetstart + match.end()) return unicodestrings def stripExtension(fullname): """ Removes extension from a filename (will only remove the last extension) Arguments : fullname - the original string Return: A string, containing the original string without the last extension """ nameparts = str(fullname).split(".") if len(nameparts) > 1: cnt = 0 modname = "" while cnt < len(nameparts)-1: modname = modname + nameparts[cnt] + "." cnt += 1 return modname.strip(".") return fullname def toHexByte(n): """ Converts a numeric value to a hex byte Arguments: n - the vale to convert (max 255) Return: A string, representing the value in hex (1 byte) """ return "%02X" % n def toAsciiOnly(inputstr): return "".join(i for i in inputstr if ord(i)<128 and ord(i) > 31) def toAscii(n): """ Converts a byte to its ascii equivalent. Null byte = space Arguments: n - A string (2 chars) representing the byte to convert to ascii Return: A string (one character), representing the ascii equivalent """ asciiequival = " " if n.__class__.__name__ == "int": n = "%02x" % n try: if n != "00": asciiequival=binascii.a2b_hex(n) else: asciiequival = " " except TypeError: asciiequival=" " return asciiequival def hex2bin(pattern): """ Converts a hex string (\\x??\\x??\\x??\\x??) to real hex bytes Arguments: pattern - A string representing the bytes to convert Return: the bytes """ pattern = pattern.replace("\\x", "") pattern = pattern.replace("\"", "") pattern = pattern.replace("\'", "") return ''.join([binascii.a2b_hex(i+j) for i,j in zip(pattern[0::2],pattern[1::2])]) def cleanHex(hex): hex = hex.replace("'","") hex = hex.replace('"',"") hex = hex.replace("\\x","") hex = hex.replace("0x","") return hex def hex2int(hex): return int(hex,16) def getVariantType(typenr): varianttypes = {} varianttypes[0x0] = "VT_EMPTY" varianttypes[0x1] = "VT_NULL" varianttypes[0x2] = "VT_I2" varianttypes[0x3] = "VT_I4" varianttypes[0x4] = "VT_R4" varianttypes[0x5] = "VT_R8" varianttypes[0x6] = "VT_CY" varianttypes[0x7] = "VT_DATE" varianttypes[0x8] = "VT_BSTR" varianttypes[0x9] = "VT_DISPATCH" varianttypes[0xA] = "VT_ERROR" varianttypes[0xB] = "VT_BOOL" varianttypes[0xC] = "VT_VARIANT" varianttypes[0xD] = "VT_UNKNOWN" varianttypes[0xE] = "VT_DECIMAL" varianttypes[0x10] = "VT_I1" varianttypes[0x11] = "VT_UI1" varianttypes[0x12] = "VT_UI2" varianttypes[0x13] = "VT_UI4" varianttypes[0x14] = "VT_I8" varianttypes[0x15] = "VT_UI8" varianttypes[0x16] = "VT_INT" varianttypes[0x17] = "VT_UINT" varianttypes[0x18] = "VT_VOID" varianttypes[0x19] = "VT_HRESULT" varianttypes[0x1A] = "VT_PTR" varianttypes[0x1B] = "VT_SAFEARRAY" varianttypes[0x1C] = "VT_CARRAY" varianttypes[0x1D] = "VT_USERDEFINED" varianttypes[0x1E] = "VT_LPSTR" varianttypes[0x1F] = "VT_LPWSTR" varianttypes[0x24] = "VT_RECORD" varianttypes[0x25] = "VT_INT_PTR" varianttypes[0x26] = "VT_UINT_PTR" varianttypes[0x2000] = "VT_ARRAY" varianttypes[0x4000] = "VT_BYREF" if typenr in varianttypes: return varianttypes[typenr] else: return "" def bin2hex(binbytes): """ Converts a binary string to a string of space-separated hexadecimal bytes. """ return ' '.join('%02x' % ord(c) for c in binbytes) def bin2hexstr(binbytes): """ Converts bytes to a string with hex Arguments: binbytes - the input to convert to hex Return : string with hex """ return ''.join('\\x%02x' % ord(c) for c in binbytes) def str2js(inputstring): """ Converts a string to a javascript string Arguments: inputstring - the input string to convert Return : string in javascript format """ length = len(inputstring) if length % 2 == 1: jsmsg = "Warning : odd size given, js pattern will be truncated to " + str(length - 1) + " bytes, it's better use an even size\n" if not silent: dbg.logLines(jsmsg,highlight=1) toreturn="" for thismatch in re.compile("..").findall(inputstring): thisunibyte = "" for thisbyte in thismatch: thisunibyte = "%02x" % ord(thisbyte) + thisunibyte toreturn += "%u" + thisunibyte return toreturn def readJSONDict(filename): """ Retrieve stored dict from JSON file """ jsondict = {} with open(filename, 'rb') as infile: jsondata = infile.read() jsondict = json.loads(jsondata) return jsondict def writeJSONDict(filename, dicttosave): """ Write dict as JSON to file """ with open(filename, 'wb') as outfile: json.dump(dicttosave, outfile) return def readPickleDict(filename): """ Retrieve stored dict from file (pickle load) """ pdict = {} pdict = pickle.load( open(filename,"rb")) return pdict def writePickleDict(filename, dicttosave): """ Write a dict to file as a pickle """ pickle.dump(dicttosave, open(filename, "wb")) return def opcodesToHex(opcodes): """ Converts pairs of chars (opcode bytes) to hex string notation Arguments : opcodes : pairs of chars Return : string with hex """ toreturn = [] opcodes = opcodes.replace(" ","") for cnt in range(0, len(opcodes), 2): thisbyte = opcodes[cnt:cnt+2] toreturn.append("\\x" + thisbyte) toreturn = ''.join(toreturn) return toreturn def rmLeading(input,toremove,toignore=""): """ Removes leading characters from an input string Arguments: input - the input string toremove - the character to remove from the begin of the string toignore - ignore this character Return: the input string without the leading character(s) """ newstring = "" cnt = 0 while cnt < len(input): if input[cnt] != toremove and input[cnt] != toignore: break cnt += 1 newstring = input[cnt:] return newstring def getVersionInfo(filename): """Retrieves version and revision numbers from a mona file Arguments : filename Return : version - string with version (or empty if not found) revision - string with revision (or empty if not found) """ file = open(filename,"rb") content = file.readlines() file.close() revision = "" version = "" for line in content: if line.startswith("$Revision"): parts = line.split(" ") if len(parts) > 1: revision = parts[1].replace("$","") if line.startswith("__VERSION__"): parts = line.split("=") if len(parts) > 1: version = parts[1].strip() return version,revision def toniceHex(data,size): """ Converts a series of bytes into a hex string, newline after 'size' nr of bytes Arguments : data - the bytes to convert size - the number of bytes to show per linecache Return : a multiline string """ flip = 1 thisline = "\"" block = "" try: # Python 2 xrange except NameError: # Python 3, xrange is now named range xrange = range for cnt in xrange(len(data)): thisline += "\\x%s" % toHexByte(ord(data[cnt])) if (flip == size) or (cnt == len(data)-1): thisline += "\"" flip = 0 block += thisline block += "\n" thisline = "\"" cnt += 1 flip += 1 return block.lower() def hexStrToInt(inputstr): """ Converts a string with hex bytes to a numeric value Arguments: inputstr - A string representing the bytes to convert. Example : 41414141 Return: the numeric value """ valtoreturn = 0 try: valtoreturn = int(inputstr, 16) except: valtoreturn = 0 return valtoreturn def to_int(inputstr): """ Converts a string to int, whether it's hex or decimal Arguments: inputstr - A string representation of a number. Example: 0xFFFF, 2345 Return: the numeric value """ if str(inputstr).lower().startswith("0x"): return hexStrToInt(inputstr) else: return int(inputstr) def toSize(toPad,size): """ Adds spaces to a string until the string reaches a certain length Arguments: input - A string size - the destination size of the string Return: the expanded string of length """ padded = toPad + " " * (size - len(toPad)) return padded.ljust(size," ") def toUnicode(input): """ Converts a series of bytes to unicode (UTF-16) bytes Arguments : input - the source bytes Return: the unicode expanded version of the input """ unicodebytes = "" # try/except, just in case .encode bails out try: unicodebytes = input.encode('UTF-16LE') except: inputlst = list(input) for inputchar in inputlst: unicodebytes += inputchar + '\x00' return unicodebytes def toJavaScript(input): """ Extracts pointers from lines of text and returns a javascript friendly version """ alllines = input.split("\n") javascriptversion = "" allbytes = "" for eachline in alllines: thisline = eachline.replace("\t","").lower().strip() if not(thisline.startswith("#")): if thisline.startswith("0x"): theptr = thisline.split(",")[0].replace("0x","") # change order to unescape format if arch == 32: ptrstr = "" byte1 = theptr[0] + theptr[1] ptrstr = "\\x" + byte1 byte2 = theptr[2] + theptr[3] ptrstr = "\\x" + byte2 + ptrstr try: byte3 = theptr[4] + theptr[5] ptrstr = "\\x" + byte3 + ptrstr except: pass try: byte4 = theptr[6] + theptr[7] ptrstr = "\\x" + byte4 + ptrstr except: pass allbytes += hex2bin(ptrstr) if arch == 64: byte1 = theptr[0] + theptr[1] byte2 = theptr[2] + theptr[3] byte3 = theptr[4] + theptr[5] byte4 = theptr[6] + theptr[7] byte5 = theptr[8] + theptr[9] byte6 = theptr[10] + theptr[11] byte7 = theptr[12] + theptr[13] byte8 = theptr[14] + theptr[15] allbytes += hex2bin("\\x" + byte8 + "\\x" + byte7 + "\\x" + byte6 + "\\x" + byte5) allbytes += hex2bin("\\x" + byte4 + "\\x" + byte3 + "\\x" + byte2 + "\\x" + byte1) javascriptversion = str2js(allbytes) return javascriptversion def getSourceDest(instruction): """ Determines source and destination register for a given instruction """ src = [] dst = [] srcp = [] dstp = [] srco = [] dsto = [] instr = [] haveboth = False seensep = False seeninstr = False regs = getAllRegs() instructionparts = multiSplit(instruction,[" ",","]) if "," in instructionparts: haveboth = True delkeys = ["DWORD","PTR","BYTE"] for d in delkeys: if d in instructionparts: instructionparts.remove(d) for p in instructionparts: regfound = False for r in regs: if r.upper() in p.upper() and not "!" in p and not len(instr) == 0: regfound = True seeninstr = True break if not regfound: if not seeninstr and not seensep: instr.append(p) if "," in p: seensep = True else: for r in regs: if r.upper() in p.upper(): if not seensep or not haveboth: dstp.append(p) if not r in dsto: dsto.append(r) break else: srcp.append(p) if not r in srco: srco.append(r) break #dbg.log("dst: %s" % dsto) #dbg.log("src: %s" % srco) src = srcp dst = dstp return src,dst def getAllRegs(): """ Return an array with all 32bit, 16bit and 8bit registers """ regs = ["EAX","EBX","ECX","EDX","ESP","EBP","ESI","EDI","EIP"] regs.append("AX") regs.append("BX") regs.append("CX") regs.append("DX") regs.append("BP") regs.append("SP") regs.append("SI") regs.append("DI") regs.append("AL") regs.append("AH") regs.append("BL") regs.append("BH") regs.append("CL") regs.append("CH") regs.append("DL") regs.append("DH") return regs def getSmallerRegs(reg): if reg == "EAX": return ["AX","AL","AH"] if reg == "AX": return ["AL","AH"] if reg == "EBX": return ["BX","BL","BH"] if reg == "BX": return ["BL","BH"] if reg == "ECX": return ["CX","CL","CH"] if reg == "CX": return ["CL","CH"] if reg == "EDX": return ["DX","DL","DH"] if reg == "DX": return ["DL","DH"] if reg == "ESP": return ["SP"] if reg == "EBP": return ["BP"] if reg == "ESI": return ["SI"] if reg == "EDI": return ["DI"] return [] def isReg(reg): """ Checks if a given string is a valid reg Argument : reg - the register to check Return: Boolean """ regs = [] if arch == 32: regs=["eax","ebx","ecx","edx","esi","edi","ebp","esp"] if arch == 64: regs=["rax","rbx","rcx","rdx","rsi","rdi","rbp","rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"] return str(reg).lower() in regs def isAddress(string): """ Check if a string is an address / consists of hex chars only Arguments: string - the string to check Return: Boolean - True if the address string only contains hex bytes """ string = string.replace("\\x","") if len(string) > 16: return False for char in string: if char.upper() not in ["A","B","C","D","E","F","1","2","3","4","5","6","7","8","9","0"]: return False return True def isHexValue(string): """ Check if a string is a hex value / consists of hex chars only (and - ) Arguments: string - the string to check Return: Boolean - True if the address string only contains hex bytes or - sign """ string = string.replace("\\x","") string = string.replace("0x","") if len(string) > 16: return False for char in string: if char.upper() not in ["A","B","C","D","E","F","1","2","3","4","5","6","7","8","9","0","-"]: return False return True def Poly_ReturnDW(value): I = random.randint(1, 3) if I == 1: if random.randint(1, 2) == 1: return dbg.assemble( "SUB EAX, EAX\n ADD EAX, 0x%08x" % value ) else: return dbg.assemble( "SUB EAX, EAX\n ADD EAX, -0x%08x" % value ) if I == 2: return dbg.assemble( "PUSH 0x%08x\n POP EAX\n" % value ) if I == 3: if random.randint(1, 2) == 1: return dbg.assemble( "XCHG EAX, EDI\n DB 0xBF\n DD 0x%08x\n XCHG EAX, EDI" % value ) else: return dbg.assemble( "XCHG EAX, EDI\n MOV EDI, 0x%08x\n XCHG EAX, EDI" % value ) return def Poly_Return0(): I = random.randint(1, 4) if I == 1: return dbg.assemble( "SUB EAX, EAX" ) if I == 2: if random.randint(1, 2) == 1: return dbg.assemble( "PUSH 0\n POP EAX" ) else: return dbg.assemble( "DB 0x6A, 0x00\n POP EAX" ) if I == 3: return dbg.assemble( "XCHG EAX, EDI\n SUB EDI, EDI\n XCHG EAX, EDI" ) if I == 4: return Poly_ReturnDW(0) return def addrToInt(string): """ Convert a textual address to an integer Arguments: string - the address Return: int - the address value """ string = string.replace("\\x","") return hexStrToInt(string) def splitAddress(address): """ Splits aa dword/qdword into individual bytes (4 or 8 bytes) Arguments: address - The string to split Return: 4 or 8 bytes """ if arch == 32: byte1 = address >> 24 & 0xFF byte2 = address >> 16 & 0xFF byte3 = address >> 8 & 0xFF byte4 = address & 0xFF return byte1,byte2,byte3,byte4 if arch == 64: byte1 = address >> 56 & 0xFF byte2 = address >> 48 & 0xFF byte3 = address >> 40 & 0xFF byte4 = address >> 32 & 0xFF byte5 = address >> 24 & 0xFF byte6 = address >> 16 & 0xFF byte7 = address >> 8 & 0xFF byte8 = address & 0xFF return byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 def bytesInRange(address, range): """ Checks if all bytes of an address are in a range Arguments: address - the address to check range - a range object containing the values all bytes need to comply with Return: a boolean """ if arch == 32: byte1,byte2,byte3,byte4 = splitAddress(address) # if the first is a null we keep the address anyway if not (byte1 == 0 or byte1 in range): return False elif not byte2 in range: return False elif not byte3 in range: return False elif not byte4 in range: return False if arch == 64: byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 = splitAddress(address) # if the first is a null we keep the address anyway if not (byte1 == 0 or byte1 in range): return False elif not byte2 in range: return False elif not byte3 in range: return False elif not byte4 in range: return False elif not byte5 in range: return False elif not byte6 in range: return False elif not byte7 in range: return False elif not byte8 in range: return False return True def readString(address): """ Reads a string from the given address until it reaches a null bytes Arguments: address - the base address (integer value) Return: the string """ toreturn = dbg.readString(address) return toreturn def getSegmentEnd(segmentstart): os = dbg.getOsVersion() offset = 0x24 if win7mode: offset = 0x28 segmentend = struct.unpack('= 0: flagtext.append(flags[val]) flag -= val if len(flagtext) == 0: flagtext = "Unknown" else: flagtext = ','.join(flagtext) return flagtext def decodeHeapHeader(headeraddress,headersize,key): # get header and decode first 4 bytes blockcnt = 0 fullheaderbytes = "" decodedheader = "" fullheaderbytes = "" while blockcnt < headersize: header = struct.unpack('= 0: fullheaderbytes = fullheaderbytes + headerbytes[bytecnt-1] + headerbytes[bytecnt] bytecnt -= 2 blockcnt += 4 return hex2bin(fullheaderbytes) def walkSegment(FirstEntry,LastValidEntry,heapbase): """ Finds all chunks in a given segment Arguments : Start and End of segment, and heapbase Returns a dictionary of MnChunk objects Key : chunk pointer """ mHeap = MnHeap(heapbase) mSegment = MnSegment(heapbase,FirstEntry,LastValidEntry) return mSegment.getChunks() def getStacks(): """ Retrieves all stacks from all threads in the current application Arguments: None Return: a dictionary, with key = threadID. Each entry contains an array with base and top of the stack """ stacks = {} global stacklistCache if len(stacklistCache) > 0: return stacklistCache else: threads = dbg.getAllThreads() for thread in threads: teb = thread.getTEB() tid = thread.getId() topStack = 0 baseStack = 0 if arch == 32: topStack = struct.unpack(' 1: subparts = split2.split(input) subpartsall = "" if len(subparts) > 1: cnt = 1 while cnt < len(subparts): subpartsall += subparts[cnt] + ":" cnt +=1 subsubparts = split3.split(subpartsall) thisinstruction = subsubparts[0].strip() return thispointer,thisinstruction else: return thispointer,thisinstruction def getNrOfDictElements(thisdict): """ Will get the total number of entries in a given dictionary Argument: the source dictionary Output : an integer """ total = 0 for dicttype in thisdict: for dictval in thisdict[dicttype]: total += 1 return total def getModuleObj(modname): """ Will return a module object if the provided module name exists Will perform a case sensitive search first, and then a case insensitive search in case nothing was found """ # Method 1 mod = dbg.getModule(modname) if mod is not None: return MnModule(modname) # Method 2 suffixes = ["",".exe",".dll"] allmod = dbg.getAllModules() for suf in suffixes: modname_search = modname + suf #WinDBG optimized if __DEBUGGERAPP__ == "WinDBG": for tmod_s in allmod: tmod = dbg.getModule(tmod_s) if not tmod == None: if tmod.getName() == modname_search: return MnModule(tmod_s) imname = dbg.getImageNameForModule(tmod.getName()) if not imname == None: if imname == modname_search: return MnModule(tmod) for tmod_s in allmod: tmod = dbg.getModule(tmod_s) if not tmod == None: if tmod.getName().lower() == modname_search.lower(): return MnModule(tmod_s) imname = dbg.getImageNameForModule(tmod.getName().lower()) if not imname == None: if imname.lower() == modname_search.lower(): return MnModule(tmod) for tmod_s in allmod: tmod = dbg.getModule(tmod_s) if not tmod == None: if tmod_s.lower() == modname_search.lower(): return MnModule(tmod_s) else: # Immunity for tmod_s in allmod: if not tmod_s == None: mname = tmod_s.getName() if mname == modname_search: return MnModule(mname) for tmod_s in allmod: if not tmod_s == None: mname = tmod_s.getName() if mname.lower() == modname_search.lower(): return MnModule(mname) return None def getPatternLength(startptr,type="normal",args={}): """ Gets length of a cyclic pattern, starting from a given pointer Arguments: startptr - the start pointer (integer value) type - optional string, indicating type of pattern : "normal" : normal pattern "unicode" : unicode pattern "upper" : uppercase pattern "lower" : lowercase pattern """ patternsize = 0 endofpattern = False global silent oldsilent=silent silent=True fullpattern = createPattern(200000,args) silent=oldsilent if type == "upper": fullpattern = fullpattern.upper() if type == "lower": fullpattern = fullpattern.lower() #if type == "unicode": # fullpattern = toUnicode(fullpattern) if type in ["normal","upper","lower","unicode"]: previousloc = -1 while not endofpattern and patternsize <= len(fullpattern): sizemeter=dbg.readMemory(startptr+patternsize,4) if type == "unicode": sizemeter=dbg.readMemory(startptr+patternsize,8) sizemeter = sizemeter.replace('\x00','') else: sizemeter=dbg.readMemory(startptr+patternsize,4) if len(sizemeter) == 4: thisloc = fullpattern.find(sizemeter) if thisloc < 0 or thisloc <= previousloc: endofpattern = True else: patternsize += 4 previousloc = thisloc else: return patternsize #maybe this is not the end yet patternsize -= 8 endofpattern = False while not endofpattern and patternsize <= len(fullpattern): sizemeter=dbg.readMemory(startptr+patternsize,4) if type == "unicode": sizemeter=dbg.readMemory(startptr+patternsize,8) sizemeter = sizemeter.replace('\x00','') else: sizemeter=dbg.readMemory(startptr+patternsize,4) if fullpattern.find(sizemeter) < 0: patternsize += 3 endofpattern = True else: patternsize += 1 if type == "unicode": patternsize = (patternsize / 2) + 1 return patternsize def getAPointer(modules,criteria,accesslevel): """ Gets the first pointer from one of the supplied module that meets a set of criteria Arguments: modules - array with module names criteria - dictionary describing the criteria the pointer needs to comply with accesslevel - the required access level Return: a pointer (integer value) or 0 if nothing was found """ pointer = 0 dbg.getMemoryPages() for a in dbg.MemoryPages.keys(): page_start = a page_size = dbg.MemoryPages[a].getSize() page_end = a + page_size #page in one of the modules ? if meetsAccessLevel(dbg.MemoryPages[a],accesslevel): pageptr = MnPointer(a) thismodulename = pageptr.belongsTo() if thismodulename != "" and thismodulename in modules: thismod = MnModule(thismodulename) start = thismod.moduleBase end = thismod.moduleTop random.seed() for cnt in xrange(page_size+1): #randomize the value theoffset = random.randint(0,page_size) thispointer = MnPointer(page_start + theoffset) if meetsCriteria(thispointer,criteria): return page_start + theoffset return pointer def haveRepetition(string, pos): first = string[pos] MIN_REPETITION = 3 if len(string) - pos > MIN_REPETITION: count = 1 while ( count < MIN_REPETITION and string[pos+count] == first): count += 1 if count >= MIN_REPETITION: return True return False def findAllPaths(graph,start_vertex,end_vertex,path=[]): path = path + [start_vertex] if start_vertex == end_vertex: return [path] if start_vertex not in graph: return [] paths = [] for vertex in graph[start_vertex]: if vertex not in path: extended_paths = findAllPaths(graph,vertex,end_vertex,path) for p in extended_paths: paths.append(p) return paths def isAsciiString(data): """ Check if a given string only contains ascii characters """ return all((ord(c) >= 32 and ord(c) <= 127) for c in data) def isAscii(b): """ Check if a given hex byte is ascii or not Argument : the byte Returns : Boolean """ return b == 0x0a or b == 0x0d or (b >= 0x20 and b <= 0x7e) def isAscii2(b): """ Check if a given hex byte is ascii or not, will not flag newline or carriage return as ascii Argument : the byte Returns : Boolean """ return b >= 0x20 and b <= 0x7e def isHexString(input): """ Checks if all characters in a string are hex (0->9, a->f, A->F) Alias for isAddress() """ return isAddress(input) def extract_chunks(iterable, size): """ Retrieves chunks of the given :size from the :iterable """ fill = object() gen = itertools.izip_longest(fillvalue=fill, *([iter(iterable)] * size)) return (tuple(x for x in chunk if x != fill) for chunk in gen) def rrange(x, y = 0): """ Creates a reversed range (from x - 1 down to y). Example: >>> rrange(10, 0) # => [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] """ return range(x - 1, y - 1, -1) def getSkeletonHeader(exploittype,portnr,extension,url,badchars='\x00\x0a\x0d'): originalauthor = "insert_name_of_person_who_discovered_the_vulnerability" name = "insert name for the exploit" cve = "insert CVE number here" if url == "": url = "" else: try: # connect to url & get author + app description u = urllib.urlretrieve(url) # extract title fh = open(u[0],'r') contents = fh.readlines() fh.close() for line in contents: if line.find(' -1: titleline = line.split('>') if len(titleline) > 1: name = titleline[1].split('<')[0].replace("\"","").replace("'","").strip() break for line in contents: if line.find('Author:') > -1 and line.find('td style') > -1: authorline = line.split("Author:") if len(authorline) > 1: originalauthor = authorline[1].split('<')[0].replace("\"","").replace("'","").strip() break for line in contents: if line.find('CVE:') > -1 and line.find('td style') > -1: cveline = line.split("CVE:") if len(cveline) > 1: tcveparts = cveline[1].split('>') if len(tcveparts) > 1: tcve = tcveparts[1].split('<')[0].replace("\"","").replace("'","").strip() if tcve.upper().strip() != "N//A": cve = tcve break except: dbg.log(" ** Unable to download %s" % url,highlight=1) url = "" monaConfig = MnConfig() thisauthor = monaConfig.get("author") if thisauthor == "": thisauthor = "" skeletonheader = "##\n" skeletonheader += "# This module requires Metasploit: http://metasploit.com/download\n" skeletonheader += "# Current source: https://github.com/rapid7/metasploit-framework\n" skeletonheader += "##\n\n" skeletonheader += "require 'msf/core'\n\n" skeletonheader += "class MetasploitModule < Msf::Exploit::Remote\n" skeletonheader += " #Rank definition: https://github.com/rapid7/metasploit-framework/wiki/Exploit-Ranking\n" skeletonheader += " #ManualRanking/LowRanking/AverageRanking/NormalRanking/GoodRanking/GreatRanking/ExcellentRanking\n" skeletonheader += " Rank = NormalRanking\n\n" if exploittype == "fileformat": skeletonheader += " include Msf::Exploit::FILEFORMAT\n" if exploittype == "network client (tcp)": skeletonheader += " include Msf::Exploit::Remote::Tcp\n" if exploittype == "network client (udp)": skeletonheader += " include Msf::Exploit::Remote::Udp\n" if cve.strip() == "": cve = "" skeletoninit = " def initialize(info = {})\n" skeletoninit += " super(update_info(info,\n" skeletoninit += " 'Name' => '" + name + "',\n" skeletoninit += " 'Description' => %q{\n" skeletoninit += " Provide information about the vulnerability / explain as good as you can\n" skeletoninit += " Make sure to keep each line less than 100 columns wide\n" skeletoninit += " },\n" skeletoninit += " 'License' => MSF_LICENSE,\n" skeletoninit += " 'Author' =>\n" skeletoninit += " [\n" skeletoninit += " '" + originalauthor + "', # Original discovery\n" skeletoninit += " '" + thisauthor + "', # MSF Module\n" skeletoninit += " ],\n" skeletoninit += " 'References' =>\n" skeletoninit += " [\n" skeletoninit += " [ 'OSVDB', '' ],\n" skeletoninit += " [ 'CVE', '" + cve + "' ],\n" skeletoninit += " [ 'URL', '" + url + "' ]\n" skeletoninit += " ],\n" skeletoninit += " 'DefaultOptions' =>\n" skeletoninit += " {\n" skeletoninit += " 'ExitFunction' => 'process', #none/process/thread/seh\n" skeletoninit += " #'InitialAutoRunScript' => 'migrate -f',\n" skeletoninit += " },\n" skeletoninit += " 'Platform' => 'win',\n" skeletoninit += " 'Payload' =>\n" skeletoninit += " {\n" skeletoninit += " 'BadChars' => \"" + bin2hexstr(badchars) + "\", # \n" skeletoninit += " 'DisableNops' => true,\n" skeletoninit += " },\n" skeletoninit2 = " 'Privileged' => false,\n" skeletoninit2 += " #Correct Date Format: \"M D Y\"\n" skeletoninit2 += " #Month format: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec\n" skeletoninit2 += " 'DisclosureDate' => 'MONTH DAY YEAR',\n" skeletoninit2 += " 'DefaultTarget' => 0))\n" if exploittype.find("network") > -1: skeletoninit2 += "\n register_options([Opt::RPORT(" + str(portnr) + ")], self.class)\n" if exploittype.find("fileformat") > -1: skeletoninit2 += "\n register_options([OptString.new('FILENAME', [ false, 'The file name.', 'msf" + extension + "']),], self.class)\n" skeletoninit2 += "\n end\n\n" return skeletonheader,skeletoninit,skeletoninit2 def shortJump(sizeofinst, offset): """ Calculate the parameter for a short relative jump from the size of instruction (which can be JMP, JNZ etc...) and the desired offset Arguments: sizeofinst - the size of the instruction used to achieve the jump offset - the desired offset from the address of the instruction Return: A binary value which can be used along with the jump instruction """ if (offset - sizeofinst) < -128 or (offset - sizeofinst) > 127: dbg.log(" ** short jump too long",highlight=1) return struct.pack("b", offset - sizeofinst) def archValue(x86, x64): if arch == 32: return x86 elif arch == 64: return x64 def readPtrSizeBytes(ptr): if arch == 32: return struct.unpack(' offset_category_index: break curr_category = c if curr_category != "win10": offset = offsets[name][c] else: win10offsets = offsets[name][c] for o in sorted(win10offsets): if o > build: break curr_build = o offset = win10offsets[o] return archValue(offset[0], offset[1]) #---------------------------------------# # Class to call commands & parse args # #---------------------------------------# class MnCommand: """ Class to call commands, show usage and parse arguments """ def __init__(self, name, description, usage, parseProc, alias=""): self.name = name self.description = description self.usage = usage self.parseProc = parseProc self.alias = alias #---------------------------------------# # Class to encode bytes # #---------------------------------------# class MnEncoder: """ Class to encode bytes """ def __init__(self,bytestoencode): self.origbytestoencode = bytestoencode self.bytestoencode = bytestoencode def encodeAlphaNum(self,badchars = []): encodedbytes = {} if not silent: dbg.log("[+] Using alphanum encoder") dbg.log("[+] Received %d bytes to encode" % len(self.origbytestoencode)) dbg.log("[+] Nr of bad chars: %d" % len(badchars)) # first, check if there are no bad char conflicts nobadchars = "\x25\x2a\x2d\x31\x32\x35\x4a\x4d\x4e\x50\x55" badbadchars = False for b in badchars: if b in nobadchars: dbg.log("*** Error: byte \\x%s cannot be a bad char with this encoder" % bin2hex(b)) badbadchars = True if badbadchars: return {} # if all is well, explode the input to a multiple of 4 while True: moduloresult = len(self.bytestoencode) % 4 if moduloresult == 0: break else: self.bytestoencode += '\x90' if not len(self.bytestoencode) == len(self.origbytestoencode): if not silent: dbg.log("[+] Added %d nops to make length of input a multiple of 4" % (len(self.bytestoencode) - len(self.origbytestoencode))) # break it down into chunks of 4 bytes toencodearray = [] toencodearray = [self.bytestoencode[max(i-4,0):i] for i in range(len(self.bytestoencode), 0, -4)][::-1] blockcnt = 1 encodedline = 0 # we have to push the blocks in reverse order blockcnt = len(toencodearray) nrblocks = len(toencodearray) while blockcnt > 0: if not silent: dbg.log("[+] Processing block %d/%d" % (blockcnt,nrblocks)) encodedbytes[encodedline] = ["\x25\x4a\x4d\x4e\x55","AND EAX,0x554E4D4A"] encodedline += 1 encodedbytes[encodedline] = ["\x25\x35\x32\x31\x2A","AND EAX,0x2A313235"] encodedline += 1 opcodes=[] startpos=7 source = "".join(bin2hex(a) for a in toencodearray[blockcnt-1]) origbytes=source[startpos-7]+source[startpos-6]+source[startpos-5]+source[startpos-4]+source[startpos-3]+source[startpos-2]+source[startpos-1]+source[startpos] reversebytes=origbytes[6]+origbytes[7]+origbytes[4]+origbytes[5]+origbytes[2]+origbytes[3]+origbytes[0]+origbytes[1] revval=hexStrToInt(reversebytes) twoval=4294967296-revval twobytes=toHex(twoval) if not silent: dbg.log("Opcode to produce : %s%s %s%s %s%s %s%s" % (origbytes[0],origbytes[1],origbytes[2],origbytes[3],origbytes[4],origbytes[5],origbytes[6],origbytes[7])) dbg.log(" reversed : %s%s %s%s %s%s %s%s" % (reversebytes[0],reversebytes[1],reversebytes[2],reversebytes[3],reversebytes[4],reversebytes[5],reversebytes[6],reversebytes[7])) dbg.log(" -----------") dbg.log(" 2's complement : %s%s %s%s %s%s %s%s" % (twobytes[0],twobytes[1],twobytes[2],twobytes[3],twobytes[4],twobytes[5],twobytes[6],twobytes[7])) #for each byte, start with last one first bcnt=3 overflow=0 while bcnt >= 0: currbyte=twobytes[(bcnt*2)]+twobytes[(bcnt*2)+1] currval=hexStrToInt(currbyte)-overflow testval=currval/3 if testval < 32: #put 1 in front of byte currbyte="1"+currbyte currval=hexStrToInt(currbyte)-overflow overflow=1 else: overflow=0 val1=currval/3 val2=currval/3 val3=currval/3 sumval=val1+val2+val3 if sumval < currval: val3 = val3 + (currval-sumval) #validate / fix badchars fixvals=self.validatebadchars_enc(val1,val2,val3,badchars) val1="%02x" % fixvals[0] val2="%02x" % fixvals[1] val3="%02x" % fixvals[2] opcodes.append(val1) opcodes.append(val2) opcodes.append(val3) bcnt=bcnt-1 # we should now have 12 bytes in opcodes if not silent: dbg.log(" -----------") dbg.log(" %s %s %s %s" % (opcodes[9],opcodes[6],opcodes[3],opcodes[0])) dbg.log(" %s %s %s %s" % (opcodes[10],opcodes[7],opcodes[4],opcodes[1])) dbg.log(" %s %s %s %s" % (opcodes[11],opcodes[8],opcodes[5],opcodes[2])) dbg.log("") thisencodedbyte = "\x2D" thisencodedbyte += hex2bin("\\x%s" % opcodes[0]) thisencodedbyte += hex2bin("\\x%s" % opcodes[3]) thisencodedbyte += hex2bin("\\x%s" % opcodes[6]) thisencodedbyte += hex2bin("\\x%s" % opcodes[9]) encodedbytes[encodedline] = [thisencodedbyte,"SUB EAX,0x%s%s%s%s" % (opcodes[9],opcodes[6],opcodes[3],opcodes[0])] encodedline += 1 thisencodedbyte = "\x2D" thisencodedbyte += hex2bin("\\x%s" % opcodes[1]) thisencodedbyte += hex2bin("\\x%s" % opcodes[4]) thisencodedbyte += hex2bin("\\x%s" % opcodes[7]) thisencodedbyte += hex2bin("\\x%s" % opcodes[10]) encodedbytes[encodedline] = [thisencodedbyte,"SUB EAX,0x%s%s%s%s" % (opcodes[10],opcodes[7],opcodes[4],opcodes[1])] encodedline += 1 thisencodedbyte = "\x2D" thisencodedbyte += hex2bin("\\x%s" % opcodes[2]) thisencodedbyte += hex2bin("\\x%s" % opcodes[5]) thisencodedbyte += hex2bin("\\x%s" % opcodes[8]) thisencodedbyte += hex2bin("\\x%s" % opcodes[11]) encodedbytes[encodedline] = [thisencodedbyte,"SUB EAX,0x%s%s%s%s" % (opcodes[11],opcodes[8],opcodes[5],opcodes[2])] encodedline += 1 encodedbytes[encodedline] = ["\x50","PUSH EAX"] encodedline += 1 blockcnt -= 1 return encodedbytes def validatebadchars_enc(self,val1,val2,val3,badchars): newvals=[] allok=0 giveup=0 type=0 origval1=val1 origval2=val2 origval3=val3 d1=0 d2=0 d3=0 lastd1=0 lastd2=0 lastd3=0 while allok==0 and giveup==0: #check if there are bad chars left charcnt=0 val1ok=1 val2ok=1 val3ok=1 while charcnt < len(badchars): if (hex2bin("%02x" % val1) in badchars): val1ok=0 if (hex2bin("%02x" % val2) in badchars): val2ok=0 if (hex2bin("%02x" % val3) in badchars): val3ok=0 charcnt=charcnt+1 if (val1ok==0) or (val2ok==0) or (val3ok==0): allok=0 else: allok=1 if allok==0: #try first by sub 1 from val1 and val2, and add more to val3 if type==0: val1=val1-1 val2=val2-1 val3=val3+2 if (val1<1) or (val2==0) or (val3 > 126): val1=origval1 val2=origval2 val3=origval3 type=1 if type==1: #then try by add 1 to val1 and val2, and sub more from val3 val1=val1+1 val2=val2+1 val3=val3-2 if (val1>126) or (val2>126) or (val3 < 1): val1=origval1 val2=origval2 val3=origval3 type=2 if type==2: #try by sub 2 from val1, and add 1 to val2 and val3 val1=val1-2 val2=val2+1 val3=val3+1 if (val1<1) or (val2>126) or (val3 > 126): val1=origval1 val2=origval2 val3=origval3 type=3 if type==3: #try by add 2 to val1, and sub 1 from val2 and val3 val1=val1+2 val2=val2-1 val3=val3-1 if (val1 > 126) or (val2 < 1) or (val3 < 1): val1=origval1 val2=origval2 val3=origval3 type=4 if type==4: if (val1ok==0): val1=val1-1 d1=d1+1 else: #now spread delta over other 2 values if (d1 > 0): val2=val2+1 val3=origval3+d1-1 d1=d1-1 else: val1=0 if (val1 < 1) or (val2 > 126) or (val3 > 126): val1=origval1 val2=origval2 val3=origval3 d1=0 type=5 if type==5: if (val1ok==0): val1=val1+1 d1=d1+1 else: #now spread delta over other 2 values if (d1 > 0): val2=val2-1 val3=origval3-d1+1 d1=d1-1 else: val1=255 if (val1>126) or (val2 < 1) or (val3 < 1): val1=origval1 val2=origval2 val3=origval3 val1ok=0 val2ok=0 val3ok=0 d1=0 d2=0 d3=0 type=6 if type==6: if (val1ok==0): val1=val1-1 #d1=d1+1 if (val2ok==0): val2=val2+1 #d2=d2+1 d3=origval1-val1+origval2-val2 val3=origval3+d3 if (lastd3==d3) and (d3 > 0): val1=origval1 val2=origval2 val3=origval3 giveup=1 else: lastd3=d3 if (val1<1) or (val2 < 1) or (val3 > 126): val1=origval1 val2=origval2 val3=origval3 giveup=1 #check results charcnt=0 val1ok=1 val2ok=1 val3ok=1 val1text="OK" val2text="OK" val3text="OK" while charcnt < len(badchars): if (val1 == badchars[charcnt]): val1ok=0 val1text="NOK" if (val2 == badchars[charcnt]): val2ok=0 val2text="NOK" if (val3 == badchars[charcnt]): val3ok=0 val3text="NOK" charcnt=charcnt+1 if (val1ok==0) or (val2ok==0) or (val3ok==0): dbg.log(" ** Unable to fix bad char issue !",highlight=1) dbg.log(" -> Values to check : %s(%s) %s(%s) %s(%s) " % (bin2hex(origval1),val1text,bin2hex(origval2),val2text,bin2hex(origval3),val3text),highlight=1) val1=origval1 val2=origval2 val3=origval3 newvals.append(val1) newvals.append(val2) newvals.append(val3) return newvals #---------------------------------------# # Class to perform call tracing # #---------------------------------------# class MnCallTraceHook(LogBpHook): def __init__(self, callptr, showargs, instruction, logfile): LogBpHook.__init__(self) self.callptr = callptr self.showargs = showargs self.logfile = logfile self.instruction = instruction def run(self,regs): # get instruction at this address thisaddress = regs["EIP"] thisinstruction = self.instruction allargs = [] argstr = "" if thisinstruction.startswith("CALL "): if self.showargs > 0: for cnt in xrange(self.showargs): thisarg = 0 try: thisarg = struct.unpack(' -1: textra += "%s = 0x%08x, " % (treg,regs[treg]) if textra != "": textra = textra.strip(" ") textra = textra.strip(",") textra = "(" + textra + ")" FILE.write("0x%08x : %s %s\n" % (thisaddress, thisinstruction, textra)) if self.showargs > 0: cnt = 0 while cnt < len(allargs): content = "" try: bytecontent = dbg.readMemory(allargs[cnt],16) content = bin2hex(bytecontent) except: content = "" FILE.write(" Arg%d at 0x%08x : 0x%08x : %s\n" % (cnt,regs["ESP"]+(cnt*4),allargs[cnt],content)) cnt += 1 FILE.close() except: #dbg.log("OOPS", highlight=1) pass if thisinstruction.startswith("RETN"): returnto = 0 try: returnto = struct.unpack(' -1: # function name, try to resolve functionaddress = dbg.getAddress(self.targetptr) if functionaddress > 0: dbg.log("Deferred Breakpoint set at %s (0x%08x)" % (self.targetptr,functionaddress),highlight=1) dbg.setBreakpoint(functionaddress) self.UnHook() dbg.log("Hook removed") dbg.run() return if self.targetptr.find("+") > -1: ptrparts = self.targetptr.split("+") modname = ptrparts[0] if not modname.lower().endswith(".dll"): modname += ".dll" themodule = getModuleObj(modname) if themodule != None and len(ptrparts) > 1: address = themodule.getBase() + int(ptrparts[1],16) if address > 0: dbg.log("Deferred Breakpoint set at %s (0x%08x)" % (self.targetptr,address),highlight=1) dbg.setBreakpoint(address) self.UnHook() dbg.log("Hook removed") dbg.run() return if self.targetptr.find("+") == -1 and self.targetptr.find(".") == -1: address = int(self.targetptr,16) thispage = dbg.getMemoryPageByAddress(address) if thispage != None: dbg.setBreakpoint(address) dbg.log("Deferred Breakpoint set at 0x%08x" % address, highlight=1) self.UnHook() dbg.log("Hook removed") dbg.run() #---------------------------------------# # Class to access config file # #---------------------------------------# class MnConfig: """ Class to perform config file operations """ def __init__(self): global configwarningshown self.configfile = "mona.ini" self.currpath = os.path.dirname(os.path.realpath(self.configfile)) # first check if we will be saving the file into Immunity folder if __DEBUGGERAPP__ == "Immunity Debugger": if not os.path.exists(os.path.join(self.currpath,"immunitydebugger.exe")): if not configwarningshown: dbg.log(" ** Warning: using mona.ini file from %s" % self.currpath, highlight=True) configwarningshown = True def get(self,parameter): """ Retrieves the contents of a given parameter from the config file or from memory if the config file has been read already (configFileCache) Arguments: parameter - the name of the parameter Return: A string, containing the contents of that parameter """ #read config file #format : parameter=value toreturn = "" curparam=[] global configFileCache #first check if parameter already exists in global cache if parameter.strip().lower() in configFileCache: toreturn = configFileCache[parameter.strip().lower()] #dbg.log("Found parameter %s in cache: %s" % (parameter, toreturn)) else: if os.path.exists(self.configfile): try: configfileobj = open(self.configfile,"rb") content = configfileobj.readlines() configfileobj.close() for thisLine in content: if not thisLine[0] == "#": currparam = thisLine.split('=') if currparam[0].strip().lower() == parameter.strip().lower() and len(currparam) > 1: #get value currvalue = "" i=1 while i < len(currparam): currvalue = currvalue + currparam[i] + "=" i += 1 toreturn = currvalue.rstrip("=").replace('\n','').replace('\r','') # drop into global cache for next time configFileCache[parameter.strip().lower()] = toreturn #dbg.log("Read parameter %s from file: %s" % (parameter, toreturn)) except: toreturn="" return toreturn def set(self,parameter,paramvalue): """ Sets/Overwrites the contents of a given parameter in the config file Arguments: parameter - the name of the parameter paramvalue - the new value of the parameter Return: nothing """ global configFileCache configFileCache[parameter.strip().lower()] = paramvalue if os.path.exists(self.configfile): #modify file try: configfileobj = open(self.configfile,"r") content = configfileobj.readlines() configfileobj.close() newcontent = [] paramfound = False for thisLine in content: thisLine = thisLine.replace('\n','').replace('\r','') if not thisLine[0] == "#": currparam = thisLine.split('=') if currparam[0].strip().lower() == parameter.strip().lower(): newcontent.append(parameter+"="+paramvalue+"\n") paramfound = True else: newcontent.append(thisLine+"\n") else: newcontent.append(thisLine+"\n") if not paramfound: newcontent.append(parameter+"="+paramvalue+"\n") #save new config file (rewrite) dbg.log("[+] Saving config file, modified parameter %s" % parameter) FILE=open(self.configfile,"w") FILE.writelines(newcontent) FILE.close() dbg.log(" mona.ini saved under %s" % self.currpath) except: dbg.log("Error writing config file : %s : %s" % (sys.exc_type,sys.exc_value),highlight=1) return "" else: #create new file try: dbg.log("[+] Creating config file, setting parameter %s" % parameter) FILE=open(self.configfile,"w") FILE.write("# -----------------------------------------------#\n") FILE.write("# mona.py configuration file #\n") FILE.write("# Corelan Consulting bv - https://www.corelan.be #\n") FILE.write("# -----------------------------------------------#\n") FILE.write(parameter+"="+paramvalue+"\n") FILE.close() except: dbg.log(" ** Error writing config file", highlight=1) return "" return "" #---------------------------------------# # Class to log entries to file # #---------------------------------------# class MnLog: """ Class to perform logfile operations """ def __init__(self, filename): self.filename = filename def reset(self,clear=True,showheader=True): """ Optionally clears a log file, write a header to the log file and return filename Optional : clear = Boolean. When set to false, the logfile won't be cleared. This method can be used to retrieve the full path to the logfile name of the current MnLog class object Logfiles are written to the debugger program folder, unless a config value 'workingfolder' is set. Return: full path to the logfile name. """ global noheader if clear: if not silent: dbg.log("[+] Preparing output file '" + self.filename +"'") if not showheader: noheader = True debuggedname = dbg.getDebuggedName() thispid = dbg.getDebuggedPid() if thispid == 0: debuggedname = "_no_name_" thisconfig = MnConfig() workingfolder = thisconfig.get("workingfolder").rstrip("\\").strip() #strip extension from debuggedname parts = debuggedname.split(".") extlen = len(parts[len(parts)-1])+1 debuggedname = debuggedname[0:len(debuggedname)-extlen] debuggedname = debuggedname.replace(" ","_") workingfolder = workingfolder.replace('%p', debuggedname) workingfolder = workingfolder.replace('%i', str(thispid)) #logfile = workingfolder + "\\" + self.filename # working folder will be created inside getAbsolutePath if needed logfile = getAbsolutePath(self.filename) if clear: if not silent: dbg.log(" - (Re)setting logfile %s" % logfile) try: if os.path.exists(logfile): try: os.delete(logfile+".old") except: pass try: os.rename(logfile,logfile+".old") except: try: os.rename(logfile,logfile+".old2") except: pass except: pass #write header if not noheader: try: with open(logfile,"w") as fh: fh.write("=" * 80 + '\n') thisversion,thisrevision = getVersionInfo(inspect.stack()[0][1]) thisversion = thisversion.replace("'","") fh.write(" Output generated by mona.py v"+thisversion+", rev "+thisrevision+" - " + __DEBUGGERAPP__ + "\n") fh.write(" Corelan Consulting bv - https://www.corelan.be\n") fh.write("=" * 80 + '\n') osver=dbg.getOsVersion() osrel=dbg.getOsRelease() fh.write(" OS : " + osver + ", release " + osrel + "\n") fh.write(" Process being debugged : " + debuggedname +" (pid " + str(thispid) + ")\n") currmonaargs = " ".join(x for x in currentArgs) fh.write(" Current mona arguments: %s\n" % currmonaargs) fh.write("=" * 80 + '\n') fh.write(" " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n") fh.write("=" * 80 + '\n') except: pass else: try: with open(logfile,"w") as fh: fh.write("") except: pass #write module table try: if not ignoremodules: showModuleTable(logfile) except: pass return logfile def write(self,entry,logfile): """ Write an entry (can be multiline) to a given logfile Arguments: entry - the data to write to the logfile logfile - the full path to the logfile Return: nothing """ towrite = "" #check if entry is int if type(entry) == int: if entry > 0: ptrx = MnPointer(entry) modname = ptrx.belongsTo() modinfo = MnModule(modname) towrite = "0x" + toHex(entry) + " : " + ptrx.__str__() + " " + modinfo.__str__() else: towrite = entry else: towrite = entry # if this fails, we got an unprintable character try: towrite = str(towrite) except: # one at a time towrite2 = "" for c in towrite: try: towrite2 += str(c) except: towrite2 += "\\x" + str(hex(ord(c))).replace("0x","") towrite = towrite2 try: with open(logfile,"a") as fh: if towrite.find('\n') > -1: fh.writelines(towrite) else: fh.write(towrite+"\n") except: pass return True #---------------------------------------# # Simple Queue class # #---------------------------------------# class MnQueue: """ Simple queue class """ def __init__(self): self.holder = [] def enqueue(self,val): self.holder.append(val) def dequeue(self): val = None try: val = self.holder[0] if len(self.holder) == 1: self.holder = [] else: self.holder = self.holder[1:] except: pass return val def IsEmpty(self): result = False if len(self.holder) == 0: result = True return result #---------------------------------------# # Class to access module properties # #---------------------------------------# class MnModule: """ Class to access module properties """ def __init__(self, modulename): modisaslr = True modissafeseh = True modrebased = True modisnx = True modisos = True modiscfg = True self.IAT = {} self.EAT = {} path = "" mzbase = 0 mzsize = 0 mztop = 0 mcodebase = 0 mcodesize = 0 mcodetop = 0 mentry = 0 mdllcharacteristics = 0 mversion = "" self.internalname = modulename if modulename != "": # if info is cached, retrieve from cache if ModInfoCached(modulename): modisaslr = getModuleProperty(modulename,"aslr") modissafeseh = getModuleProperty(modulename,"safeseh") modrebased = getModuleProperty(modulename,"rebase") modisnx = getModuleProperty(modulename,"nx") modisos = getModuleProperty(modulename,"os") modiscfg = getModuleProperty(modulename,"cfg") path = getModuleProperty(modulename,"path") mzbase = getModuleProperty(modulename,"base") mzsize = getModuleProperty(modulename,"size") mztop = getModuleProperty(modulename,"top") mversion = getModuleProperty(modulename,"version") mentry = getModuleProperty(modulename,"entry") mcodebase = getModuleProperty(modulename,"codebase") mcodesize = getModuleProperty(modulename,"codesize") mcodetop = getModuleProperty(modulename,"codetop") mdllcharacteristics = getModuleProperty(modulename, "dllcharacteristics") else: #gather info manually - this code should only get called from populateModuleInfo() self.moduleobj = dbg.getModule(modulename) modissafeseh = True modisaslr = True modisnx = True modrebased = False modisos = False modiscfg = False #if self.moduleobj == None: # dbg.log("*** Error - self.moduleobj is None, key %s" % modulename, highlight=1) mod = self.moduleobj mzbase = mod.getBaseAddress() mzrebase = mod.getFixupbase() mzsize = mod.getSize() mversion = mod.getVersion() mentry = mod.getEntry() mcodebase = mod.getCodebase() mcodesize = mod.getCodesize() mcodetop = mcodebase + mcodesize mdllcharacteristics = 0 mversion=mversion.replace(", ",".") mversionfields=mversion.split('(') mversion=mversionfields[0].replace(" ","") if mversion=="": mversion="-1.0-" path=mod.getPath() if mod.getIssystemdll() == 0: modisos = "WINDOWS" in path.upper() else: modisos = True mztop = mzbase + mzsize if mzbase > 0: peoffset=struct.unpack('10: sectionaddress,sectionsize=struct.unpack(' 0: # get address of DataDirectory DataDirectory_location = OptionalHeader_location + 0x60 # get size of Import Table importtable_size = struct.unpack(' 0: ptr = iatEntry ptrx = MnPointer(iatEntry) modname = ptrx.belongsTo() tmod = MnModule(modname) thisfunc = dbglib.Function(dbg,ptr) thisfuncfullname = thisfunc.getName().lower() if thisfuncfullname.endswith(".unknown") or thisfuncfullname.endswith(".%08x" % ptr): if not tmod is None: imagename = tmod.getShortName() eatlist = tmod.getEAT() if iatEntry in eatlist: thisfuncfullname = "." + imagename + "!" + eatlist[iatEntry] thisfuncname = thisfuncfullname.split('.') IAT[thisloc] = thisfuncname[1].strip(">") else: IAT[thisloc] = imagename + "!0x%08x" % iatEntry else: IAT[thisloc] = thisfuncfullname.replace(".","!") iatcnt += 1 if len(IAT) == 0: #search method nr 2, not accurate, but will find *something* funccalls = self.getFunctionCalls() for functype in funccalls: for fptr in funccalls[functype]: ptr=struct.unpack('= self.moduleBase and ptr <= self.moduleTop: if not ptr in IAT: thisfunc = dbglib.Function(dbg,ptr) thisfuncfullname = thisfunc.getName().lower() thisfuncname = [] if thisfuncfullname.endswith(".unknown") or thisfuncfullname.endswith(".%08x" % ptr): iatptr = struct.unpack('") self.IAT = IAT IATCache[self.moduleKey] = IAT else: dbg.log(" Retrieving IAT from cache") IAT = IATCache[self.moduleKey] #IAT = self.IAT except: import traceback dbg.logLines(traceback.format_exc()) return IAT return IAT def getEAT(self): eatlist = {} if len(self.EAT) == 0: try: # avoid major suckage, let's do it ourselves # find optional header PEHeader_ref = self.moduleBase + 0x3c PEHeader_location = self.moduleBase + struct.unpack(' 0: # get address of DataDirectory DataDirectory_location = OptionalHeader_location + 0x60 # get size of Export Table exporttable_size = struct.unpack(' 0: # get start of export table eatAddr = self.moduleBase + exporttable_rva nr_of_names = struct.unpack(' 0: allvalues.append(defvalue) # sort list descending allvalues.sort(reverse=True) flagvalues = [] remaining = flag for flagvalue in allvalues: if flagvalue <= remaining: remaining -= flagvalue if remaining >= 0: flagvalues.append(flagvalue) return flagvalues def getNtGlobalFlagNames(flag): names = [] allvalues = getNtGlobalFlagDefinitions() currentvalues = getNtGlobalFlagValues(flag) for defvalue in currentvalues: if defvalue > 0: names.append(allvalues[defvalue][0]) return names def getNtGlobalFlagValueData(flagvalue): toreturn = ["",""] if flagvalue in getNtGlobalFlagDefinitions(): toreturn = getNtGlobalFlagDefinitions()[flagvalue] return toreturn def getActiveFlagNames(flagvalue): currentflags = getNtGlobalFlagValues(flagvalue) flagdefs = getNtGlobalFlagDefinitions() flagnames = [] if len(currentflags) == 0: currentflags = [0] for flag in currentflags: if flag in flagdefs: flagdata = flagdefs[flag] flagnames.append(flagdata[0]) return ",".join(flagnames) def getNtGlobalFlagValueName(flagvalue): data = getNtGlobalFlagValueData(flagvalue) toreturn = "" if data[0] != "": toreturn += "+" + data[0] else: toreturn += " " toreturn += " - " toreturn += data[1] return toreturn #---------------------------------------# # Class for heap structures # #---------------------------------------# class MnHeap: """ Class for heap structures """ heapbase = 0 EncodeFlagMask = 0 Encoding = 0 # _HEAP # Windows XP # ---------- # +0x000 Entry : _HEAP_ENTRY # +0x008 Signature : Uint4B # +0x00c Flags : Uint4B # +0x010 ForceFlags : Uint4B # +0x014 VirtualMemoryThreshold : Uint4B # +0x018 SegmentReserve : Uint4B # +0x01c SegmentCommit : Uint4B # +0x020 DeCommitFreeBlockThreshold : Uint4B # +0x024 DeCommitTotalFreeThreshold : Uint4B # +0x028 TotalFreeSize : Uint4B # +0x02c MaximumAllocationSize : Uint4B # +0x030 ProcessHeapsListIndex : Uint2B # +0x032 HeaderValidateLength : Uint2B # +0x034 HeaderValidateCopy : Ptr32 Void # +0x038 NextAvailableTagIndex : Uint2B # +0x03a MaximumTagIndex : Uint2B # +0x03c TagEntries : Ptr32 _HEAP_TAG_ENTRY # +0x040 UCRSegments : Ptr32 _HEAP_UCR_SEGMENT # +0x044 UnusedUnCommittedRanges : Ptr32 _HEAP_UNCOMMMTTED_RANGE # +0x048 AlignRound : Uint4B # +0x04c AlignMask : Uint4B # +0x050 VirtualAllocdBlocks : _LIST_ENTRY # +0x058 Segments : [64] Ptr32 _HEAP_SEGMENT # +0x158 u : __unnamed # +0x168 u2 : __unnamed # +0x16a AllocatorBackTraceIndex : Uint2B # +0x16c NonDedicatedListLength : Uint4B # +0x170 LargeBlocksIndex : Ptr32 Void # +0x174 PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY # +0x178 FreeLists : [128] _LIST_ENTRY # +0x578 LockVariable : Ptr32 _HEAP_LOCK # +0x57c CommitRoutine : Ptr32 long # +0x580 FrontEndHeap : Ptr32 Void # +0x584 FrontHeapLockCount : Uint2B # +0x586 FrontEndHeapType : UChar # +0x587 LastSegmentIndex : UChar # Windows 7 # --------- # +0x000 Entry : _HEAP_ENTRY # +0x008 SegmentSignature : Uint4B # +0x00c SegmentFlags : Uint4B # +0x010 SegmentListEntry : _LIST_ENTRY # +0x018 Heap : Ptr32 _HEAP # +0x01c BaseAddress : Ptr32 Void # +0x020 NumberOfPages : Uint4B # +0x024 FirstEntry : Ptr32 _HEAP_ENTRY # +0x028 LastValidEntry : Ptr32 _HEAP_ENTRY # +0x02c NumberOfUnCommittedPages : Uint4B # +0x030 NumberOfUnCommittedRanges : Uint4B # +0x034 SegmentAllocatorBackTraceIndex : Uint2B # +0x036 Reserved : Uint2B # +0x038 UCRSegmentList : _LIST_ENTRY # +0x040 Flags : Uint4B # +0x044 ForceFlags : Uint4B # +0x048 CompatibilityFlags : Uint4B # +0x04c EncodeFlagMask : Uint4B # +0x050 Encoding : _HEAP_ENTRY # +0x058 PointerKey : Uint4B # +0x05c Interceptor : Uint4B # +0x060 VirtualMemoryThreshold : Uint4B # +0x064 Signature : Uint4B # +0x068 SegmentReserve : Uint4B # +0x06c SegmentCommit : Uint4B # +0x070 DeCommitFreeBlockThreshold : Uint4B # +0x074 DeCommitTotalFreeThreshold : Uint4B # +0x078 TotalFreeSize : Uint4B # +0x07c MaximumAllocationSize : Uint4B # +0x080 ProcessHeapsListIndex : Uint2B # +0x082 HeaderValidateLength : Uint2B # +0x084 HeaderValidateCopy : Ptr32 Void # +0x088 NextAvailableTagIndex : Uint2B # +0x08a MaximumTagIndex : Uint2B # +0x08c TagEntries : Ptr32 _HEAP_TAG_ENTRY # +0x090 UCRList : _LIST_ENTRY # +0x098 AlignRound : Uint4B # +0x09c AlignMask : Uint4B # +0x0a0 VirtualAllocdBlocks : _LIST_ENTRY # +0x0a8 SegmentList : _LIST_ENTRY # +0x0b0 AllocatorBackTraceIndex : Uint2B # +0x0b4 NonDedicatedListLength : Uint4B # +0x0b8 BlocksIndex : Ptr32 Void # +0x0bc UCRIndex : Ptr32 Void # +0x0c0 PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY # +0x0c4 FreeLists : _LIST_ENTRY # +0x0cc LockVariable : Ptr32 _HEAP_LOCK # +0x0d0 CommitRoutine : Ptr32 long # +0x0d4 FrontEndHeap : Ptr32 Void # +0x0d8 FrontHeapLockCount : Uint2B # +0x0da FrontEndHeapType : UChar # +0x0dc Counters : _HEAP_COUNTERS # +0x130 TuningParameters : _HEAP_TUNING_PARAMETERS def __init__(self,address): self.heapbase = address self.VirtualAllocdBlocks = {} self.LookAsideList = {} self.SegmentList = {} self.lalheads = {} self.Encoding = 0 self.FrontEndHeap = 0 return None def getEncodingKey(self): """ Retrieves the Encoding key from the current heap Return: Int, containing the Encoding key (on Windows 7 and up) or zero on older Operating Systems """ self.Encoding = 0 if win7mode: offset = archValue(0x4c,0x7c) self.EncodeFlagMask = struct.unpack(' 0 and self.FrontEndHeapType == 0x1 and len(self.lalheads) == 0: lalindex = 0 startloc = self.FrontEndHeap while lalindex < 128: thisptr = self.FrontEndHeap + (0x30 * lalindex) lalheadfields = {} # read the next 0x30 bytes and break down into lal head elements lalheadbin = dbg.readMemory(thisptr,0x30) lalheadfields["Next"] = struct.unpack(' 0 and self.FrontEndHeapType == 0x1: lalindex = 0 startloc = self.FrontEndHeap while lalindex < 128: thisptr = self.FrontEndHeap + (0x30 * lalindex) lalhead_flink = struct.unpack(' Chunks VA Blocks """ statedata = {} segments = getSegmentsForHeap(self.heapbase) for seg in segments: segstart = segments[seg][0] segend = segments[seg][1] FirstEntry = segments[seg][2] LastValidEntry = segments[seg][3] datablocks = walkSegment(FirstEntry,LastValidEntry,self.heapbase) statedata[seg] = datablocks return statedata """ Low Fragmentation Heap """ class MnLFH(): # +0x000 Lock : _RTL_CRITICAL_SECTION # +0x018 SubSegmentZones : _LIST_ENTRY # +0x020 ZoneBlockSize : Uint4B # +0x024 Heap : Ptr32 Void # +0x028 SegmentChange : Uint4B # +0x02c SegmentCreate : Uint4B # +0x030 SegmentInsertInFree : Uint4B # +0x034 SegmentDelete : Uint4B # +0x038 CacheAllocs : Uint4B # +0x03c CacheFrees : Uint4B # +0x040 SizeInCache : Uint4B # +0x048 RunInfo : _HEAP_BUCKET_RUN_INFO # +0x050 UserBlockCache : [12] _USER_MEMORY_CACHE_ENTRY # +0x110 Buckets : [128] _HEAP_BUCKET # +0x310 LocalData : [1] _HEAP_LOCAL_DATA # blocks : LocalData->SegmentInfos->SubSegments (Mgmt List)->SubSegs # class attributes Lock = None SubSegmentZones = None ZoneBlockSize = None Heap = None SegmentChange = None SegmentCreate = None SegmentInsertInFree = None SegmentDelete = None CacheAllocs = None CacheFrees = None SizeInCache = None RunInfo = None UserBlockCache = None Buckets = None LocalData = None def __init__(self,lfhbase): self.lfhbase = lfhbase self.populateLFHFields() return def populateLFHFields(self): # read 0x310 bytes and split into pieces FLHHeader = dbg.readMemory(self.lfhbase,0x310) self.Lock = FLHHeader[0:0x18] self.SubSegmentZones = [] self.SubSegmentZones.append(struct.unpack(' 0: self.firstentry = firstentry if lastvalidentry > 0: self.lastvalidentry = lastvalidentry self.chunks = {} def getChunks(self): """ Enumerate all chunks in the current segment Output : Dictionary, key = chunkptr Values : MnChunk objects chunktype will be set to "chunk" """ thischunk = self.firstentry allchunksfound = False allchunks = {} nextchunk = thischunk cnt = 0 savedprevsize = 0 mHeap = MnHeap(self.heapbase) key = mHeap.getEncodingKey() while not allchunksfound: thissize = 0 prevsize = 0 flag = 0 unused = 0 segmentid = 0 tag = 0 headersize = 0x8 try: fullheaderbin = "" if key == 0 and not win7mode: fullheaderbin = dbg.readMemory(thischunk,headersize) else: fullheaderbin = decodeHeapHeader(thischunk,headersize,key) sizebytes = fullheaderbin[0:2] thissize = struct.unpack(' 0: nextchunk = thischunk + (thissize * 8) else: nextchunk += headersize chunktype = "chunk" if "virtall" in getHeapFlag(flag).lower() or "internal" in getHeapFlag(flag).lower(): #chunktype = "virtualalloc" headersize = 0x20 if not thischunk in allchunks and thissize > 0: mChunk = MnChunk(thischunk,chunktype,headersize,self.heapbase,self.segmentstart,thissize,prevsize,segmentid,flag,unused,tag) allchunks[thischunk] = mChunk thischunk = nextchunk if nextchunk >= self.lastvalidentry: allchunksfound = True if "last" in getHeapFlag(flag).lower(): allchunksfound = True cnt += 1 self.chunks = allchunks return allchunks """ Chunk class """ class MnChunk: chunkptr = 0 chunktype = "" headersize = 0 extraheadersize = 0 heapbase = 0 segmentbase = 0 size = 0 prevsize = 0 segment = 0 flag = 0 flags = 0 unused = 0 tag = 0 flink = 0 blink = 0 commitsize = 0 reservesize = 0 remaining = 0 hasust = False dph_block_information_startstamp = 0 dph_block_information_heap = 0 dph_block_information_requestedsize = 0 dph_block_information_actualsize = 0 dph_block_information_traceindex = 0 dph_block_information_stacktrace = 0 dph_block_information_endstamp = 0 def __init__(self,chunkptr,chunktype,headersize,heapbase,segmentbase,size,prevsize,segment,flag,unused,tag,flink=0,blink=0,commitsize=0,reservesize=0): self.chunkptr = chunkptr self.chunktype = chunktype self.extraheadersize = 0 self.remaining = 0 self.dph_block_information_startstamp = 0 self.dph_block_information_heap = 0 self.dph_block_information_requestedsize = 0 self.dph_block_information_actualsize = 0 self.dph_block_information_traceindex = 0 self.dph_block_information_stacktrace = 0 self.dph_block_information_endstamp = 0 self.hasust = False # if ust/hpa is enabled, the chunk header is followed by 32bytes of DPH_BLOCK_INFORMATION header info currentflagnames = getNtGlobalFlagNames(getNtGlobalFlag()) if "ust" in currentflagnames: self.hasust = True if "hpa" in currentflagnames: # reader header info if arch == 32: self.extraheadersize = 0x20 try: raw_dph_header = dbg.readMemory(chunkptr + headersize,0x20) self.dph_block_information_startstamp = struct.unpack(' 0: dbg.log(" Extra header due to GFlags: 0x%x (%d) bytes" % (self.extraheadersize,self.extraheadersize)) if self.dph_block_information_stacktrace > 0: dbg.log(" DPH_BLOCK_INFORMATION Header size: 0x%x (%d)" % (self.extraheadersize,self.extraheadersize)) dbg.log(" StartStamp : 0x%08x" % self.dph_block_information_startstamp) dbg.log(" Heap : 0x%08x" % self.dph_block_information_heap) dbg.log(" RequestedSize : 0x%08x" % self.dph_block_information_requestedsize) requestedsize = self.dph_block_information_requestedsize dbg.log(" ActualSize : 0x%08x" % self.dph_block_information_actualsize) dbg.log(" TraceIndex : 0x%08x" % self.dph_block_information_traceindex) dbg.log(" StackTrace : 0x%08x" % self.dph_block_information_stacktrace) dbg.log(" EndStamp : 0x%08x" % self.dph_block_information_endstamp) dbg.log(" Size initial allocation request: 0x%x (%d)" % (requestedsize,requestedsize)) dbg.log(" Total space for data: 0x%x (%d)" % (self.usersize + self.unused - self.headersize,self.usersize + self.unused - self.headersize)) dbg.log(" Delta between initial size and total space for data: 0x%x (%d)" % (self.unused - self.headersize, self.unused-self.headersize)) if showdata: dsize = self.usersize + self.remaining if dsize > 0 and dsize < 32: contents = bin2hex(dbg.readMemory(self.userptr,self.usersize+self.remaining)) else: contents = bin2hex(dbg.readMemory(self.userptr,32)) + " ..." dbg.log(" Data : %s" % contents) dbg.log("") return def showChunkLine(self,showdata = False): return #---------------------------------------# # Class to access pointer properties # #---------------------------------------# class MnPointer: """ Class to access pointer properties """ def __init__(self,address): # check that the address is an integer if not type(address) == int and not type(address) == long: raise Exception("address should be an integer or long") self.address = address NullRange = [0] AsciiRange = range(1,128) AsciiPrintRange = range(20,127) AsciiUppercaseRange = range(65,91) AsciiLowercaseRange = range(97,123) AsciiAlphaRange = AsciiUppercaseRange + AsciiLowercaseRange AsciiNumericRange = range(48,58) AsciiSpaceRange = [32] self.HexAddress = toHex(address) # define the characteristics of the pointer byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 = (0,)*8 if arch == 32: byte1,byte2,byte3,byte4 = splitAddress(address) elif arch == 64: byte1,byte2,byte3,byte4,byte5,byte6,byte7,byte8 = splitAddress(address) # Nulls self.hasNulls = (byte1 == 0) or (byte2 == 0) or (byte3 == 0) or (byte4 == 0) # Starts with null self.startsWithNull = (byte1 == 0) # Unicode self.isUnicode = ((byte1 == 0) and (byte3 == 0)) # Unicode reversed self.isUnicodeRev = ((byte2 == 0) and (byte4 == 0)) if arch == 64: self.hasNulls = self.hasNulls or (byte5 == 0) or (byte6 == 0) or (byte7 == 0) or (byte8 == 0) self.isUnicode = self.isUnicode and ((byte5 == 0) and (byte7 == 0)) self.isUnicodeRev = self.isUnicodeRev and ((byte6 == 0) and (byte8 == 0)) # Unicode transform self.unicodeTransform = UnicodeTransformInfo(self.HexAddress) # Ascii if not self.isUnicode and not self.isUnicodeRev: self.isAscii = bytesInRange(address, AsciiRange) else: self.isAscii = bytesInRange(address, NullRange + AsciiRange) # AsciiPrintable if not self.isUnicode and not self.isUnicodeRev: self.isAsciiPrintable = bytesInRange(address, AsciiPrintRange) else: self.isAsciiPrintable = bytesInRange(address, NullRange + AsciiPrintRange) # Uppercase if not self.isUnicode and not self.isUnicodeRev: self.isUppercase = bytesInRange(address, AsciiUppercaseRange) else: self.isUppercase = bytesInRange(address, NullRange + AsciiUppercaseRange) # Lowercase if not self.isUnicode and not self.isUnicodeRev: self.isLowercase = bytesInRange(address, AsciiLowercaseRange) else: self.isLowercase = bytesInRange(address, NullRange + AsciiLowercaseRange) # Numeric if not self.isUnicode and not self.isUnicodeRev: self.isNumeric = bytesInRange(address, AsciiNumericRange) else: self.isNumeric = bytesInRange(address, NullRange + AsciiNumericRange) # Alpha numeric if not self.isUnicode and not self.isUnicodeRev: self.isAlphaNumeric = bytesInRange(address, AsciiAlphaRange + AsciiNumericRange + AsciiSpaceRange) else: self.isAlphaNumeric = bytesInRange(address, NullRange + AsciiAlphaRange + AsciiNumericRange + AsciiSpaceRange) # Uppercase + Numbers if not self.isUnicode and not self.isUnicodeRev: self.isUpperNum = bytesInRange(address, AsciiUppercaseRange + AsciiNumericRange) else: self.isUpperNum = bytesInRange(address, NullRange + AsciiUppercaseRange + AsciiNumericRange) # Lowercase + Numbers if not self.isUnicode and not self.isUnicodeRev: self.isLowerNum = bytesInRange(address, AsciiLowercaseRange + AsciiNumericRange) else: self.isLowerNum = bytesInRange(address, NullRange + AsciiLowercaseRange + AsciiNumericRange) def __str__(self): """ Get pointer properties (human readable format) Arguments: None Return: String with various properties about the pointer """ outstring = "" if self.startsWithNull: outstring += "startnull," elif self.hasNulls: outstring += "null," #check if this pointer is unicode transform hexaddr = self.HexAddress outstring += UnicodeTransformInfo(hexaddr) if self.isUnicode: outstring += "unicode," if self.isUnicodeRev: outstring += "unicodereverse," if self.isAsciiPrintable: outstring += "asciiprint," if self.isAscii: outstring += "ascii," if self.isUppercase: outstring == "upper," if self.isLowercase: outstring += "lower," if self.isNumeric: outstring+= "num," if self.isAlphaNumeric and not (self.isUppercase or self.isLowercase or self.isNumeric): outstring += "alphanum," if self.isUpperNum and not (self.isUppercase or self.isNumeric): outstring += "uppernum," if self.isLowerNum and not (self.isLowercase or self.isNumeric): outstring += "lowernum," outstring = outstring.rstrip(",") outstring += " {" + getPointerAccess(self.address)+"}" return outstring def getAddress(self): return self.address def isUnicode(self): return self.isUnicode def isUnicodeRev(self): return self.isUnicodeRev def isUnicodeTransform(self): return self.unicodeTransform != "" def isAscii(self): return self.isAscii def isAsciiPrintable(self): return self.isAsciiPrintable def isUppercase(self): return self.isUppercase def isLowercase(self): return self.isLowercase def isUpperNum(self): return self.isUpperNum def isLowerNum(self): return self.isLowerNum def isNumeric(self): return self.isNumeric def isAlphaNumeric(self): return self.alphaNumeric def hasNulls(self): return self.hasNulls def startsWithNull(self): return self.startsWithNull def belongsTo(self): """ Retrieves the module a given pointer belongs to Arguments: None Return: String with the name of the module a pointer belongs to, or empty if pointer does not belong to a module """ if len(g_modules)==0: populateModuleInfo() for thismodule,modproperties in g_modules.iteritems(): thisbase = getModuleProperty(thismodule,"base") thistop = getModuleProperty(thismodule,"top") if (self.address >= thisbase) and (self.address <= thistop): return thismodule return "" def isOnStack(self): """ Checks if the pointer is on one of the stacks of one of the threads in the process Arguments: None Return: Boolean - True if pointer is on stack """ stacks = getStacks() for stack in stacks: if (stacks[stack][0] <= self.address) and (self.address < stacks[stack][1]): return True return False def isInHeap(self): """ Checks if the pointer is part of one of the pages associated with process heaps/segments Arguments: None Return: Boolean - True if pointer is in heap """ segmentcnt = 0 for heap in dbg.getHeapsAddress(): # part of a segment ? segments = getSegmentsForHeap(heap) for segment in segments: if segmentcnt == 0: # in heap data structure if self.address >= heap and self.address <= segment: return True segmentcnt += 1 if self.address >= segment: last = segments[segment][3] if self.address >= segment and self.address <= last: return True # maybe it's in a VA List ? for heap in dbg.getHeapsAddress(): mHeap = MnHeap(heap) valist = mHeap.getVirtualAllocdBlocks() if len(valist) > 0: for vachunk in valist: thischunk = valist[vachunk] #dbg.log("self: 0x%08x, vachunk: 0x%08x, commitsize: 0x%08x, vachunk+(thischunk.commitsize)*8: 0x%08x" % (self.address,vachunk,thischunk.commitsize,vachunk+(thischunk.commitsize*8))) if self.address >= vachunk and self.address <= (vachunk+(thischunk.commitsize*heapgranularity)): return True return False def getHeapInfo(self): global silent oldsilent = silent silent = True foundinheap, foundinsegment, foundinva, foundinchunk = self.showHeapBlockInfo() silent = oldsilent return [foundinheap, foundinsegment, foundinva, foundinchunk] def getHeapInfo_old(self): """ Returns heap related information about a given pointer """ heapinfo = {} heapinfo["heap"] = 0 heapinfo["segment"] = 0 heapinfo["chunk"] = 0 heapinfo["size"] = 0 allheaps = dbg.getHeapsAddress() for heap in allheaps: dbg.log("checking heap 0x%08x for 0x%08x" % (heap,self.address)) theap = dbg.getHeap(heap) heapchunks = theap.getChunks(heap) if len(heapchunks) > 0 and not silent: dbg.log("Querying segment(s) for heap 0x%s" % toHex(heap)) for hchunk in heapchunks: chunkbase = hchunk.get("address") chunksize = hchunk.get("size") if self.address >= chunkbase and self.address <= (chunkbase+chunksize): heapinfo["heap"] = heap heapinfo["segment"] = 0 heapinfo["chunk"] = chunkbase heapinfo["size"] = chunksize return heapinfo return heapinfo def showObjectInfo(self): # check if chunk is a DOM object if __DEBUGGERAPP__ == "WinDBG": cmdtorun = "dds 0x%08x L 1" % self.address output = dbg.nativeCommand(cmdtorun) outputlower = output.lower() outputlines = output.split("\n") if "vftable" in outputlower: # is this Internet Explorer ? ieversion = 0 if isModuleLoadedInProcess('iexplore.exe') and isModuleLoadedInProcess('mshtml.dll'): ieversionstr = getModuleProperty('iexplore.exe','version') dbg.log(" Internet Explorer v%s detected" % ieversionstr) ieversion = 0 if ieversionstr.startswith("8."): ieversion = 8 if ieversionstr.startswith("9."): ieversion = 9 if ieversionstr.startswith("10."): ieversion = 10 dbg.log(" 0x%08x may be the start of an object, vtable pointer: %s" % (self.address,outputlines[0])) vtableptr_s = outputlines[0][10:18] try: vtableptr = hexStrToInt(vtableptr_s) dbg.log(" Start of vtable at 0x%08x: (showing first 4 entries only)" % vtableptr) cmdtorun = "dds 0x%08x L 4" % vtableptr output = dbg.nativeCommand(cmdtorun) outputlines = output.split("\n") cnt = 0 for line in outputlines: if line.replace(" ","") != "": dbg.log(" +0x%x -> %s" % (cnt,line)) cnt += 4 if "mshtml!" in outputlower and ieversion > 7: # see if we can find the object type, refcounter, attribute count, parent, etc refcounter = None attributeptr = None try: refcounter = dbg.readLong(self.address + 4) except: pass try: if ieversion == 8: attributeptr = dbg.readLong(self.address + 0xc) if ieversion == 9: attributeptr = dbg.readLong(self.address + 0x10) except: pass if not refcounter is None and not attributeptr is None: dbg.log(" Refcounter: 0x%x (%d)" % (refcounter,refcounter)) if refcounter > 0x20000: dbg.log(" Note: a huge refcounter value may indicate this is not a real DOM object") if attributeptr == 0: dbg.log(" No attributes found") else: ptrx = MnPointer(attributeptr) if ptrx.isInHeap(): dbg.log(" Attribute info structure stored at 0x%08x" % attributeptr) offset_nr = 0x4 nr_multiplier = 4 offset_tableptr = 0xc offset_tabledata = 0 variant_offset = 4 attname_offset = 8 attvalue_offset = 0xc if ieversion == 9: nr_multiplier = 1 offset_nr = 0x4 offset_tableptr = 0x8 offset_tabledata = 4 variant_offset = 1 attname_offset = 4 attvalue_offset = 8 nr_attributes = dbg.readLong(attributeptr + offset_nr) / nr_multiplier attributetableptr = dbg.readLong(attributeptr + offset_tableptr) dbg.log(" +0x%02x : Nr of attributes: %d" % (offset_nr,nr_attributes)) dbg.log(" +0x%02x : Attribute table at 0x%08x" % (offset_tableptr,attributetableptr)) attcnt = 0 while attcnt < nr_attributes: try: dbg.log(" Attribute %d (at 0x%08x) :" % (attcnt+1,attributetableptr)) sec_dword = "%08x" % struct.unpack(' 0x1: att_name = "" try: att_name_ptr = dbg.readLong(attributetableptr+attname_offset) att_name_ptr_value = dbg.readLong(att_name_ptr+4) att_name = dbg.readWString(att_name_ptr_value) except: att_name = "" dbg.log(" 0x%08x + 0x%02x (0x%08x): 0x%08x : &Attribute name : '%s'" % (attributetableptr,attname_offset,attributetableptr+attname_offset,att_name_ptr,att_name)) att_value_ptr = dbg.readLong(attributetableptr+attvalue_offset) ptrx = MnPointer(att_value_ptr) if ptrx.isInHeap(): att_value = "" if variant_type == 0x8: att_value = dbg.readWString(att_value_ptr) if variant_type == 0x16: attv = dbg.readLong(att_value_ptr) att_value = "0x%08x (%s)" % (attv,int("0x%08x" % attv,16)) if variant_type == 0x1e: att_from = dbg.readLong(att_value_ptr) att_value = dbg.readString(att_from) if variant_type == 0x1f: att_from = dbg.readLong(att_value_ptr) att_value = dbg.readWString(att_from) else: att_value = "0x%08x (%s)" % (att_value_ptr,int("0x%08x" % att_value_ptr,16)) dbg.log(" 0x%08x + 0x%02x (0x%08x): 0x%08x : &Value : %s" % (attributetableptr,attvalue_offset,attributetableptr+attvalue_offset,att_value_ptr,att_value)) except: dbg.logLines(traceback.format_exc(),highlight=True) break attributetableptr += 0x10 attcnt += 1 else: dbg.log(" Invalid attribute ptr found (0x%08x). This may not be a real DOM object." % attributeptr) offset_domtree = 0x14 if ieversion == 9: offset_domtree = 0x1C domtreeptr = dbg.readLong(self.address + offset_domtree) if not domtreeptr is None: dptrx = MnPointer(domtreeptr) if dptrx.isInHeap(): currobj = self.address moreparents = True parentcnt = 0 dbg.log(" Object +0x%02x : Ptr to DOM Tree info: 0x%08x" % (offset_domtree,domtreeptr)) while moreparents: # walk tree, get parents parentspaces = " " * parentcnt cmdtorun = "dds poi(poi(poi(0x%08x+0x%02x)+4)) L 1" % (currobj,offset_domtree) output = dbg.nativeCommand(cmdtorun) outputlower = output.lower() outputlines = output.split("\n") if "vftable" in outputlines[0]: dbg.log(" %s Parent : %s" % (parentspaces,outputlines[0])) parts = outputlines[0].split(" ") try: currobj = int(parts[0],16) except: currobj = 0 else: moreparents = False parentcnt += 3 if currobj == 0: moreparents = False except: dbg.logLines(traceback.format_exc(),highlight=True) pass return def showHeapBlockInfo(self): """ Find address in heap and print out info about heap, segment, chunk it belongs to """ allheaps = [] heapkey = 0 foundinheap = None foundinsegment = None foundinva = None foundinchunk = None dumpsize = 0 dodump = False try: allheaps = dbg.getHeapsAddress() except: allheaps = [] for heapbase in allheaps: mHeap = MnHeap(heapbase) heapbase_extra = "" frontendinfo = [] frontendheapptr = 0 frontendheaptype = 0 if win7mode: heapkey = mHeap.getEncodingKey() if mHeap.usesLFH(): frontendheaptype = 0x2 heapbase_extra = " [LFH] " frontendheapptr = mHeap.getLFHAddress() frontendinfo = [frontendheaptype,frontendheapptr] segments = mHeap.getHeapSegmentList() #segments for seg in segments: segstart = segments[seg][0] segend = segments[seg][1] FirstEntry = segments[seg][2] LastValidEntry = segments[seg][3] allchunks = walkSegment(FirstEntry,LastValidEntry,heapbase) for chunkptr in allchunks: thischunk = allchunks[chunkptr] thissize = thischunk.size*8 headersize = thischunk.headersize if self.address >= chunkptr and self.address < (chunkptr + thissize): # found it ! if not silent: dbg.log("") dbg.log("Address 0x%08x found in " % self.address) thischunk.showChunk(showdata = True) self.showObjectInfo() self.showHeapStackTrace(thischunk) dodump = True dumpsize = thissize foundinchunk = thischunk foundinsegment = seg foundinheap = heapbase break if not foundinchunk == None: break # VA if foundinchunk == None: # maybe it's in VirtualAllocdBlocks vachunks = mHeap.getVirtualAllocdBlocks() for vaptr in vachunks: thischunk = vachunks[vaptr] if self.address >= vaptr and self.address <= vaptr + (thischunk.commitsize*8): if not silent: dbg.log("") dbg.log("Address 0x%08x found in VirtualAllocdBlocks of heap 0x%08x" % (self.address,heapbase)) thischunk.showChunk(showdata = True) self.showObjectInfo() self.showHeapStackTrace(thischunk) thissize = thischunk.usersize dumpsize = thissize dodump = True foundinchunk = thischunk foundinva = vaptr foundinheap = heapbase break # perhaps chunk is in FEA # if it is, it won't be a VA chunk if foundinva == None: if not win7mode: foundinlal = False foundinfreelist = False FrontEndHeap = mHeap.getFrontEndHeap() if FrontEndHeap > 0: fea_lal = mHeap.getLookAsideList() for lal_table_entry in sorted(fea_lal.keys()): nr_of_chunks = len(fea_lal[lal_table_entry]) lalhead = struct.unpack('= lalchunk.chunkptr) and (self.address < lalchunk.chunkptr+chunksize): foundinlal = True if not silent: dbg.log("Address is part of chunk on LookAsideList[%d], heap 0x%08x" % (lal_table_entry,mHeap.heapbase)) break if foundinlal: expectedsize = lal_table_entry * 8 if not silent: dbg.log(" LAL [%d] @0x%08x, Expected Chunksize: 0x%x (%d), %d chunks, Flink: 0x%08x" % (lal_table_entry,FrontEndHeap + (0x30 * lal_table_entry),expectedsize,expectedsize,nr_of_chunks,lalhead)) for chunkindex in fea_lal[lal_table_entry]: lalchunk = fea_lal[lal_table_entry][chunkindex] foundchunk = lalchunk chunksize = lalchunk.size * 8 flag = getHeapFlag(lalchunk.flag) extra = " " if (self.address >= lalchunk.chunkptr) and (self.address < lalchunk.chunkptr+chunksize): extra = " --> " if not silent: dbg.log("%sChunkPtr: 0x%08x, UserPtr: 0x%08x, Flink: 0x%08x, ChunkSize: 0x%x, UserSize: 0x%x, UserSpace: 0x%x (%s)" % (extra,lalchunk.chunkptr,lalchunk.userptr,lalchunk.flink,chunksize,lalchunk.usersize,lalchunk.usersize + lalchunk.remaining,flag)) if not silent: self.showObjectInfo() dumpsize = chunksize dodump = True break if not foundinlal: # or maybe in BEA thisfreelist = mHeap.getFreeList() thisfreelistinusebitmap = mHeap.getFreeListInUseBitmap() for flindex in thisfreelist: freelist_addy = heapbase + 0x178 + (8 * flindex) expectedsize = ">1016" expectedsize2 = ">0x%x" % 1016 if flindex != 0: expectedsize2 = str(8 * flindex) expectedsize = "0x%x" % (8 * flindex) for flentry in thisfreelist[flindex]: freelist_chunk = thisfreelist[flindex][flentry] chunksize = freelist_chunk.size * 8 if (self.address >= freelist_chunk.chunkptr) and (self.address < freelist_chunk.chunkptr+chunksize): foundinfreelist = True if not silent: dbg.log("Address is part of chunk on FreeLists[%d] at 0x%08x, heap 0x%08x:" % (flindex,freelist_addy,mHeap.heapbase)) break if foundinfreelist: flindicator = 0 for flentry in thisfreelist[flindex]: freelist_chunk = thisfreelist[flindex][flentry] chunksize = freelist_chunk.size * 8 extra = " " if (self.address >= freelist_chunk.chunkptr) and (self.address < freelist_chunk.chunkptr+chunksize): extra = " --> " foundchunk = freelist_chunk if not silent: dbg.log("%sChunkPtr: 0x%08x, UserPtr: 0x%08x, Flink: 0x%08x, Blink: 0x%08x, ChunkSize: 0x%x (%d), Usersize: 0x%x (%d)" % (extra,freelist_chunk.chunkptr,freelist_chunk.userptr,freelist_chunk.flink,freelist_chunk.blink,chunksize,chunksize,freelist_chunk.usersize,freelist_chunk.usersize)) if flindex != 0 and chunksize != (8*flindex): dbg.log(" ** Header may be corrupted! **", highlight = True) flindicator = 1 if flindex > 1 and int(thisfreelistinusebitmap[flindex]) != flindicator: if not silent: dbg.log(" ** FreeListsInUseBitmap mismatch for index %d! **" % flindex, highlight = True) if not silent: self.showObjectInfo() dumpsize = chunksize dodump = True break if dodump and dumpsize > 0 and dumpsize < 1025 and not silent: self.dumpObjectAtLocation(dumpsize) return foundinheap, foundinsegment, foundinva, foundinchunk def showHeapStackTrace(self,thischunk): # show stacktrace if any if __DEBUGGERAPP__ == "WinDBG": stacktrace_address = thischunk.dph_block_information_stacktrace stacktrace_index = thischunk.dph_block_information_traceindex stacktrace_startstamp = 0xabcdaaaa if thischunk.hasust and stacktrace_address > 0: if stacktrace_startstamp == thischunk.dph_block_information_startstamp: cmd2run = "dds 0x%08x L 24" % (stacktrace_address) output = dbg.nativeCommand(cmd2run) outputlines = output.split("\n") if "!" in output: dbg.log("Stack trace, index 0x%x:" % stacktrace_index) dbg.log("--------------------------") for outputline in outputlines: if "!" in outputline: lineparts = outputline.split(" ") if len(lineparts) > 2: firstpart = len(lineparts[0])+1 dbg.log(outputline[firstpart:]) return def memLocation(self): """ Gets the memory location associated with a given pointer (modulename, stack, heap or empty) Arguments: None Return: String """ memloc = self.belongsTo() if memloc == "": if self.isOnStack(): return "Stack" if self.isInHeap(): return "Heap" return "??" return memloc def getPtrFunction(self): funcinfo = "" global silent silent = True if __DEBUGGERAPP__ == "WinDBG": lncmd = "ln 0x%08x" % self.address lnoutput = dbg.nativeCommand(lncmd) for line in lnoutput.split("\n"): if line.replace(" ","") != "" and line.find("%08x" % self.address) > -1: lineparts = line.split("|") funcrefparts = lineparts[0].split(")") if len(funcrefparts) > 1: funcinfo = funcrefparts[1].replace(" ","") break if funcinfo == "": memloc = self.belongsTo() if not memloc == "": mod = MnModule(memloc) if not mod is None: start = mod.moduleBase offset = self.address - start offsettxt = "" if offset > 0: offsettxt = "+0x%08x" % offset else: offsettxt = "__base__" funcinfo = memloc+offsettxt silent = False return funcinfo def dumpObjectAtLocation(self,size,levels=0,nestedsize=0,customthislog="",customlogfile=""): dumpdata = {} origdumpdata = {} if __DEBUGGERAPP__ == "WinDBG": addy = self.address if not silent: dbg.log("") dbg.log("----------------------------------------------------") if (size < 0x500): dbg.log("[+] Dumping object at 0x%08x, 0x%02x bytes" % (addy,size)) else: dbg.log("[+] Dumping object at 0x%08x, 0x%02x bytes (output below will be limited to the first 0x500 bytes !)" % (addy,size)) size = 0x500 if levels > 0: dbg.log("[+] Also dumping up to %d levels deep, max size of nested objects: 0x%02x bytes" % (levels, nestedsize)) dbg.log("") parentlist = [] levelcnt = 0 if customthislog == "" and customlogfile == "": logfile = MnLog("dumpobj.txt") thislog = logfile.reset() else: logfile = customlogfile thislog = customthislog addys = [addy] parent = "" parentdata = {} while levelcnt <= levels: thisleveladdys = [] for addy in addys: cmdtorun = "dps 0x%08x L 0x%02x/%x" % (addy,size,archValue(4,8)) startaddy = addy endaddy = addy + size output = dbg.nativeCommand(cmdtorun) outputlines = output.split("\n") offset = 0 for outputline in outputlines: if not outputline.replace(" ","") == "": loc = outputline[0:archValue(8,17)].replace("`","") content = outputline[archValue(10,19):archValue(18,36)].replace("`","") symbol = outputline[archValue(19,37):] if not "??" in content and symbol.replace(" ","") == "": contentaddy = hexStrToInt(content) info = self.getLocInfo(hexStrToInt(loc),contentaddy,startaddy,endaddy) info.append(content) dumpdata[hexStrToInt(loc)] = info else: info = ["",symbol,"",content] dumpdata[hexStrToInt(loc)] = info if addy in parentdata: pdata = parentdata[addy] parent = "Referenced at 0x%08x (object 0x%08x, offset +0x%02x)" % (pdata[0],pdata[1],pdata[0]-pdata[1]) else: parent = "" cmd2torun = "!heap -p -a 0x%08x" % (addy) output2 = dbg.nativeCommand(cmd2torun) heapdata = output2.split("\n") self.printObjDump(dumpdata,logfile,thislog,size,parent,heapdata) for loc in dumpdata: thisdata = dumpdata[loc] if thisdata[0] == "ptr_obj": thisptr = int(thisdata[3],16) thisleveladdys.append(thisptr) parentdata[thisptr] = [loc,addy] if levelcnt == 0: origdumpdata = dumpdata dumpdata = {} addys = thisleveladdys size = nestedsize levelcnt += 1 dumpdata = origdumpdata return dumpdata def printObjDump(self,dumpdata,logfile,thislog,size=0,parent="",heapdata=[]): # dictionary, key = address # 0 = type # 1 = content info # 2 = string type # 3 = content sortedkeys = sorted(dumpdata) if len(sortedkeys) > 0: startaddy = sortedkeys[0] sizem = "" parentinfo = "" if size > 0: sizem = " (0x%02x bytes)" % size logfile.write("",thislog) if parent == "": logfile.write("=" * 60,thislog) line = ">> Object at 0x%08x%s:" % (startaddy,sizem) if not silent: dbg.log("") dbg.log(line) logfile.write(line,thislog) if parent != "": line = " %s" % parent if not silent: dbg.log(line) logfile.write(line,thislog) line = "Offset Address Contents Info" if arch == 64: line = "Offset Address Contents Info" logfile.write(line,thislog) if not silent: dbg.log(line) line = "------ ------- -------- -----" if arch == 64: line = "------ ------- -------- -----" logfile.write(line,thislog) if not silent: dbg.log(line) offset = 0 for loc in sortedkeys: info = dumpdata[loc] if len(info) > 1: content = "" if len(info) > 3: content = info[3] contentinfo = toAsciiOnly(info[1]) offsetstr = toSize("%02x" % offset,4) line = "+%s 0x%08x | 0x%s %s" % (offsetstr,loc,content,contentinfo) if not silent: dbg.log(line) logfile.write(line,thislog) offset += archValue(4,8) if len(sortedkeys) > 0: dbg.log("") for heapdataline in heapdata: logfile.write(heapdataline, thislog) dbg.log(heapdataline) return def getLocInfo(self,loc,addy,startaddy,endaddy): locinfo = [] if addy >= startaddy and addy <= endaddy: offset = addy - startaddy locinfo = ["self","ptr to self+0x%08x" % offset,""] return locinfo ismapped = False extra = "" ptrx = MnPointer(addy) memloc = ptrx.memLocation() if not "??" in memloc: if "Stack" in memloc or "Heap" in memloc: extra = "(%s) " % memloc else: detailmemloc = ptrx.getPtrFunction() extra = " (%s.%s)" % (memloc,detailmemloc) # maybe it's a pointer to an object ? cmd2run = "dps 0x%08x L 1" % addy output = dbg.nativeCommand(cmd2run) outputlines = output.split("\n") if len(outputlines) > 0: if not "??" in outputlines[0]: ismapped = True ptraddy = outputlines[0][archValue(10,19):archValue(18,36)].replace("`","") ptrinfo = outputlines[0][archValue(19,37):] if ptrinfo.replace(" ","") != "": if "vftable" in ptrinfo or "Heap" in memloc: locinfo = ["ptr_obj","%sptr to 0x%08x : %s" % (extra,hexStrToInt(ptraddy),ptrinfo),str(addy)] else: locinfo = ["ptr","%sptr to 0x%08x : %s" % (extra,hexStrToInt(ptraddy),ptrinfo),str(addy)] return locinfo if ismapped: # pointer to a string ? try: strdata = dbg.readString(addy) if len(strdata) > 2: datastr = strdata if len(strdata) > 80: datastr = strdata[0:80] + "..." locinfo = ["ptr_str","%sptr to ASCII (0x%02x) '%s'" % (extra,len(strdata),datastr),"ascii"] return locinfo except: pass # maybe it's unicode ? try: strdata = dbg.readWString(addy) if len(strdata) > 2: datastr = strdata if len(strdata) > 80: datastr = strdata[0:80] + "..." locinfo = ["ptr_str","%sptr to UNICODE (0x%02x) '%s'" % (extra,len(strdata),datastr),"unicode"] return locinfo except: pass # maybe the pointer points into a function ? ptrf = ptrx.getPtrFunction() if not ptrf == "": locinfo = ["ptr_func","%sptr to %s" % (extra,ptrf),str(addy)] return locinfo # BSTR Unicode ? try: bstr = struct.unpack(' 2 and (bstr == len(strdata)+1): datastr = strdata if len(strdata) > 80: datastr = strdata[0:80] + "..." locinfo = ["ptr_str","%sptr to BSTR UNICODE (0x%02x) '%s'" % (extra,bstr,datastr),"unicode"] return locinfo except: pass # pointer to a BSTR ASCII? try: strdata = dbg.readString(addy+4) if len(strdata) > 2 and (bstr == len(strdata)/2): datastr = strdata if len(strdata) > 80: datastr = strdata[0:80] + "..." locinfo = ["ptr_str","%sptr to BSTR ASCII (0x%02x) '%s'" % (extra,bstr,datastr),"ascii"] return locinfo except: pass # pointer itself is a string ? if ptrx.isUnicode: b1,b2,b3,b4,b5,b6,b7,b8 = (0,)*8 if arch == 32: b1,b2,b3,b4 = splitAddress(addy) if arch == 64: b1,b2,b3,b4,b5,b6,b7,b8 = splitAddress(addy) ptrstr = toAscii(toHexByte(b2)) + toAscii(toHexByte(b4)) if arch == 64: ptrstr += toAscii(toHexByte(b6)) + toAscii(toHexByte(b8)) if ptrstr.replace(" ","") != "" and not toHexByte(b2) == "00": locinfo = ["str","= UNICODE '%s' %s" % (ptrstr,extra),"unicode"] return locinfo if ptrx.isAsciiPrintable: b1,b2,b3,b4,b5,b6,b7,b8 = (0,)*8 if arch == 32: b1,b2,b3,b4 = splitAddress(addy) if arch == 64: b1,b2,b3,b4,b5,b6,b7,b8 = splitAddress(addy) ptrstr = toAscii(toHexByte(b1)) + toAscii(toHexByte(b2)) + toAscii(toHexByte(b3)) + toAscii(toHexByte(b4)) if arch == 64: ptrstr += toAscii(toHexByte(b5)) + toAscii(toHexByte(b6)) + toAscii(toHexByte(b7)) + toAscii(toHexByte(b8)) if ptrstr.replace(" ","") != "" and not toHexByte(b1) == "00" and not toHexByte(b2) == "00" and not toHexByte(b3) == "00" and not toHexByte(b4) == "00": if arch != 64 or (not toHexByte(b5) == "00" and not toHexByte(b6) == "00" and not toHexByte(b7) == "00" and not toHexByte(b8) == "00"): locinfo = ["str","= ASCII '%s' %s" % (ptrstr,extra),"ascii"] return locinfo # pointer to heap ? if "Heap" in memloc: if not "??" in outputlines[0]: ismapped = True ptraddy = outputlines[0][archValue(10,19):archValue(18,36)] locinfo = ["ptr_obj","%sptr to 0x%08x" % (extra,hexStrToInt(ptraddy)),str(addy)] return locinfo # nothing special to report return ["","",""] #---------------------------------------# # Various functions # #---------------------------------------# def getDefaultProcessHeap(): peb = dbg.getPEBAddress() defprocheap = struct.unpack(' 0 and (nextbase+subtract != lastindex): segstart = readPtrSizeBytes(segbase + archValue(0x24,0x40)) segend = readPtrSizeBytes(segbase + archValue(0x28,0x48)) if not segbase in segmentinfo: segmentinfo[segbase] = [segbase,segend,segstart,segend] else: allsegmentsfound = True segmentcnt += 1 else: offset = archValue(0x058,0x0a0) i = 0 while not allsegmentsfound: thisbase = readPtrSizeBytes(heapbase + offset + i*archValue(4,8)) if thisbase > 0 and not thisbase in segmentinfo: # get start and end of segment segstart = thisbase segend = getSegmentEnd(segstart) # get first and last valid entry firstentry = readPtrSizeBytes(segstart + archValue(0x20,0x38)) lastentry = readPtrSizeBytes(segstart + archValue(0x24,0x40)) segmentinfo[thisbase] = [segstart,segend,firstentry,lastentry] else: allsegmentsfound = True i += 1 # avoid infinite loop if i > 100: allsegmentsfound = True except: pass segmentlistCache[heapbase] = segmentinfo return segmentinfo def containsBadChars(address,badchars="\x0a\x0d"): """ checks if the address contains bad chars Arguments: address - the address badchars - string with the characters that should be avoided (defaults to 0x0a and 0x0d) Return: Boolean - True if badchars are found """ bytes = splitAddress(address) chars = [] for byte in bytes: chars.append(chr(byte)) # check each char for char in chars: if char in badchars: return True return False def meetsCriteria(pointer,criteria): """ checks if an address meets the listed criteria Arguments: pointer - the MnPointer instance of the address criteria - a dictionary with all the criteria to be met Return: Boolean - True if all the conditions are met """ # Unicode if "unicode" in criteria and not (pointer.isUnicode or pointer.unicodeTransform != ""): return False if "unicoderev" in criteria and not pointer.isUnicodeRev: return False # Ascii if "ascii" in criteria and not pointer.isAscii: return False # Ascii printable if "asciiprint" in criteria and not pointer.isAsciiPrintable: return False # Uppercase if "upper" in criteria and not pointer.isUppercase: return False # Lowercase if "lower" in criteria and not pointer.isLowercase: return False # Uppercase numeric if "uppernum" in criteria and not pointer.isUpperNum: return False # Lowercase numeric if "lowernum" in criteria and not pointer.isLowerNum: return False # Numeric if "numeric" in criteria and not pointer.isNumeric: return False # Alpha numeric if "alphanum" in criteria and not pointer.isAlphaNumeric: return False # Bad chars if "badchars" in criteria and containsBadChars(pointer.getAddress(), criteria["badchars"]): return False # Nulls if "nonull" in criteria and pointer.hasNulls: return False if "startswithnull" in criteria and not pointer.startsWithNull: return False return True def search(sequences,criteria=[]): """ Alias for 'searchInRange' search for byte sequences in a specified address range Arguments: sequences - array of byte sequences to search for start - the start address of the search (defaults to 0) end - the end address of the search criteria - Dictionary containing the criteria each pointer should comply with Return: Dictionary (opcode sequence => List of addresses) """ return searchInRange(sequences,criteria) def searchInRange(sequences, start=0, end=TOP_USERLAND,criteria=[]): """ search for byte sequences in a specified address range Arguments: sequences - array of byte sequences to search for start - the start address of the search (defaults to 0) end - the end address of the search criteria - Dictionary containing the criteria each pointer should comply with Return: Dictionary (opcode sequence => List of addresses) """ if not "accesslevel" in criteria: criteria["accesslevel"] = "*" global ptr_counter global ptr_to_get found_opcodes = {} if (ptr_to_get < 0) or (ptr_to_get > 0 and ptr_counter < ptr_to_get): if not sequences: return {} # check that start is before end if start > end: start, end = end, start dbg.setStatusBar("Searching...") dbg.getMemoryPages() process_error_found = False for a in dbg.MemoryPages.keys(): if (ptr_to_get < 0) or (ptr_to_get > 0 and ptr_counter < ptr_to_get): # get end address of the page page_start = a page_size = dbg.MemoryPages[a].getSize() page_end = a + page_size if ( start > page_end or end < page_start ): # we are outside the search range, skip continue if (not meetsAccessLevel(dbg.MemoryPages[a],criteria["accesslevel"])): #skip this page, not executable continue # if the criteria check for nulls or unicode, we can skip # modules that start with 00 start_fb = toHex(page_start)[0:2] end_fb = toHex(page_end)[0:2] if ( ("nonull" in criteria and criteria["nonull"]) and start_fb == "00" and end_fb == "00" ): if not silent: dbg.log(" !Skipped search of range %08x-%08x (Has nulls)" % (page_start,page_end)) continue if (( ("startswithnull" in criteria and criteria["startswithnull"])) and (start_fb != "00" or end_fb != "00")): if not silent: dbg.log(" !Skipped search of range %08x-%08x (Doesn't start with null)" % (page_start,page_end)) continue mem = dbg.MemoryPages[a].getMemory() if not mem: continue # loop on each sequence for seq in sequences: if (ptr_to_get < 0) or (ptr_to_get > 0 and ptr_counter < ptr_to_get): buf = None human_format = "" if type(seq) == str: human_format = seq.replace("\n"," # ") buf = dbg.assemble(seq) else: human_format = seq[0].replace("\n"," # ") buf = seq[1] recur_find = [] try: buf_len = len(buf) mem_list = mem.split( buf ) total_length = buf_len * -1 except: process_error_found = True dbg.log(" ** Unable to process searchPattern '%s'. **" % human_format) break for i in mem_list: total_length = total_length + len(i) + buf_len seq_address = a + total_length recur_find.append( seq_address ) #The last one is the remaining slice from the split #so remove it from the list del recur_find[ len(recur_find) - 1 ] page_find = [] for i in recur_find: if ( i >= start and i <= end ): ptr = MnPointer(i) # check if pointer meets criteria if not meetsCriteria(ptr, criteria): continue page_find.append(i) ptr_counter += 1 if ptr_to_get > 0 and ptr_counter >= ptr_to_get: #stop search if human_format in found_opcodes: found_opcodes[human_format] += page_find else: found_opcodes[human_format] = page_find return found_opcodes #add current pointers to the list and continue if len(page_find) > 0: if human_format in found_opcodes: found_opcodes[human_format] += page_find else: found_opcodes[human_format] = page_find if process_error_found: break return found_opcodes # search for byte sequences in a module def searchInModule(sequences, name,criteria=[]): """ search for byte sequences in a specified module Arguments: sequences - array of byte sequences to search for name - the name of the module to search in Return: Dictionary (text opcode => array of addresses) """ module = dbg.getModule(name) if(not module): self.log("module %s not found" % name) return [] # get the base and end address of the module start = module.getBaseAddress() end = start + module.getSize() return searchInRange(sequences, start, end, criteria) def getRangesOutsideModules(): """ This function will enumerate all memory ranges that are not asssociated with a module Arguments : none Returns : array of arrays, each containing a start and end address """ ranges=[] moduleranges=[] #get all ranges associated with modules #force full rebuild to get all modules populateModuleInfo() for thismodule,modproperties in g_modules.iteritems(): top = 0 base = 0 for modprop,modval in modproperties.iteritems(): if modprop == "top": top = modval if modprop == "base": base = modval moduleranges.append([base,top]) #sort them moduleranges.sort() #get all ranges before, after and in between modules startpointer = 0 endpointer = TOP_USERLAND for modbase,modtop in moduleranges: endpointer = modbase-1 ranges.append([startpointer,endpointer]) startpointer = modtop+1 ranges.append([startpointer,TOP_USERLAND]) #return array return ranges def isModuleLoadedInProcess(modulename): if len(g_modules) == 0: populateModuleInfo() modulefound = False module = dbg.getModule(modulename) if(not module): modulefound = False else: modulefound = True return modulefound def UnicodeTransformInfo(hexaddr): """ checks if the address can be used as unicode ansi transform Arguments: hexaddr - a string containing the address in hex format (4 bytes - 8 characters) Return: string with unicode transform info, or empty if address is not unicode transform """ outstring = "" transform=0 almosttransform=0 begin = hexaddr[0] + hexaddr[1] middle = hexaddr[4] + hexaddr[5] twostr=hexaddr[2]+hexaddr[3] begintwostr = hexaddr[6]+hexaddr[7] threestr=hexaddr[4]+hexaddr[5]+hexaddr[6] fourstr=hexaddr[4]+hexaddr[5]+hexaddr[6]+hexaddr[7] beginfourstr = hexaddr[0]+hexaddr[1]+hexaddr[2]+hexaddr[3] threestr=threestr.upper() fourstr=fourstr.upper() begintwostr = begintwostr.upper() beginfourstr = beginfourstr.upper() uniansiconv = [ ["20AC","80"], ["201A","82"], ["0192","83"], ["201E","84"], ["2026","85"], ["2020","86"], ["2021","87"], ["02C6","88"], ["2030","89"], ["0106","8A"], ["2039","8B"], ["0152","8C"], ["017D","8E"], ["2018","91"], ["2019","92"], ["201C","93"], ["201D","94"], ["2022","95"], ["2013","96"], ["2014","97"], ["02DC","98"], ["2122","99"], ["0161","9A"], ["203A","9B"], ["0153","9C"], ["017E","9E"], ["0178","9F"] ] # 4 possible cases : # 00xxBBBB # 00xxBBBC (close transform) # AAAA00xx # AAAABBBB convbyte="" transbyte="" ansibytes="" #case 1 and 2 if begin == "00": for ansirec in uniansiconv: if ansirec[0]==fourstr: convbyte=ansirec[1] transbyte=ansirec[1] transform=1 break if transform==1: outstring +="unicode ansi transformed : 00"+twostr+"00"+convbyte+"," ansistring="" for ansirec in uniansiconv: if ansirec[0][:3]==threestr: if (transform==0) or (transform==1 and ansirec[1] != transbyte): convbyte=ansirec[1] ansibytes=ansirec[0] ansistring=ansistring+"00"+twostr+"00"+convbyte+"->00"+twostr+ansibytes+" / " almosttransform=1 if almosttransform==1: if transform==0: outstring += "unicode possible ansi transform(s) : " + ansistring else: outstring +=" / alternatives (close pointers) : " + ansistring #case 3 if middle == "00": transform = 0 for ansirec in uniansiconv: if ansirec[0]==beginfourstr: convbyte=ansirec[1] transform=1 break if transform==1: outstring +="unicode ansi transformed : 00"+convbyte+"00"+begintwostr+"," #case 4 if begin != "00" and middle != "00": convbyte1="" convbyte2="" transform = 0 for ansirec in uniansiconv: if ansirec[0]==beginfourstr: convbyte1=ansirec[1] transform=1 break if transform == 1: for ansirec in uniansiconv: if ansirec[0]==fourstr: convbyte2=ansirec[1] transform=2 break if transform==2: outstring +="unicode ansi transformed : 00"+convbyte1+"00"+convbyte2+"," # done outstring = outstring.rstrip(" / ") if outstring: if not outstring.endswith(","): outstring += "," return outstring def getSearchSequences(searchtype,searchcriteria="",type="",criteria={}): """ will build array with search sequences for a given search type Arguments: searchtype = "jmp", "seh" SearchCriteria (optional): in case of "jmp" : string containing a register Return: array with all searches to perform """ offsets = [ "", "0x04","0x08","0x0c","0x10","0x12","0x1C","0x20","0x24"] regs=["eax","ebx","ecx","edx","esi","edi","ebp"] search=[] if searchtype.lower() == "jmp": if not searchcriteria: searchcriteria = "esp" searchcriteria = searchcriteria.lower() min = 0 max = 0 if "mindistance" in criteria: min = criteria["mindistance"] if "maxdistance" in criteria: max = criteria["maxdistance"] minval = min while minval <= max: extraval = "" if minval != 0: operator = "" negoperator = "-" if minval < 0: operator = "-" negoperator = "" thisval = str(minval).replace("-","") thishexval = toHex(int(thisval)) extraval = operator + thishexval if minval == 0: search.append("jmp " + searchcriteria ) search.append("call " + searchcriteria) for roffset in offsets: search.append("push "+searchcriteria+"\nret "+roffset) for reg in regs: if reg != searchcriteria: search.append("push " + searchcriteria + "\npop "+reg+"\njmp "+reg) search.append("push " + searchcriteria + "\npop "+reg+"\ncall "+reg) search.append("mov "+reg+"," + searchcriteria + "\njmp "+reg) search.append("mov "+reg+"," + searchcriteria + "\ncall "+reg) search.append("xchg "+reg+","+searchcriteria+"\njmp " + reg) search.append("xchg "+reg+","+searchcriteria+"\ncall " + reg) for roffset in offsets: search.append("push " + searchcriteria + "\npop "+reg+"\npush "+reg+"\nret "+roffset) search.append("mov "+reg+"," + searchcriteria + "\npush "+reg+"\nret "+roffset) search.append("xchg "+reg+","+searchcriteria+"\npush " + reg + "\nret " + roffset) else: # offset jumps search.append("add " + searchcriteria + "," + operator + thishexval + "\njmp " + searchcriteria) search.append("add " + searchcriteria + "," + operator + thishexval + "\ncall " + searchcriteria) search.append("sub " + searchcriteria + "," + negoperator + thishexval + "\njmp " + searchcriteria) search.append("sub " + searchcriteria + "," + negoperator + thishexval + "\ncall " + searchcriteria) for roffset in offsets: search.append("add " + searchcriteria + "," + operator + thishexval + "\npush " + searchcriteria + "\nret " + roffset) search.append("sub " + searchcriteria + "," + negoperator + thishexval + "\npush " + searchcriteria + "\nret " + roffset) if minval > 0: search.append("jmp " + searchcriteria + extraval) search.append("call " + searchcriteria + extraval) minval += 1 if searchtype.lower() == "seh": if type == "rop": dbg.log(" - Looking for addresses that will help with SEH overwrite & ROP" ) for roffset in offsets: for r1 in regs: if type == "rop": search.append( ["add esp,4\npop " + r1+"\npop esp\nret "+roffset,dbg.assemble("add esp,4\npop " + r1+"\npop esp\nret "+roffset)] ) search.append( ["pop " + r1+"\nadd esp,4\npop esp\nret "+roffset,dbg.assemble("pop " + r1+"\nadd esp,4\npop esp\nret "+roffset)] ) else: search.append( ["add esp,4\npop " + r1+"\nret "+roffset,dbg.assemble("add esp,4\npop " + r1+"\nret "+roffset)] ) search.append( ["pop " + r1+"\nadd esp,4\nret "+roffset,dbg.assemble("pop " + r1+"\nadd esp,4\nret "+roffset)] ) for r2 in regs: if type == "rop": search.append( ["pop "+r1+"\npop "+r2+"\npop esp\nret "+roffset,dbg.assemble("pop "+r1+"\npop "+r2+"\npop esp\nret "+roffset)] ) for r3 in regs: search.append( ["pop "+r1+"\npop "+r2+"\npop "+r3+"\ncall ["+r3+"]",dbg.assemble("pop "+r1+"\npop "+r2+"\npop "+r3+"\ncall ["+r3+"]")] ) else: thissearch = ["pop "+r1+"\npop "+r2+"\nret "+roffset,dbg.assemble("pop "+r1+"\npop "+r2+"\nret "+roffset)] search.append( thissearch ) if type != "rop": search.append( ["add esp,8\nret "+roffset,dbg.assemble("add esp,8\nret "+roffset)]) search.append( ["popad\npush ebp\nret "+roffset,dbg.assemble("popad\npush ebp\nret "+roffset)]) else: search.append( ["add esp,8\npop esp\nret "+roffset,dbg.assemble("add esp,8\npop esp\nret "+roffset)]) if type != "rop": #popad + jmp/call search.append(["popad\njmp ebp",dbg.assemble("popad\njmp ebp")]) search.append(["popad\ncall ebp",dbg.assemble("popad\ncall ebp")]) #call / jmp dword search.append(["call dword ptr ss:[esp+08]","\xff\x54\x24\x08"]) search.append(["call dword ptr ss:[esp+08]","\xff\x94\x24\x08\x00\x00\x00"]) search.append(["call dword ptr ds:[esp+08]","\x3e\xff\x54\x24\x08"]) search.append(["jmp dword ptr ss:[esp+08]","\xff\x64\x24\x08"]) search.append(["jmp dword ptr ss:[esp+08]","\xff\xa4\x24\x08\x00\x00\x00"]) search.append(["jmp dword ptr ds:[esp+08]","\x3e\xff\x64\x24\x08"]) search.append(["call dword ptr ss:[esp+14]","\xff\x54\x24\x14"]) search.append(["call dword ptr ss:[esp+14]","\xff\x94\x24\x14\x00\x00\x00"]) search.append(["call dword ptr ds:[esp+14]","\x3e\xff\x54\x24\x14"]) search.append(["jmp dword ptr ss:[esp+14]","\xff\x64\x24\x14"]) search.append(["jmp dword ptr ss:[esp+14]","\xff\xa4\x24\x14\x00\x00\x00"]) search.append(["jmp dword ptr ds:[esp+14]","\x3e\xff\x64\x24\x14"]) search.append(["call dword ptr ss:[esp+1c]","\xff\x54\x24\x1c"]) search.append(["call dword ptr ss:[esp+1c]","\xff\x94\x24\x1c\x00\x00\x00"]) search.append(["call dword ptr ds:[esp+1c]","\x3e\xff\x54\x24\x1c"]) search.append(["jmp dword ptr ss:[esp+1c]","\xff\x64\x24\x1c"]) search.append(["jmp dword ptr ss:[esp+1c]","\xff\xa4\x24\x1c\x00\x00\x00"]) search.append(["jmp dword ptr ds:[esp+1c]","\x3e\xff\x64\x24\x1c"]) search.append(["call dword ptr ss:[esp+2c]","\xff\x54\x24\x2c"]) search.append(["call dword ptr ss:[esp+2c]","\xff\x94\x24\x2c\x00\x00\x00"]) search.append(["call dword ptr ds:[esp+2c]","\x3e\xff\x54\x24\x2c"]) search.append(["jmp dword ptr ss:[esp+2c]","\xff\x64\x24\x2c"]) search.append(["jmp dword ptr ss:[esp+2c]","\xff\xa4\x24\x2c\x00\x00\x00"]) search.append(["jmp dword ptr ds:[esp+2c]","\x3e\xff\x64\x24\x2c"]) search.append(["call dword ptr ss:[esp+44]","\xff\x54\x24\x44"]) search.append(["call dword ptr ss:[esp+44]","\xff\x94\x24\x44\x00\x00\x00"]) search.append(["call dword ptr ds:[esp+44]","\x3e\xff\x54\x24\x44"]) search.append(["jmp dword ptr ss:[esp+44]","\xff\x64\x24\x44"]) search.append(["jmp dword ptr ss:[esp+44]","\xff\xa4\x24\x44\x00\x00\x00"]) search.append(["jmp dword ptr ds:[esp+44]","\x3e\xff\x64\x24\x44"]) search.append(["call dword ptr ss:[esp+50]","\xff\x54\x24\x50"]) search.append(["call dword ptr ss:[esp+50]","\xff\x94\x24\x50\x00\x00\x00"]) search.append(["call dword ptr ds:[esp+50]","\x3e\xff\x54\x24\x50"]) search.append(["jmp dword ptr ss:[esp+50]","\xff\x64\x24\x50"]) search.append(["jmp dword ptr ss:[esp+50]","\xff\xa4\x24\x50\x00\x00\x00"]) search.append(["jmp dword ptr ds:[esp+50]","\x3e\xff\x64\x24\x50"]) search.append(["call dword ptr ss:[ebp+0c]","\xff\x55\x0c"]) search.append(["call dword ptr ss:[ebp+0c]","\xff\x95\x0c\x00\x00\x00"]) search.append(["call dword ptr ds:[ebp+0c]","\x3e\xff\x55\x0c"]) search.append(["jmp dword ptr ss:[ebp+0c]","\xff\x65\x0c"]) search.append(["jmp dword ptr ss:[ebp+0c]","\xff\xa5\x0c\x00\x00\x00"]) search.append(["jmp dword ptr ds:[ebp+0c]","\x3e\xff\x65\x0c"]) search.append(["call dword ptr ss:[ebp+24]","\xff\x55\x24"]) search.append(["call dword ptr ss:[ebp+24]","\xff\x95\x24\x00\x00\x00"]) search.append(["call dword ptr ds:[ebp+24]","\x3e\xff\x55\x24"]) search.append(["jmp dword ptr ss:[ebp+24]","\xff\x65\x24"]) search.append(["jmp dword ptr ss:[ebp+24]","\xff\xa5\x24\x00\x00\x00"]) search.append(["jmp dword ptr ds:[ebp+24]","\x3e\xff\x65\x24"]) search.append(["call dword ptr ss:[ebp+30]","\xff\x55\x30"]) search.append(["call dword ptr ss:[ebp+30]","\xff\x95\x30\x00\x00\x00"]) search.append(["call dword ptr ds:[ebp+30]","\x3e\xff\x55\x30"]) search.append(["jmp dword ptr ss:[ebp+30]","\xff\x65\x30"]) search.append(["jmp dword ptr ss:[ebp+30]","\xff\xa5\x30\x00\x00\x00"]) search.append(["jmp dword ptr ds:[ebp+30]","\x3e\xff\x65\x30"]) search.append(["call dword ptr ss:[ebp-04]","\xff\x55\xfc"]) search.append(["call dword ptr ss:[ebp-04]","\xff\x95\xfc\xff\xff\xff"]) search.append(["call dword ptr ds:[ebp-04]","\x3e\xff\x55\xfc"]) search.append(["jmp dword ptr ss:[ebp-04]","\xff\x65\xfc",]) search.append(["jmp dword ptr ss:[ebp-04]","\xff\xa5\xfc\xff\xff\xff",]) search.append(["jmp dword ptr ds:[ebp-04]","\x3e\xff\x65\xfc",]) search.append(["call dword ptr ss:[ebp-0c]","\xff\x55\xf4"]) search.append(["call dword ptr ss:[ebp-0c]","\xff\x95\xf4\xff\xff\xff"]) search.append(["call dword ptr ds:[ebp-0c]","\x3e\xff\x55\xf4"]) search.append(["jmp dword ptr ss:[ebp-0c]","\xff\x65\xf4",]) search.append(["jmp dword ptr ss:[ebp-0c]","\xff\xa5\xf4\xff\xff\xff",]) search.append(["jmp dword ptr ds:[ebp-0c]","\x3e\xff\x65\xf4",]) search.append(["call dword ptr ss:[ebp-18]","\xff\x55\xe8"]) search.append(["call dword ptr ss:[ebp-18]","\xff\x95\xe8\xff\xff\xff"]) search.append(["call dword ptr ds:[ebp-18]","\x3e\xff\x55\xe8"]) search.append(["jmp dword ptr ss:[ebp-18]","\xff\x65\xe8",]) search.append(["jmp dword ptr ss:[ebp-18]","\xff\xa5\xe8\xff\xff\xff",]) search.append(["jmp dword ptr ds:[ebp-18]","\x3e\xff\x65\xe8",]) return search def getModulesToQuery(criteria): """ This function will return an array of modulenames Arguments: Criteria - dictionary with module criteria Return: array with module names that meet the given criteria """ if len(g_modules) == 0: populateModuleInfo() modulestoquery=[] for thismodule,modproperties in g_modules.iteritems(): #is this module excluded ? thismod = MnModule(thismodule) included = True if not thismod.isExcluded: #check other criteria if ("safeseh" in criteria) and ((not criteria["safeseh"]) and thismod.isSafeSEH): included = False if ("aslr" in criteria) and ((not criteria["aslr"]) and thismod.isAslr): included = False if ("rebase" in criteria) and ((not criteria["rebase"]) and thismod.isRebase): included = False if ("os" in criteria) and ((not criteria["os"]) and thismod.isOS): included = False if ("nx" in criteria) and ((not criteria["nx"]) and thismod.isNX): included = False if ("cfg" in criteria) and ((not criteria["cfg"]) and thismod.isCFG): included = False else: included = False #override all previous decision if "modules" criteria was provided thismodkey = thismod.moduleKey.lower().strip() if ("modules" in criteria) and (criteria["modules"] != ""): included = False modulenames=criteria["modules"].split(",") for modulename in modulenames: modulename = modulename.strip('"').strip("'").lower() modulenamewithout = modulename.replace("*","") if len(modulenamewithout) <= len(thismodkey): #endswith ? if modulename[0] == "*": if modulenamewithout == thismodkey[len(thismodkey)-len(modulenamewithout):len(thismodkey)]: if not thismod.moduleKey in modulestoquery and not thismod.isExcluded: modulestoquery.append(thismod.moduleKey) #startswith ? if modulename[len(modulename)-1] == "*": if (modulenamewithout == thismodkey[0:len(modulenamewithout)] and not thismod.isExcluded): if not thismod.moduleKey in modulestoquery: modulestoquery.append(thismod.moduleKey) #contains ? if ((modulename[0] == "*" and modulename[len(modulename)-1] == "*") or (modulename.find("*") == -1)) and not thismod.isExcluded: if thismodkey.find(modulenamewithout) > -1: if not thismod.moduleKey in modulestoquery: modulestoquery.append(thismod.moduleKey) if included: modulestoquery.append(thismod.moduleKey) return modulestoquery def getPointerAccess(address): """ Returns access level of specified address, in human readable format Arguments: address - integer value Return: Access level (human readable format) """ global MemoryPageACL paccess = "" try: page = dbg.getMemoryPageByAddress( address ) if page in MemoryPageACL: paccess = MemoryPageACL[page] else: paccess = page.getAccess( human = True ) MemoryPageACL[page] = paccess except: paccess = "" return paccess def getModuleProperty(modname,parameter): """ Returns value of a given module property Argument : modname - module name parameter name - (see populateModuleInfo()) Returns : value associated with the given parameter / module combination """ modname=modname.strip() parameter=parameter.lower() valtoreturn="" # try case sensitive first for thismodule,modproperties in g_modules.iteritems(): if thismodule.strip() == modname: return modproperties[parameter] return valtoreturn def populateModuleInfo(): """ Populate global dictionary with information about all loaded modules Return: Dictionary """ if not silent: dbg.setStatusBar("Getting modules info...") dbg.log("[+] Generating module info table, hang on...") dbg.log(" - Processing modules") dbg.updateLog() global g_modules g_modules={} allmodules=dbg.getAllModules() curmod = "" for key in allmodules.keys(): modinfo={} thismod = MnModule(key) if not thismod is None: modinfo["path"] = thismod.modulePath modinfo["base"] = thismod.moduleBase modinfo["size"] = thismod.moduleSize modinfo["top"] = thismod.moduleTop modinfo["safeseh"] = thismod.isSafeSEH modinfo["aslr"] = thismod.isAslr modinfo["nx"] = thismod.isNX modinfo["rebase"] = thismod.isRebase modinfo["version"] = thismod.moduleVersion modinfo["os"] = thismod.isOS modinfo["cfg"] = thismod.isCFG modinfo["name"] = key modinfo["entry"] = thismod.moduleEntry modinfo["codebase"] = thismod.moduleCodebase modinfo["codesize"] = thismod.moduleCodesize modinfo["codetop"] = thismod.moduleCodetop modinfo["dllcharacteristics"] = thismod.moduleDllCharacteristics g_modules[thismod.moduleKey] = modinfo else: if not silent: dbg.log(" - Oops, potential issue with module %s, skipping module" % key) if not silent: dbg.log(" - Done. Let's rock 'n roll.") dbg.setStatusBar("") dbg.updateLog() def ModInfoCached(modulename): """ Check if the information about a given module is already cached in the global Dictionary Arguments: modulename - name of the module to check Return: Boolean - True if the module info is cached """ if (getModuleProperty(modulename,"base") == ""): return False else: return True def showModuleTable(logfile="", modules=[]): """ Shows table with all loaded modules and their properties. Arguments : empty string - output will be sent to log window or filename - output will be written to the filename modules - dictionary with modules to query - result of a populateModuleInfo() call """ thistable = "" if len(g_modules) == 0: populateModuleInfo() thistable += "----------------------------------------------------------------------------------------------------------------------------------------------\n" thistable += " Module info :\n" thistable += "----------------------------------------------------------------------------------------------------------------------------------------------\n" if arch == 32: thistable += " Base | Top | Size | Rebase | SafeSEH | ASLR | CFG | NXCompat | OS Dll | Version, Modulename & Path, DLLCharacteristics\n" elif arch == 64: thistable += " Base | Top | Size | Rebase | SafeSEH | ASLR | CFG | NXCompat | OS Dll | Version, Modulename & Path, DLLCharacteristics\n" thistable += "----------------------------------------------------------------------------------------------------------------------------------------------\n" for thismodule,modproperties in g_modules.iteritems(): if (len(modules) > 0 and modproperties["name"] in modules or len(logfile)>0): rebase = toSize(str(modproperties["rebase"]),7) base = toSize(str("0x" + toHex(modproperties["base"])),10) top = toSize(str("0x" + toHex(modproperties["top"])),10) size = toSize(str("0x" + toHex(modproperties["size"])),10) safeseh = toSize(str(modproperties["safeseh"]),7) aslr = toSize(str(modproperties["aslr"]),5) cfg = toSize(str(modproperties["cfg"]),5) nx = toSize(str(modproperties["nx"]),7) isos = toSize(str(modproperties["os"]),7) version = str(modproperties["version"]) path = str(modproperties["path"]) name = str(modproperties["name"]) dllflag = "0x%x" % modproperties["dllcharacteristics"] thistable += " " + base + " | " + top + " | " + size + " | " + rebase +"| " +safeseh + " | " + aslr + " | "+ cfg + " | " + nx + " | " + isos + "| " + version + " [" + name + "] (" + path + ") " + dllflag + "\n" thistable += "-----------------------------------------------------------------------------------------------------------------------------------------\n" tableinfo = thistable.split('\n') if logfile == "": for tline in tableinfo: dbg.log(tline) else: with open(logfile,"a") as fh: fh.writelines(thistable) #-----------------------------------------------------------------------# # This is where the action is #-----------------------------------------------------------------------# def processResults(all_opcodes,logfile,thislog,specialcases = {},ptronly = False): """ Write the output of a search operation to log file Arguments: all_opcodes - dictionary containing the results of a search logfile - the MnLog object thislog - the filename to write to Return: written content in log file first 20 pointers are shown in the log window """ ptrcnt = 0 cnt = 0 global silent if all_opcodes: dbg.log("[+] Writing results to %s" % thislog) for hf in all_opcodes: if not silent: try: dbg.log(" - Number of pointers of type '%s' : %d " % (hf,len(all_opcodes[hf]))) except: dbg.log(" - Number of pointers of type '' : %d " % (len(all_opcodes[hf]))) if not ptronly: if not silent: dbg.log("[+] Results : ") messageshown = False for optext,pointers in all_opcodes.iteritems(): for ptr in pointers: ptrinfo = "" modinfo = "" ptrx = MnPointer(ptr) modname = ptrx.belongsTo() if not modname == "": modobj = MnModule(modname) ptrextra = "" rva=0 if (modobj.isRebase or modobj.isAslr): rva = ptr - modobj.moduleBase ptrextra = " (b+0x" + toHex(rva)+") " ptrinfo = "0x" + toHex(ptr) + ptrextra + " : " + optext + " | " + ptrx.__str__() + " " + modobj.__str__() else: ptrinfo = "0x" + toHex(ptr) + " : " + optext + " | " + ptrx.__str__() if ptrx.isOnStack(): ptrinfo += " [Stack] " elif ptrx.isInHeap(): ptrinfo += " [Heap] " logfile.write(ptrinfo,thislog) if (ptr_to_get > -1) or (cnt < 20): if not silent: dbg.log(" %s" % ptrinfo,address=ptr) cnt += 1 ptrcnt += 1 if (ptr_to_get == -1 or ptr_to_get > 20) and cnt == 20 and not silent and not messageshown: dbg.log("... Please wait while I'm processing all remaining results and writing everything to file...") messageshown = True if cnt < ptrcnt: if not silent: dbg.log("[+] Done. Only the first %d pointers are shown here. For more pointers, open %s..." % (cnt,thislog)) else: allptr = [] ptrcnt = 0 ptrinfo = "" dbg.log("... Please wait while I'm processing results and writing everything to file...") for optext,pointers in all_opcodes.iteritems(): for ptr in pointers: if not ptr in allptr: ptrinfo += "0x%s\n" % toHex(ptr) ptrcnt += 1 if not silent: dbg.log("[+] Writing results to file") logfile.write(ptrinfo,thislog) if not silent: dbg.log("[+] Done") dbg.log(" Found a total of %d pointers" % ptrcnt, highlight=1) dbg.setStatusBar("Done. Found %d pointers" % ptrcnt) def mergeOpcodes(all_opcodes,found_opcodes): """ merges two dictionaries together Arguments: all_opcodes - the target dictionary found_opcodes - the source dictionary Return: Dictionary (merged dictionaries) """ if found_opcodes: for hf in found_opcodes: if hf in all_opcodes: if isinstance(all_opcodes[hf], dict): all_opcodes[hf].update(found_opcodes[hf]) else: all_opcodes[hf] += found_opcodes[hf] else: all_opcodes[hf] = found_opcodes[hf] return all_opcodes def findSEH(modulecriteria={},criteria={}): """ Performs a search for pointers to gain code execution in a SEH overwrite exploit Arguments: modulecriteria - dictionary with criteria modules need to comply with. Default settings are : ignore aslr, rebase and safeseh protected modules criteria - dictionary with criteria the pointers need to comply with. Return: Dictionary (pointers) """ type = "" if "rop" in criteria: type = "rop" search = getSearchSequences("seh",0,type) found_opcodes = {} all_opcodes = {} modulestosearch = getModulesToQuery(modulecriteria) if not silent: dbg.log("[+] Querying %d modules" % len(modulestosearch)) starttime = datetime.datetime.now() for thismodule in modulestosearch: if not silent: dbg.log(" - Querying module %s" % thismodule) dbg.updateLog() #search found_opcodes = searchInModule(search,thismodule,criteria) #merge results all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) #search outside modules if "all" in criteria: if "accesslevel" in criteria: if criteria["accesslevel"].find("R") == -1: if not silent: dbg.log("[+] Setting pointer access level criteria to 'R', to increase search results") criteria["accesslevel"] = "R" if not silent: dbg.log(" New pointer access level : %s" % criteria["accesslevel"]) if criteria["all"]: rangestosearch = getRangesOutsideModules() if not silent: dbg.log("[+] Querying memory outside modules") for thisrange in rangestosearch: if not silent: dbg.log(" - Querying 0x%08x - 0x%08x" % (thisrange[0],thisrange[1])) found_opcodes = searchInRange(search, thisrange[0], thisrange[1],criteria) all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) if not silent: dbg.log(" - Search complete, processing results") dbg.updateLog() return all_opcodes def findJMP(modulecriteria={},criteria={},register="esp"): """ Performs a search for pointers to jump to a given register Arguments: modulecriteria - dictionary with criteria modules need to comply with. Default settings are : ignore aslr and rebased modules criteria - dictionary with criteria the pointers need to comply with. register - the register to jump to Return: Dictionary (pointers) """ search = getSearchSequences("jmp",register,"",criteria) found_opcodes = {} all_opcodes = {} modulestosearch = getModulesToQuery(modulecriteria) if not silent: dbg.log("[+] Querying %d modules" % len(modulestosearch)) starttime = datetime.datetime.now() for thismodule in modulestosearch: if not silent: dbg.log(" - Querying module %s" % thismodule) dbg.updateLog() #search found_opcodes = searchInModule(search,thismodule,criteria) #merge results all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) if not silent: dbg.log(" - Search complete, processing results") dbg.updateLog() return all_opcodes def findROPFUNC(modulecriteria={},criteria={},searchfuncs=[]): """ Performs a search for pointers to pointers to interesting functions to facilitate a ROP exploit Arguments: modulecriteria - dictionary with criteria modules need to comply with. Default settings are : ignore aslr and rebased modules criteria - dictionary with criteria the pointers need to comply with. optional : searchfuncs - array with functions to include in the search Return: Dictionary (pointers) """ found_opcodes = {} all_opcodes = {} ptr_counter = 0 ropfuncs = {} funccallresults = [] ropfuncoffsets = {} functionnames = [] offsets = {} modulestosearch = getModulesToQuery(modulecriteria) if searchfuncs == []: functionnames = ["virtualprotect","virtualalloc","heapalloc","winexec","setprocessdeppolicy","heapcreate","setinformationprocess","writeprocessmemory","memcpy","memmove","strncpy","createmutex","getlasterror","strcpy","loadlibrary","freelibrary","getmodulehandle","getprocaddress","openfile","createfile","createfilemapping","mapviewoffile","openfilemapping"] offsets["kernel32.dll"] = ["virtualprotect","virtualalloc","writeprocessmemory"] # on newer OSes, functions are stored in kernelbase.dll offsets["kernelbase.dll"] = ["virtualprotect","virtualalloc","writeprocessmemory"] else: functionnames = searchfuncs offsets["kernel32.dll"] = searchfuncs # on newer OSes, functions are stored in kernelbase.dll offsets["kernelbase.dll"] = searchfuncs if not silent: dbg.log("[+] Looking for pointers to interesting functions...") curmod = "" #ropfuncfilename="ropfunc.txt" #objropfuncfile = MnLog(ropfuncfilename) #ropfuncfile = objropfuncfile.reset() offsetpointers = {} # populate absolute pointers for themod in offsets: fnames = offsets[themod] try: themodule = MnModule(themod) if not themodule is None: allfuncs = themodule.getEAT() for fn in allfuncs: for fname in fnames: if allfuncs[fn].lower().find(fname.lower()) > -1: #dbg.log("Found match: %s %s -> %s ?" % (themod, allfuncs[fn].lower(), fname.lower())) fname = allfuncs[fn].lower() if not fname in offsetpointers: offsetpointers[fname] = fn break except: continue # found pointers to functions # now query IATs #dbg.log("%s" % modulecriteria) isrebased = False for key in modulestosearch: curmod = dbg.getModule(key) #dbg.log("Searching in IAT of %s" % key) #is this module going to get rebase ? themodule = MnModule(key) isrebased = themodule.isRebase if not silent: dbg.log(" - Querying %s" % (key)) allfuncs = themodule.getIAT() dbg.updateLog() for fn in allfuncs: thisfuncname = allfuncs[fn].lower() thisfuncfullname = thisfuncname if not meetsCriteria(MnPointer(fn), criteria): continue ptr = 0 try: ptr=struct.unpack(' -1: extra = "" extrafunc = "" if isrebased: extra = " [Warning : module is likely to get rebased !]" extrafunc = "-rebased" if not silent: dbg.log(" 0x%s : ptr to %s (0x%s) (%s) %s" % (toHex(fn),thisfuncname,toHex(ptr),key,extra)) logtxt = thisfuncfullname.lower().strip()+extrafunc+" | 0x" + toHex(ptr) if logtxt in ropfuncs: ropfuncs[logtxt] += [fn] else: ropfuncs[logtxt] = [fn] ptr_counter += 1 if ptr_to_get > 0 and ptr_counter >= ptr_to_get: ropfuncs,ropfuncoffsets return ropfuncs,ropfuncoffsets def assemble(instructions,encoder=""): """ Assembles one or more instructions to opcodes Arguments: instructions = the instructions to assemble (separated by #) Return: Dictionary (pointers) """ if not silent: dbg.log("Opcode results : ") dbg.log("---------------- ") allopcodes="" instructions = instructions.replace('"',"").replace("'","") splitter=re.compile('#') instructions=splitter.split(instructions) for instruct in instructions: try: instruct = instruct.strip() assembled=dbg.assemble(instruct) strAssembled="" for assemOpc in assembled: if (len(hex(ord(assemOpc)))) == 3: subAssembled = "\\x0"+hex(ord(assemOpc)).replace('0x','') strAssembled = strAssembled+subAssembled else: strAssembled = strAssembled+hex(ord(assemOpc)).replace('0x', '\\x') if len(strAssembled) < 30: if not silent: dbg.log(" %s = %s" % (instruct,strAssembled)) allopcodes=allopcodes+strAssembled else: if not silent: dbg.log(" %s => Unable to assemble this instruction !" % instruct,highlight=1) except: if not silent: dbg.log(" Could not assemble %s " % instruct) pass if not silent: dbg.log(" Full opcode : %s " % allopcodes) return allopcodes def findROPGADGETS(modulecriteria={},criteria={},endings=[],maxoffset=40,depth=5,split=False,pivotdistance=0,fast=False,mode="all", sortedprint=False, technique=""): """ Searches for rop gadgets Arguments: modulecriteria - dictionary with criteria modules need to comply with. Default settings are : ignore aslr and rebased modules criteria - dictionary with criteria the pointers need to comply with. endings - array with all rop gadget endings to look for. Default : RETN and RETN+offsets maxoffset - maximum offset value for RETN if endings are set to RETN depth - maximum number of instructions to go back split - Boolean that indicates whether routine should write all gadgets to one file, or split per module pivotdistance - minimum distance a stackpivot needs to be fast - Boolean indicating if you want to process less obvious gadgets as well mode - internal use only sortedprint - sort pointers before printing output to rop.txt technique - create all chains if empty. otherwise, create virtualalloc or virtualprotect chain (based on what is specified) Return: Output is written to files, containing rop gadgets, suggestions, stack pivots and virtualprotect/virtualalloc routine (if possible) """ found_opcodes = {} all_opcodes = {} ptr_counter = 0 valid_techniques = ["virtualalloc", "virtualprotect"] modulestosearch = getModulesToQuery(modulecriteria) progressid=str(dbg.getDebuggedPid()) progressfilename="_rop_progress_"+dbg.getDebuggedName()+"_"+progressid+".log" objprogressfile = MnLog(progressfilename) progressfile = objprogressfile.reset() dbg.log("[+] Progress will be written to %s" % progressfilename) dbg.log("[+] Maximum offset : %d" % maxoffset) dbg.log("[+] (Minimum/optional maximum) stackpivot distance : %s" % str(pivotdistance)) dbg.log("[+] Max nr of instructions : %d" % depth) dbg.log("[+] Split output into module rop files ? %s" % split) #dbg.log("[+] Technique: %s" % technique) if technique != "" and technique in valid_techniques: dbg.log("[+] Only creating rop chain for '%s'" % technique) else: dbg.log("[+] Going to create rop chains for all relevant/supported techniques: %s" % technique) usefiles = False filestouse = [] vplogtxt = "" suggestions = {} if "f" in criteria: if criteria["f"] != "": if type(criteria["f"]).__name__.lower() != "bool": usefiles = True rawfilenames = criteria["f"].replace('"',"") allfiles = [getAbsolutePath(f) for f in rawfilenames.split(',')] #check if files exist dbg.log("[+] Attempting to use %d rop file(s) as input" % len(allfiles)) for fname in allfiles: fname = fname.strip() if not os.path.exists(fname): dbg.log(" ** %s : Does not exist !" % fname, highlight=1) else: filestouse.append(fname) if len(filestouse) == 0: dbg.log(" ** Unable to find any of the source files, aborting... **", highlight=1) return search = [] if not usefiles: if len(endings) == 0: #RETN only search.append("RETN") for i in range(0, maxoffset + 1, 2): search.append("RETN 0x"+ toHexByte(i)) else: for ending in endings: dbg.log("[+] Custom ending : %s" % ending) if ending != "": search.append(ending) if len(modulestosearch) == 0: dbg.log("[-] No modules selected, aborting search", highlight = 1) return dbg.log("[+] Enumerating %d endings in %d module(s)..." % (len(search),len(modulestosearch))) for thismodule in modulestosearch: dbg.log(" - Querying module %s" % thismodule) dbg.updateLog() #search found_opcodes = searchInModule(search,thismodule,criteria) #merge results all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) dbg.log(" - Search complete :") else: dbg.log("[+] Reading input files") for filename in filestouse: dbg.log(" - Reading %s" % filename) all_opcodes = mergeOpcodes(all_opcodes,readGadgetsFromFile(filename)) dbg.updateLog() tp = 0 for endingtype in all_opcodes: if len(all_opcodes[endingtype]) > 0: if usefiles: dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]) / 2)) tp = tp + len(all_opcodes[endingtype]) / 2 else: dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]))) tp = tp + len(all_opcodes[endingtype]) global silent if not usefiles: dbg.log(" - Filtering and mutating %d gadgets" % tp) else: dbg.log(" - Categorizing %d gadgets" % tp) silent = True dbg.updateLog() ropgadgets = {} interestinggadgets = {} stackpivots = {} stackpivots_safeseh = {} adcnt = 0 tc = 1 issafeseh = False step = 0 updateth = 1000 if (tp >= 2000 and tp < 5000): updateth = 500 if (tp < 2000): updateth = 100 for endingtype in all_opcodes: if len(all_opcodes[endingtype]) > 0: for endingtypeptr in all_opcodes[endingtype]: adcnt=adcnt+1 if usefiles: adcnt = adcnt - 0.5 if adcnt > (tc*updateth): thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") updatetext = " - Progress update : " + str(tc*updateth) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (" + str((tc*updateth*100)/tp)+"%)" objprogressfile.write(updatetext.strip(),progressfile) dbg.log(updatetext) dbg.updateLog() tc += 1 if not usefiles: #first get max backward instruction #immlib libanalyze might blow up at (self.ip=opcode[0] # Instruction pointer), so we have to catch exceptions here try: thisopcode = dbg.disasmBackward(endingtypeptr,depth+1) thisptr = thisopcode.getAddress() except: dbg.log(" ** Unable to backward disassemble at 0x%0x, depth %d, skipping location\n" % (endingtypeptr, depth+1)) thisopcode = "" thisptr = 0 # we now have a range to mine startptr = thisptr currentmodulename = MnPointer(thisptr).belongsTo() modinfo = MnModule(currentmodulename) issafeseh = modinfo.isSafeSEH while startptr <= endingtypeptr and startptr != 0x0: # get the entire chain from startptr to endingtypeptr thischain = "" msfchain = [] thisopcodebytes = "" chainptr = startptr if isGoodGadgetPtr(startptr,criteria) and not startptr in ropgadgets and not startptr in interestinggadgets: invalidinstr = False while chainptr < endingtypeptr and not invalidinstr: thisopcode = dbg.disasm(chainptr) thisinstruction = getDisasmInstruction(thisopcode) if isGoodGadgetInstr(thisinstruction) and not isGadgetEnding(thisinstruction,search): thischain = thischain + " # " + thisinstruction msfchain.append([chainptr,thisinstruction]) thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) chainptr = dbg.disasmForwardAddressOnly(chainptr,1) else: invalidinstr = True if endingtypeptr == chainptr and startptr != chainptr and not invalidinstr: fullchain = thischain + " # " + endingtype msfchain.append([endingtypeptr,endingtype]) thisopcode = dbg.disasm(endingtypeptr) thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) msfchain.append(["raw",thisopcodebytes]) if isInterestingGadget(fullchain): interestinggadgets[startptr] = fullchain #this may be a good stackpivot too stackpivotdistance = getStackPivotDistance(fullchain,pivotdistance) if stackpivotdistance > 0: #safeseh or not ? if issafeseh: if not stackpivotdistance in stackpivots_safeseh: stackpivots_safeseh.setdefault(stackpivotdistance,[[startptr,fullchain]]) else: stackpivots_safeseh[stackpivotdistance] += [[startptr,fullchain]] else: if not stackpivotdistance in stackpivots: stackpivots.setdefault(stackpivotdistance,[[startptr,fullchain]]) else: stackpivots[stackpivotdistance] += [[startptr,fullchain]] else: if not fast: ropgadgets[startptr] = fullchain startptr = startptr+1 else: if step == 0: startptr = endingtypeptr if step == 1: thischain = endingtypeptr chainptr = startptr ptrx = MnPointer(chainptr) modname = ptrx.belongsTo() issafeseh = False if modname != "": thism = MnModule(modname) issafeseh = thism.isSafeSEH if isGoodGadgetPtr(startptr,criteria) and not startptr in ropgadgets and not startptr in interestinggadgets: fullchain = thischain if isInterestingGadget(fullchain): interestinggadgets[startptr] = fullchain #this may be a good stackpivot too stackpivotdistance = getStackPivotDistance(fullchain,pivotdistance) if stackpivotdistance > 0: #safeseh or not ? if issafeseh: if not stackpivotdistance in stackpivots_safeseh: stackpivots_safeseh.setdefault(stackpivotdistance,[[startptr,fullchain]]) else: stackpivots_safeseh[stackpivotdistance] += [[startptr,fullchain]] else: if not stackpivotdistance in stackpivots: stackpivots.setdefault(stackpivotdistance,[[startptr,fullchain]]) else: stackpivots[stackpivotdistance] += [[startptr,fullchain]] else: if not fast: ropgadgets[startptr] = fullchain step = -1 step += 1 thistimestamp = datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") updatetext = " - Progress update : " + str(tp) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (100%)" objprogressfile.write(updatetext.strip(),progressfile) dbg.log(updatetext) dbg.updateLog() if mode == "all": if len(ropgadgets) > 0 and len(interestinggadgets) > 0: # another round of filtering updatetext = "[+] Creating suggestions list" dbg.log(updatetext) objprogressfile.write(updatetext.strip(),progressfile) suggestions = getRopSuggestion(interestinggadgets,ropgadgets) #see if we can propose something updatetext = "[+] Processing suggestions" dbg.log(updatetext) objprogressfile.write(updatetext.strip(),progressfile) suggtowrite="" for suggestedtype in suggestions: limitnr = 0x7fffffff if suggestedtype.startswith("pop "): # only write up to 10 pop r32 into suggestions file limitnr = 10 gcnt = 0 suggtowrite += "[%s]\n" % suggestedtype for suggestedpointer in suggestions[suggestedtype]: if gcnt < limitnr: sptr = MnPointer(suggestedpointer) modname = sptr.belongsTo() modinfo = MnModule(modname) if not modinfo.moduleBase.__class__.__name__ == "instancemethod": rva = suggestedpointer - modinfo.moduleBase suggesteddata = suggestions[suggestedtype][suggestedpointer] if not modinfo.moduleBase.__class__.__name__ == "instancemethod": ptrinfo = "0x" + toHex(suggestedpointer) + " (RVA : 0x" + toHex(rva) + ") : " + suggesteddata + " ** [" + modname + "] ** | " + sptr.__str__()+"\n" else: ptrinfo = "0x" + toHex(suggestedpointer) + " : " + suggesteddata + " ** [" + modname + "] ** | " + sptr.__str__()+"\n" suggtowrite += ptrinfo else: break gcnt += 1 if arch == 32: dbg.log("[+] Launching ROP generator") updatetext = "Attempting to create rop chain proposals" objprogressfile.write(updatetext.strip(),progressfile) vplogtxt = createRopChains(suggestions,interestinggadgets,ropgadgets,modulecriteria,criteria,objprogressfile,progressfile,technique) dbg.logLines(vplogtxt.replace("\t"," ")) dbg.log(" ROP generator finished") else: updatetext = "[+] Oops, no gadgets found, aborting.." dbg.log(updatetext) objprogressfile.write(updatetext.strip(),progressfile) #done, write to log files dbg.setStatusBar("Writing to logfiles...") dbg.log("") logfile = MnLog("stackpivot.txt") thislog = logfile.reset() objprogressfile.write("Writing " + str(len(stackpivots)+len(stackpivots_safeseh))+" stackpivots with minimum offset " + str(pivotdistance)+" to file " + thislog,progressfile) dbg.log("[+] Writing stackpivots to file " + thislog) logfile.write("Stack pivots, minimum distance " + str(pivotdistance) + ", in descending order",thislog) logfile.write("------------------------------------------------------------------------------",thislog) logfile.write("", thislog) logfile.write("", thislog) logfile.write("Non-SafeSEH protected pivots :",thislog) logfile.write("------------------------------",thislog) logfile.write("", thislog) arrtowrite = "" pivotcount = 0 try: with open(thislog,"a") as fh: arrtowrite = "" stackpivots_index = sorted(stackpivots, reverse=True) # returns sorted keys as an array, in descending order for sdist in stackpivots_index: for spivot, schain in stackpivots[sdist]: ptrx = MnPointer(spivot) modname = ptrx.belongsTo() sdisthex = "%02x" % sdist ptrinfo = "0x" + toHex(spivot) + " : {pivot " + str(sdist) + " / 0x" + sdisthex + "} : " + schain + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" pivotcount += 1 arrtowrite += ptrinfo fh.writelines(arrtowrite) except: pass logfile.write("", thislog) logfile.write("", thislog) logfile.write("", thislog) logfile.write("**********************************************************************************************************", thislog) logfile.write("", thislog) logfile.write("", thislog) logfile.write("", thislog) logfile.write("SafeSEH protected pivots :",thislog) logfile.write("--------------------------",thislog) logfile.write("", thislog) arrtowrite = "" try: with open(thislog, "a") as fh: arrtowrite = "" stackpivots_safeseh_index = sorted(stackpivots_safeseh, reverse=True) for sdist in stackpivots_safeseh_index: for spivot, schain in stackpivots_safeseh[sdist]: ptrx = MnPointer(spivot) modname = ptrx.belongsTo() #modinfo = MnModule(modname) sdisthex = "%02x" % sdist ptrinfo = "0x" + toHex(spivot) + " : {pivot " + str(sdist) + " / 0x" + sdisthex + "} : " + schain + " ** [" + modname + "] SafeSEH ** | " + ptrx.__str__()+"\n" pivotcount += 1 arrtowrite += ptrinfo fh.writelines(arrtowrite) except: pass dbg.log(" Wrote %d pivots to file " % pivotcount) arrtowrite = "" if mode == "all": if len(suggestions) > 0: logfile = MnLog("rop_suggestions.txt") thislog = logfile.reset() objprogressfile.write("Writing all suggestions to file "+thislog,progressfile) dbg.log("[+] Writing suggestions to file " + thislog ) logfile.write("Suggestions",thislog) logfile.write("-----------",thislog) with open(thislog, "a") as fh: fh.writelines(suggtowrite) fh.write("\n") nrsugg = len(suggtowrite.split("\n")) dbg.log(" Wrote %d suggestions to file" % nrsugg) if not split: logfile = MnLog("rop.txt") thislog = logfile.reset() objprogressfile.write("Gathering interesting gadgets",progressfile) dbg.log("[+] Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)") logfile.write("Interesting gadgets",thislog) logfile.write("-------------------",thislog) dbg.updateLog() try: with open(thislog, "a") as fh: arrtowrite = "" if sortedprint: arrptrs = [] dbg.log(" Sorting interesting gadgets first") for gadget in interestinggadgets: arrptrs.append(gadget) arrptrs.sort() dbg.log(" Done sorting, let's go") for gadget in arrptrs: ptrx = MnPointer(gadget) modname = ptrx.belongsTo() #modinfo = MnModule(modname) ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" arrtowrite += ptrinfo else: for gadget in interestinggadgets: ptrx = MnPointer(gadget) modname = ptrx.belongsTo() #modinfo = MnModule(modname) ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" arrtowrite += ptrinfo objprogressfile.write("Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)",progressfile) fh.writelines(arrtowrite) dbg.log(" Wrote %d interesting gadgets to file" % len(interestinggadgets)) except: pass arrtowrite="" if not fast: objprogressfile.write("Enumerating other gadgets (" + str(len(ropgadgets))+")",progressfile) dbg.log("[+] Writing other gadgets to file " + thislog + " (" + str(len(ropgadgets))+" gadgets)") try: logfile.write("",thislog) logfile.write("Other gadgets",thislog) logfile.write("-------------",thislog) with open(thislog, "a") as fh: arrtowrite="" if sortedprint: arrptrs = [] dbg.log(" Sorting other gadgets too") for gadget in ropgadgets: arrptrs.append(gadget) arrptrs.sort() dbg.log(" Done sorting, let's go") for gadget in arrptrs: ptrx = MnPointer(gadget) modname = ptrx.belongsTo() #modinfo = MnModule(modname) ptrinfo = "0x" + toHex(gadget) + " : " + ropgadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" arrtowrite += ptrinfo else: for gadget in ropgadgets: ptrx = MnPointer(gadget) modname = ptrx.belongsTo() #modinfo = MnModule(modname) ptrinfo = "0x" + toHex(gadget) + " : " + ropgadgets[gadget] + " ** [" + modname + "] ** | " + ptrx.__str__()+"\n" arrtowrite += ptrinfo dbg.log(" Wrote %d other gadgets to file" % len(ropgadgets)) objprogressfile.write("Writing results to file " + thislog + " (" + str(len(ropgadgets))+" other gadgets)",progressfile) fh.writelines(arrtowrite) except: pass else: dbg.log("[+] Writing results to individual files (grouped by module)") dbg.updateLog() for thismodule in modulestosearch: thismodname = thismodule.replace(" ","_") thismodversion = getModuleProperty(thismodule,"version") logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") thislog = logfile.reset() logfile.write("Interesting gadgets",thislog) logfile.write("-------------------",thislog) for gadget in interestinggadgets: ptrx = MnPointer(gadget) modname = ptrx.belongsTo() modinfo = MnModule(modname) thismodversion = getModuleProperty(modname,"version") thismodname = modname.replace(" ","_") logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") thislog = logfile.reset(False) ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** " + modinfo.__str__() + " ** | " + ptrx.__str__()+"\n" with open(thislog, "a") as fh: fh.write(ptrinfo) if not fast: for thismodule in modulestosearch: thismodname = thismodule.replace(" ","_") thismodversion = getModuleProperty(thismodule,"version") logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") logfile.write("Other gadgets",thislog) logfile.write("-------------",thislog) for gadget in ropgadgets: ptrx = MnPointer(gadget) modname = ptrx.belongsTo() modinfo = MnModule(modname) thismodversion = getModuleProperty(modname,"version") thismodname = modname.replace(" ","_") logfile = MnLog("rop_"+thismodname+"_"+thismodversion+".txt") thislog = logfile.reset(False) ptrinfo = "0x" + toHex(gadget) + " : " + ropgadgets[gadget] + " ** " + modinfo.__str__() + " ** | " + ptrx.__str__()+"\n" with open(thislog, "a") as fh: fh.write(ptrinfo) thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") objprogressfile.write("Done (" + thistimestamp+")",progressfile) dbg.log("Done") return interestinggadgets,ropgadgets,suggestions,vplogtxt #----- JOP gadget finder ----- # def findJOPGADGETS(modulecriteria={},criteria={},depth=6): """ Searches for jop gadgets Arguments: modulecriteria - dictionary with criteria modules need to comply with. Default settings are : ignore aslr and rebased modules criteria - dictionary with criteria the pointers need to comply with. depth - maximum number of instructions to go back Return: Output is written to files, containing jop gadgets and suggestions """ found_opcodes = {} all_opcodes = {} ptr_counter = 0 modulestosearch = getModulesToQuery(modulecriteria) progressid=toHex(dbg.getDebuggedPid()) progressfilename="_jop_progress_"+dbg.getDebuggedName()+"_"+progressid+".log" objprogressfile = MnLog(progressfilename) progressfile = objprogressfile.reset() dbg.log("[+] Progress will be written to %s" % progressfilename) dbg.log("[+] Max nr of instructions : %d" % depth) filesok = 0 usefiles = False filestouse = [] vplogtxt = "" suggestions = {} fast = False search = [] jopregs = ["EAX","EBX","ECX","EDX","ESI","EDI","EBP"] offsetval = 0 for jreg in jopregs: search.append("JMP " + jreg) search.append("JMP [" + jreg + "]") for offsetval in range(0, 40+1, 2): search.append("JMP [" + jreg + "+0x" + toHexByte(offsetval)+"]") search.append("JMP [ESP]") for offsetval in range(0, 40+1, 2): search.append("JMP [ESP+0x" + toHexByte(offsetval) + "]") dbg.log("[+] Enumerating %d endings in %d module(s)..." % (len(search),len(modulestosearch))) for thismodule in modulestosearch: dbg.log(" - Querying module %s" % thismodule) dbg.updateLog() #search found_opcodes = searchInModule(search,thismodule,criteria) #merge results all_opcodes = mergeOpcodes(all_opcodes,found_opcodes) dbg.log(" - Search complete :") dbg.updateLog() tp = 0 for endingtype in all_opcodes: if len(all_opcodes[endingtype]) > 0: if usefiles: dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]) / 2)) tp = tp + len(all_opcodes[endingtype]) / 2 else: dbg.log(" Ending : %s, Nr found : %d" % (endingtype,len(all_opcodes[endingtype]))) tp = tp + len(all_opcodes[endingtype]) global silent dbg.log(" - Filtering and mutating %d gadgets" % tp) dbg.updateLog() jopgadgets = {} interestinggadgets = {} adcnt = 0 tc = 1 issafeseh = False step = 0 for endingtype in all_opcodes: if len(all_opcodes[endingtype]) > 0: for endingtypeptr in all_opcodes[endingtype]: adcnt += 1 if usefiles: adcnt = adcnt - 0.5 if adcnt > (tc*1000): thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") updatetext = " - Progress update : " + str(tc*1000) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (" + str((tc*1000*100)/tp)+"%)" objprogressfile.write(updatetext.strip(),progressfile) dbg.log(updatetext) dbg.updateLog() tc += 1 #first get max backward instruction thisopcode = dbg.disasmBackward(endingtypeptr,depth+1) thisptr = thisopcode.getAddress() # we now have a range to mine startptr = thisptr while startptr <= endingtypeptr and startptr != 0x0: # get the entire chain from startptr to endingtypeptr thischain = "" msfchain = [] thisopcodebytes = "" chainptr = startptr if isGoodGadgetPtr(startptr,criteria) and not startptr in jopgadgets and not startptr in interestinggadgets: # new pointer invalidinstr = False while chainptr < endingtypeptr and not invalidinstr: thisopcode = dbg.disasm(chainptr) thisinstruction = getDisasmInstruction(thisopcode) if isGoodJopGadgetInstr(thisinstruction) and not isGadgetEnding(thisinstruction,search): thischain = thischain + " # " + thisinstruction msfchain.append([chainptr,thisinstruction]) thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) chainptr = dbg.disasmForwardAddressOnly(chainptr,1) else: invalidinstr = True if endingtypeptr == chainptr and startptr != chainptr and not invalidinstr: fullchain = thischain + " # " + endingtype msfchain.append([endingtypeptr,endingtype]) thisopcode = dbg.disasm(endingtypeptr) thisopcodebytes = thisopcodebytes + opcodesToHex(thisopcode.getDump().lower()) msfchain.append(["raw",thisopcodebytes]) if isInterestingJopGadget(fullchain): interestinggadgets[startptr] = fullchain else: if not fast: jopgadgets[startptr] = fullchain startptr = startptr+1 thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") updatetext = " - Progress update : " + str(tp) + " / " + str(tp) + " items processed (" + thistimestamp + ") - (100%)" objprogressfile.write(updatetext.strip(),progressfile) dbg.log(updatetext) dbg.updateLog() logfile = MnLog("jop.txt") thislog = logfile.reset() objprogressfile.write("Enumerating gadgets",progressfile) dbg.log("[+] Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)") logfile.write("Interesting gadgets",thislog) logfile.write("-------------------",thislog) dbg.updateLog() arrtowrite = "" try: with open(thislog, "a") as fh: arrtowrite = "" for gadget in interestinggadgets: ptrx = MnPointer(gadget) modname = ptrx.belongsTo() modinfo = MnModule(modname) ptrinfo = "0x" + toHex(gadget) + " : " + interestinggadgets[gadget] + " ** " + modinfo.__str__() + " ** | " + ptrx.__str__()+"\n" arrtowrite += ptrinfo objprogressfile.write("Writing results to file " + thislog + " (" + str(len(interestinggadgets))+" interesting gadgets)",progressfile) fh.writelines(arrtowrite) except: pass return interestinggadgets,jopgadgets,suggestions,vplogtxt #----- File compare ----- # def findFILECOMPARISON(modulecriteria={},criteria={},allfiles=[],tomatch="",checkstrict=True,rangeval=0,fast=False): """ Compares two or more files generated with mona.py and lists the entries that have been found in all files Arguments: modulecriteria = not used criteria = not used allfiles = array with filenames to compare tomatch = variable containing a string each line should contain checkstrict = Boolean, when set to True, both the pointer and the instructions should be exactly the same Return: File containing all matching pointers """ dbg.setStatusBar("Comparing files...") dbg.updateLog() filenotfound = False for fcnt in xrange(len(allfiles)): fname = allfiles[fcnt] fname = fname.strip() if os.path.exists(fname): dbg.log(" - %d. %s" % (fcnt, allfiles[fcnt])) else: dbg.log(" ** %s : Does not exist !" % allfiles[fcnt], highlight=1) filenotfound = True if filenotfound: return objcomparefile = MnLog("filecompare.txt") comparefile = objcomparefile.reset() objcomparefilenot = MnLog("filecompare_not.txt") comparefilenot = objcomparefilenot.reset() objcomparefilenot.write("Source files:",comparefilenot) for fcnt in xrange(len(allfiles)): objcomparefile.write(" - " + str(fcnt)+". "+allfiles[fcnt],comparefile) objcomparefilenot.write(" - " + str(fcnt)+". "+allfiles[fcnt],comparefilenot) objcomparefile.write("",comparefile) objcomparefile.write("Pointers found :",comparefile) objcomparefile.write("----------------",comparefile) objcomparefilenot.write("",comparefilenot) objcomparefilenot.write("Pointers not found :",comparefilenot) objcomparefilenot.write("-------------------",comparefilenot) # transform the files into dictionaries dbg.log("[+] Reading input files ...") all_input_files = {} all_pointers = {} fcnt = 0 for thisfile in allfiles: filedata = {} content = [] with open(thisfile,"rb") as inputfile: content = inputfile.readlines() pointerlist = [] for thisLine in content: refpointer,instr = splitToPtrInstr(thisLine) instr = instr.replace('\n','').replace('\r','').strip(":") if refpointer != -1 and not refpointer in filedata: filedata[refpointer] = instr pointerlist.append(refpointer) all_input_files[fcnt] = filedata all_pointers[fcnt] = pointerlist fcnt += 1 # select smallest one dbg.log("[+] Finding shortest array, to use as the reference") shortestarray = 0 shortestlen = 0 for inputfile in all_input_files: if (len(all_input_files[inputfile]) < shortestlen) or (shortestlen == 0): shortestlen = len(all_input_files[inputfile]) shortestarray = inputfile dbg.log(" Reference file: %s (%d pointers)" % (allfiles[shortestarray],shortestlen)) fileorder = [] fileorder.append(shortestarray) cnt = 0 while cnt <= len(all_input_files): if not cnt in fileorder: fileorder.append(cnt) cnt += 1 remaining = [] fulllist = [] if rangeval == 0: dbg.log("[+] Starting compare, please wait...") dbg.updateLog() fcnt = 1 remaining = all_pointers[shortestarray] fulllist = all_pointers[shortestarray] while fcnt < len(fileorder)-1 and len(remaining) > 0: dbg.log(" Comparing %d reference pointers with %s" % (len(remaining),allfiles[fileorder[fcnt]])) remaining = list(set(remaining).intersection(set(all_pointers[fileorder[fcnt]]))) fulllist = list(set(fulllist).union(set(all_pointers[fileorder[fcnt]]))) fcnt += 1 else: dbg.log("[+] Exploding reference list with values within range") dbg.updateLog() # create first reference list with ALL pointers within the range allrefptr = [] reflist = all_pointers[shortestarray] for refptr in reflist: start_range = refptr - rangeval if start_range < 0: start_range = 0 end_range = refptr + rangeval if start_range > end_range: tmp = start_range start_range = end_range end_range = tmp while start_range <= end_range: if not start_range in allrefptr: allrefptr.append(start_range) start_range += 1 # do normal intersection dbg.log("[+] Starting compare, please wait...") dbg.updateLog() s_remaining = allrefptr s_fulllist = allrefptr fcnt = 1 while fcnt < len(fileorder)-1 and len(s_remaining) > 0: s_remaining = list(set(s_remaining).intersection(set(all_pointers[fileorder[fcnt]]))) s_fulllist = list(set(s_fulllist).union(set(all_pointers[fileorder[fcnt]]))) fcnt += 1 for s in s_remaining: if not s in remaining: remaining.append(s) for s in s_fulllist: if not s in fulllist: fulllist.append(s) nonmatching = list(set(fulllist) - set(remaining)) dbg.log(" Total nr of unique pointers : %d" % len(fulllist)) dbg.log(" Nr of matching pointers before filtering : %d" % len(remaining)) dbg.log(" Nr of non-matching pointers before filtering : %d" % len(nonmatching)) dbg.log("[+] Transforming results into output...") outputlines = "" outputlines_not = "" # start building output remaining.sort() for remptr in remaining: if fast: outputlines += "0x%08x\n" % remptr else: thisinstr = all_input_files[shortestarray][remptr] include = True if checkstrict: # check if all entries are the same fcnt = 1 while (fcnt < len(fileorder)-1) and include: if thisinstr != all_input_files[fileorder[fcnt]][remptr]: include = False fcnt += 1 else: include = True if include and (tomatch == "" or tomatch in thisinstr): outputlines += "0x%08x : %s\n" % (remptr,thisinstr) for nonptr in nonmatching: if fast: outputlines_not += "0x%08x\n" % nonptr else: thisinstr = "" if nonptr in all_input_files[shortestarray]: thisinstr = all_input_files[shortestarray][nonptr] outputlines_not += "File(%d) 0x%08x : %s\n" % (shortestarray,nonptr,thisinstr) for fileindex in all_input_files: if fileindex != shortestarray: these_entries = all_input_files[fileindex] if nonptr in these_entries: thisinstr = these_entries[nonptr] outputlines_not += " File (%d). %s\n" % (fileindex,thisinstr) else: outputlines_not += " File (%d). Entry not found \n" % fileindex dbg.log("[+] Writing output to files") objcomparefile.write(outputlines, comparefile) objcomparefilenot.write(outputlines_not, comparefilenot) nrmatching = len(outputlines.split("\n")) - 1 dbg.log(" Wrote %d matching pointers to file" % nrmatching) dbg.log("[+] Done.") return #------------------# # Heap state # #------------------# def getCurrentHeapState(): heapstate = {} allheaps = [] try: allheaps = dbg.getHeapsAddress() except: allheaps = [] if len(allheaps) > 0: for heap in allheaps: objHeap = MnHeap(heap) thisheapstate = objHeap.getState() heapstate[heap] = thisheapstate return heapstate #------------------# # Cyclic pattern # #------------------# def createPattern(size,args={}): """ Create a cyclic (metasploit) pattern of a given size Arguments: size - value indicating desired length of the pattern if value is > 20280, the pattern will repeat itself until it reaches desired length Return: string containing the cyclic pattern """ char1="ABCDEFGHIJKLMNOPQRSTUVWXYZ" char2="abcdefghijklmnopqrstuvwxyz" char3="0123456789" if "extended" in args: char3 += ",.;+=-_!&()#@({})[]%" # ascii, 'filename' friendly if "c1" in args and args["c1"] != "": char1 = args["c1"] if "c2" in args and args["c2"] != "": char2 = args["c2"] if "c3" in args and args["c3"] != "": char3 = args["c3"] if not silent: if not "extended" in args and size > 20280 and (len(char1) <= 26 or len(char2) <= 26 or len(char3) <= 10): msg = "** You have asked to create a pattern > 20280 bytes, but with the current settings\n" msg += "the pattern generator can't create a pattern of " + str(size) + " bytes. As a result,\n" msg += "the pattern will be repeated for " + str(size-20280)+" bytes until it reaches a length of " + str(size) + " bytes.\n" msg += "If you want a unique pattern larger than 20280 bytes, please either use the -extended option\n" msg += "or extend one of the 3 charsets using options -c1, -c2 and/or -c3 **\n" dbg.logLines(msg,highlight=1) pattern = [] max = int(size) while len(pattern) < max: for ch1 in char1: for ch2 in char2: for ch3 in char3: if len(pattern) < max: pattern.append(ch1) if len(pattern) < max: pattern.append(ch2) if len(pattern) < max: pattern.append(ch3) pattern = "".join(pattern) return pattern def findOffsetInPattern(searchpat,size=20280,args = {}): """ Check if a given searchpattern can be found in a cyclic pattern Arguments: searchpat : the ascii value or hexstr to search for Return: entries in the log window, indicating if the pattern was found and at what position """ mspattern="" searchpats = [] modes = [] modes.append("normal") modes.append("upper") modes.append("lower") extratext = "" patsize=int(size) if patsize == -1: size = 500000 patsize = size global silent oldsilent=silent for mode in modes: silent=oldsilent if mode == "normal": silent=True mspattern=createPattern(size,args) silent=oldsilent extratext = " " elif mode == "upper": silent=True mspattern=createPattern(size,args).upper() silent=oldsilent extratext = " (uppercase) " elif mode == "lower": silent=True mspattern=createPattern(size,args).lower() silent=oldsilent extratext = " (lowercase) " if len(searchpat)==3: #register ? searchpat = searchpat.upper() regs = dbg.getRegs() if searchpat in regs: searchpat = "0x" + toHex(regs[searchpat]) if len(searchpat)==4: ascipat=searchpat if not silent: dbg.log("Looking for %s in pattern of %d bytes" % (ascipat,patsize)) if ascipat in mspattern: patpos = mspattern.find(ascipat) if not silent: dbg.log(" - Pattern %s found in cyclic pattern%sat position %d" % (ascipat,extratext,patpos),highlight=1) else: #reversed ? ascipat_r = ascipat[3]+ascipat[2]+ascipat[1]+ascipat[0] if ascipat_r in mspattern: patpos = mspattern.find(ascipat_r) if not silent: dbg.log(" - Pattern %s (%s reversed) found in cyclic pattern%sat position %d" % (ascipat_r,ascipat,extratext,patpos),highlight=1) else: if not silent: dbg.log(" - Pattern %s not found in cyclic pattern%s" % (ascipat_r,extratext)) if len(searchpat)==8: searchpat="0x"+searchpat if len(searchpat)==10: hexpat=searchpat ascipat3 = toAscii(hexpat[8]+hexpat[9])+toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[2]+hexpat[3]) if not silent: dbg.log("Looking for %s in pattern of %d bytes" % (ascipat3,patsize)) if ascipat3 in mspattern: patpos = mspattern.find(ascipat3) if not silent: dbg.log(" - Pattern %s (%s) found in cyclic pattern%sat position %d" % (ascipat3,hexpat,extratext,patpos),highlight=1) else: #maybe it's reversed ascipat4=toAscii(hexpat[2]+hexpat[3])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[8]+hexpat[9]) if not silent: dbg.log("Looking for %s in pattern of %d bytes" % (ascipat4,patsize)) if ascipat4 in mspattern: patpos = mspattern.find(ascipat4) if not silent: dbg.log(" - Pattern %s (%s reversed) found in cyclic pattern%sat position %d" % (ascipat4,hexpat,extratext,patpos),highlight=1) else: if not silent: dbg.log(" - Pattern %s not found in cyclic pattern%s " % (ascipat4,extratext)) def findPatternWild(modulecriteria,criteria,pattern,base,top,patterntype): """ Performs a search for instructions, accepting wildcards Arguments : modulecriteria - dictionary with criteria modules need to comply with. criteria - dictionary with criteria the pointers need to comply with. pattern - the pattern to search for. base - the base address in memory the search should start at top - the top address in memory the search should not go beyond patterntype - type of search to conduct (str or bin) """ global silent rangestosearch = [] tmpsearch = [] allpointers = {} results = {} mindistance = 4 maxdistance = 40 if "mindistance" in criteria: mindistance = criteria["mindistance"] if "maxdistance" in criteria: maxdistance = criteria["maxdistance"] maxdepth = 8 preventbreak = True if "all" in criteria: preventbreak = False if "depth" in criteria: maxdepth = criteria["depth"] if not silent: dbg.log("[+] Type of search: %s" % patterntype) dbg.log("[+] Searching for matches up to %d instructions deep" % maxdepth) if len(modulecriteria) > 0: modulestosearch = getModulesToQuery(modulecriteria) # convert modules to ranges for modulename in modulestosearch: objmod = MnModule(modulename) mBase = objmod.moduleBase mTop = objmod.moduleTop if mBase < base and base < mTop: mBase = base if mTop > top: mTop = top if mBase >= base and mBase < top: if not [mBase,mTop] in rangestosearch: rangestosearch.append([mBase,mTop]) # if no modules were specified, then also add the other ranges (outside modules) if not "modules" in modulecriteria: outside = getRangesOutsideModules() for range in outside: mBase = range[0] mTop = range[1] if mBase < base and base < mTop: mBase = base if mTop > top: mTop = top if mBase >= base and mBase < top: if not [mBase,mTop] in rangestosearch: rangestosearch.append([mBase,mTop]) else: rangestosearch.append([base,top]) pattern = pattern.replace("'","").replace('"',"").replace(" "," ").replace(", ",",").replace(" ,",",").replace("# ","#").replace(" #","#") if len(pattern) == 0: dbg.log("** Invalid search pattern **") return # break apart the instructions # search for the first instruction(s) allinstructions = pattern.split("#") instructionparts = [] instrfound = False for instruction in allinstructions: instruction = instruction.strip().lower() if instrfound and instruction != "": instructionparts.append(instruction) else: if instruction != "*" and instruction != "": instructionparts.append(instruction) instrfound = True # remove wildcards placed at the end for i in rrange(len(instructionparts)): if instructionparts[i] == "*": instructionparts.pop(i) else: break # glue simple instructions together if possible # reset array allinstructions = [] stopnow = False mergeinstructions = [] mergestopped = False mergetxt = "" for instr in instructionparts: if instr.find("*") == -1 and instr.find("r32") == -1 and not mergestopped: mergetxt += instr + "\n" else: allinstructions.append(instr) mergestopped = True mergetxt = mergetxt.strip("\n") searchPattern = [] remaining = allinstructions if mergetxt != "": searchPattern.append(mergetxt) else: # at this point, we're sure the first instruction has some kind of r32 and/or offset variable # get all of the combinations for this one # and use them as searchPattern cnt = 0 stopped = False for instr in allinstructions: if instr != "*" and (instr.find("r32") > -1 or instr.find("*") > -1) and not stopped: if instr.find("r32") > -1: for reg in dbglib.Registers32BitsOrder: thisinstr = instr.replace("r32",reg.lower()) if instr.find("*") > -1: # contains a wildcard offset startdist = mindistance while startdist < maxdistance: operator = "" if startdist < 0: operator = "-" replacewith = operator + "0x%02x" % startdist thisinstr2 = thisinstr.replace("*",replacewith) searchPattern.append(thisinstr2) startdist += 1 else: searchPattern.append(thisinstr) else: # no r32 if instr.find("*") > -1: # contains a wildcard offset startdist = mindistance while startdist < maxdistance: operator = "" if startdist < 0: operator = "-" replacewith = operator + "0x%02x" % startdist thisinstr2 = instr.replace("*",replacewith) searchPattern.append(thisinstr2) startdist += 1 else: searchPattern.append(instr) remaining.pop(cnt) stopped = True cnt += 1 # search for all these beginnings if len(searchPattern) > 0: if not silent: dbg.log("[+] Started search (%d start patterns)" % len(searchPattern)) dbg.updateLog() for ranges in rangestosearch: mBase = ranges[0] mTop = ranges[1] if not silent: dbg.log("[+] Searching startpattern between 0x%s and 0x%s" % (toHex(mBase),toHex(mTop))) dbg.updateLog() oldsilent=silent silent=True pointers = searchInRange(searchPattern,mBase,mTop,criteria) silent=oldsilent allpointers = mergeOpcodes(allpointers,pointers) # for each of the findings, see if it contains the other instructions too # disassemble forward up to 'maxdepth' instructions for ptrtypes in allpointers: for ptrs in allpointers[ptrtypes]: thisline = "" try: for depth in xrange(maxdepth): tinstr = getDisasmInstruction(dbg.disasmForward(ptrs, depth)).lower() + "\n" if tinstr != "???": thisline += tinstr else: thisline = "" break except: continue allfound = True thisline = thisline.strip("\n") if thisline != "": parts = thisline.split("\n") maxparts = len(parts)-1 partcnt = 1 searchfor = "" remcnt = 0 lastpos = 0 remmax = len(remaining) while remcnt < remmax: searchfor = remaining[remcnt] searchlist = [] if searchfor == "*": while searchfor == "*" and remcnt < remmax: searchfor = remaining[remcnt+1] rangemin = partcnt rangemax = maxparts remcnt += 1 else: rangemin = partcnt rangemax = partcnt if searchfor.find("r32") > -1: for reg in dbglib.Registers32BitsOrder: searchlist.append(searchfor.replace("r32",reg.lower())) else: searchlist.append(searchfor) partfound = False while rangemin <= rangemax and not partfound and rangemax <= maxparts: for searchfor in searchlist: if parts[rangemin].find(searchfor) > -1: partfound = True lastpos = rangemin partcnt = lastpos # set counter to current position break if not partfound and preventbreak: #check if current instruction would break chain if wouldBreakChain(parts[rangemin]): # bail out partfound = False break rangemin += 1 remcnt += 1 partcnt += 1 if not partfound: allfound = False break if allfound: theline = " # ".join(parts[:lastpos+1]) if theline != "": if not theline in results: results[theline] = [ptrs] else: results[theline] += [ptrs] return results def wouldBreakChain(instruction): """ Checks if the given instruction would potentially break the instruction chain Argument : instruction: the instruction to check Returns : boolean """ goodinstruction = isGoodGadgetInstr(instruction) if goodinstruction: return False return True def findPattern(modulecriteria,criteria,pattern,ptype,base,top,consecutive=False,rangep2p=0,level=0,poffset=0,poffsetlevel=0): """ Performs a find in memory for a given pattern Arguments: modulecriteria - dictionary with criteria modules need to comply with. criteria - dictionary with criteria the pointers need to comply with. One of the criteria can be "p2p", indicating that the search should look for pointers to pointers to the pattern pattern - the pattern to search for. ptype - the type of the pattern, can be 'asc', 'bin', 'ptr', 'instr' or 'file' If no type is specified, the routine will try to 'guess' the types when type is set to file, it won't actually search in memory for pattern, but it will read all pointers from that file and search for pointers to those pointers (so basically, type 'file' is only useful in combination with -p2p) base - the base address in memory the search should start at top - the top address in memory the search should not go beyond consecutive - Boolean, indicating if consecutive pointers should be skipped rangep2p - if not set to 0, the pointer to pointer search will also look rangep2p bytes back for each pointer, thus allowing you to find close pointer to pointers poffset - only used when doing p2p, will add offset to found pointer address before looking to ptr to ptr poffsetlevel - apply the offset at this level of the chain level - number of levels deep to look for ptr to ptr. level 0 is default, which means search for pointer to searchpattern Return: all pointers (or pointers to pointers) to the given search pattern in memory """ wildcardsearch = False rangestosearch = [] tmpsearch = [] p2prangestosearch = [] global silent if len(modulecriteria) > 0: modulestosearch = getModulesToQuery(modulecriteria) # convert modules to ranges for modulename in modulestosearch: objmod = MnModule(modulename) mBase = objmod.moduleBase mTop = objmod.moduleTop if mBase < base and base < mTop: mBase = base if mTop > top: mTop = top if mBase >= base and mBase < top: if not [mBase,mTop] in rangestosearch: rangestosearch.append([mBase,mTop]) # if no modules were specified, then also add the other ranges (outside modules) if not "modules" in modulecriteria: outside = getRangesOutsideModules() for range in outside: mBase = range[0] mTop = range[1] if mBase < base and base < mTop: mBase = base if mTop > top: mTop = top if mBase >= base and mBase < top: if not [mBase,mTop] in rangestosearch: rangestosearch.append([mBase,mTop]) else: rangestosearch.append([base,top]) tmpsearch.append([0,TOP_USERLAND]) allpointers = {} originalPattern = pattern # guess the type if it is not specified if ptype == "": if len(pattern) > 2 and pattern[0:2].lower() == "0x": ptype = "ptr" elif "\\x" in pattern: ptype = "bin" else: ptype = "asc" if ptype == "bin" and ".." in pattern: wildcardsearch = True if not silent: dbg.log(" - Wildcard \\x.. detected") if "unic" in criteria and ptype == "asc": ptype = "bin" binpat = "" pattern = pattern.replace('"',"") for thischar in pattern: binpat += "\\x" + str(toHexByte(ord(thischar))) + "\\x00" pattern = binpat originalPattern += " (unicode)" if not silent: dbg.log(" - Expanded ascii pattern to unicode, switched search mode to bin") bytes = "" patternfilename = "" split1 = re.compile(' ') split2 = re.compile(':') split3 = re.compile("\*") if not silent: dbg.log(" - Treating search pattern as %s" % ptype) if ptype == "ptr": pattern = pattern.replace("0x","") value = int(pattern,16) bytes = struct.pack(' 3 and pattern[2:4] == "..": dbg.log(" *** Can't start a wildcard search with a wildcard. Specify a byte instead ***",highlight =1) return else: # search for the first byte and then check wildcards later foundstartbytes = False sindex = 0 while not foundstartbytes: b = pattern[sindex:sindex+4] if not ".." in b: bytes += hex2bin(pattern[sindex:sindex+4]) else: foundstartbytes = True sindex += 4 elif ptype == "asc": if pattern.startswith('"') and pattern.endswith('"'): pattern = pattern.replace('"',"") elif pattern.startswith("'") and pattern.endswith("'"): pattern = pattern.replace("'","") bytes = pattern elif ptype == "instr": pattern = pattern.replace("'","").replace('"',"").replace(" "," ").replace(", ",",").replace(" #","#").replace("# ","#") silent = True bytes = hex2bin(assemble(pattern,"")) silent = False if bytes == "": dbg.log("Invalid instruction - could not assemble %s" % pattern,highlight=1) return elif ptype == "file": patternfilename = pattern.replace("'","").replace('"',"") dbg.log(" - Search patterns = all pointers in file %s" % patternfilename) dbg.log(" Extracting pointers...") FILE=open(patternfilename,"r") contents = FILE.readlines() FILE.close() extracted = 0 for thisLine in contents: if thisLine.lower().startswith("0x"): lineparts=split1.split(thisLine) thispointer = lineparts[0] #get type = from : to * if len(lineparts) > 1: subparts = split2.split(thisLine) if len(subparts) > 1: if subparts[1] != "": subsubparts = split3.split(subparts[1]) if not subsubparts[0] in allpointers: allpointers[subsubparts[0]] = [hexStrToInt(thispointer)] else: allpointers[subsubparts[0]] += [hexStrToInt(thispointer)] extracted += 1 dbg.log(" %d pointers extracted." % extracted) dbg.updateLog() fakeptrcriteria = {} fakeptrcriteria["accesslevel"] = "*" if "p2p" in criteria or level > 0: #save range for later, search in all of userland for now p2prangestosearch = rangestosearch rangestosearch = tmpsearch if ptype != "file": for ranges in rangestosearch: mBase = ranges[0] mTop = ranges[1] if not silent: dbg.log("[+] Searching from 0x%s to 0x%s" % (toHex(mBase),toHex(mTop))) dbg.updateLog() searchPattern = [] searchPattern.append([originalPattern, bytes]) oldsilent=silent silent=True pointers = searchInRange(searchPattern,mBase,mTop,criteria) silent=oldsilent allpointers = mergeOpcodes(allpointers,pointers) # filter out bad ones if wildcardsearch is enabled if wildcardsearch and ptype == "bin": nrbytes = ( len(pattern) / 4) - len(bytes) if nrbytes > 0: maskpart = pattern[len(bytes)*4:] tocomparewith_tmp = maskpart.split("\\x") tocomparewith = [] for tcw in tocomparewith_tmp: if len(tcw) == 2: tocomparewith.append(tcw) dbg.log("[+] Applying wildcard mask, %d remaining bytes: %s" % (nrbytes,maskpart)) remptrs = {} for ptrtype in allpointers: for ptr in allpointers[ptrtype]: rfrom = ptr + len(bytes) bytesatlocation = dbg.readMemory(rfrom,nrbytes) #dbg.log("Read %d bytes from 0x%08x" % (len(bytesatlocation),rfrom)) compareindex = 0 wildcardmatch = True for thisbyte in bytesatlocation: thisbytestr = bin2hexstr(thisbyte).replace("\\x","") thisbytecompare = tocomparewith[compareindex] if thisbytecompare != ".." and thisbytestr.lower() != thisbytecompare.lower(): wildcardmatch=False break compareindex += 1 if wildcardmatch: if not ptrtype in remptrs: remptrs[ptrtype] = [ptr] else: remptrs[ptrtype].append(ptr) allpointers = remptrs if ptype == "file" and level == 0: level = 1 if consecutive: # get all pointers and sort them rawptr = {} for ptrtype in allpointers: for ptr in allpointers[ptrtype]: if not ptr in rawptr: rawptr[ptr]=ptrtype if not silent: dbg.log("[+] Number of pointers to process : %d" % len(rawptr)) sortedptr = rawptr.items() sortedptr.sort(key = itemgetter(0)) #skip consecutive ones and increment size consec_delta = len(bytes) previousptr = 0 savedptr = 0 consec_size = 0 allpointers = {} for ptr,ptrinfo in sortedptr: if previousptr == 0: previousptr = ptr savedptr = ptr if previousptr != ptr: if ptr <= (previousptr + consec_delta): previousptr = ptr else: key = ptrinfo + " ("+ str(previousptr+consec_delta-savedptr) + ")" if not key in allpointers: allpointers[key] = [savedptr] else: allpointers[key] += [savedptr] previousptr = ptr savedptr = ptr #recursive search ? if len(allpointers) > 0: remainingpointers = allpointers if level > 0: thislevel = 1 while thislevel <= level: if not silent: pcnt = 0 for ptype,ptrs in remainingpointers.iteritems(): for ptr in ptrs: pcnt += 1 dbg.log("[+] %d remaining types found at this level, total of %d pointers" % (len(remainingpointers),pcnt)) dbg.log("[+] Looking for pointers to pointers, level %d..." % thislevel) poffsettxt = "" if thislevel == poffsetlevel: dbg.log(" I will apply offset %d (decimal) to discovered pointers to pointers..." % poffset) poffsettxt = "%d(%xh)" % (poffset,poffset) dbg.updateLog() searchPattern = [] foundpointers = {} for ptype,ptrs in remainingpointers.iteritems(): for ptr in ptrs: cnt = 0 #if thislevel == poffsetlevel: # ptr = ptr + poffset while cnt <= rangep2p: bytes = struct.pack(' ptr to " + originalPattern + ") ** ", bytes]) else: searchPattern.append(["ptr" + poffsettxt + " to 0x" + toHex(ptr-cnt) +" (-> close ptr to " + originalPattern + ") ** ", bytes]) cnt += 1 #only apply rangep2p in level 1 if thislevel == 1: rangep2p = 0 remainingpointers = {} for ranges in p2prangestosearch: mBase = ranges[0] mTop = ranges[1] if not silent: dbg.log("[+] Searching from 0x%s to 0x%s" % (toHex(mBase),toHex(mTop))) dbg.updateLog() oldsilent = silent silent=True pointers = searchInRange(searchPattern,mBase,mTop,fakeptrcriteria) silent=oldsilent for ptrtype in pointers: if not ptrtype in remainingpointers: if poffsetlevel == thislevel: # fixup found pointers, apply offset now ptrlist = [] for thisptr in pointers[ptrtype]: thisptr = thisptr + poffset ptrlist.append(thisptr) pointers[ptrtype] = ptrlist remainingpointers[ptrtype] = pointers[ptrtype] thislevel += 1 if len(remainingpointers) == 0: if not silent: dbg.log("[+] No more pointers left, giving up...", highlight=1) break allpointers = remainingpointers return allpointers # def compareFileWithMemory(filename,startpos,skipmodules=False,findunicode=False): # dbg.log("[+] Reading file %s..." % filename) # srcdata_normal=[] # srcdata_unicode=[] # tagresults=[] # criteria = {} # criteria["accesslevel"] = "*" # try: # srcfile = open(filename,"rb") # content = srcfile.readlines() # srcfile.close() # for eachLine in content: # srcdata_normal += eachLine # for eachByte in srcdata_normal: # eachByte+=struct.pack('B', 0) # srcdata_unicode += eachByte # dbg.log(" Read %d bytes from file" % len(srcdata_normal)) # except: # dbg.log("Error while reading file %s" % filename, highlight=1) # return # # loop normal and unicode # comparetable=dbg.createTable('mona Memory comparison results',['Address','Status','BadChars','Type','Location']) # modes = ["normal", "unicode"] # if not findunicode: # modes.remove("unicode") # objlogfile = MnLog("compare.txt") # logfile = objlogfile.reset() # for mode in modes: # if mode == "normal": # srcdata = srcdata_normal # if mode == "unicode": # srcdata = srcdata_unicode # maxcnt = len(srcdata) # if maxcnt < 8: # dbg.log("Error - file does not contain enough bytes (min 8 bytes needed)",highlight=1) # return # locations = [] # if startpos == 0: # dbg.log("[+] Locating all copies in memory (%s)" % mode) # btcnt = 0 # cnt = 0 # linecount = 0 # hexstr = "" # hexbytes = "" # for eachByte in srcdata: # if cnt < 8: # hexbytes += eachByte # if len((hex(ord(srcdata[cnt]))).replace('0x',''))==1: # hexchar=hex(ord(srcdata[cnt])).replace('0x', '\\x0') # else: # hexchar = hex(ord(srcdata[cnt])).replace('0x', '\\x') # hexstr += hexchar # cnt += 1 # dbg.log(" - searching for "+hexstr) # global silent # silent = True # results = findPattern({},criteria,hexstr,"bin",0,TOP_USERLAND,False) # for type in results: # for ptr in results[type]: # ptrinfo = MnPointer(ptr).memLocation() # if not skipmodules or (skipmodules and (ptrinfo in ["Heap","Stack","??"])): # locations.append(ptr) # if len(locations) == 0: # dbg.log(" Oops, no copies found") # else: # startpos_fixed = startpos # locations.append(startpos_fixed) # if len(locations) > 0: # dbg.log(" - Comparing %d location(s)" % (len(locations))) # dbg.log("Comparing bytes from file with memory :") # for location in locations: # memcompare(location,srcdata,comparetable,mode, smart=(mode == 'normal')) # silent = False # return def compareFormattedFileWithMemory(filename,format,startpos,skipmodules=False,findunicode=False): isDebug=False def out(x): dbg.log(x) def ok(x): dbg.log("[+] " + x) def verbose(x): if isDebug: dbg.log("[dbg] " + x) def warn(x): dbg.log("[?] " + x, highlight=1) def err(x): dbg.log(x, highlight=1) #Class ported from https://github.com/mgeeky/expdevBadChars, author: mgeeky, Mariusz B. #Ported by: onlylonly, Z.Y Liew class BytesParser(): formats_rex = { 'xxd': r'^[^0-9a-f]*[0-9a-f]{2,}\:\s((?:[0-9a-f]{4}\s)+)\s+.+$', 'hexdump': r'^[^0-9a-f]*[0-9a-f]{2,}\s+([0-9a-f\s]+[0-9a-f])$', 'classic-hexdump':r'^[0-9a-f]*[0-9a-f]{2,}(?:\:|\s)+\s([0-9a-f\s]+)\s{2,}.+$', 'hexdump-C': r'^[0-9a-f]*[0-9a-f]{2,}\s+\s([0-9a-f\s]+)\s*\|', 'escaped-hexes': r'^[^\'"]*((?:\'[\\\\x0-9a-f]{8,}\')|(?:"[\\\\x0-9a-f]{8,}"))', 'hexstring': r'^([0-9a-f]+)$', 'msfvenom-powershell': r'^[^0x]+((?:0x[0-9a-f]{1,2},?)+)$', 'byte-array': r'^[^0x]*((?:0x[0-9a-f]{2}(?:,\s?))+)', 'js-unicode': r'^[^%u0-9a-f]*((?:%u[0-9a-f]{4})+)$', 'dword': r'^(?:((?:0x[0-9a-f]{1,8}\s[<>\w\+]+)|(?:0x[0-9a-f]{1,8})):\s*)?((?:0x[0-9a-f]{8},?\s*)+)$', } formats_aliases = { 'classic-hexdump': ['ollydbg'], 'escaped-hexes': ['msfvenom-ruby','msfvenom-c', 'msfvenom-carray', 'msfvenom-python'], 'dword': ['gdb'] } formats_compiled = {} def __init__(self, input, name = None, format = None): #convert list to string self.input = ''.join(input) self.name = name self.bytes = [] self.parsed = False self.format = None BytesParser.compile_regexps() if format: verbose("Using user-specified format: %s" % format) try: self.format = BytesParser.interpret_format_name(format) except Exception as e: verbose(str(e)) #exit when user-specified format not in both formats_rex and formats_aliases assert (format in BytesParser.formats_rex.keys() or self.format is not None), \ "Format '%s' is not implemented." % format if self.format is None: self.format = format else: self.recognize_format() #do not normalize input on raw format to prevent input tempering if str(self.format).lower() != "raw": self.normalize_input() if not self.format: self.parsed = False else: if self.fetch_bytes(): ok("Fetched %d bytes successfully from %s" % (len(self.bytes), self.name)) self.parsed = True else: if format and len(format): err("Could not parse %s with user-specified format: %s" % (self.name, format)) else: err("Recognized input %s as formatted with %s but failed fetching bytes." % (self.name, self.format)) def normalize_input(self): input = [] for line in self.input.split('\n'): line = line.strip() line2 = line.encode('string-escape') input.append(line2) self.input = '\n'.join(input) @staticmethod def interpret_format_name(name): if str(format).lower() == "raw": return "raw" for k, v in BytesParser.formats_aliases.items(): if name.lower() in v: return k raise Exception("Format name: %s not recognized as alias." % name) @staticmethod def compile_regexps(): if len(BytesParser.formats_compiled) == 0: for name, rex in BytesParser.formats_rex.items(): BytesParser.formats_compiled[name] = re.compile(rex, re.I) @staticmethod def make_line_printable(line): return ''.join([c if c in string.printable else '.' for c in line]) def recognize_format(self): for line in self.input.split('\n'): if self.format: break for format, rex in BytesParser.formats_compiled.items(): line = BytesParser.make_line_printable(line) verbose("Trying format %s on ('%s')" % (format, line)) if rex.match(line): ok("%s has been recognized as %s formatted." % (self.name, format)) self.format = format break if not self.format: if not all(c in string.printable for c in self.input): ok("%s has been recognized as RAW bytes." % (self.name)) self.format = 'raw' return True else: err("Could not recognize input bytes format of the %s!" % self.name) return False return (len(self.format) > 0) @staticmethod def post_process_bytes_line(line): outb = [] l = line.strip()[:] strip = ['0x', ',', ' ', '\\', 'x', '%u', '+', '.', "'", '"'] for s in strip: l = l.replace(s, '') for i in xrange(0, len(l), 2): outb.append(int(l[i:i+2], 16)) return outb @staticmethod def preprocess_bytes_line(line): l = line.strip()[:] strip = ['(byte)', '+', '.'] for s in strip: l = l.replace(s, '') return l @staticmethod def unpack_dword(line): outs = '' i = 0 for m in re.finditer(r'((?:0x[0-9a-f]{8}(?!:),?\s*))', line): l = m.group(0) l = l.replace(',', '') l = l.replace(' ', '') dword = int(l, 16) unpack = reversed([ (dword & 0xff000000) >> 24, (dword & 0x00ff0000) >> 16, (dword & 0x0000ff00) >> 8, (dword & 0x000000ff) ]) i += 4 for b in unpack: outs += '%02x' % b verbose("After callback ('%s')" % outs) return BytesParser.formats_compiled['hexstring'].match(outs) def fetch_bytes(self): if not self.format: err("fetch_bytes(): Format has not been specified!") return False if self.format == 'raw': verbose("Parsing %s as raw bytes." % self.name) self.bytes = [ord(c) for c in list(self.input)] return len(self.bytes) > 0 for line in self.input.split('\n'): callback_called = False if self.format in BytesParser.formats_callbacks.keys() and \ BytesParser.formats_callbacks[self.format]: verbose("Before callback ('%s')" % line) m = BytesParser.formats_callbacks[self.format].__func__(line) callback_called = True else: line = BytesParser.preprocess_bytes_line(line[:]) m = BytesParser.formats_compiled[self.format].match(line) if m: extract = '' for mg in m.groups()[0:]: if len(mg) > 0: extract = mg bytes = BytesParser.post_process_bytes_line(extract) if not bytes: err("Could not process %s bytes line ('%s') as %s formatted! Quitting." \ % (self.name, line, self.format)) else: verbose("Line ('%s'), bytes ('%s'), extracted ('%s'), len: %d" % (line, extract, bytes, len(bytes))) self.bytes.extend(bytes) else: if callback_called: verbose("Callback failure: transformed string ('%s') did not catched on returned match" % (line)) else: verbose("Parsing line ('%s') failed with format '%s'." % (line, self.format)) return len(self.bytes) > 0 @staticmethod def get_available_format(): #check is input format valid? avail_formats = ['raw',] avail_formats.extend(BytesParser.formats_rex.keys()) for k, v in BytesParser.formats_aliases.items(): avail_formats.extend(v) formats = ', '.join(["'"+x+"'" for x in avail_formats]) #list all available formats return formats @staticmethod def is_valid_format(format): avail_formats = BytesParser.get_available_format() return format in avail_formats def get_bytes(self): return self.bytes formats_callbacks = { 'dword': unpack_dword } ########## END Class : BytesParser dbg.log("[+] Reading file %s..." % filename) srcdata_normal=[] srcdata_unicode=[] tagresults=[] criteria = {} criteria["accesslevel"] = "*" try: srcfile = open(filename,"rb") content = srcfile.readlines() srcfile.close() for eachLine in content: srcdata_normal += eachLine for eachByte in srcdata_normal: eachByte+=struct.pack('B', 0) srcdata_unicode += eachByte dbg.log(" Read %d bytes from file" % len(srcdata_normal)) except: dbg.log("Error while reading file %s" % filename, highlight=1) return # loop normal and unicode comparetable=dbg.createTable('mona Memory comparison results',['Address','Status','BadChars','Type','Location']) modes = ["normal", "unicode"] if not findunicode: modes.remove("unicode") objlogfile = MnLog("compare.txt") logfile = objlogfile.reset() for mode in modes: if mode == "normal": srcdata = srcdata_normal if mode == "unicode": srcdata = srcdata_unicode #check is user supplied input is valid input if format and not BytesParser.is_valid_format(format): err("Format that was specified is not recognized.") err("Valid formats: %s" % BytesParser.get_available_format()) #parse input file b = BytesParser(srcdata, filename, format) if not b.parsed: return False else: srcdata = b.get_bytes() #convert bytes array(from BytesParser) to string array #mona expect input as string array bytetostr = [] for eachByte in srcdata: bytetostr += chr(eachByte) srcdata = bytetostr maxcnt = len(srcdata) if maxcnt < 8: dbg.log("Error - file does not contain enough bytes (min 8 bytes needed)",highlight=1) return locations = [] if startpos == 0: dbg.log("[+] Locating all copies in memory (%s)" % mode) btcnt = 0 cnt = 0 linecount = 0 hexstr = "" hexbytes = "" for eachByte in srcdata: if cnt < 8: hexbytes += eachByte if len((hex(ord(srcdata[cnt]))).replace('0x',''))==1: hexchar=hex(ord(srcdata[cnt])).replace('0x', '\\x0') else: hexchar = hex(ord(srcdata[cnt])).replace('0x', '\\x') hexstr += hexchar cnt += 1 dbg.log(" - searching for "+hexstr) global silent silent = True results = findPattern({},criteria,hexstr,"bin",0,TOP_USERLAND,False) for _type in results: for ptr in results[_type]: ptrinfo = MnPointer(ptr).memLocation() if not skipmodules or (skipmodules and (ptrinfo in ["Heap","Stack","??"])): locations.append(ptr) if len(locations) == 0: dbg.log(" Oops, no copies found") else: startpos_fixed = startpos locations.append(startpos_fixed) if len(locations) > 0: dbg.log(" - Comparing %d location(s)" % (len(locations))) dbg.log("Comparing bytes from file with memory :") for location in locations: memcompare(location,srcdata,comparetable,mode, smart=(mode == 'normal')) silent = False return def memoized(func): ''' A function decorator to make a function cache it's return values. If a function returns a generator, it's transformed into a list and cached that way. ''' cache = {} def wrapper(*args): if args in cache: return cache[args] import time; start = time.time() val = func(*args) if isinstance(val, types.GeneratorType): val = list(val) cache[args] = val return val wrapper.__doc__ = func.__doc__ wrapper.func_name = '%s_memoized' % func.func_name return wrapper class MemoryComparator(object): ''' Solve the memory comparison problem with a special dynamic programming algorithm similar to that for the LCS problem ''' Chunk = namedtuple('Chunk', 'unmodified i j dx dy xchunk ychunk') move_to_gradient = { 0: (0, 0), 1: (0, 1), 2: (1, 1), 3: (2, 1), } def __init__(self, x, y): self.x, self.y = x, y @memoized def get_last_unmodified_chunk(self): ''' Returns the index of the last chunk of size > 1 that is unmodified ''' try: return max(i for i, c in enumerate(self.get_chunks()) if c.unmodified and c.dx > 1) except: # no match return -1 @memoized def get_grid(self): ''' Builds a 2-d suffix grid for our DP algorithm. ''' x = self.x y = self.y[:len(x)*2] width, height = len(x), len(y) values = [[0] * (width + 1) for j in range(height + 1)] moves = [[0] * (width + 1) for j in range(height + 1)] equal = [[x[i] == y[j] for i in range(width)] for j in range(height)] equal.append([False] * width) for j, i in itertools.product(rrange(height + 1), rrange(width + 1)): value = values[j][i] if i >= 1 and j >= 1: if equal[j-1][i-1]: values[j-1][i-1] = value + 1 moves[j-1][i-1] = 2 elif value > values[j][i-1]: values[j-1][i-1] = value moves[j-1][i-1] = 2 if i >= 1 and not equal[j][i-1] and value - 2 > values[j][i-1]: values[j][i-1] = value - 2 moves[j][i-1] = 1 if i >= 1 and j >= 2 and not equal[j-2][i-1] and value - 1 > values[j-2][i-1]: values[j-2][i-1] = value - 1 moves[j-2][i-1] = 3 return (values, moves) @memoized def get_blocks(self): ''' Compares two binary strings under the assumption that y is the result of applying the following transformations onto x: * change single bytes in x (likely) * expand single bytes in x to two bytes (less likely) * drop single bytes in x (even less likely) Returns a generator that yields elements of the form (unmodified, xdiff, ydiff), where each item represents a binary chunk with "unmodified" denoting whether the chunk is the same in both strings, "xdiff" denoting the size of the chunk in x and "ydiff" denoting the size of the chunk in y. Example: >>> x = "abcdefghijklm" >>> y = "mmmcdefgHIJZklm" >>> list(MemoryComparator(x, y).get_blocks()) [(False, 2, 3), (True, 5, 5), (False, 3, 4), (True, 3, 3)] ''' x, y = self.x, self.y _, moves = self.get_grid() # walk the grid path = [] i, j = 0, 0 while True: dy, dx = self.move_to_gradient[moves[j][i]] if dy == dx == 0: break path.append((dy == 1 and x[i] == y[j], dy, dx)) j, i = j + dy, i + dx for i, j in zip(range(i, len(x)), itertools.count(j)): if j < len(y): path.append((x[i] == y[j], 1, 1)) else: path.append((False, 0, 1)) i = j = 0 for unmodified, subpath in itertools.groupby(path, itemgetter(0)): ydiffs = map(itemgetter(1), subpath) dx, dy = len(ydiffs), sum(ydiffs) yield unmodified, dx, dy i += dx j += dy @memoized def get_chunks(self): i = j = 0 for unmodified, dx, dy in self.get_blocks(): yield self.Chunk(unmodified, i, j, dx, dy, self.x[i:i+dx], self.y[j:j+dy]) i += dx j += dy @memoized def guess_mapping(self): ''' Tries to guess how the bytes in x have been mapped to substrings in y by applying nasty heuristics. Examples: >>> list(MemoryComparator("abcdefghijklm", "mmmcdefgHIJZklm").guess_mapping()) [('m', 'm'), ('m',), ('c',), ('d',), ('e',), ('f',), ('g',), ('H', 'I'), ('J',), ('Z',), ('k',), ('l',), ('m',)] >>> list(MemoryComparator("abcdefgcbadefg", "ABBCdefgCBBAdefg").guess_mapping()) [('A',), ('B', 'B'), ('C',), ('d',), ('e',), ('f',), ('g',), ('C',), ('B', 'B'), ('A',), ('d',), ('e',), ('f',), ('g',)] ''' x, y = self.x, self.y mappings_by_byte = defaultdict(lambda: defaultdict(int)) for c in self.get_chunks(): dx, dy = c.dx, c.dy # heuristics to detect expansions if dx < dy and dy - dx <= 3 and dy <= 5: for i, b in enumerate(c.xchunk): slices = set() for start in range(i, min(2*i + 1, dy)): for size in range(1, min(dy - start + 1, 3)): slc = tuple(c.ychunk[start:start+size]) if slc in slices: continue mappings_by_byte[b][slc] += 1 slices.add(slc) for b, values in mappings_by_byte.iteritems(): mappings_by_byte[b] = sorted(values.items(), key=lambda value, count : (-count, -len(value))) for c in self.get_chunks(): dx, dy, xchunk, ychunk = c.dx, c.dy, c.xchunk, c.ychunk if dx < dy: # expansion # try to apply heuristics for small chunks if dx <= 10: res = [] for b in xchunk: if dx == dy or dy >= 2*dx: break for value, count in mappings_by_byte[b]: if tuple(ychunk[:len(value)]) != value: continue res.append(value) ychunk = ychunk[len(value):] dy -= len(value) break else: yield (ychunk[0],) ychunk = ychunk[1:] dy -= 1 dx -= 1 for c in res: yield c # ... or do it the stupid way. If n bytes were changed to m, simply do # as much drops/expansions as necessary at the beginning and than # yield the rest of the y chunk as single-byte modifications for k in range(dy - dx): yield tuple(ychunk[2*k:2*k+2]) ychunk = ychunk[2*(dy - dx):] elif dx > dy: for _ in range(dx - dy): yield () for b in ychunk: yield (b,) def read_memory(dbg, location, max_size): ''' read the maximum amount of memory from the given address ''' for i in rrange(max_size + 1, 0): mem = dbg.readMemory(location, i) if len(mem) == i: return mem # we should never get here, i == 0 should always fulfill the above condition assert False def shorten_bytes(bytes, size=8): if len(bytes) <= size: return bin2hex(bytes) return '%02x ... %02x' % (ord(bytes[0]), ord(bytes[-1])) def draw_byte_table(mapping, log, columns=16): hrspace = 3 * columns - 1 hr = '-'*hrspace log(' ,' + hr + '.') log(' |' + ' Comparison results:'.ljust(hrspace) + '|') log(' |' + hr + '|') for i, chunk in enumerate(extract_chunks(mapping, columns)): chunk = list(chunk) # save generator result in a list src, mapped = zip(*chunk) values = [] for left, right in zip(src, mapped): if left == right: values.append('') # byte matches original elif len(right) == 0: values.append('-1') # byte dropped elif len(right) == 2: values.append('+1') # byte expanded else: values.append(bin2hex(right)) # byte modified line1 = '%3x' % (i * columns) + ' |' + bin2hex(src) line2 = ' |' + ' '.join(sym.ljust(2) for sym in values) # highlight lines if a modification was detected - removed, looks bad in WinDBG #highlight = any(x != y for x, y in chunk) #for l in (line1, line2): log(line1.ljust(5 + hrspace) + '| File') log(line2.ljust(5 + hrspace) + '| Memory') log(' `' + hr + "'") def draw_chunk_table(cmp, log): ''' Outputs a table that compares the found memory chunks side-by-side in input file vs. memory ''' table = [('', '', '', '', 'File', 'Memory', 'Note')] delims = (' ', ' ', ' ', ' | ', ' | ', ' | ', '') last_unmodified = cmp.get_last_unmodified_chunk() for c in cmp.get_chunks(): if c.dy == 0: note = 'missing' elif c.dx > c.dy: note = 'compacted' elif c.dx < c.dy: note = 'expanded' elif c.unmodified: note = 'unmodified!' else: note = 'corrupted' table.append((c.i, c.j, c.dx, c.dy, shorten_bytes(c.xchunk), shorten_bytes(c.ychunk), note)) # draw the table sizes = tuple(max(len(str(c)) for c in col) for col in zip(*table)) for i, row in enumerate(table): log(''.join(str(x).ljust(size) + delim for x, size, delim in zip(row, sizes, delims))) if i == 0 or (i == last_unmodified + 1 and i < len(table)): log('-' * (sum(sizes) + sum(len(d) for d in delims))) def guess_bad_chars(cmp, log, logsilent): guessed_badchars = [] ''' Tries to guess bad characters and outputs them ''' bytes_in_changed_blocks = defaultdict(int) chunks = cmp.get_chunks() last_unmodified = cmp.get_last_unmodified_chunk() for i, c in enumerate(chunks): if c.unmodified: continue if i == last_unmodified + 1: # only report the first character as bad in the final corrupted chunk bytes_in_changed_blocks[c.xchunk[0]] += 1 break for b in set(c.xchunk): bytes_in_changed_blocks[b] += 1 # guess bad chars likely_bc = [char for char, count in bytes_in_changed_blocks.iteritems() if count > 2] if likely_bc: if not logsilent: log("Very likely bad chars: %s" % bin2hex(sorted(likely_bc))) guessed_badchars += list(sorted(likely_bc)) if not logsilent: log("Possibly bad chars: %s" % bin2hex(sorted(bytes_in_changed_blocks))) guessed_badchars += list(sorted(bytes_in_changed_blocks)) # list bytes already omitted from the input bytes_omitted_from_input = set(map(chr, range(0, 256))) - set(cmp.x) if bytes_omitted_from_input: log("Bytes omitted from input: %s" % bin2hex(sorted(bytes_omitted_from_input))) guessed_badchars += list(sorted( bytes_omitted_from_input)) # return list, use list(set(..)) to remove dups return list(set(guessed_badchars)) def memcompare(location, src, comparetable, sctype, smart=True, tablecols=16): ''' Thoroughly compares an input binary string with a location in memory and outputs the results. ''' # set up logging objlogfile = MnLog("compare.txt") logfile = objlogfile.reset(False) # helpers def log(msg='', **kw): msg = str(msg) dbg.log(msg, address=location, **kw) objlogfile.write(msg, logfile) def add_to_table(msg,badbytes = []): locinfo = MnPointer(location).memLocation() badbstr = " " if len(badbytes) > 0: badbstr = "%s " % bin2hex(sorted(badbytes)) comparetable.add(0, ['0x%08x' % location, msg, badbstr, sctype, locinfo]) objlogfile.write("-" * 100,logfile) log('[+] Comparing with memory at location : 0x%08x (%s)' % (location,MnPointer(location).memLocation()), highlight=1) dbg.updateLog() mem = read_memory(dbg, location, 2*len(src)) if smart: cmp = MemoryComparator(src, mem) mapped_chunks = map(''.join, cmp.guess_mapping()) else: mapped_chunks = list(mem[:len(src)]) + [()] * (len(src) - len(mem)) mapping = zip(src, mapped_chunks) broken = [(i,x,y) for i,(x,y) in enumerate(mapping) if x != y] if not broken: log('!!! Hooray, %s shellcode unmodified !!!' % sctype, focus=1, highlight=1) add_to_table('Unmodified') guessed_bc = guess_bad_chars(cmp, log, True) else: log("Only %d original bytes of '%s' code found." % (len(src) - len(broken), sctype)) draw_byte_table(mapping, log, columns=tablecols) log() guessed_bc = [] if smart: # print additional analysis draw_chunk_table(cmp, log) log() guessed_bc = guess_bad_chars(cmp, log, False) log() add_to_table('Corruption after %d bytes' % broken[0][0],guessed_bc) #-----------------------------------------------------------------------# # ROP related functions #-----------------------------------------------------------------------# def createRopChains(suggestions,interestinggadgets,allgadgets,modulecriteria,criteria,objprogressfile,progressfile,technique): """ Will attempt to produce ROP chains """ global ptr_to_get global ptr_counter global silent global noheader global ignoremodules #vars vplogtxt = "" # RVA ? showrva = False if "rva" in criteria: showrva = True #define rop routines routinedefs = {} routinesetup = {} virtualprotect = [["esi","api"],["ebp","jmp esp"],["ebx",0x201],["edx",0x40],["ecx","&?W"],["edi","ropnop"],["eax","nop"]] virtualalloc = [["esi","api"],["ebp","jmp esp"],["ebx",0x01],["edx",0x1000],["ecx",0x40],["edi","ropnop"],["eax","nop"]] setinformationprocess = [["ebp","api"],["edx",0x22],["ecx","&","0x00000002"],["ebx",0xffffffff],["eax",0x4],["edi","pop"]] setprocessdeppolicy = [["ebp","api"],["ebx","&","0x00000000"],["edi","pop"]] routinedefs["VirtualProtect"] = virtualprotect routinedefs["VirtualAlloc"] = virtualalloc # only run these on older systems osver=dbg.getOsVersion() if not (osver == "6" or osver == "7" or osver == "8" or osver == "10" or osver == "11" or osver == "vista" or osver == "win7" or osver == "2008server" or osver == "win8" or osver == "win8.1" or osver == "win10"): routinedefs["SetInformationProcess"] = setinformationprocess routinedefs["SetProcessDEPPolicy"] = setprocessdeppolicy modulestosearch = getModulesToQuery(modulecriteria) routinesetup["VirtualProtect"] = """-------------------------------------------- EAX = NOP (0x90909090) ECX = lpOldProtect (ptr to W address) EDX = NewProtect (0x40) EBX = dwSize ESP = lPAddress (automatic) EBP = ReturnTo (ptr to jmp esp) ESI = ptr to VirtualProtect() EDI = ROP NOP (RETN) --- alternative chain --- EAX = ptr to &VirtualProtect() ECX = lpOldProtect (ptr to W address) EDX = NewProtect (0x40) EBX = dwSize ESP = lPAddress (automatic) EBP = POP (skip 4 bytes) ESI = ptr to JMP [EAX] EDI = ROP NOP (RETN) + place ptr to "jmp esp" on stack, below PUSHAD --------------------------------------------""" routinesetup["VirtualAlloc"] = """-------------------------------------------- EAX = NOP (0x90909090) ECX = flProtect (0x40) EDX = flAllocationType (0x1000) EBX = dwSize ESP = lpAddress (automatic) EBP = ReturnTo (ptr to jmp esp) ESI = ptr to VirtualAlloc() EDI = ROP NOP (RETN) --- alternative chain --- EAX = ptr to &VirtualAlloc() ECX = flProtect (0x40) EDX = flAllocationType (0x1000) EBX = dwSize ESP = lpAddress (automatic) EBP = POP (skip 4 bytes) ESI = ptr to JMP [EAX] EDI = ROP NOP (RETN) + place ptr to "jmp esp" on stack, below PUSHAD --------------------------------------------""" routinesetup["SetInformationProcess"] = """-------------------------------------------- EAX = SizeOf(ExecuteFlags) (0x4) ECX = &ExecuteFlags (ptr to 0x00000002) EDX = ProcessExecuteFlags (0x22) EBX = NtCurrentProcess (0xffffffff) ESP = ReturnTo (automatic) EBP = ptr to NtSetInformationProcess() ESI = EDI = ROP NOP (4 byte stackpivot) --------------------------------------------""" routinesetup["SetProcessDEPPolicy"] = """-------------------------------------------- EAX = ECX = EDX = EBX = dwFlags (ptr to 0x00000000) ESP = ReturnTo (automatic) EBP = ptr to SetProcessDEPPolicy() ESI = EDI = ROP NOP (4 byte stackpivot) --------------------------------------------""" updatetxt = "" # restrict techniques if needed validatedroutinedefs = {} if technique != "": for routine in routinedefs: if technique.lower() == routine.lower(): validatedroutinedefs[routine] = routinedefs[routine] routinedefs = validatedroutinedefs for routine in routinedefs: thischain = {} updatetxt = "Attempting to produce rop chain for %s" % routine dbg.log("[+] %s" % updatetxt) objprogressfile.write("- " + updatetxt,progressfile) vplogtxt += "\n" vplogtxt += "#" * 80 vplogtxt += "\n\nRegister setup for " + routine + "() :\n" + routinesetup[routine] + "\n\n" targetOS = "(XP/2003 Server and up)" if routine == "SetInformationProcess": targetOS = "(XP/2003 Server only)" if routine == "SetProcessDEPPolicy": targetOS = "(XP SP3/Vista SP1/2008 Server SP1, can be called only once per process)" title = "ROP Chain for %s() [%s] :" % (routine,targetOS) vplogtxt += "\n%s\n" % title vplogtxt += ("-" * len(title)) + "\n\n" vplogtxt += "*** [ Ruby ] ***\n\n" vplogtxt += " def create_rop_chain()\n" vplogtxt += '\n # rop chain generated with mona.py - www.corelan.be' vplogtxt += "\n rop_gadgets = \n" vplogtxt += " [\n" thischaintxt = "" dbg.updateLog() modused = {} skiplist = [] replacelist = {} toadd = {} movetolast = [] regsequences = [] stepcnt = 1 for step in routinedefs[routine]: thisreg = step[0] thistarget = step[1] if thisreg in replacelist: thistarget = replacelist[thisreg] thistimestamp=datetime.datetime.now().strftime("%a %Y/%m/%d %I:%M:%S %p") dbg.log(" %s: Step %d/%d: %s" % (thistimestamp,stepcnt,len(routinedefs[routine]),thisreg)) stepcnt += 1 if not thisreg in skiplist: regsequences.append(thisreg) # this must be done first, so we can determine deviations to the chain using # replacelist and skiplist arrays if str(thistarget) == "api": objprogressfile.write(" * Enumerating ROPFunc info (IAT Query)",progressfile) #dbg.log(" Enumerating ROPFunc info") # routine to put api pointer in thisreg funcptr,functext = getRopFuncPtr(routine,modulecriteria,criteria,"iat", objprogressfile, progressfile) if routine == "SetProcessDEPPolicy" and funcptr == 0: # read EAT funcptr,functext = getRopFuncPtr(routine,modulecriteria,criteria,"eat", objprogressfile, progressfile) extra = "" if funcptr == 0: extra = "[-] Unable to find ptr to " thischain[thisreg] = [[0,extra + routine + "() (-> to be put in " + thisreg + ")",0]] else: thischain[thisreg] = putValueInReg(thisreg,funcptr,routine + "() [" + MnPointer(funcptr).belongsTo() + "]",suggestions,interestinggadgets,criteria) else: objprogressfile.write(" Function pointer : 0x%0x" % funcptr, progressfile) objprogressfile.write(" * Getting pickup gadget",progressfile) thischain[thisreg],skiplist = getPickupGadget(thisreg,funcptr,functext,suggestions,interestinggadgets,criteria,modulecriteria,routine) # if skiplist is not empty, then we are using the alternative pickup (via jmp [eax]) # this means we have to make some changes to the routine # and place this pickup at the end if len(skiplist) > 0: if routine.lower() == "virtualprotect" or routine.lower() == "virtualalloc": replacelist["ebp"] = "pop" #set up call to finding jmp esp oldsilent = silent silent=True ptr_counter = 0 ptr_to_get = 3 jmpreg = findJMP(modulecriteria,criteria,"esp") ptr_counter = 0 ptr_to_get = -1 jmpptr = 0 jmptype = "" silent=oldsilent total = getNrOfDictElements(jmpreg) if total > 0: ptrindex = random.randint(1,total) indexcnt= 1 for regtype in jmpreg: for ptr in jmpreg[regtype]: if indexcnt == ptrindex: jmpptr = ptr jmptype = regtype break indexcnt += 1 if jmpptr > 0: toadd[thistarget] = [jmpptr,"ptr to '" + jmptype + "'"] else: toadd[thistarget] = [jmpptr,"ptr to 'jmp esp'"] # make sure the pickup is placed last movetolast.append(thisreg) if str(thistarget).startswith("jmp"): targetreg = str(thistarget).split(" ")[1] #set up call to finding jmp esp oldsilent = silent silent=True ptr_counter = 0 ptr_to_get = 3 jmpreg = findJMP(modulecriteria,criteria,targetreg) ptr_counter = 0 ptr_to_get = -1 jmpptr = 0 jmptype = "" silent=oldsilent total = getNrOfDictElements(jmpreg) if total > 0: ptrindex = random.randint(1,total) indexcnt= 1 for regtype in jmpreg: for ptr in jmpreg[regtype]: if indexcnt == ptrindex: jmpptr = ptr jmptype = regtype break indexcnt += 1 jmpinfo = "" jmpmodinfo = "" if jmpptr == 0: jmptype = "" jmpinfo = "Unable to find ptr to 'JMP ESP'" else: jmpinfo = MnPointer(jmpptr).belongsTo() tmod = MnModule(jmpinfo) jmpmodinfo = getGadgetAddressInfo(jmpptr) thischain[thisreg] = putValueInReg(thisreg,jmpptr,"& " + jmptype + " [" + jmpinfo + "]" + jmpmodinfo,suggestions,interestinggadgets,criteria) if str(thistarget) == "ropnop": ropptr = 0 for poptype in suggestions: if poptype.startswith("pop "): for retptr in suggestions[poptype]: if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: ropptr = retptr+1 break if poptype.startswith("inc "): for retptr in suggestions[poptype]: if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: ropptr = retptr+1 break if poptype.startswith("dec "): for retptr in suggestions[poptype]: if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: ropptr = retptr+1 break if poptype.startswith("neg "): for retptr in suggestions[poptype]: if getOffset(interestinggadgets[retptr]) == 0 and interestinggadgets[retptr].count("#") == 2: ropptr = retptr+2 break if ropptr == 0: for emptytype in suggestions: if emptytype.startswith("empty "): for retptr in suggestions[emptytype]: if interestinggadgets[retptr].startswith("# XOR"): if getOffset(interestinggadgets[retptr]) == 0: ropptr = retptr+2 break if ropptr > 0: thismodname = MnPointer(ropptr).belongsTo() tmod = MnModule(thismodname) ropnopinfo = getGadgetAddressInfo(ropptr) thischain[thisreg] = putValueInReg(thisreg,ropptr,"RETN (ROP NOP) [" + thismodname + "]" + ropnopinfo,suggestions,interestinggadgets,criteria) else: thischain[thisreg] = putValueInReg(thisreg,ropptr,"[-] Unable to find ptr to RETN (ROP NOP)",suggestions,interestinggadgets,criteria) if thistarget.__class__.__name__ == "int" or thistarget.__class__.__name__ == "long": thischain[thisreg] = putValueInReg(thisreg,thistarget,"0x" + toHex(thistarget) + "-> " + thisreg,suggestions,interestinggadgets,criteria) if str(thistarget) == "nop": thischain[thisreg] = putValueInReg(thisreg,0x90909090,"nop",suggestions,interestinggadgets,criteria) if str(thistarget).startswith("&?"): #pointer to rwptr = getAPointer(modulestosearch,criteria,"RW") if rwptr == 0: rwptr = getAPointer(modulestosearch,criteria,"W") if rwptr != 0: rwmodname = MnPointer(rwptr).belongsTo() rwmodinfo = getGadgetAddressInfo(rwptr) thischain[thisreg] = putValueInReg(thisreg,rwptr,"&Writable location [" + rwmodname+"]" + rwmodinfo,suggestions,interestinggadgets,criteria) else: thischain[thisreg] = putValueInReg(thisreg,rwptr,"[-] Unable to find writable location",suggestions,interestinggadgets,criteria) if str(thistarget).startswith("pop"): #get distance if "pop " + thisreg in suggestions: popptr = getShortestGadget(suggestions["pop "+thisreg]) junksize = getJunk(interestinggadgets[popptr])-4 thismodname = MnPointer(popptr).belongsTo() tmodinfo = getGadgetAddressInfo(popptr) thischain[thisreg] = [[popptr,"",junksize],[popptr,"skip 4 bytes [" + thismodname + "]" + tmodinfo]] else: thischain[thisreg] = [[0,"[-] Couldn't find a gadget to put a pointer to a stackpivot (4 bytes) into "+ thisreg,0]] if str(thistarget)==("&"): pattern = step[2] base = 0 top = TOP_USERLAND type = "ptr" al = criteria["accesslevel"] criteria["accesslevel"] = "R" ptr_counter = 0 ptr_to_get = 2 oldsilent = silent silent=True allpointers = findPattern(modulecriteria,criteria,pattern,type,base,top) silent = oldsilent criteria["accesslevel"] = al if len(allpointers) > 0: theptr = 0 for ptrtype in allpointers: for ptrs in allpointers[ptrtype]: theptr = ptrs break thischain[thisreg] = putValueInReg(thisreg,theptr,"&" + str(pattern) + " [" + MnPointer(theptr).belongsTo() + "]",suggestions,interestinggadgets,criteria) else: thischain[thisreg] = putValueInReg(thisreg,0,"[-] Unable to find ptr to " + str(pattern),suggestions,interestinggadgets,criteria) returnoffset = 0 delayedfill = 0 junksize = 0 # get longest modulename longestmod = 0 fillersize = 0 for step in routinedefs[routine]: thisreg = step[0] if thisreg in thischain: for gadget in thischain[thisreg]: thismodname = sanitize_module_name(MnPointer(gadget[0]).belongsTo()) if len(thismodname) > longestmod: longestmod = len(thismodname) if showrva: fillersize = longestmod + 8 else: fillersize = 0 # modify the chain order (regsequences array) for reg in movetolast: if reg in regsequences: regsequences.remove(reg) regsequences.append(reg) regimpact = {} # create the current chain ropdbchain = "" tohex_array = [] for step in regsequences: thisreg = step vplogtxt += " #[---INFO:gadgets_to_set_%s:---]\n" % (thisreg) thischaintxt += " #[---INFO:gadgets_to_set_%s:---]\n" % (thisreg) if thisreg in thischain: for gadget in thischain[thisreg]: gadgetstep = gadget[0] steptxt = gadget[1] junksize = 0 showfills = False if len(gadget) > 2: junksize = gadget[2] if gadgetstep in interestinggadgets and steptxt == "": thisinstr = interestinggadgets[gadgetstep].lstrip() if thisinstr.startswith("#"): thisinstr = thisinstr[2:len(thisinstr)] showfills = True thismodname = MnPointer(gadgetstep).belongsTo() thisinstr += " [" + thismodname + "]" tmod = MnModule(thismodname) thisinstr += getGadgetAddressInfo(gadgetstep) if not thismodname in modused: modused[thismodname] = [tmod.moduleBase,tmod.__str__()] modprefix = "base_" + sanitize_module_name(thismodname) if showrva: alignsize = longestmod - len(sanitize_module_name(thismodname)) vplogtxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) thischaintxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) else: vplogtxt += " 0x%s, # %s %s\n" % (toHex(gadgetstep),thisinstr,steptxt) thischaintxt += " 0x%s, # %s %s\n" % (toHex(gadgetstep),thisinstr,steptxt) ropdbchain += ' %s\n' % (toHex(gadgetstep-tmod.moduleBase),thisinstr.strip(" ")) tohex_array.append(gadgetstep) if showfills: vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) if returnoffset > 0: ropdbchain += ' Filler\n' returnoffset = getOffset(interestinggadgets[gadgetstep]) if delayedfill > 0: vplogtxt += createJunk(delayedfill,"Filler (compensate)",fillersize) thischaintxt += createJunk(delayedfill,"Filler (compensate)",fillersize) ropdbchain += ' Filler\n' delayedfill = 0 if thisinstr.startswith("POP "): delayedfill = junksize else: vplogtxt += createJunk(junksize,"Filler (compensate)",fillersize) thischaintxt += createJunk(junksize,"Filler (compensate)",fillersize) if junksize > 0: ropdbchain += ' Filler\n' else: # still could be a pointer thismodname = MnPointer(gadgetstep).belongsTo() if thismodname != "": tmod = MnModule(thismodname) if not thismodname in modused: modused[thismodname] = [tmod.moduleBase,tmod.__str__()] modprefix = "base_" + sanitize_module_name(thismodname) if showrva: alignsize = longestmod - len(sanitize_module_name(thismodname)) vplogtxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),steptxt) thischaintxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(gadgetstep-tmod.moduleBase),toSize("",alignsize),steptxt) else: vplogtxt += " 0x%s, # %s\n" % (toHex(gadgetstep),steptxt) thischaintxt += " 0x%s, # %s\n" % (toHex(gadgetstep),steptxt) ropdbchain += ' %s\n' % (toHex(gadgetstep-tmod.moduleBase),steptxt.strip(" ")) else: vplogtxt += " 0x%s,%s # %s\n" % (toHex(gadgetstep),toSize("",fillersize),steptxt) thischaintxt += " 0x%s,%s # %s\n" % (toHex(gadgetstep),toSize("",fillersize),steptxt) ropdbchain += ' %s\n' % (toHex(gadgetstep),steptxt.strip(" ")) if steptxt.startswith("[-]"): vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) ropdbchain += ' Filler\n' returnoffset = 0 if delayedfill > 0: vplogtxt += createJunk(delayedfill,"Filler (compensate)",fillersize) thischaintxt += createJunk(delayedfill,"Filler (compensate)",fillersize) ropdbchain += ' Filler\n' delayedfill = 0 vplogtxt += createJunk(junksize,"",fillersize) thischaintxt += createJunk(junksize,"",fillersize) if fillersize > 0: ropdbchain += ' Filler\n' # finish it off steptxt = "" vplogtxt += " #[---INFO:pushad:---]\n" thischaintxt += " #[---INFO:pushad:---]\n" if "pushad" in suggestions: shortest_pushad = getShortestGadget(suggestions["pushad"]) junksize = getJunk(interestinggadgets[shortest_pushad]) thisinstr = interestinggadgets[shortest_pushad].lstrip() if thisinstr.startswith("#"): thisinstr = thisinstr[2:len(thisinstr)] regimpact = getRegImpact(thisinstr) thismodname = MnPointer(shortest_pushad).belongsTo() thisinstr += " [" + thismodname + "]" tmod = MnModule(thismodname) thisinstr += getGadgetAddressInfo(shortest_pushad) if not thismodname in modused: modused[thismodname] = [tmod.moduleBase,tmod.__str__()] modprefix = "base_" + sanitize_module_name(thismodname) if showrva: alignsize = longestmod - len(thismodname) vplogtxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(shortest_pushad - tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) thischaintxt += " %s + 0x%s,%s # %s %s\n" % (modprefix,toHex(shortest_pushad - tmod.moduleBase),toSize("",alignsize),thisinstr,steptxt) else: vplogtxt += " 0x%s, # %s %s\n" % (toHex(shortest_pushad),thisinstr,steptxt) thischaintxt += " 0x%s, # %s %s\n" % (toHex(shortest_pushad),thisinstr,steptxt) ropdbchain += ' %s\n' % (toHex(shortest_pushad-tmod.moduleBase),thisinstr.strip(" ")) vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) if fillersize > 0: ropdbchain += ' Filler\n' vplogtxt += createJunk(junksize,"",fillersize) thischaintxt += createJunk(junksize,"",fillersize) if fillersize > 0: ropdbchain += ' Filler\n' else: vplogtxt += " 0x00000000,%s # %s\n" % (toSize("",fillersize),"[-] Unable to find pushad gadget") thischaintxt += " 0x00000000,%s # %s\n" % (toSize("",fillersize),"[-] Unable to find pushad gadget") ropdbchain += ' Unable to find PUSHAD gadget\n' vplogtxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) thischaintxt += createJunk(returnoffset,"Filler (RETN offset compensation)",fillersize) if returnoffset > 0: ropdbchain += ' Filler\n' # anything else to add ? if len(toadd) > 0: vplogtxt += " #[---INFO:extras:---]\n" thischaintxt += " #[---INFO:extras:---]\n" for adds in toadd: theptr = toadd[adds][0] freetext = toadd[adds][1] if theptr > 0: thismodname = MnPointer(theptr).belongsTo() freetext += " [" + thismodname + "]" tmod = MnModule(thismodname) freetext += getGadgetAddressInfo(theptr) if not thismodname in modused: modused[thismodname] = [tmod.moduleBase,tmod.__str__()] modprefix = "base_" + sanitize_module_name(thismodname) if showrva: alignsize = longestmod - len(thismodname) vplogtxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(theptr - tmod.moduleBase),toSize("",alignsize),freetext) thischaintxt += " %s + 0x%s,%s # %s\n" % (modprefix,toHex(theptr - tmod.moduleBase),toSize("",alignsize),freetext) else: vplogtxt += " 0x%s, # %s\n" % (toHex(theptr),freetext) thischaintxt += " 0x%s, # %s\n" % (toHex(theptr),freetext) ropdbchain += ' %s\n' % (toHex(theptr-tmod.moduleBase),freetext.strip(" ")) else: vplogtxt += " 0x%s, # <- Unable to find %s\n" % (toHex(theptr),freetext) thischaintxt += " 0x%s, # <- Unable to find %s\n" % (toHex(theptr),freetext) ropdbchain += ' Unable to find %s\n' % (toHex(theptr),freetext.strip(" ")) vplogtxt += ' ].flatten.pack("V*")\n' vplogtxt += '\n return rop_gadgets\n\n' vplogtxt += ' end\n' vplogtxt += '\n\n # Call the ROP chain generator inside the \'exploit\' function :\n\n' calltxt = "rop_chain = create_rop_chain(" argtxt = "" vplogtxtpy = "" vplogtxtc = "" vplogtxtjs = "" argtxtpy = "" if showrva: for themod in modused: repr_mod = sanitize_module_name(themod) vplogtxt += " # " + modused[themod][1] + "\n" vplogtxtpy += " # " + modused[themod][1] + "\n" vplogtxtc += " // " + modused[themod][1] + "\n" vplogtxtjs += " // " + modused[themod][1] + "\n" vplogtxt += " base_" + repr_mod + " = 0x%s\n" % toHex(modused[themod][0]) vplogtxtjs += " var base_" + repr_mod + " = 0x%s;\n" % toHex(modused[themod][0]) vplogtxtpy += " base_" + repr_mod + " = 0x%s\n" % toHex(modused[themod][0]) vplogtxtc += " unsigned int base_" + repr_mod + " = 0x%s;\n" % toHex(modused[themod][0]) calltxt += "base_" + repr_mod + "," argtxt += "base_" + repr_mod + "," argtxtpy += "base_" + repr_mod + "," calltxt = calltxt.rstrip(",") + ")\n" argtxt = argtxt.strip(",") argtxtpy = argtxtpy.strip(",") argtxtjs = argtxtpy.replace(".","") vplogtxt = vplogtxt.replace("create_rop_chain()","create_rop_chain(" + argtxt + ")") vplogtxt += '\n ' + calltxt vplogtxt += '\n\n\n' # C vplogtxt += "*** [ C ] ***\n\n" vplogtxt += " #define CREATE_ROP_CHAIN(name, ...) \\\n" vplogtxt += " int name##_length = create_rop_chain(NULL, ##__VA_ARGS__); \\\n" vplogtxt += " unsigned int name[name##_length / sizeof(unsigned int)]; \\\n" vplogtxt += " create_rop_chain(name, ##__VA_ARGS__);\n\n" vplogtxt += " int create_rop_chain(unsigned int *buf, %s)\n" % ", ".join("unsigned int %s" % _ for _ in argtxt.split(",")) vplogtxt += " {\n" vplogtxt += " // rop chain generated with mona.py - www.corelan.be\n" vplogtxt += " unsigned int rop_gadgets[] = {\n" vplogtxt += thischaintxt.replace("#", "//") vplogtxt += " };\n" vplogtxt += " if(buf != NULL) {\n" vplogtxt += " memcpy(buf, rop_gadgets, sizeof(rop_gadgets));\n" vplogtxt += " };\n" vplogtxt += " return sizeof(rop_gadgets);\n" vplogtxt += " }\n\n" vplogtxt += vplogtxtc vplogtxt += " // use the 'rop_chain' variable after this call, it's just an unsigned int[]\n" vplogtxt += " CREATE_ROP_CHAIN(rop_chain, %s);\n" % argtxtpy vplogtxt += " // alternatively just allocate a large enough buffer and get the rop chain, i.e.:\n" vplogtxt += " // unsigned int rop_chain[256];\n" vplogtxt += " // int rop_chain_length = create_rop_chain(rop_chain, %s);\n\n" % argtxtpy # Python vplogtxt += "*** [ Python ] ***\n\n" vplogtxt += " def create_rop_chain(%s):\n" % argtxt vplogtxt += "\n # rop chain generated with mona.py - www.corelan.be\n" vplogtxt += " rop_gadgets = [\n" vplogtxt += thischaintxt vplogtxt += " ]\n" vplogtxt += " return ''.join(struct.pack(' 1: # add everything ic = 1 while ic < len(comments): comment += "," + comments[ic] ic += 1 tptrcnt += 1 comment = comment.replace(" ","") if tptrcnt < len(allptr): vplogtxt += " \"" + toJavaScript(tptr) + "\" + // " + comments[0].replace(" ","").replace(" ","") + " : " + comment + "\n" else: vplogtxt += " \"" + toJavaScript(tptr) + "\"); // " + comments[0].replace(" ","").replace(" ","") + " : " + comment + "\n\n" else: vplogtxt += " function get_rop_chain(%s) {\n" % argtxtjs vplogtxt += " var rop_gadgets = [\n" vplogtxt += thischaintxt.replace(" #"," //").replace(".","") vplogtxt += " ];\n" vplogtxt += " return rop_gadgets;\n" vplogtxt += " }\n\n" vplogtxt += " function gadgets2uni(gadgets) {\n" vplogtxt += " var uni = \"\";\n" vplogtxt += " for(var i=0;i -1: ofile = MnLog(shortmodname+"_virtualprotect.xml") thisofile = ofile.reset(showheader = False) ofile.write(ropdb,thisofile) if ropdbchain.lower().find("virtualalloc") > -1: ofile = MnLog(shortmodname+"_virtualalloc.xml") thisofile = ofile.reset(showheader = False) ofile.write(ropdb,thisofile) ignoremodules = False #go to the next one vpfile = MnLog("rop_chains.txt") thisvplog = vpfile.reset() vpfile.write(vplogtxt,thisvplog) dbg.log("[+] ROP chains written to file %s" % thisvplog) objprogressfile.write("Done creating rop chains",progressfile) return vplogtxt def getGadgetAddressInfo(gadgetptr): gadgetmodname = MnPointer(gadgetptr).belongsTo() infotxt = "" tmod = MnModule(gadgetmodname) if (tmod.isRebase): infotxt += " ** REBASED" if (tmod.isAslr): infotxt += " ** ASLR" return infotxt def getRegImpact(instructionstr): rimpact = {} instrlineparts = instructionstr.split(" # ") changers = ["ADD","SUB","ADC","INC","DEC","XOR"] for i in instrlineparts: instrparts = i.split(" ") dreg = "" dval = 0 if len(instrparts) > 1: if instrparts[0] in changers: dreg = instrparts[1] if instrparts[0] == "INC": dval = -1 elif instrparts[0] == "DEC": dval = 1 else: vparts = i.split(",") if len(vparts) > 1: vpart = vparts[1] dval = vpart if dreg != "": if not dreg in rimpact: rimpact[dreg] = dval else: rimpact[dreg] = rimpact[dreg] + dval return rimpact def getPickupGadget(targetreg,targetval,freetext,suggestions,interestinggadgets,criteria,modulecriteria,routine=""): """ Will attempt to find a gadget that will pickup a pointer to pointer into a register Arguments : the destination register, the value to pick up, some free text about the value, suggestions and interestinggadgets dictionaries Returns : an array with the gadgets """ shortest_pickup = 0 thisshortest_pickup = 0 shortest_move = 0 popptr = 0 pickupfrom = "" pickupreg = "" pickupfound = False pickupchain = [] movechain = [] movechain1 = [] movechain2 = [] disablelist = [] allregs = ["eax","ebx","ecx","edx","ebp","esi","edi"] for pickuptypes in suggestions: if pickuptypes.find("pickup pointer into " + targetreg) > -1: thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): shortest_pickup = thisshortest_pickup smallparts = pickuptypes.split(" ") pickupreg = smallparts[len(smallparts)-1].lower() parts2 = interestinggadgets[shortest_pickup].split("#") #parts2[0] is empty smallparts = parts2[1].split("[") smallparts2 = smallparts[1].split("]") pickupfrom = smallparts2[0].lower() pickupfound = True if (pickupfrom.find("+") > -1): pickupfields = pickupfrom.split("+") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if (pickupfrom.find("-") > -1): pickupfields = pickupfrom.split("-") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if shortest_pickup == 0: # no direct pickup, look for indirect pickup, but prefer EAX first for movetypes in suggestions: if movetypes.find("move eax") == 0 and movetypes.endswith("-> " + targetreg): typeparts = movetypes.split(" ") movefrom = "eax" shortest_move = getShortestGadget(suggestions[movetypes]) movechain = getGadgetMoveRegToReg(movefrom,targetreg,suggestions,interestinggadgets) for pickuptypes in suggestions: if pickuptypes.find("pickup pointer into " + movefrom) > -1: thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): shortest_pickup = thisshortest_pickup smallparts = pickuptypes.split(" ") pickupreg = smallparts[len(smallparts)-1].lower() parts2 = interestinggadgets[shortest_pickup].split("#") #parts2[0] is empty smallparts = parts2[1].split("[") smallparts2 = smallparts[1].split("]") pickupfrom = smallparts2[0].lower() pickupfound = True if (pickupfrom.find("+") > -1): pickupfields = pickupfrom.split("+") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if (pickupfrom.find("-") > -1): pickupfields = pickupfrom.split("-") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if pickupfound: break if shortest_pickup == 0: # no direct pickup, look for indirect pickup for movetypes in suggestions: if movetypes.find("move") == 0 and movetypes.endswith("-> " + targetreg): typeparts = movetypes.split(" ") movefrom = typeparts[1] if movefrom != "esp": shortest_move = getShortestGadget(suggestions[movetypes]) movechain = getGadgetMoveRegToReg(movefrom,targetreg,suggestions,interestinggadgets) for pickuptypes in suggestions: if pickuptypes.find("pickup pointer into " + movefrom) > -1: thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): shortest_pickup = thisshortest_pickup smallparts = pickuptypes.split(" ") pickupreg = smallparts[len(smallparts)-1].lower() parts2 = interestinggadgets[shortest_pickup].split("#") #parts2[0] is empty smallparts = parts2[1].split("[") smallparts2 = smallparts[1].split("]") pickupfrom = smallparts2[0].lower() pickupfound = True if (pickupfrom.find("+") > -1): pickupfields = pickupfrom.split("+") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if (pickupfrom.find("-") > -1): pickupfields = pickupfrom.split("-") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if pickupfound: break if shortest_pickup == 0: movechain = [] #double move for movetype1 in suggestions: if movetype1.find("move") == 0 and movetype1.endswith("-> " + targetreg): interimreg = movetype1.split(" ")[1] if interimreg != "esp": for movetype2 in suggestions: if movetype2.find("move") == 0 and movetype2.endswith("-> " + interimreg): topickupreg= movetype2.split(" ")[1] if topickupreg != "esp": move1 = getShortestGadget(suggestions[movetype1]) move2 = getShortestGadget(suggestions[movetype2]) for pickuptypes in suggestions: if pickuptypes.find("pickup pointer into " + topickupreg) > -1: thisshortest_pickup = getShortestGadget(suggestions[pickuptypes]) if shortest_pickup == 0 or (thisshortest_pickup != 0 and thisshortest_pickup < shortest_pickup): shortest_pickup = thisshortest_pickup smallparts = pickuptypes.split(" ") pickupreg = smallparts[len(smallparts)-1].lower() parts2 = interestinggadgets[shortest_pickup].split("#") #parts2[0] is empty smallparts = parts2[1].split("[") smallparts2 = smallparts[1].split("]") pickupfrom = smallparts2[0].lower() pickupfound = True if (pickupfrom.find("+") > -1): pickupfields = pickupfrom.split("+") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if (pickupfrom.find("-") > -1): pickupfields = pickupfrom.split("-") if pickupfields[1].lower in allregs: pickupfound = False shortest_pickup = 0 if pickupfound: movechain = [] movechain1 = getGadgetMoveRegToReg(interimreg,targetreg,suggestions,interestinggadgets) movechain2 = getGadgetMoveRegToReg(topickupreg,interimreg,suggestions,interestinggadgets) break if shortest_pickup > 0: # put a value in a register if targetval > 0: poproutine = putValueInReg(pickupfrom,targetval,freetext,suggestions,interestinggadgets,criteria) for popsteps in poproutine: pickupchain.append([popsteps[0],popsteps[1],popsteps[2]]) else: pickupchain.append([0,"[-] Unable to find API pointer -> " + pickupfrom,0]) # pickup junksize = getJunk(interestinggadgets[shortest_pickup]) pickupchain.append([shortest_pickup,"",junksize]) # move if needed if len(movechain) > 0: for movesteps in movechain: pickupchain.append([movesteps[0],movesteps[1],movesteps[2]]) if len(movechain2) > 0: for movesteps in movechain2: pickupchain.append([movesteps[0],movesteps[1],movesteps[2]]) if len(movechain1) > 0: for movesteps in movechain1: pickupchain.append([movesteps[0],movesteps[1],movesteps[2]]) elif (routine.lower() == "virtualalloc" or routine.lower() == "virtualprotect"): # use alternative technique, in case of virtualprotect/virtualalloc routine if "pop " + targetreg in suggestions and "pop eax" in suggestions: # find a jmp [eax] pattern = "jmp [eax]" base = 0 top = TOP_USERLAND type = "instr" al = criteria["accesslevel"] criteria["accesslevel"] = "X" global ptr_to_get global ptr_counter ptr_counter = 0 ptr_to_get = 5 theptr = 0 global silent oldsilent = silent silent=True allpointers = findPattern(modulecriteria,criteria,pattern,type,base,top) silent = oldsilent criteria["accesslevel"] = al thismodname = "" if len(allpointers) > 0: for ptrtype in allpointers: for ptrs in allpointers[ptrtype]: theptr = ptrs thismodname = MnPointer(theptr).belongsTo() break if theptr > 0: popptrtar = getShortestGadget(suggestions["pop "+targetreg]) popptreax = getShortestGadget(suggestions["pop eax"]) junksize = getJunk(interestinggadgets[popptrtar])-4 pickupchain.append([popptrtar,"",junksize]) pickupchain.append([theptr,"JMP [EAX] [" + thismodname + "]",0]) junksize = getJunk(interestinggadgets[popptreax])-4 pickupchain.append([popptreax,"",junksize]) pickupchain.append([targetval,freetext,0]) disablelist.append("eax") pickupfound = True if not pickupfound: pickupchain.append([0,"[-] Unable to find gadgets to pickup the desired API pointer into " + targetreg,0]) pickupchain.append([targetval,freetext,0]) return pickupchain,disablelist def getRopFuncPtr(apiname,modulecriteria,criteria,mode, objprogressfile, progressfile): """ Will get a pointer to pointer to the given API name in the IAT of the selected modules Arguments : apiname : the name of the function modulecriteria & criteria : module/pointer criteria Returns : a pointer (integer value, 0 if no pointer was found) text (with optional info) """ global silent oldsilent = silent silent = True global ptr_to_get ptr_to_get = -1 rfuncsearch = apiname.lower() selectedmodules = False if "modules" in modulecriteria: if len(modulecriteria["modules"]) > 0: selectedmodules = True arrfuncsearch = [rfuncsearch] if rfuncsearch == "virtualloc": arrfuncsearch.append("virtuallocstub") ropfuncptr = 0 ropfuncoffsets = {} ropfunctext = "ptr to &" + apiname + "()" objprogressfile.write(" * Ropfunc - Looking for %s (IAT) - modulecriteria: %s" % (ropfunctext, modulecriteria), progressfile) if mode == "iat": if rfuncsearch != "": ropfuncs,ropfuncoffsets = findROPFUNC(modulecriteria,criteria, [rfuncsearch]) else: ropfuncs,ropfuncoffsets = findROPFUNC(modulecriteria) silent = oldsilent #first look for good one objprogressfile.write(" * Ropfunc - Found %d pointers" % len(ropfuncs), progressfile) for ropfunctypes in ropfuncs: #dbg.log("Ropfunc - %s %s" % (ropfunctypes, rfuncsearch)) if ropfunctypes.lower().find(rfuncsearch) > -1 and ropfunctypes.lower().find("rebased") == -1: ropfuncptr = ropfuncs[ropfunctypes][0] break if ropfuncptr == 0: for ropfunctypes in ropfuncs: if ropfunctypes.lower().find(rfuncsearch) > -1: ropfuncptr = ropfuncs[ropfunctypes][0] break #dbg.log("Ropfunc - Selected pointer: 0x%08x" % ropfuncptr) #haven't found pointer, and you were looking at specific modules only? remove module restriction, but still exclude ASLR/rebase if (ropfuncptr == 0) and selectedmodules: objprogressfile.write(" * Ropfunc - No results yet, expanding search to all non ASLR/rebase modules", progressfile) oldsilent = silent silent = True limitedmodulecriteria = {} limitedmodulecriteria["aslr"] = False limitedmodulecriteria["rebase"] = False limitedmodulecriteria["os"] = False ropfuncs,ropfuncoffsets = findROPFUNC(limitedmodulecriteria,criteria) silent = oldsilent for ropfunctypes in ropfuncs: #dbg.log("Ropfunc - %s %s" % (ropfunctypes, rfuncsearch)) if ropfunctypes.lower().find(rfuncsearch) > -1 and ropfunctypes.lower().find("rebased") == -1: ropfuncptr = ropfuncs[ropfunctypes][0] break #still haven't found ? clear out modulecriteria, include ASLR/rebase modules (but not OS modules) if (ropfuncptr == 0) and not selectedmodules: objprogressfile.write(" * Ropfunc - Still no results, now going to search in all application modules", progressfile) oldsilent = silent silent = True limitedmodulecriteria = {} # search in anything except known OS modules - bad idea anyway limitedmodulecriteria["os"] = False ropfuncs2,ropfuncoffsets2 = findROPFUNC(limitedmodulecriteria,criteria) silent = oldsilent for ropfunctypes in ropfuncs2: if ropfunctypes.lower().find(rfuncsearch) > -1 and ropfunctypes.lower().find("rebased") == -1: ropfuncptr = ropfuncs2[ropfunctypes][0] ropfunctext += " (skipped module criteria, check if pointer is reliable !)" break if ropfuncptr == 0: ropfunctext = "[-] Unable to find ptr to &" + apiname+"()" else: ropfptrmodname = MnPointer(ropfuncptr).belongsTo() tmod = MnModule(ropfptrmodname) ropfptrmodinfo = getGadgetAddressInfo(ropfuncptr) ropfunctext += " [IAT " + ropfptrmodname + "]" + ropfptrmodinfo else: # read EAT modulestosearch = getModulesToQuery(modulecriteria) for mod in modulestosearch: tmod = MnModule(mod) funcs = tmod.getEAT() for func in funcs: funcname = funcs[func].lower() if funcname.find(rfuncsearch) > -1: ropfuncptr = func break if ropfuncptr == 0: ropfunctext = "[-] Unable to find required API pointer" return ropfuncptr,ropfunctext def putValueInReg(reg,value,freetext,suggestions,interestinggadgets,criteria): putchain = [] allownull = True popptr = 0 gadgetfound = False offset = 0 if "+" in reg: try: rval = reg.split("+")[1].strip("h") offset = int(rval,16) * (-1) reg = reg.split("+")[0] except: reg = reg.split("+")[0] offset = 0 elif "-" in reg: try: rval = reg.split("-")[1].strip("h") offset = int(rval,16) reg = reg.split("-")[0] except: reg = reg.split("-")[0] offset = 0 if value != 0: value = value + offset if value < 0: value = 0xffffffff + value + 1 negvalue = 4294967296 - value ptrval = MnPointer(value) if meetsCriteria(ptrval,criteria): # easy way - just pop it into a register for poptype in suggestions: if poptype.find("pop "+reg) == 0: popptr = getShortestGadget(suggestions[poptype]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([value,freetext,0]) gadgetfound = True break if not gadgetfound: # move for movetype in suggestions: if movetype.startswith("move") and movetype.endswith("-> " + reg): # get "from" reg fromreg = movetype.split(" ")[1].lower() for poptype in suggestions: if poptype.find("pop "+fromreg) == 0: popptr = getShortestGadget(suggestions[poptype]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([value,freetext,0]) moveptr = getShortestGadget(suggestions[movetype]) movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) for movesteps in movechain: putchain.append([movesteps[0],movesteps[1],movesteps[2]]) gadgetfound = True break if gadgetfound: break if not gadgetfound or not meetsCriteria(ptrval,criteria): if meetsCriteria(MnPointer(negvalue),criteria): if "pop " + reg in suggestions and "neg "+reg in suggestions: popptr = getShortestGadget(suggestions["pop "+reg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([negvalue,"Value to negate, will become 0x" + toHex(value),0]) negptr = getShortestGadget(suggestions["neg "+reg]) junksize = getJunk(interestinggadgets[negptr]) putchain.append([negptr,"",junksize]) gadgetfound = True if not gadgetfound: for movetype in suggestions: if movetype.startswith("move") and movetype.endswith("-> " + reg): fromreg = movetype.split(" ")[1] if "pop " + fromreg in suggestions and "neg " + fromreg in suggestions: popptr = getShortestGadget(suggestions["pop "+fromreg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([negvalue,"Value to negate, will become 0x" + toHex(value)]) negptr = getShortestGadget(suggestions["neg "+fromreg]) junksize = getJunk(interestinggadgets[negptr]) putchain.append([negptr,"",junksize]) movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) for movesteps in movechain: putchain.append([movesteps[0],movesteps[1],movesteps[2]]) gadgetfound = True break if not gadgetfound: # can we do this using add/sub via another register ? for movetype in suggestions: if movetype.startswith("move") and movetype.endswith("-> " + reg): fromreg = movetype.split(" ")[1] if "pop "+ fromreg in suggestions and "add value to " + fromreg in suggestions: # check each value & see if delta meets pointer criteria #dbg.log("move %s into %s" % (fromreg,reg)) for addinstr in suggestions["add value to " + fromreg]: if not gadgetfound: theinstr = interestinggadgets[addinstr][3:len(interestinggadgets[addinstr])] #dbg.log("%s" % theinstr) instrparts = theinstr.split("#") totalvalue = 0 #gadget might contain multiple add/sub instructions for indivinstr in instrparts: instrvalueparts = indivinstr.split(',') if len(instrvalueparts) > 1: # only look at real values if isHexValue(instrvalueparts[1].rstrip()): thisval = hexStrToInt(instrvalueparts[1]) if instrvalueparts[0].lstrip().startswith("ADD"): totalvalue += thisval if instrvalueparts[0].lstrip().startswith("SUB"): totalvalue -= thisval # subtract totalvalue from target value if totalvalue > 0: deltaval = value - totalvalue if deltaval < 0: deltaval = 0xffffffff + deltaval + 1 deltavalhex = toHex(deltaval) if meetsCriteria(MnPointer(deltaval),criteria): #dbg.log(" Instruction : %s, Delta : %s, To pop in reg : %s" % (theinstr,toHex(totalvalue),deltavalhex),highlight=1) popptr = getShortestGadget(suggestions["pop "+fromreg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([deltaval,"put delta into " + fromreg + " (-> put 0x" + toHex(value) + " into " + reg + ")",0]) junksize = getJunk(interestinggadgets[addinstr]) putchain.append([addinstr,"",junksize]) movptr = getShortestGadget(suggestions["move "+fromreg + " -> " + reg]) junksize = getJunk(interestinggadgets[movptr]) putchain.append([movptr,"",junksize]) gadgetfound = True if not gadgetfound: if "pop " + reg in suggestions and "neg "+reg in suggestions and "dec "+reg in suggestions: toinc = 0 while not meetsCriteria(MnPointer(negvalue-toinc),criteria): toinc += 1 if toinc > 250: break if toinc <= 250: popptr = getShortestGadget(suggestions["pop "+reg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value),0]) negptr = getShortestGadget(suggestions["neg "+reg]) cnt = 0 decptr = getShortestGadget(suggestions["dec "+reg]) junksize = getJunk(interestinggadgets[negptr]) putchain.append([negptr,"",junksize]) junksize = getJunk(interestinggadgets[decptr]) while cnt < toinc: putchain.append([decptr,"",junksize]) cnt += 1 gadgetfound = True if not gadgetfound: for movetype in suggestions: if movetype.startswith("move") and movetype.endswith("-> " + reg): fromreg = movetype.split(" ")[1] if "pop " + fromreg in suggestions and "neg " + fromreg in suggestions and "dec "+fromreg in suggestions: toinc = 0 while not meetsCriteria(MnPointer(negvalue-toinc),criteria): toinc += 1 if toinc > 250: break if toinc <= 250: popptr = getShortestGadget(suggestions["pop "+fromreg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value),0]) negptr = getShortestGadget(suggestions["neg "+fromreg]) junksize = getJunk(interestinggadgets[negptr]) cnt = 0 decptr = getShortestGadget(suggestions["dec "+fromreg]) putchain.append([negptr,"",junksize]) junksize = getJunk(interestinggadgets[decptr]) while cnt < toinc: putchain.append([decptr,"",junksize]) cnt += 1 movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) for movesteps in movechain: putchain.append([movesteps[0],movesteps[1],movesteps[2]]) gadgetfound = True break if not gadgetfound and "pop " + reg in suggestions and "neg "+reg in suggestions and "inc "+reg in suggestions: toinc = 0 while not meetsCriteria(MnPointer(negvalue-toinc),criteria): toinc -= 1 if toinc < -250: break if toinc > -250: popptr = getShortestGadget(suggestions["pop "+reg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value),0]) negptr = getShortestGadget(suggestions["neg "+reg]) junksize = getJunk(interestinggadgets[negptr]) putchain.append([negptr,"",junksize]) incptr = getShortestGadget(suggestions["inc "+reg]) junksize = getJunk(interestinggadgets[incptr]) while toinc < 0: putchain.append([incptr,"",junksize]) toinc += 1 gadgetfound = True if not gadgetfound: for movetype in suggestions: if movetype.startswith("move") and movetype.endswith("-> " + reg): fromreg = movetype.split(" ")[1] if "pop " + fromreg in suggestions and "neg " + fromreg in suggestions and "inc "+fromreg in suggestions: toinc = 0 while not meetsCriteria(MnPointer(negvalue-toinc),criteria): toinc -= 1 if toinc < -250: break if toinc > -250: popptr = getShortestGadget(suggestions["pop "+fromreg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,""]) putchain.append([negvalue-toinc,"Value to negate, destination value : 0x" + toHex(value)]) negptr = getShortestGadget(suggestions["neg "+fromreg]) junksize = getJunk(interestinggadgets[negptr]) putchain.append([negptr,"",junksize]) decptr = getShortestGadget(suggestions["inc "+fromreg]) junksize = getJunk(interestinggadgets[incptr]) while toinc < 0 : putchain.append([incptr,"",junksize]) toinc += 1 movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) for movesteps in movechain: putchain.append([movesteps[0],movesteps[1],movesteps[2]]) gadgetfound = True break if not gadgetfound and "add value to " + reg in suggestions and "pop " + reg in suggestions: addtypes = ["ADD","ADC","XOR", "SUB"] for addtype in addtypes: for ptrs in suggestions["add value to " + reg]: thisinstr = interestinggadgets[ptrs] thisparts = thisinstr.split("#") addinstr = thisparts[1].lstrip().split(",") if thisparts[1].startswith(addtype): if addtype == "ADD" or addtype == "ADC": addvalue = hexStrToInt(addinstr[1]) delta = value - addvalue if delta < 0: delta = 0xffffffff + delta + 1 if addtype == "XOR": delta = hexStrToInt(addinstr[1]) ^ value if addtype == "SUB": addvalue = hexStrToInt(addinstr[1]) delta = value + addvalue if delta < 0: delta = 0xffffffff + delta + 1 if meetsCriteria(MnPointer(delta),criteria): popptr = getShortestGadget(suggestions["pop "+reg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([delta,"Diff to desired value",0]) junksize = getJunk(interestinggadgets[ptrs]) putchain.append([ptrs,"",junksize]) gadgetfound = True break if not gadgetfound: for movetype in suggestions: if movetype.startswith("move") and movetype.endswith("-> " + reg): fromreg = movetype.split(" ")[1] if "add value to " + fromreg in suggestions and "pop " + fromreg in suggestions: addtypes = ["ADD","ADC","XOR","SUB"] for addtype in addtypes: for ptrs in suggestions["add value to " + fromreg]: thisinstr = interestinggadgets[ptrs] thisparts = thisinstr.split("#") addinstr = thisparts[1].lstrip().split(",") if thisparts[1].startswith(addtype): if addtype == "ADD" or addtype == "ADC": addvalue = hexStrToInt(addinstr[1]) delta = value - addvalue if delta < 0: delta = 0xffffffff + delta + 1 if addtype == "XOR": delta = hexStrToInt(addinstr[1]) ^ value if addtype == "SUB": addvalue = hexStrToInt(addinstr[1]) delta = value + addvalue if delta < 0: delta = 0xffffffff + delta + 1 #dbg.log("0x%s : %s, delta : 0x%s" % (toHex(ptrs),thisinstr,toHex(delta))) if meetsCriteria(MnPointer(delta),criteria): popptr = getShortestGadget(suggestions["pop "+fromreg]) junksize = getJunk(interestinggadgets[popptr])-4 putchain.append([popptr,"",junksize]) putchain.append([delta,"Diff to desired value",0]) junksize = getJunk(interestinggadgets[ptrs]) putchain.append([ptrs,"",junksize]) movechain = getGadgetMoveRegToReg(fromreg,reg,suggestions,interestinggadgets) for movesteps in movechain: putchain.append([movesteps[0],movesteps[1],movesteps[2]]) gadgetfound = True break if not gadgetfound and "inc " + reg in suggestions and value <= 64: cnt = 0 # can we clear the reg ? clearsteps = clearReg(reg,suggestions,interestinggadgets) for cstep in clearsteps: putchain.append([cstep[0],cstep[1],cstep[2]]) # inc incptr = getShortestGadget(suggestions["inc "+reg]) junksize = getJunk(interestinggadgets[incptr]) while cnt < value: putchain.append([incptr,"",junksize]) cnt += 1 gadgetfound = True if not gadgetfound: putchain.append([0,"[-] Unable to find gadget to put " + toHex(value) + " into " + reg,0]) return putchain def getGadgetMoveRegToReg(fromreg,toreg,suggestions,interestinggadgets): movechain = [] movetype = "move " + fromreg + " -> " + toreg if movetype in suggestions: moveptr = getShortestGadget(suggestions[movetype]) moveinstr = interestinggadgets[moveptr].lstrip() if moveinstr.startswith("# XOR") or moveinstr.startswith("# OR") or moveinstr.startswith("# AD"): clearchain = clearReg(toreg,suggestions,interestinggadgets) for cc in clearchain: movechain.append([cc[0],cc[1],cc[2]]) junksize = getJunk(interestinggadgets[moveptr]) movechain.append([moveptr,"",junksize]) else: movetype1 = "xor " + fromreg + " -> " + toreg movetype2 = "xor " + toreg + " -> " + fromreg if movetype1 in suggestions and movetype2 in suggestions: moveptr1 = getShortestGadget(suggestions[movetype1]) junksize = getJunk(interestinggadgets[moveptr1]) movechain.append([moveptr1,"",junksize]) moveptr2 = getShortestGadget(suggestions[movetype2]) junksize = getJunk(interestinggadgets[moveptr2]) movechain.append([moveptr2,"",junksize]) return movechain def clearReg(reg,suggestions,interestinggadgets): clearchain = [] clearfound = False if not "clear " + reg in suggestions: if not "inc " + reg in suggestions or not "pop " + reg in suggestions: # maybe it will work using a move from another register for inctype in suggestions: if inctype.startswith("inc"): increg = inctype.split(" ")[1] iptr = getShortestGadget(suggestions["inc " + increg]) for movetype in suggestions: if movetype == "move " + increg + " -> " + reg and "pop " + increg in suggestions: moveptr = getShortestGadget(suggestions[movetype]) moveinstr = interestinggadgets[moveptr].lstrip() if not(moveinstr.startswith("# XOR") or moveinstr.startswith("# OR") or moveinstr.startswith("# AD")): #kewl pptr = getShortestGadget(suggestions["pop " + increg]) junksize = getJunk(interestinggadgets[pptr])-4 clearchain.append([pptr,"",junksize]) clearchain.append([0xffffffff," ",0]) junksize = getJunk(interestinggadgets[iptr]) clearchain.append([iptr,"",junksize]) junksize = getJunk(interestinggadgets[moveptr]) clearchain.append([moveptr,"",junksize]) clearfound = True break if not clearfound: clearchain.append([0,"[-] Unable to find a gadget to clear " + reg,0]) else: #pop FFFFFFFF into reg, then do inc reg => 0 pptr = getShortestGadget(suggestions["pop " + reg]) junksize = getJunk(interestinggadgets[pptr])-4 clearchain.append([pptr,"",junksize]) clearchain.append([0xffffffff," ",0]) iptr = getShortestGadget(suggestions["inc " + reg]) junksize = getJunk(interestinggadgets[iptr]) clearchain.append([iptr,"",junksize]) else: shortest_clear = getShortestGadget(suggestions["clear " + reg]) junksize = getJunk(interestinggadgets[shortest_clear]) clearchain.append([shortest_clear,"",junksize]) return clearchain def getGadgetValueToReg(reg,value,suggestions,interestinggadgets): negfound = False blocktxt = "" blocktxt2 = "" tonegate = 4294967296 - value nregs = ["eax","ebx","ecx","edx","edi"] junksize = 0 junk2size = 0 negateline = " 0x" + toHex(tonegate)+", # value to negate, target value : 0x" + toHex(value) + ", target reg : " + reg +"\n" if "neg " + reg in suggestions: negfound = True negptr = getShortestGadget(suggestions["neg " + reg]) if "pop "+reg in suggestions: pptr = getShortestGadget(suggestions["pop " + reg]) blocktxt2 += " 0x" + toHex(pptr)+", "+interestinggadgets[pptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" blocktxt2 += negateline junk2size = getJunk(interestinggadgets[pptr])-4 else: blocktxt2 += " 0x????????,# find a way to pop the next value into " + reg + "\n" blocktxt2 += negateline blocktxt2 += " 0x" + toHex(negptr)+", "+interestinggadgets[negptr].strip()+" ("+MnPointer(negptr).belongsTo()+")\n" junksize = getJunk(interestinggadgets[negptr])-4 if not negfound: nregs.remove(reg) for thisreg in nregs: if "neg "+ thisreg in suggestions and not negfound: blocktxt2 = "" junk2size = 0 negfound = True #get pop first if "pop "+thisreg in suggestions: pptr = getShortestGadget(suggestions["pop " + thisreg]) blocktxt2 += " 0x" + toHex(pptr)+", "+interestinggadgets[pptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" blocktxt2 += negateline junk2size = getJunk(interestinggadgets[pptr])-4 else: blocktxt2 += " 0x????????,# find a way to pop the next value into "+thisreg+"\n" blocktxt2 += negateline negptr = getShortestGadget(suggestions["neg " + thisreg]) blocktxt2 += " 0x" + toHex(negptr)+", "+interestinggadgets[negptr].strip()+" ("+MnPointer(negptr).belongsTo()+")\n" junk2size = junk2size + getJunk(interestinggadgets[negptr])-4 #now move it to reg if "move " + thisreg + " -> " + reg in suggestions: bptr = getShortestGadget(suggestions["move " + thisreg + " -> " + reg]) if interestinggadgets[bptr].strip().startswith("# ADD"): if not "clear " + reg in suggestions: # other way to clear reg, using pop + inc ? if not "inc " + reg in suggestions or not "pop " + reg in suggestions: blocktxt2 += " 0x????????, # find pointer to clear " + reg+"\n" else: #pop FFFFFFFF into reg, then do inc reg => 0 pptr = getShortestGadget(suggestions["pop " + reg]) blocktxt2 += " 0x" + toHex(pptr)+", "+interestinggadgets[pptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" blocktxt2 += " 0xffffffff, # pop value into " + reg + "\n" blocktxt2 += createJunk(getJunk(interestinggadgets[pptr])-4) iptr = getShortestGadget(suggestions["inc " + reg]) blocktxt2 += " 0x" + toHex(iptr)+", "+interestinggadgets[iptr].strip()+" ("+MnPointer(pptr).belongsTo()+")\n" junksize += getJunk(interestinggadgets[iptr]) else: clearptr = getShortestGadget(suggestions["empty " + reg]) blocktxt2 += " 0x" + toHex(clearptr)+", "+interestinggadgets[clearptr].strip()+" ("+MnPointer(clearptr).belongsTo()+")\n" junk2size = junk2size + getJunk(interestinggadgets[clearptr])-4 blocktxt2 += " 0x" + toHex(bptr)+", "+interestinggadgets[bptr].strip()+" ("+MnPointer(bptr).belongsTo()+")\n" junk2size = junk2size + getJunk(interestinggadgets[bptr])-4 else: negfound = False if negfound: blocktxt += blocktxt2 else: blocktxt = "" junksize = junksize + junk2size return blocktxt,junksize def getOffset(instructions): offset = 0 instrparts = instructions.split("#") retpart = instrparts[len(instrparts)-1].strip() retparts = retpart.split(" ") if len(retparts) > 1: offset = hexStrToInt(retparts[1]) return offset def getJunk(instructions): junkpop = instructions.count("POP ") * 4 junkpush = instructions.count("PUSH ") * -4 junkpushad = instructions.count("PUSHAD ") * -32 junkpopad = instructions.count("POPAD") * 32 junkinc = instructions.count("INC ESP") * 1 junkdec = instructions.count("DEC ESP") * -1 junkesp = 0 if instructions.find("ADD ESP,") > -1: instparts = instructions.split("#") for part in instparts: thisinstr = part.strip() if thisinstr.startswith("ADD ESP,"): value = thisinstr.split(",") junkesp += hexStrToInt(value[1]) if instructions.find("SUB ESP,") > -1: instparts = instructions.split("#") for part in instparts: thisinstr = part.strip() if thisinstr.startswith("SUB ESP,"): value = thisinstr.split(",") junkesp -= hexStrToInt(value[1]) junk = junkpop + junkpush + junkpopad + junkpushad + junkesp return junk def createJunk(size,message="filler (compensate)",alignsize=0): bytecnt = 0 dword = 0 junktxt = "" while bytecnt < size: dword = 0 junktxt += " 0x" while dword < 4 and bytecnt < size : junktxt += "41" dword += 1 bytecnt += 1 junktxt += "," junktxt += toSize("",alignsize + 4 - dword) junktxt += " # "+message+"\n" return junktxt def getShortestGadget(chaintypedict): shortest = 100 shortestptr = 0 shortestinstr = "A" * 1000 thischaindict = chaintypedict.copy() #shuffle dict so returning ptrs would be different each time while thischaindict: typeptr, thisinstr = random.choice(thischaindict.items()) if thisinstr.startswith("# XOR") or thisinstr.startswith("# OR") or thisinstr.startswith("# AD"): thisinstr += " " # make sure we don prefer MOV or XCHG thiscount = thisinstr.count("#") thischaindict.pop(typeptr) if thiscount < shortest: shortest = thiscount shortestptr = typeptr shortestinstr = thisinstr else: if thiscount == shortest: if len(thisinstr) < len(shortestinstr): shortest = thiscount shortestptr = typeptr shortestinstr = thisinstr return shortestptr def isInterestingGadget(instructions): if isAsciiString(instructions): interesting = [ "POP E", "XCHG E", "LEA E", "PUSH E", "XOR E", "AND E", "NEG E", "OR E", "ADD E", "SUB E", "INC E", "DEC E", "POPAD", "PUSHAD", "SUB A", "ADD A", "NOP", "ADC E", "SUB BH", "SUB BL", "ADD BH", "ADD BL", "SUB CH", "SUB CL", "ADD CH", "ADD CL", "SUB DH", "SUB DL", "ADD DH", "ADD DL", "MOV E", "CLC", "CLD", "FS:", "FPA", "TEST " ] notinteresting = [ "MOV ESP,EBP", "LEA ESP" ] regs = dbglib.Registers32BitsOrder[:] if arch == 64: interesting.extend(["POP R", "XCHG R", "LEA R", "PUSH R", "XOR R", "AND R", "NEG R", "OR R", "ADD R", "SUB R", "INC R", "DEC R", "SUB R", "ADD R", "ADC R", "MOV R"]) notinteresting.extend(["MOV RSP, RBP", "LEA RSP"]) regs.extend(dbglib.Registers64BitsOrder) individual = instructions.split("#") cnt = 0 allgood = True toskip = False while (cnt < len(individual)-1) and allgood: # do not check last one, which is the ending instruction thisinstr = individual[cnt].strip().upper() if thisinstr != "": toskip = False foundinstruction = False for notinterest in notinteresting: if thisinstr.find(notinterest) > -1: toskip= True if not toskip: for interest in interesting: if thisinstr.find(interest) > -1: foundinstruction = True if not foundinstruction: #check the conditional instructions if thisinstr.find("MOV DWORD PTR DS:[E") > -1: thisinstrparts = thisinstr.split(",") if len(thisinstrparts) > 1: if thisinstrparts[1] in regs: foundinstruction = True # other exceptions - don't combine ADD BYTE or ADD DWORD with XCHG EAX,ESI - EAX may not be writeable #if instructions.strip().startswith("# XCHG") and (thisinstr.find("ADD DWORD") > -1 or thisinstr.find("ADD BYTE") > -1) and not instructions.strip().startswith("# XCHG EAX,ESI") : # allow - tricky case, but sometimes needed # foundinstruction = True allgood = foundinstruction else: allgood = False cnt += 1 return allgood return False def isInterestingJopGadget(instructions): interesting = [ "POP E", "XCHG E", "LEA E", "PUSH E", "XOR E", "AND E", "NEG E", "OR E", "ADD E", "SUB E", "INC E", "DEC E", "POPAD", "PUSHAD", "SUB A", "ADD A", "NOP", "ADC E", "SUB BH", "SUB BL", "ADD BH", "ADD BL", "SUB CH", "SUB CL", "ADD CH", "ADD CL", "SUB DH", "SUB DL", "ADD DH", "ADD DL", "MOV E", "CLC", "CLD", "FS:", "FPA" ] notinteresting = [ "MOV ESP,EBP", "LEA ESP" ] regs = dbglib.Registers32BitsOrder[:] individual = instructions.split("#") cnt = 0 allgood = True popfound = False toskip = False # what is the jmp instruction ? lastinstruction = individual[len(individual)-1].replace("[","").replace("+"," ").replace("]","").strip() jmp = lastinstruction.split(' ')[1].strip().upper().replace(" ","") regs = ["EAX","EBX","ECX","EDX","ESI","EDI","EBP","ESP"] regs.remove(jmp) if jmp != "ESP": if instructions.find("POP "+jmp) > -1: popfound=True else: for reg in regs: poploc = instructions.find("POP "+reg) if (poploc > -1): if (instructions.find("MOV "+reg+","+jmp) > poploc) or (instructions.find("XCHG "+reg+","+jmp) > poploc) or (instructions.find("XCHG "+jmp+","+reg) > poploc): popfound = True allgood = popfound return allgood def readGadgetsFromFile(filename): """ Reads a mona/msf generated rop file Arguments : filename - the full path + filename of the source file Return : dictionary containing the gadgets (grouped by ending type) """ readopcodes = {} srcfile = open(filename,"rb") content = srcfile.readlines() srcfile.close() msffiledetected = False #what kind of file do we have for thisLine in content: if thisLine.find("mod:") > -1 and thisLine.find("ver:") > -1 and thisLine.find("VA") > -1: msffiledetected = True break if msffiledetected: dbg.log("[+] Importing MSF ROP file...") addrline = 0 ending = "" thisinstr = "" thisptr = "" for thisLine in content: if thisLine.find("[addr:") == 0: thisLineparts = thisLine.split("]") if addrline == 0: thisptr = hexStrToInt(thisLineparts[0].replace("[addr: ","")) thisLineparts = thisLine.split(" ") thisinstrpart = thisLineparts[len(thisLineparts)-1].upper().strip() if thisinstrpart != "": thisinstr += " # " + thisinstrpart ending = thisinstrpart addrline += 1 else: addrline = 0 if thisptr != "" and ending != "" and thisinstr != "": if not ending in readopcodes: readopcodes[ending] = [thisptr,thisinstr] else: readopcodes[ending] += ([thisptr,thisinstr]) thisptr = "" ending = "" thisinstr = "" else: dbg.log("[+] Importing Mona legacy ROP file...") for thisLine in content: if isAsciiString(thisLine.replace("\r","").replace("\n","")): refpointer,instr = splitToPtrInstr(thisLine) if refpointer != -1: #get ending instrparts = instr.split("#") ending = instrparts[len(instrparts)-1] if not ending in readopcodes: readopcodes[ending] = [refpointer,instr] else: readopcodes[ending] += ([refpointer,instr]) return readopcodes def isGoodGadgetPtr(gadget,criteria): if gadget in CritCache: return CritCache[gadget] else: gadgetptr = MnPointer(gadget) status = meetsCriteria(gadgetptr,criteria) CritCache[gadget] = status return status def getStackPivotDistance(gadget,distance=0): offset = 0 distance_str = str(distance).lower() mindistance = 0 maxdistance = 0 if "," not in distance_str: # only mindistance maxdistance = 99999999 mindistance = to_int(distance_str) else: mindistance, maxdistance = distance_str.split(",") mindistance = to_int(mindistance) maxdistance = to_int(maxdistance) gadgets = filter(lambda x: x.strip(), gadget.split(" # ")) for g in gadgets: if "ADD ESP," in g: offset += hexStrToInt(g.split(",")[1]) elif "SUB ESP," in g: offset += hexStrToInt(g.split(",")[1]) elif "INC ESP" in g: offset += 1 elif "DEC ESP" in g: offset -= 1 elif "POP " in g: offset += 4 elif "PUSH " in g: offset -= 4 elif "POPAD" in g: offset += 32 elif "PUSHAD" in g: offset -= 32 elif ("DWORD PTR" in g or "[" in g) and "FS" not in g: return 0 if mindistance <= offset and offset <= maxdistance: return offset else: return 0 def isGoodGadgetInstr(instruction): if isAsciiString(instruction): forbidden = [ "???", "LEAVE", "JMP ", "CALL ", "JB ", "JL ", "JE ", "JNZ ", "JGE ", "JNS ","SAL ", "LOOP", "LOCK", "BOUND", "SAR", "IN ", "OUT ", "RCL", "RCR", "ROL", "ROR", "SHL", "SHR", "INT", "JECX", "JNP", "JPO", "JPE", "JCXZ", "JA", "JB", "JNA", "JNB", "JC", "JNC", "JG", "JLE", "MOVS", "CMPS", "SCAS", "LODS", "STOS", "REP", "REPE", "REPZ", "REPNE", "REPNZ", "LDS", "FST", "FIST", "FMUL", "FDIVR", "FSTP", "FST", "FLD", "FDIV", "FXCH", "JS ", "FIDIVR", "SBB", "SALC", "ENTER", "CWDE", "FCOM", "LAHF", "DIV", "JO", "OUT", "IRET", "FILD", "RETF","HALT","HLT","AAM","FINIT","INT3" ] for instr in forbidden: if instruction.upper().find(instr) > -1: return False return True return False def isGoodJopGadgetInstr(instruction): if isAsciiString(instruction): forbidden = [ "???", "LEAVE", "RETN", "CALL ", "JB ", "JL ", "JE ", "JNZ ", "JGE ", "JNS ","SAL ", "LOOP", "LOCK", "BOUND", "SAR", "IN ", "OUT ", "RCL", "RCR", "ROL", "ROR", "SHL", "SHR", "INT", "JECX", "JNP", "JPO", "JPE", "JCXZ", "JA", "JB", "JNA", "JNB", "JC", "JNC", "JG", "JLE", "MOVS", "CMPS", "SCAS", "LODS", "STOS", "REP", "REPE", "REPZ", "REPNE", "REPNZ", "LDS", "FST", "FIST", "FMUL", "FDIVR", "FSTP", "FST", "FLD", "FDIV", "FXCH", "JS ", "FIDIVR", "SBB", "SALC", "ENTER", "CWDE", "FCOM", "LAHF", "DIV", "JO", "OUT", "IRET", "FILD", "RETF","HALT","HLT","AAM","FINIT" ] for instr in forbidden: if instruction.upper().find(instr) > -1: return False return True return False def isGadgetEnding(instruction,endings,verbosity=False): for ending in endings: if instruction.lower().find(ending.lower()) > -1: return True return False def getRopSuggestion(ropchains,allchains): suggestions={} if arch == 32: arch_aware_regs = dbglib.Registers32BitsOrder[:] arch_aware_regs.remove('ESP') else: arch_aware_regs = dbglib.Registers64BitsOrder[:] arch_aware_regs.remove('RSP') regs = dbglib.Registers32BitsOrder[:] regs.remove('ESP') if arch == 64: regs.extend(dbglib.Registers64BitsOrder) regs.remove('RSP') # pushad # ====================== if arch == 32: # we don't care about pushad in 64 bit pushad_allowed = [ "INC ","DEC ","OR ","XOR ","LEA ","ADD ","SUB ", "PUSHAD", "RETN ", "NOP", "POP ","PUSH EAX","PUSH EDI","ADC ","FPATAN","MOV E" , "TEST ", "CMP "] for r in regs: pushad_allowed.append("MOV "+r+",DWORD PTR DS:[ESP") #stack pushad_allowed.append("MOV "+r+",DWORD PTR SS:[ESP") #stack pushad_allowed.append("MOV "+r+",DWORD PTR DS:[ESI") #virtualprotect pushad_allowed.append("MOV "+r+",DWORD PTR SS:[ESI") #virtualprotect pushad_allowed.append("MOV "+r+",DWORD PTR DS:[EBP") #stack pushad_allowed.append("MOV "+r+",DWORD PTR SS:[EBP") #stack for r2 in regs: pushad_allowed.append("MOV "+r+","+r2) pushad_allowed.append("XCHG "+r+","+r2) pushad_allowed.append("LEA "+r+","+r2) pushad_notallowed = ["POP ESP","POPAD","PUSH ESP","MOV ESP","ADD ESP", "INC ESP","DEC ESP","XOR ESP","LEA ESP","SS:","DS:"] for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() if gadgetinstructions.find("PUSHAD") == 2: # does chain only contain allowed instructions # one pop is allowed, as long as it's not pop esp # push edi and push eax are allowed too (ropnop) if gadgetinstructions.count("POP ") < 2 and suggestedGadgetCheck(gadgetinstructions,pushad_allowed,pushad_notallowed): toadd={} toadd[gadget] = gadgetinstructions if not "pushad" in suggestions: suggestions["pushad"] = toadd else: suggestions["pushad"] = mergeOpcodes(suggestions["pushad"],toadd) # pick up a pointer # ========================= pickedupin = [] resulthash = "" allowedpickup = True for r in arch_aware_regs: for r2 in arch_aware_regs: pickup_allowed = ["NOP","RETN ","INC ","DEC ","OR ","XOR ","MOV ","LEA ","ADD ","SUB ","POP","ADC ","FPATAN", "TEST ", "CMP "] pickup_target = ["MOV "+r+","+PTR_SIZE_DIRECTIVE+" SS:["+r2+"]", "MOV "+r+","+PTR_SIZE_DIRECTIVE+" DS:["+r2+"]"] pickup_allowed.append("MOV "+r+","+PTR_SIZE_DIRECTIVE+" SS:["+r2+"]") pickup_allowed.append("MOV "+r+","+PTR_SIZE_DIRECTIVE+" DS:["+r2+"]") pickup_notallowed = ["POP "+r, "MOV "+r+",E", "LEA "+r+",E", "MOV ESP", "XOR ESP", "LEA ESP", "MOV DWORD PTR", "DEC ESP"] if arch == 64: pickup_notallowed.extend(["MOV RSP", "XOR RSP", "LEA RSP", "DEC RSP", "MOV QWORD PTR"]) for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() allowedpickup = False for allowed in pickup_target: if gadgetinstructions.find(allowed) == 2 and gadgetinstructions.count("DWORD PTR") == 1: allowedpickup = True break if allowedpickup: if suggestedGadgetCheck(gadgetinstructions,pickup_allowed,pickup_notallowed): toadd={} toadd[gadget] = gadgetinstructions resulthash = "pickup pointer into "+r.lower() if not resulthash in suggestions: suggestions[resulthash] = toadd else: suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) if not r in pickedupin: pickedupin.append(r) if len(pickedupin) == 0: for r in arch_aware_regs: for r2 in arch_aware_regs: pickup_allowed = ["NOP","RETN ","INC ","DEC ","OR ","XOR ","MOV ","LEA ","ADD ","SUB ","POP", "ADC ","FPATAN", "TEST ", "CMP "] pickup_allowed.append("MOV "+r+","+PTR_SIZE_DIRECTIVE+" SS:["+r2+"+") pickup_allowed.append("MOV "+r+","+PTR_SIZE_DIRECTIVE+" DS:["+r2+"+") pickup_target = ["MOV "+r+","+PTR_SIZE_DIRECTIVE+" SS:["+r2+"+", "MOV "+r+","+PTR_SIZE_DIRECTIVE+" DS:["+r2+"+"] pickup_notallowed = ["POP "+r, "MOV "+r+",E", "LEA "+r+",E", "MOV ESP", "XOR ESP", "LEA ESP", "MOV DWORD PTR"] if arch == 64: pickup_notallowed.extend(["MOV RSP", "XOR RSP", "LEA RSP", "MOV QWORD PTR"]) for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() allowedpickup = False for allowed in pickup_target: if gadgetinstructions.find(allowed) == 2 and gadgetinstructions.count(PTR_SIZE_DIRECTIVE) == 1: allowedpickup = True break if allowedpickup: if suggestedGadgetCheck(gadgetinstructions,pickup_allowed,pickup_notallowed): toadd={} toadd[gadget] = gadgetinstructions resulthash = "pickup pointer into "+r.lower() if not resulthash in suggestions: suggestions[resulthash] = toadd else: suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) if not r in pickedupin: pickedupin.append(r) # move pointer into another pointer # ================================= for reg in arch_aware_regs: #from for reg2 in arch_aware_regs: #to if reg != reg2: moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "XCHG ", "ADC ","FPATAN", "TEST ", "CMP "] moveptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:","PUSHAD","POPAD", "DEC ESP", "DEC RSP"] suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) # if we didn't find any, expand the search if not ("move " + reg + " -> " + reg2).lower() in suggestions: moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "XCHG ", "ADC ","FPATAN", "TEST ", "CMP "] moveptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"PUSHAD","POPAD", "DEC ESP", "DEC RSP"] suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) reg2 = STACK_POINTER #special case if reg != reg2: moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "MOV ", "XCHG ", "ADC ", "TEST ", "CMP "] moveptr_notallowed = ["ADD "+reg2, "ADC "+reg2, "POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:","PUSHAD","POPAD", "DEC ESP", "DEC RSP"] suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) # xor pointer into another pointer # ================================= for reg in arch_aware_regs: #from for reg2 in arch_aware_regs: #to if reg != reg2: xorptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "XCHG ", "ADC ","FPATAN", "TEST ", "CMP "] xorptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:","PUSHAD","POPAD", "DEC ESP", "DEC RSP"] suggestions = mergeOpcodes(suggestions,getRegToReg("XOR",reg,reg2,ropchains,xorptr_allowed,xorptr_notallowed)) # get stack pointer # ================= for reg in arch_aware_regs: moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ","MOV ", "ADC ","FPATAN", "TEST ", "CMP "] moveptr_notallowed = ["POP ESP","MOV ESP,","XCHG ESP,","XOR ESP","LEA ESP,","AND ESP", "ADD ESP", "],","SUB ESP","OR ESP", "POP "+reg,"MOV "+reg,"XCHG "+reg,"XOR "+reg,"LEA "+reg,"AND "+reg] suggestions = mergeOpcodes(suggestions,getRegToReg("MOVE",STACK_POINTER,reg,allchains,moveptr_allowed,moveptr_notallowed)) # add something to register # ========================= for reg in arch_aware_regs: #from for reg2 in arch_aware_regs: #to if reg != reg2: moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ","FPATAN", "TEST ", "CMP "] moveptr_notallowed = ["POP "+reg2,"MOV "+reg2+",","XCHG "+reg2+",","XOR "+reg2,"LEA "+reg2+",","AND "+reg2,"DS:","SS:", "DEC ESP", "DEC RSP"] suggestions = mergeOpcodes(suggestions,getRegToReg("ADD",reg,reg2,ropchains,moveptr_allowed,moveptr_notallowed)) # add value to register # ========================= for reg in regs: #to moveptr_allowed = ["NOP","RETN","POP ","INC ","DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ", "SUB ","FPATAN", "TEST ", "CMP "] moveptr_notallowed = ["POP "+reg,"MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP", "DEC RSP"] suggestions = mergeOpcodes(suggestions, getRegToReg("ADDVAL",reg,reg,ropchains,moveptr_allowed,moveptr_notallowed)) #inc reg # ======= for reg in regs: moveptr_allowed = ["NOP","RETN","POP ","INC " + reg,"DEC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ", "SUB ","FPATAN", "TEST ", "CMP "] moveptr_notallowed = ["POP "+reg,"MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP", "DEC RSP", "DEC "+reg] suggestions = mergeOpcodes(suggestions,getRegToReg("INC",reg,reg,ropchains,moveptr_allowed,moveptr_notallowed)) #dec reg # ======= for reg in regs: moveptr_allowed = ["NOP","RETN","POP ","DEC " + reg,"INC ","OR ","XOR ","ADD ","PUSH ","AND ", "ADC ", "SUB ","FPATAN", "TEST ", "CMP "] moveptr_notallowed = ["POP "+reg,"MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP", "DEC RSP", "INC "+reg] suggestions = mergeOpcodes(suggestions,getRegToReg("DEC",reg,reg,ropchains,moveptr_allowed,moveptr_notallowed)) #popad reg # ======= if arch == 32: popad_allowed = ["POPAD","RETN","INC ","DEC ","OR ","XOR ","ADD ","AND ", "ADC ", "SUB ","FPATAN","POP ", "TEST ", "CMP "] popad_notallowed = ["POP ESP","PUSH ESP","MOV ESP","ADD ESP", "INC ESP","DEC ESP","XOR ESP","LEA ESP","SS:","DS:"] for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() if gadgetinstructions.find("POPAD") == 2: if suggestedGadgetCheck(gadgetinstructions,popad_allowed,popad_notallowed): toadd={} toadd[gadget] = gadgetinstructions if not "popad" in suggestions: suggestions["popad"] = toadd else: suggestions["popad"] = mergeOpcodes(suggestions["popad"],toadd) # pop # === for reg in regs: pop_allowed = "POP "+reg+" # RETN" pop_notallowed = [] for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() if gadgetinstructions.find(pop_allowed) == 2: resulthash = "pop "+reg.lower() toadd = {} toadd[gadget] = gadgetinstructions if not resulthash in suggestions: suggestions[resulthash] = toadd else: suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) # check if we have a pop for each reg for reg in regs: r = reg.lower() if not "pop "+r in suggestions: pop_notallowed = ["MOV "+reg+",","XCHG "+reg+",","XOR "+reg,"LEA "+reg+",","DS:","SS:", "DEC ESP", "DEC "+reg, "INC " + reg,"PUSH ","XOR "+reg] if arch == 64: moveptr_notallowed.append("DEC RSP") for rchain in ropchains: rparts = ropchains[rchain].strip().split("#") chainok = False if rparts[1].strip() == "POP " + reg: chainok = True if chainok: for rpart in rparts: thisinstr = rpart.strip() for pna in pop_notallowed: if thisinstr.find(pna) > -1: chainok = False break if chainok: toadd = {} toadd[rchain] = thisinstr if not "pop " + r in suggestions: suggestions["pop " + r] = toadd else: suggestions["pop " + r] = mergeOpcodes(suggestions["pop " + r],toadd) # neg # === for reg in regs: neg_allowed = "NEG "+reg+" # RETN" neg_notallowed = [] for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() if gadgetinstructions.find(neg_allowed) == 2: resulthash = "neg "+reg.lower() toadd = {} toadd[gadget] = gadgetinstructions if not resulthash in suggestions: suggestions[resulthash] = toadd else: suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) # empty # ===== for reg in regs: empty_allowed = ["XOR "+reg+","+reg+" # RETN","MOV "+reg+",FFFFFFFF # INC "+reg+" # RETN", "SUB "+reg+","+reg+" # RETN", "PUSH 0 # POP "+reg + " # RETN", "IMUL "+reg+","+reg+",0 # RETN"] empty_notallowed = [] for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() for empty in empty_allowed: if gadgetinstructions.find(empty) == 2: resulthash = "clear "+reg.lower() toadd = {} toadd[gadget] = gadgetinstructions if not resulthash in suggestions: suggestions[resulthash] = toadd else: suggestions[resulthash] = mergeOpcodes(suggestions[resulthash],toadd) return suggestions def getRegToReg(type,fromreg,toreg,ropchains,moveptr_allowed,moveptr_notallowed): moveptr = [] instrwithout = "" toreg = toreg.upper() srcval = False resulthash = "" musthave = "" if type == "MOVE": moveptr.append("MOV "+toreg+","+fromreg) moveptr.append("LEA "+toreg+","+fromreg) #if not (fromreg == "ESP" or toreg == "ESP"): moveptr.append("XCHG "+fromreg+","+toreg) moveptr.append("XCHG "+toreg+","+fromreg) moveptr.append("PUSH "+fromreg) moveptr.append("ADD "+toreg+","+fromreg) moveptr.append("ADC "+toreg+","+fromreg) moveptr.append("XOR "+toreg+","+fromreg) if type == "XOR": moveptr.append("XOR "+toreg+","+fromreg) if type == "ADD": moveptr.append("ADD "+toreg+","+fromreg) moveptr.append("ADC "+toreg+","+fromreg) moveptr.append("XOR "+toreg+","+fromreg) if type == "ADDVAL": moveptr.append("ADD "+toreg+",") moveptr.append("ADC "+toreg+",") moveptr.append("XOR "+toreg+",") moveptr.append("SUB "+toreg+",") srcval = True resulthash = "add value to " + toreg if type == "INC": moveptr.append("INC "+toreg) resulthash = "inc " + toreg if type == "DEC": moveptr.append("DEC "+toreg) resulthash = "dec " + toreg results = {} if resulthash == "": resulthash = type +" "+fromreg+" -> "+toreg resulthash = resulthash.lower() for tocheck in moveptr: origtocheck = tocheck for gadget in ropchains: gadgetinstructions = ropchains[gadget].strip() if gadgetinstructions.find(tocheck) == 2: moveon = True if srcval: #check if src is a value inparts = gadgetinstructions.split(",") if len(inparts) > 1: subinparts = inparts[1].split(" ") if isHexString(subinparts[0].strip()): tocheck = tocheck + subinparts[0].strip() else: moveon = False if moveon: instrwithout = gadgetinstructions.replace(tocheck,"") if tocheck == "PUSH "+fromreg: popreg = instrwithout.find("POP "+toreg) popall = instrwithout.find("POP") #make sure pop matches push nrpush = gadgetinstructions.count("PUSH ") nrpop = gadgetinstructions.count("POP ") pushpopmatch = False if nrpop >= nrpush: pushes = [] pops = [] ropparts = gadgetinstructions.split(" # ") pushindex = 0 popindex = 0 cntpush = 0 cntpop = nrpush for parts in ropparts: if parts.strip() != "": if parts.strip().find("PUSH ") > -1: pushes.append(parts) if parts.strip() == "PUSH "+fromreg: cntpush += 1 if parts.strip().find("POP ") > -1: pops.append(parts) if parts.strip() == "POP "+toreg: cntpop -= 1 if cntpush == cntpop: #dbg.log("%s : POPS : %d, PUSHES : %d, pushindex : %d, popindex : %d" % (gadgetinstructions,len(pops),len(pushes),pushindex,popindex)) #dbg.log("push at %d, pop at %d" % (cntpush,cntpop)) pushpopmatch = True if (popreg == popall) and instrwithout.count("POP "+toreg) == 1 and pushpopmatch: toadd={} toadd[gadget] = gadgetinstructions if not resulthash in results: results[resulthash] = toadd else: results[resulthash] = mergeOpcodes(results[resulthash],toadd) else: if suggestedGadgetCheck(instrwithout,moveptr_allowed,moveptr_notallowed): toadd={} toadd[gadget] = gadgetinstructions if not resulthash in results: results[resulthash] = toadd else: results[resulthash] = mergeOpcodes(results[resulthash],toadd) tocheck = origtocheck return results def suggestedGadgetCheck(instructions,allowed,notallowed): individual = instructions.split("#") cnt = 0 allgood = True toskip = False while (cnt < len(individual)-1) and allgood: # do not check last one, which is the ending instruction thisinstr = individual[cnt].upper() if thisinstr.strip() != "": toskip = False foundinstruction = False for notok in notallowed: if thisinstr.find(notok) > -1: toskip= True if not toskip: for ok in allowed: if thisinstr.find(ok) > -1: foundinstruction = True allgood = foundinstruction else: allgood = False cnt += 1 return allgood def dumpMemoryToFile(address,size,filename): """ Dump 'size' bytes of memory to a file Arguments: address - the address where to read size - the number of bytes to read filename - the name of the file where to write the file Return: Boolean - True if the write succeeded """ WRITE_SIZE = 10000 dbg.log("Dumping %d bytes from address 0x%08x to %s..." % (size, address, filename)) out = open(filename,'wb') # write by increments of 10000 bytes current = 0 while current < size : bytesToWrite = size - current if ( bytesToWrite >= WRITE_SIZE): bytes = dbg.readMemory(address+current,WRITE_SIZE) out.write(bytes) current += WRITE_SIZE else: bytes = dbg.readMemory(address+current,bytesToWrite) out.write(bytes) current += bytesToWrite out.close() return True def checkSEHOverwrite(address, nseh, seh): """ Checks if the current SEH record is overwritten with a cyclic pattern Input : address of SEH record, nseh value, seh value Returns : array. Non empty array = SEH is overwritten Array contents : [0] : type (normal, upper, lower, unicode) [1] : offset to nseh """ pattypes = ["normal","upper","lower","unicode"] overwritten = [] global silent silent = True fullpattern = createPattern(50000,{}) for pattype in pattypes: regpattern = fullpattern hexpat = toHex(seh) hexpat = toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[2]+hexpat[3])+toAscii(hexpat[0]+hexpat[1]) factor = 1 goback = 4 if pattype == "upper": regpattern = regpattern.upper() if pattype == "lower": regpattern = regpattern.lower() if pattype == "unicode": hexpat = dbg.readMemory(address,8) hexpat = hexpat.replace('\x00','') goback = 2 offset = regpattern.find(hexpat)-goback thissize = 0 if offset > -1: thepointer = MnPointer(address) if thepointer.isOnStack(): thissize = getPatternLength(address+4,pattype) if thissize > 0: overwritten = [pattype,offset] break silent = False return overwritten def goFindMSP(distance = 0,args = {}): """ Finds all references to cyclic pattern in memory Arguments: None Return: Dictonary with results of the search operation """ results = {} regs = dbg.getRegs() criteria = {} criteria["accesslevel"] = "*" tofile = "" global silent oldsilent = silent silent=True fullpattern = createPattern(50000,args) factor = 1 #are we attached to an application ? if dbg.getDebuggedPid() == 0: dbg.log("*** Attach to an application, and trigger a crash with a cyclic pattern ! ***",highlight=1) return {} #1. find beginning of cyclic pattern in memory ? patbegin = createPattern(6,args) silent=oldsilent pattypes = ["normal","unicode","lower","upper"] if not silent: dbg.log("[+] Looking for cyclic pattern in memory") tofile += "[+] Looking for cyclic pattern in memory\n" for pattype in pattypes: dbg.updateLog() searchPattern = [] #create search pattern factor = 1 if pattype == "normal": searchPattern.append([patbegin, patbegin]) if pattype == "unicode": patbegin_unicode = "" factor = 0.5 for pbyte in patbegin: patbegin_unicode += pbyte + "\x00" searchPattern.append([patbegin_unicode, patbegin_unicode]) if pattype == "lower": searchPattern.append([patbegin.lower(), patbegin.lower()]) if pattype == "upper": searchPattern.append([patbegin.upper(), patbegin.upper()]) #search pointers = searchInRange(searchPattern,0,TOP_USERLAND,criteria) memory={} if len(pointers) > 0: for ptrtypes in pointers: for ptr in pointers[ptrtypes]: #get size thissize = getPatternLength(ptr,pattype,args) if thissize > 0: if not silent: dbg.log(" Cyclic pattern (%s) found at 0x%s (length %d bytes)" % (pattype,toHex(ptr),thissize)) tofile += " Cyclic pattern (%s) found at 0x%s (length %d bytes)\n" % (pattype,toHex(ptr),thissize) if not ptr in memory: memory[ptr] = ([thissize,pattype]) #get distance from ESP if "ESP" in regs: thisesp = regs["ESP"] thisptr = MnPointer(ptr) if thisptr.isOnStack(): if ptr > thisesp: if not silent: dbg.log(" - Stack pivot between %d & %d bytes needed to land in this pattern" % (ptr-thisesp,ptr-thisesp+thissize)) tofile += " - Stack pivot between %d & %d bytes needed to land in this pattern\n" % (ptr-thisesp,ptr-thisesp+thissize) if not "memory" in results: results["memory"] = memory #2. registers overwritten ? if not silent: dbg.log("[+] Examining registers") tofile += "\n[+] Examining registers\n" registers = {} registers_to = {} for reg in regs: for pattype in pattypes: dbg.updateLog() regpattern = fullpattern hexpat = toHex(regs[reg]) hexpatr = hexpat factor = 1 hexpat = toAscii(hexpat[6]+hexpat[7])+toAscii(hexpat[4]+hexpat[5])+toAscii(hexpat[2]+hexpat[3])+toAscii(hexpat[0]+hexpat[1]) hexpatrev = toAscii(hexpatr[0]+hexpatr[1])+toAscii(hexpatr[2]+hexpatr[3])+toAscii(hexpatr[4]+hexpatr[5])+toAscii(hexpatr[6]+hexpatr[7]) if pattype == "upper": regpattern = regpattern.upper() if pattype == "lower": regpattern = regpattern.lower() if pattype == "unicode": regpattern = toUnicode(regpattern) factor = 0.5 offset = regpattern.find(hexpat) if offset > -1: if pattype == "unicode": offset = offset * factor if not silent: dbg.log(" %s contains %s pattern : 0x%s (offset %d)" % (reg,pattype,toHex(regs[reg]),offset)) tofile += " %s contains %s pattern : 0x%s (offset %d)\n" % (reg,pattype,toHex(regs[reg]),offset) if not reg in registers: registers[reg] = ([regs[reg],offset,pattype]) else: # maybe it's reversed ? offset = regpattern.find(hexpatrev) if offset > -1: if pattype == "unicode": offset = offset * factor if not silent: dbg.log(" %s contains %s pattern (reversed) : 0x%s (offset %d)" % (reg,pattype,toHex(regs[reg]),offset)) tofile += " %s contains %s pattern (reversed) : 0x%s (offset %d)\n" % (reg,pattype,toHex(regs[reg]),offset) if not reg in registers: registers[reg] = ([regs[reg],offset,pattype]) # maybe register points into cyclic pattern mempat = "" try: mempat = dbg.readMemory(regs[reg],4) except: pass if mempat != "": if pattype == "normal": regpattern = fullpattern if pattype == "upper": regpattern = fullpattern.upper() if pattype == "lower": regpattern = fullpattern.lower() if pattype == "unicode": mempat = dbg.readMemory(regs[reg],8) mempat = mempat.replace('\x00','') offset = regpattern.find(mempat) if offset > -1: thissize = getPatternLength(regs[reg],pattype,args) if thissize > 0: if not silent: dbg.log(" %s (0x%s) points at offset %d in %s pattern (length %d)" % (reg,toHex(regs[reg]),offset,pattype,thissize)) tofile += " %s (0x%s) points at offset %d in %s pattern (length %d)\n" % (reg,toHex(regs[reg]),offset,pattype,thissize) if not reg in registers_to: registers_to[reg] = ([regs[reg],offset,thissize,pattype]) else: registers_to[reg] = ([regs[reg],offset,thissize,pattype]) else: # reversed ? offset = regpattern.find(mempat[::-1]) if offset > -1: thissize = getPatternLength(regs[reg],pattype,args) if thissize > 0: if not silent: dbg.log(" %s (0x%s) points at offset %d in (reversed) %s pattern (length %d)" % (reg,toHex(regs[reg]),offset,pattype,thissize)) tofile += " %s (0x%s) points at offset %d in (reversed) %s pattern (length %d)\n" % (reg,toHex(regs[reg]),offset,pattype,thissize) if not reg in registers_to: registers_to[reg] = ([regs[reg],offset,thissize,pattype]) else: registers_to[reg] = ([regs[reg],offset,thissize,pattype]) if not "registers" in results: results["registers"] = registers if not "registers_to" in results: results["registers_to"] = registers_to #3. SEH record overwritten ? seh = {} if not silent: dbg.log("[+] Examining SEH chain") tofile += "\n[+] Examining SEH chain\n" thissehchain=dbg.getSehChain() for chainentry in thissehchain: address = chainentry[0] sehandler = chainentry[1] nseh = 0 nsehvalue = 0 nsehascii = "" try: nsehascii = dbg.readMemory(address,4) nsehvalue = struct.unpack(' -1: thepointer = MnPointer(chainentry[0]) if thepointer.isOnStack(): thissize = getPatternLength(address+4,pattype) if thissize > 0: thissize = (thissize - takeout)/divide if not silent: dbg.log(" SEH record (nseh field) at 0x%s overwritten with %s pattern : 0x%s (offset %d), followed by %d bytes of cyclic data after the handler" % (toHex(chainentry[0]),pattype,nseh,offset,thissize)) tofile += " SEH record (nseh field) at 0x%s overwritten with %s pattern : 0x%s (offset %d), followed by %d bytes of cyclic data after the handler\n" % (toHex(chainentry[0]),pattype,nseh,offset,thissize) if not chainentry[0]+4 in seh: seh[chainentry[0]+4] = ([chainentry[1],offset,pattype,thissize]) if not "seh" in results: results["seh"] = seh stack = {} stackcontains = {} #4. walking stack if "ESP" in regs: curresp = regs["ESP"] if not silent: if distance == 0: extratxt = "(entire stack)" else: extratxt = "(+- "+str(distance)+" bytes)" dbg.log("[+] Examining stack %s - looking for cyclic pattern" % extratxt) tofile += "\n[+] Examining stack %s - looking for cyclic pattern\n" % extratxt # get stack this address belongs to stacks = getStacks() thisstackbase = 0 thisstacktop = 0 if distance < 1: for tstack in stacks: if (stacks[tstack][0] < curresp) and (curresp < stacks[tstack][1]): thisstackbase = stacks[tstack][0] thisstacktop = stacks[tstack][1] else: thisstackbase = curresp - distance thisstacktop = curresp + distance + 8 stackcounter = thisstackbase sign="" if not silent: dbg.log(" Walking stack from 0x%s to 0x%s (0x%s bytes)" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter))) tofile += " Walking stack from 0x%s to 0x%s (0x%s bytes)\n" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter)) # stack contains part of a cyclic pattern ? while stackcounter < thisstacktop-4: espoffset = stackcounter - curresp stepsize = 4 dbg.updateLog() if espoffset > -1: sign="+" else: sign="-" cont = dbg.readMemory(stackcounter,4) if len(cont) == 4: contat = cont if contat != "": for pattype in pattypes: dbg.updateLog() regpattern = fullpattern hexpat = contat if pattype == "upper": regpattern = regpattern.upper() if pattype == "lower": regpattern = regpattern.lower() if pattype == "unicode": hexpat1 = dbg.readMemory(stackcounter,4) hexpat2 = dbg.readMemory(stackcounter+4,4) hexpat1 = hexpat1.replace('\x00','') hexpat2 = hexpat2.replace('\x00','') if hexpat1 == "" or hexpat2 == "": #no unicode hexpat = "" break else: hexpat = hexpat1 + hexpat2 if len(hexpat) == 4: offset = regpattern.find(hexpat) currptr = stackcounter if offset > -1: thissize = getPatternLength(currptr,pattype) offsetvalue = int(str(espoffset).replace("-","")) if thissize > 0: stepsize = thissize if thissize/4*4 != thissize: stepsize = (thissize/4*4) + 4 # align stack again if not silent: espoff = 0 espsign = "+" if ((stackcounter + thissize) >= curresp): espoff = (stackcounter + thissize) - curresp else: espoff = curresp - (stackcounter + thissize) espsign = "-" dbg.log(" 0x%s : Contains %s cyclic pattern at ESP%s0x%s (%s%s) : offset %d, length %d (-> 0x%s : ESP%s0x%s)" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,offset,thissize,toHex(stackcounter+thissize-1),espsign,rmLeading(toHex(espoff),"0"))) tofile += " 0x%s : Contains %s cyclic pattern at ESP%s0x%s (%s%s) : offset %d, length %d (-> 0x%s : ESP%s0x%s)\n" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,offset,thissize,toHex(stackcounter+thissize-1),espsign,rmLeading(toHex(espoff),"0")) if not currptr in stackcontains: stackcontains[currptr] = ([offsetvalue,sign,offset,thissize,pattype]) else: #if we are close to ESP, change stepsize to 1 if offsetvalue <= 256: stepsize = 1 stackcounter += stepsize # stack has pointer into cyclic pattern ? if not silent: if distance == 0: extratxt = "(entire stack)" else: extratxt = "(+- "+str(distance)+" bytes)" dbg.log("[+] Examining stack %s - looking for pointers to cyclic pattern" % extratxt) tofile += "\n[+] Examining stack %s - looking for pointers to cyclic pattern\n" % extratxt # get stack this address belongs to stacks = getStacks() thisstackbase = 0 thisstacktop = 0 if distance < 1: for tstack in stacks: if (stacks[tstack][0] < curresp) and (curresp < stacks[tstack][1]): thisstackbase = stacks[tstack][0] thisstacktop = stacks[tstack][1] else: thisstackbase = curresp - distance thisstacktop = curresp + distance + 8 stackcounter = thisstackbase sign="" if not silent: dbg.log(" Walking stack from 0x%s to 0x%s (0x%s bytes)" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter))) tofile += " Walking stack from 0x%s to 0x%s (0x%s bytes)\n" % (toHex(stackcounter),toHex(thisstacktop-4),toHex(thisstacktop-4-stackcounter)) while stackcounter < thisstacktop-4: espoffset = stackcounter - curresp dbg.updateLog() if espoffset > -1: sign="+" else: sign="-" cont = dbg.readMemory(stackcounter,4) if len(cont) == 4: cval="" for sbytes in cont: tval = hex(ord(sbytes)).replace("0x","") if len(tval) < 2: tval="0"+tval cval = tval+cval try: contat = dbg.readMemory(hexStrToInt(cval),4) except: contat = "" if contat != "": for pattype in pattypes: dbg.updateLog() regpattern = fullpattern hexpat = contat if pattype == "upper": regpattern = regpattern.upper() if pattype == "lower": regpattern = regpattern.lower() if pattype == "unicode": hexpat1 = dbg.readMemory(stackcounter,4) hexpat2 = dbg.readMemory(stackcounter+4,4) hexpat1 = hexpat1.replace('\x00','') hexpat2 = hexpat2.replace('\x00','') if hexpat1 == "" or hexpat2 == "": #no unicode hexpat = "" break else: hexpat = hexpat1 + hexpat2 if len(hexpat) == 4: offset = regpattern.find(hexpat) currptr = hexStrToInt(cval) if offset > -1: thissize = getPatternLength(currptr,pattype) if thissize > 0: offsetvalue = int(str(espoffset).replace("-","")) if not silent: dbg.log(" 0x%s : Pointer into %s cyclic pattern at ESP%s0x%s (%s%s) : 0x%s : offset %d, length %d" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,toHex(currptr),offset,thissize)) tofile += " 0x%s : Pointer into %s cyclic pattern at ESP%s0x%s (%s%s) : 0x%s : offset %d, length %d\n" % (toHex(stackcounter),pattype,sign,rmLeading(toHex(offsetvalue),"0"),sign,offsetvalue,toHex(currptr),offset,thissize) if not currptr in stack: stack[currptr] = ([offsetvalue,sign,offset,thissize,pattype]) stackcounter += 4 else: dbg.log("** Are you connected to an application ?",highlight=1) if not "stack" in results: results["stack"] = stack if not "stackcontains" in results: results["stackcontains"] = stack if tofile != "": objfindmspfile = MnLog("findmsp.txt") findmspfile = objfindmspfile.reset() objfindmspfile.write(tofile,findmspfile) return results #-----------------------------------------------------------------------# # convert arguments to criteria #-----------------------------------------------------------------------# def args2criteria(args,modulecriteria,criteria): thisversion,thisrevision = getVersionInfo(inspect.stack()[0][1]) thisversion = thisversion.replace("'","") dbg.logLines("\n---------- Mona command started on %s (v%s, rev %s) ----------" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),thisversion,thisrevision)) dbg.log("[+] Processing arguments and criteria") global ptr_to_get # meets access level ? criteria["accesslevel"] = "X" if "x" in args : if not args["x"].upper() in ["*","R","RW","RX","RWX","W","WX","X"]: dbg.log("invalid access level : %s" % args["x"], highlight=1) criteria["accesslevel"] = "" else: criteria["accesslevel"] = args["x"].upper() dbg.log(" - Pointer access level : %s" % criteria["accesslevel"]) # query OS modules ? if "o" in args and args["o"]: modulecriteria["os"] = False dbg.log(" - Ignoring OS modules") # allow nulls ? if "n" in args and args["n"]: criteria["nonull"] = True dbg.log(" - Ignoring pointers that have null bytes") # override list of modules to query ? if "m" in args: if type(args["m"]).__name__.lower() != "bool": modulecriteria["modules"] = args["m"] dbg.log(" - Only querying modules %s" % args["m"]) # limit nr of pointers to search ? if "p" in args: if str(args["p"]).lower() != "true": ptr_to_get = int(args["p"].strip()) if ptr_to_get > 0: dbg.log(" - Maximum nr of pointers to return : %d" % ptr_to_get) # only want to see specific type of pointers ? if "cp" in args: ptrcriteria = args["cp"].split(",") for ptrcrit in ptrcriteria: ptrcrit=ptrcrit.strip("'") ptrcrit=ptrcrit.strip('"').lower().strip() criteria[ptrcrit] = True dbg.log(" - Pointer criteria : %s" % ptrcriteria) if "cbp" in args: dbg.log(" * Trying to use '-cbp' instead of '-cpb'?", highlight=True) if not "cpb" in args: dbg.log(" * I'll try to fix your typo myself, but please pay attention to the syntax next time", highlight=True) args["cpb"] = args["cbp"] if "cpb" in args: badchars = args["cpb"] badchars = badchars.replace("'","") badchars = badchars.replace('"',"") badchars = badchars.replace("\\x","") # see if we need to expand .. bpos = 0 newbadchars = "" while bpos < len(badchars): curchar = badchars[bpos]+badchars[bpos+1] if curchar == "..": pos = bpos if pos > 1 and pos <= len(badchars)-4: # get byte before and after .. bytebefore = badchars[pos-2] + badchars[pos-1] byteafter = badchars[pos+2] + badchars[pos+3] bbefore = int(bytebefore,16) bafter = int(byteafter,16) insertbytes = "" bbefore += 1 while bbefore < bafter: insertbytes += "%02x" % bbefore bbefore += 1 newbadchars += insertbytes else: newbadchars += curchar bpos += 2 badchars = newbadchars cnt = 0 strb = "" while cnt < len(badchars): strb=strb+binascii.a2b_hex(badchars[cnt]+badchars[cnt+1]) cnt=cnt+2 criteria["badchars"] = strb dbg.log(" - Bad char filter will be applied to pointers : %s " % args["cpb"]) if "cm" in args: modcriteria = args["cm"].split(",") for modcrit in modcriteria: modcrit=modcrit.strip("'") modcrit=modcrit.strip('"').lower().strip() #each criterium has 1 or 2 parts : criteria=value modcritparts = modcrit.split("=") try: if len(modcritparts) < 2: # set to True, no value given modulecriteria[modcritparts[0].strip()] = True else: # read the value modulecriteria[modcritparts[0].strip()] = (modcritparts[1].strip() == "true") except: continue if (inspect.stack()[1][3] == "procShowMODULES"): modcriteria = args["cm"].split(",") for modcrit in modcriteria: modcrit=modcrit.strip("'") modcrit=modcrit.strip('"').lower().strip() if modcrit.startswith("+"): modulecriteria[modcrit]=True else: modulecriteria[modcrit]=False dbg.log(" - Module criteria : %s" % modcriteria) return modulecriteria,criteria #manage breakpoint on selected exported/imported functions from selected modules def doManageBpOnFunc(modulecriteria,criteria,funcfilter,mode="add",type="export"): """ Sets a breakpoint on selected exported/imported functions from selected modules Arguments : modulecriteria - Dictionary funcfilter - comma separated string indicating functions to set bp on must contains "*" to select all functions mode - "add" to create bp's, "del" to remove bp's Returns : nothing """ type = type.lower() namecrit = funcfilter.strip('"').strip("'").split(",") if mode == "add" or mode == "del" or mode == "list": if not silent: dbg.log("[+] Enumerating %sed functions" % type) modulestosearch = getModulesToQuery(modulecriteria) bpfuncs = {} for thismodule in modulestosearch: if not silent: dbg.log(" Querying module %s" % thismodule) # get all themod = dbg.getModule(thismodule) tmod = MnModule(thismodule) shortname = tmod.getShortName() syms = themod.getSymbols() # get funcs funcs = {} if type == "export": funcs = tmod.getEAT() else: funcs = tmod.getIAT() if not silent: dbg.log(" Total nr of %sed functions : %d" % (type,len(funcs))) for func in funcs: if meetsCriteria(MnPointer(func), criteria): funcname = funcs[func].lower() setbp = False if "*" in namecrit: setbp = True else: for crit in namecrit: crit = crit.lower() tcrit = crit.replace("*","") if (crit.startswith("*") and crit.endswith("*")) or (crit.find("*") == -1): if funcname.find(tcrit) > -1: setbp = True elif crit.startswith("*"): if funcname.endswith(tcrit): setbp = True elif crit.endswith("*"): if funcname.startswith(tcrit): setbp = True if setbp: if type == "export": if not func in bpfuncs: bpfuncs[func] = funcs[func] else: ptr = 0 try: #read pointer of imported function ptr=struct.unpack(' 0: if not ptr in bpfuncs: bpfuncs[ptr] = funcs[func] if __DEBUGGERAPP__ == "WinDBG": # let's do a few searches for crit in namecrit: if crit.find("*") == -1: crit = "*" + crit + "*" modsearch = "x %s!%s" % (shortname,crit) output = dbg.nativeCommand(modsearch) outputlines = output.split("\n") for line in outputlines: if line.replace(" ","") != "": linefields = line.split(" ") if len(linefields) > 1: ptr = hexStrToInt(linefields[0]) cnt = 1 while cnt < len(linefields)-1: if linefields[cnt] != "": funcname = linefields[cnt] break cnt += 1 if not ptr in bpfuncs: bpfuncs[ptr] = funcname if not silent: dbg.log("[+] Total nr of breakpoints to process : %d" % len(bpfuncs)) if len(bpfuncs) > 0: for funcptr in bpfuncs: if mode == "add": dbg.log("Set bp at 0x%s (%s in %s)" % (toHex(funcptr),bpfuncs[funcptr],MnPointer(funcptr).belongsTo())) try: dbg.setBreakpoint(funcptr) except: dbg.log("Failed setting bp at 0x%s" % toHex(funcptr)) elif mode == "del": dbg.log("Remove bp at 0x%s (%s in %s)" % (toHex(funcptr),bpfuncs[funcptr],MnPointer(funcptr).belongsTo())) try: dbg.deleteBreakpoint(funcptr) except: dbg.log("Skipped removal of bp at 0x%s" % toHex(funcptr)) elif mode == "list": dbg.log("Match found at 0x%s (%s in %s)" % (toHex(funcptr),bpfuncs[funcptr],MnPointer(funcptr).belongsTo())) return def getAbsolutePath(filename): # attempt to read input file from workingfolder (if any) # unless absolute path has been specified if os.path.isabs(filename) or filename == "": return filename else: debuggedname = dbg.getDebuggedName() thispid = dbg.getDebuggedPid() if thispid == 0: debuggedname = "_no_name_" thisconfig = MnConfig() workingfolder = thisconfig.get("workingfolder").rstrip("\\").strip() #strip extension from debuggedname parts = debuggedname.split(".") extlen = len(parts[len(parts)-1])+1 debuggedname = debuggedname[0:len(debuggedname)-extlen] debuggedname = debuggedname.replace(" ","_") workingfolder = workingfolder.replace('%p', debuggedname) workingfolder = workingfolder.replace('%i', str(thispid)) # create workingfolder (if it does not exist yet) #does working folder exist ? if workingfolder != "": if not os.path.exists(workingfolder): try: dbg.log(" - Creating working folder %s" % workingfolder) #recursively create folders os.makedirs(workingfolder) dbg.log(" - Folder created") except: dbg.log(" ** Unable to create working folder %s, the debugger program folder will be used instead" % workingfolder,highlight=1) return os.path.join(workingfolder, filename) #-----------------------------------------------------------------------# # main #-----------------------------------------------------------------------# def main(args): dbg.createLogWindow() global currentArgs currentArgs = copy.copy(args) try: starttime = datetime.datetime.now() ptr_counter = 0 # initialize list of commands commands = {} # ----- HELP ----- # def getBanner(): banners = {} bannertext = "" bannertext += " |------------------------------------------------------------------|\n" bannertext += " | __ __ |\n" bannertext += " | _________ ________ / /___ _____ / /____ ____ _____ ___ |\n" bannertext += " | / ___/ __ \/ ___/ _ \/ / __ `/ __ \ / __/ _ \/ __ `/ __ `__ \ |\n" bannertext += " | / /__/ /_/ / / / __/ / /_/ / / / / / /_/ __/ /_/ / / / / / / |\n" bannertext += " | \___/\____/_/ \___/_/\__,_/_/ /_/ \__/\___/\__,_/_/ /_/ /_/ |\n" bannertext += " | |\n" bannertext += " | https://www.corelan.be | https://www.corelan-training.com |\n" bannertext += " |------------------------------------------------------------------|\n" banners[0] = bannertext bannertext = "" bannertext += " |------------------------------------------------------------------|\n" bannertext += " | _ __ ___ ___ _ __ __ _ _ __ _ _ |\n" bannertext += " | | '_ ` _ \ / _ \ | '_ \ / _` | | '_ \ | | | | |\n" bannertext += " | | | | | | || (_) || | | || (_| | _ | |_) || |_| | |\n" bannertext += " | |_| |_| |_| \___/ |_| |_| \__,_|(_)| .__/ \__, | |\n" bannertext += " | |_| |___/ |\n" bannertext += " | |\n" bannertext += " |------------------------------------------------------------------|\n" banners[1] = bannertext bannertext = "" bannertext += " |------------------------------------------------------------------|\n" bannertext += " | |\n" bannertext += " | _____ ___ ____ ____ ____ _ |\n" bannertext += " | / __ `__ \/ __ \/ __ \/ __ `/ https://www.corelan.be |\n" bannertext += " | / / / / / / /_/ / / / / /_/ / https://www.corelan-training.com|\n" bannertext += " | /_/ /_/ /_/\____/_/ /_/\__,_/ |\n" bannertext += " | |\n" bannertext += " |------------------------------------------------------------------|\n" banners[2] = bannertext bannertext = "" bannertext += "\n .##.....##..#######..##....##....###........########..##....##\n" bannertext += " .###...###.##.....##.###...##...##.##.......##.....##..##..##.\n" bannertext += " .####.####.##.....##.####..##..##...##......##.....##...####..\n" bannertext += " .##.###.##.##.....##.##.##.##.##.....##.....########.....##...\n" bannertext += " .##.....##.##.....##.##..####.#########.....##...........##...\n" bannertext += " .##.....##.##.....##.##...###.##.....##.###.##...........##...\n" bannertext += " .##.....##..#######..##....##.##.....##.###.##...........##...\n\n" banners[3] = bannertext # pick random banner bannerlist = [] for i in range (0, len(banners)): bannerlist.append(i) random.shuffle(bannerlist) return banners[bannerlist[0]] def procHelp(args): dbg.log(" 'mona' - Exploit Development Swiss Army Knife - %s (%sbit)" % (__DEBUGGERAPP__,str(arch))) dbg.log(" Plugin version : %s r%s" % (__VERSION__,__REV__)) dbg.log(" Python version : %s" % (getPythonVersion())) if __DEBUGGERAPP__ == "WinDBG": pykdversion = dbg.getPyKDVersionNr() dbg.log(" PyKD version %s" % pykdversion) dbg.log(" Written by Corelan - https://www.corelan.be") dbg.log(" Project page : https://github.com/corelan/mona") dbg.logLines(getBanner(),highlight=1) dbg.log("Global options :") dbg.log("----------------") dbg.log("You can use one or more of the following global options on any command that will perform") dbg.log("a search in one or more modules, returning a list of pointers :") dbg.log(" -n : Skip modules that start with a null byte. If this is too broad, use") dbg.log(" option -cp nonull instead") dbg.log(" -o : Ignore OS modules") dbg.log(" -p : Stop search after pointers.") dbg.log(" -m : only query the given modules. Be sure what you are doing !") dbg.log(" You can specify multiple modules (comma separated)") dbg.log(" Tip : you can use -m * to include all modules. All other module criteria will be ignored") dbg.log(" Other wildcards : *blah.dll = ends with blah.dll, blah* = starts with blah,") dbg.log(" blah or *blah* = contains blah") dbg.log(" -cm : Apply some additional criteria to the modules to query.") dbg.log(" You can use one or more of the following criteria :") dbg.log(" aslr,safeseh,rebase,nx,os") dbg.log(" You can enable or disable a certain criterium by setting it to true or false") dbg.log(" Example : -cm aslr=true,safeseh=false") dbg.log(" Suppose you want to search for p/p/r in aslr enabled modules, you could call") dbg.log(" !mona seh -cm aslr") dbg.log(" -cp : Apply some criteria to the pointers to return") dbg.log(" Available options are :") dbg.log(" unicode,ascii,asciiprint,upper,lower,uppernum,lowernum,numeric,alphanum,nonull,startswithnull,unicoderev") dbg.log(" Note : Multiple criteria will be evaluated using 'AND', except if you are looking for unicode + one crit") dbg.log(" -cpb '\\x00\\x01' : Provide list with bad chars, applies to pointers") dbg.log(" You can use .. to indicate a range of bytes (in between 2 bad chars)") dbg.log(" -x : Specify desired access level of the returning pointers. If not specified,") dbg.log(" only executable pointers will be returned.") dbg.log(" Access levels can be one of the following values : R,W,X,RW,RX,WX,RWX or *") if not args: args = [] if len(args) > 1: thiscmd = args[1].lower().strip() if thiscmd in commands: dbg.log("") dbg.log("Usage of command '%s' :" % thiscmd) dbg.log("%s" % ("-" * (22 + len(thiscmd)))) dbg.logLines(commands[thiscmd].usage) dbg.log("") else: aliasfound = False for cmd in commands: if commands[cmd].alias == thiscmd: dbg.log("") dbg.log("Usage of command '%s' :" % thiscmd) dbg.log("%s" % ("-" * (22 + len(thiscmd)))) dbg.logLines(commands[cmd].usage) dbg.log("") aliasfound = True if not aliasfound: dbg.logLines("\nCommand %s does not exist. Run !mona to get a list of available commands\n" % thiscmd,highlight=1) else: dbg.logLines("\nUsage :") dbg.logLines("-------\n") dbg.log(" !mona ") dbg.logLines("\nAvailable commands and parameters :\n") items = commands.items() items.sort(key = itemgetter(0)) for item in items: if commands[item[0]].usage != "": aliastxt = "" if commands[item[0]].alias != "": aliastxt = " / " + commands[item[0]].alias dbg.logLines("%s | %s" % (item[0] + aliastxt + (" " * (20 - len(item[0]+aliastxt))), commands[item[0]].description)) dbg.log("") dbg.log("Want more info about a given command ? Run !mona help ",highlight=1) dbg.log("") commands["help"] = MnCommand("help", "show help", "!mona help [command]",procHelp) # ----- Config file management ----- # def procConfig(args): #did we specify -get, -set or -add? showerror = False if not "set" in args and not "get" in args and not "add" in args: showerror = True if "set" in args: if type(args["set"]).__name__.lower() == "bool": showerror = True else: #count nr of words params = args["set"].split(" ") if len(params) < 2: showerror = True if "add" in args: if type(args["add"]).__name__.lower() == "bool": showerror = True else: #count nr of words params = args["add"].split(" ") if len(params) < 2: showerror = True if "get" in args: if type(args["get"]).__name__.lower() == "bool": showerror = True else: #count nr of words params = args["get"].split(" ") if len(params) < 1: showerror = True if showerror: dbg.log("Usage :") dbg.logLines(configUsage,highlight=1) return else: if "get" in args: dbg.log("Reading value from configuration file") monaConfig = MnConfig() thevalue = monaConfig.get(args["get"]) dbg.log("Parameter %s = %s" % (args["get"],thevalue)) if "set" in args: dbg.log("Writing value to configuration file") monaConfig = MnConfig() value = args["set"].split(" ") configparam = value[0].strip() dbg.log("Old value of parameter %s = %s" % (configparam,monaConfig.get(configparam))) configvalue = args["set"][0+len(configparam):len(args["set"])] monaConfig.set(configparam,configvalue) dbg.log("New value of parameter %s = %s" % (configparam,configvalue)) if "add" in args: dbg.log("Writing value to configuration file") monaConfig = MnConfig() value = args["add"].split(" ") configparam = value[0].strip() dbg.log("Old value of parameter %s = %s" % (configparam,monaConfig.get(configparam))) configvalue = monaConfig.get(configparam).strip() + "," + args["add"][0+len(configparam):len(args["add"])].strip() monaConfig.set(configparam,configvalue) dbg.log("New value of parameter %s = %s" % (configparam,configvalue)) # ----- Jump to register ----- # def procFindJ(args): return procFindJMP(args) def procFindJMP(args): #default criteria modulecriteria={} modulecriteria["aslr"] = False modulecriteria["rebase"] = False if (inspect.stack()[1][3] == "procFindJ"): dbg.log(" ** Note : command 'j' has been replaced with 'jmp'. Now launching 'jmp' instead...",highlight=1) criteria={} all_opcodes={} global ptr_to_get ptr_to_get = -1 distancestr = "" mindistance = 0 maxdistance = 0 #did user specify -r ? showerror = False if "r" in args: if type(args["r"]).__name__.lower() == "bool": showerror = True else: #valid register ? thisreg = args["r"].upper().strip() validregs = dbglib.Registers32BitsOrder if not thisreg in validregs: showerror = True else: showerror = True if "distance" in args: if type(args["distance"]).__name__.lower() == "bool": showerror = True else: distancestr = args["distance"] distanceparts = distancestr.split(",") for parts in distanceparts: valueparts = parts.split("=") if len(valueparts) > 1: if valueparts[0].lower() == "min": try: mindistance = int(valueparts[1]) except: mindistance = 0 if valueparts[0].lower() == "max": try: maxdistance = int(valueparts[1]) except: maxdistance = 0 if maxdistance < mindistance: tmp = maxdistance maxdistance = mindistance mindistance = tmp criteria["mindistance"] = mindistance criteria["maxdistance"] = maxdistance if showerror: dbg.log("Usage :") dbg.logLines(jmpUsage,highlight=1) return else: modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) # go for it ! all_opcodes=findJMP(modulecriteria,criteria,args["r"].lower().strip()) # write to log logfile = MnLog("jmp.txt") thislog = logfile.reset() processResults(all_opcodes,logfile,thislog) # ----- Exception Handler Overwrites ----- # def procFindSEH(args): #default criteria modulecriteria={} modulecriteria["safeseh"] = False modulecriteria["aslr"] = False modulecriteria["rebase"] = False criteria = {} specialcases = {} all_opcodes = {} global ptr_to_get ptr_to_get = -1 #what is the caller function (backwards compatibility with pvefindaddr) modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) if "rop" in args: criteria["rop"] = True if "all" in args: criteria["all"] = True specialcases["maponly"] = True else: criteria["all"] = False specialcases["maponly"] = False # go for it ! all_opcodes = findSEH(modulecriteria,criteria) #report findings to log logfile = MnLog("seh.txt") thislog = logfile.reset() processResults(all_opcodes,logfile,thislog,specialcases) # ----- MODULES ------ # def procShowMODULES(args): modulecriteria={} criteria={} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) modulestosearch = getModulesToQuery(modulecriteria) showModuleTable("",modulestosearch) logfile = MnLog("modules.txt") thislog = logfile.reset() # ----- ROP ----- # def procFindROPFUNC(args): #default criteria modulecriteria={} modulecriteria["aslr"] = False #modulecriteria["rebase"] = False modulecriteria["os"] = False criteria={} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) ropfuncs = {} ropfuncoffsets ={} ropfuncs,ropfuncoffsets = findROPFUNC(modulecriteria,criteria) #report findings to log dbg.log("[+] Processing pointers to interesting rop functions") logfile = MnLog("ropfunc.txt") thislog = logfile.reset() processResults(ropfuncs,logfile,thislog) global silent silent = True dbg.log("[+] Processing offsets to pointers to interesting rop functions") logfile = MnLog("ropfunc_offset.txt") thislog = logfile.reset() processResults(ropfuncoffsets,logfile,thislog) def procStackPivots(args): procROP(args,"stackpivot") def procROP(args,mode="all"): #default criteria modulecriteria={} modulecriteria["aslr"] = False modulecriteria["rebase"] = False modulecriteria["os"] = False criteria={} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) # handle optional arguments depth = 6 maxoffset = 40 thedistance = 8 split = False fast = False sortedprint = False endingstr = "" endings = [] technique = "" if "depth" in args: if type(args["depth"]).__name__.lower() != "bool": try: depth = int(args["depth"]) except: pass if "offset" in args: if type(args["offset"]).__name__.lower() != "bool": try: maxoffset = int(args["offset"]) except: pass if "distance" in args: if type(args["distance"]).__name__.lower() != "bool": try: thedistance = args["distance"] except: pass if "split" in args: if type(args["split"]).__name__.lower() == "bool": split = args["split"] if "s" in args: if type(args["s"]).__name__.lower() != "bool": technique = args["s"].replace("'","").replace('"',"").strip().lower() if "fast" in args: if type(args["fast"]).__name__.lower() == "bool": fast = args["fast"] if "end" in args: if type(args["end"]).__name__.lower() == "str": endingstr = args["end"].replace("'","").replace('"',"").strip() endings = endingstr.split("#") if "f" in args: if args["f"] != "": criteria["f"] = args["f"] if "sort" in args: sortedprint = True if "rva" in args: criteria["rva"] = True if mode == "stackpivot": fast = False endings = "" split = False else: mode = "all" findROPGADGETS(modulecriteria,criteria,endings,maxoffset,depth,split,thedistance,fast,mode,sortedprint,technique) def procJseh(args): results = [] showred=0 showall=False if "all" in args: showall = True nrfound = 0 dbg.log("-----------------------------------------------------------------------") dbg.log("Search for jmp/call dword[ebp/esp+nn] (and other) combinations started ") dbg.log("-----------------------------------------------------------------------") opcodej=["\xff\x54\x24\x08", #call dword ptr [esp+08] "\xff\x64\x24\x08", #jmp dword ptr [esp+08] "\xff\x54\x24\x14", #call dword ptr [esp+14] "\xff\x54\x24\x14", #jmp dword ptr [esp+14] "\xff\x54\x24\x1c", #call dword ptr [esp+1c] "\xff\x54\x24\x1c", #jmp dword ptr [esp+1c] "\xff\x54\x24\x2c", #call dword ptr [esp+2c] "\xff\x54\x24\x2c", #jmp dword ptr [esp+2c] "\xff\x54\x24\x44", #call dword ptr [esp+44] "\xff\x54\x24\x44", #jmp dword ptr [esp+44] "\xff\x54\x24\x50", #call dword ptr [esp+50] "\xff\x54\x24\x50", #jmp dword ptr [esp+50] "\xff\x55\x0c", #call dword ptr [ebp+0c] "\xff\x65\x0c", #jmp dword ptr [ebp+0c] "\xff\x55\x24", #call dword ptr [ebp+24] "\xff\x65\x24", #jmp dword ptr [ebp+24] "\xff\x55\x30", #call dword ptr [ebp+30] "\xff\x65\x30", #jmp dword ptr [ebp+30] "\xff\x55\xfc", #call dword ptr [ebp-04] "\xff\x65\xfc", #jmp dword ptr [ebp-04] "\xff\x55\xf4", #call dword ptr [ebp-0c] "\xff\x65\xf4", #jmp dword ptr [ebp-0c] "\xff\x55\xe8", #call dword ptr [ebp-18] "\xff\x65\xe8", #jmp dword ptr [ebp-18] "\x83\xc4\x08\xc3", #add esp,8 + ret "\x83\xc4\x08\xc2"] #add esp,8 + ret X fakeptrcriteria = {} fakeptrcriteria["accesslevel"] = "*" for opjc in opcodej: addys = [] addys = searchInRange( [[opjc, opjc]], 0, TOP_USERLAND, fakeptrcriteria) results += addys for ptrtypes in addys: for ad1 in addys[ptrtypes]: ptr = MnPointer(ad1) module = ptr.belongsTo() if not module: module="" page = dbg.getMemoryPageByAddress( ad1 ) access = page.getAccess( human = True ) op = dbg.disasm( ad1 ) opstring=op.getDisasm() dbg.log("Found %s at 0x%08x - Access: (%s) - Outside of a loaded module" % (opstring, ad1, access), address = ad1,highlight=1) nrfound+=1 else: if showall: page = dbg.getMemoryPageByAddress( ad1 ) access = page.getAccess( human = True ) op = dbg.disasm( ad1 ) opstring=op.getDisasm() thismod = MnModule(module) if not thismod.isSafeSEH: #if ismodulenosafeseh(module[0])==1: extratext="=== Safeseh : NO ===" showred=1 else: extratext="Safeseh protected" showred=0 dbg.log("Found %s at 0x%08x (%s) - Access: (%s) - %s" % (opstring, ad1, module,access,extratext), address = ad1,highlight=showred) nrfound+=1 dbg.log("Search complete") if results: dbg.log("Found %d address(es)" % nrfound) return "Found %d address(es) (Check the log Windows for details)" % nrfound else: dbg.log("No addresses found") return "Sorry, no addresses found" def procJOP(args,mode="all"): #default criteria modulecriteria={} modulecriteria["aslr"] = False modulecriteria["rebase"] = False modulecriteria["os"] = False criteria={} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) # handle optional arguments depth = 6 if "depth" in args: if type(args["depth"]).__name__.lower() != "bool": try: depth = int(args["depth"]) except: pass findJOPGADGETS(modulecriteria,criteria,depth) def procCreatePATTERN(args): size = 0 pattern = "" if "?" in args and args["?"] != "": try: if "0x" in args["?"].lower(): try: size = int(args["?"],16) except: size = 0 else: size = int(args["?"]) except: size = 0 if size == 0: dbg.log("Please enter a valid size",highlight=1) else: pattern = createPattern(size,args) dbg.log("Creating cyclic pattern of %d bytes" % size) dbg.log(pattern) global ignoremodules ignoremodules = True objpatternfile = MnLog("pattern.txt") patternfile = objpatternfile.reset() # ASCII objpatternfile.write("\nPattern of " + str(size) + " bytes :\n",patternfile) objpatternfile.write("-" * (19 + len(str(size))),patternfile) objpatternfile.write("\nASCII:",patternfile) objpatternfile.write("\n" + pattern,patternfile) # Hex patternhex = "" for patternchar in pattern: patternhex += str(hex(ord(patternchar))).replace("0x","\\x") objpatternfile.write("\n\nHEX:\n",patternfile) objpatternfile.write(patternhex,patternfile) # Javascript patternjs = str2js(pattern) objpatternfile.write("\n\nJAVASCRIPT (unescape() friendly):\n",patternfile) objpatternfile.write(patternjs,patternfile) if not silent: dbg.log("Note: don't copy this pattern from the log window, it might be truncated !",highlight=1) dbg.log("It's better to open %s and copy the pattern from the file" % patternfile,highlight=1) ignoremodules = False return def procOffsetPATTERN(args): egg = "" if "?" in args and args["?"] != "": try: egg = args["?"] except: egg = "" if egg == "": dbg.log("Please enter a valid target",highlight=1) else: findOffsetInPattern(egg,-1,args) return # ----- Comparing file output ----- # def procFileCOMPARE(args): modulecriteria={} criteria={} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) allfiles=[] tomatch="" checkstrict=True rangeval = 0 fast = False if "ptronly" in args or "ptrsonly" in args: fast = True if "f" in args: if args["f"] != "": rawfilenames=args["f"].replace('"',"") allfiles = [getAbsolutePath(f) for f in rawfilenames.split(',')] dbg.log("[+] Number of files to be examined : %d " % len(allfiles)) if "range" in args: if not type(args["range"]).__name__.lower() == "bool": strrange = args["range"].lower() if strrange.startswith("0x") and len(strrange) > 2 : rangeval = int(strrange,16) else: try: rangeval = int(args["range"]) except: rangeval = 0 if rangeval > 0: dbg.log("[+] Find overlap using pointer +/- range, value %d" % rangeval) dbg.log(" Note : this will significantly slow down the comparison process !") else: dbg.log("Please provide a numeric value ^(> 0) with option -range",highlight=1) return else: if "contains" in args: if type(args["contains"]).__name__.lower() == "str": tomatch = args["contains"].replace("'","").replace('"',"") if "nostrict" in args: if type(args["nostrict"]).__name__.lower() == "bool": checkstrict = not args["nostrict"] dbg.log("[+] Instructions must match in all files ? %s" % checkstrict) # maybe one of the arguments is a folder callfiles = allfiles allfiles = [] for tfile in callfiles: if os.path.isdir(tfile): # folder, get all files from this folder for root,dirs,files in os.walk(tfile): for dfile in files: allfiles.append(os.path.join(root,dfile)) else: allfiles.append(tfile) if len(allfiles) > 1: findFILECOMPARISON(modulecriteria,criteria,allfiles,tomatch,checkstrict,rangeval,fast) else: dbg.log("Please specify at least 2 filenames to compare",highlight=1) # ----- Find bytes in memory ----- # def procFind(args): modulecriteria={} criteria={} pattern = "" base = 0 offset = 0 top = TOP_USERLAND consecutive = False ftype = "" level = 0 offsetlevel = 0 if not "a" in args: args["a"] = "*" ptronly = False if "ptronly" in args or "ptrsonly" in args: ptronly = True #search for all pointers by default if not "x" in args: args["x"] = "*" modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) if criteria["accesslevel"] == "": return if not "s" in args: dbg.log("-s is a mandatory argument",highlight=1) return pattern = args["s"] if "unicode" in args: criteria["unic"] = True if "b" in args: try: base = int(args["b"],16) except: dbg.log("invalid base address: %s" % args["b"],highlight=1) return if "t" in args: try: top = int(args["t"],16) except: dbg.log("invalid top address: %s" % args["t"],highlight=1) return if "offset" in args: if not args["offset"].__class__.__name__ == "bool": if "0x" in args["offset"].lower(): try: offset = 0 - int(args["offset"],16) except: dbg.log("invalid offset value",highlight=1) return else: try: offset = 0 - int(args["offset"]) except: dbg.log("invalid offset value",highlight=1) return else: dbg.log("invalid offset value",highlight=1) return if "level" in args: try: level = int(args["level"]) except: dbg.log("invalid level value",highlight=1) return if "offsetlevel" in args: try: offsetlevel = int(args["offsetlevel"]) except: dbg.log("invalid offsetlevel value",highlight=1) return if "c" in args: dbg.log(" - Skipping consecutive pointers, showing size instead") consecutive = True if "type" in args: if not args["type"] in ["bin","asc","ptr","instr","file"]: dbg.log("Invalid search type : %s" % args["type"], highlight=1) return ftype = args["type"] if ftype == "file": filename = args["s"].replace('"',"").replace("'","") #see if we can read the file if not os.path.isfile(filename): dbg.log("Unable to find/read file %s" % filename,highlight=1) return rangep2p = 0 if "p2p" in args or level > 0: dbg.log(" - Looking for pointers to pointers") criteria["p2p"] = True if "r" in args: try: rangep2p = int(args["r"]) except: pass if rangep2p > 0: dbg.log(" - Will search for close pointers (%d bytes backwards)" % rangep2p) if "p2p" in args: level = 1 if level > 0: dbg.log(" - Recursive levels : %d" % level) allpointers = findPattern(modulecriteria,criteria,pattern,ftype,base,top,consecutive,rangep2p,level,offset,offsetlevel) logfile = MnLog("find.txt") thislog = logfile.reset() processResults(allpointers,logfile,thislog,{},ptronly) return # ---- Find instructions, wildcard search ----- # def procFindWild(args): modulecriteria={} criteria={} pattern = "" patterntype = "" base = 0 top = TOP_USERLAND modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) if not "s" in args: dbg.log("-s is a mandatory argument",highlight=1) return pattern = args["s"] patterntypes = ["bin","str"] if "type" in args: if type(args["type"]).__name__.lower() != "bool": if args["type"] in patterntypes: patterntype = args["type"] else: dbg.log("-type argument only takes one of these values: %s" % patterntypes,highlight=1) return else: dbg.log("Please specify a valid value for -type. Valid values are %s" % patterntypes,highlight=1) return if patterntype == "": if "\\x" in pattern: patterntype = "bin" else: patterntype = "str" if "b" in args: base,addyok = getAddyArg(args["b"]) if not addyok: dbg.log("invalid base address: %s" % args["b"],highlight=1) return if "t" in args: top,addyok = getAddyArg(args["t"]) if not addyok: dbg.log("invalid top address: %s" % args["t"],highlight=1) return if "depth" in args: try: criteria["depth"] = int(args["depth"]) except: dbg.log("invalid depth value",highlight=1) return if "all" in args: criteria["all"] = True if "distance" in args: if type(args["distance"]).__name__.lower() == "bool": dbg.log("invalid distance value(s)",highlight=1) else: distancestr = args["distance"] distanceparts = distancestr.split(",") for parts in distanceparts: valueparts = parts.split("=") if len(valueparts) > 1: if valueparts[0].lower() == "min": try: mindistance = int(valueparts[1]) except: mindistance = 0 if valueparts[0].lower() == "max": try: maxdistance = int(valueparts[1]) except: maxdistance = 0 if maxdistance < mindistance: tmp = maxdistance maxdistance = mindistance mindistance = tmp criteria["mindistance"] = mindistance criteria["maxdistance"] = maxdistance allpointers = findPatternWild(modulecriteria,criteria,pattern,base,top,patterntype) logfile = MnLog("findwild.txt") thislog = logfile.reset() processResults(allpointers,logfile,thislog) return # ----- assemble: assemble instructions to opcodes ----- # def procAssemble(args): opcodes = "" encoder = "" if not 's' in args: dbg.log("Mandatory argument -s missing", highlight=1) return opcodes = args['s'] if 'e' in args: # TODO: implement encoder support dbg.log("Encoder support not yet implemented", highlight=1) return encoder = args['e'].lowercase() if encoder not in ["ascii"]: dbg.log("Invalid encoder : %s" % encoder, highlight=1) return assemble(opcodes,encoder) # ----- info: show information about an address ----- # def procInfo(args): if not "a" in args: dbg.log("Missing mandatory argument -a", highlight=1) return address,addyok = getAddyArg(args["a"]) if not addyok: dbg.log("%s is an invalid address" % args["a"], highlight=1) return ptr = MnPointer(address) modname = ptr.belongsTo() modinfo = None if modname != "": modinfo = MnModule(modname) rebase = "" rva=0 if modinfo : rva = address - modinfo.moduleBase procFlags(args) dbg.log("") dbg.log("[+] Information about address 0x%s" % toHex(address)) dbg.log(" %s" % ptr.__str__()) thepage = dbg.getMemoryPageByAddress(address) dbg.log(" Address is part of page 0x%08x - 0x%08x" % (thepage.getBaseAddress(),thepage.getBaseAddress()+thepage.getSize())) section = "" try: section = thepage.getSection() except: section = "" if section != "": dbg.log(" Section : %s" % section) if ptr.isOnStack(): stacks = getStacks() stackref = "" for tid in stacks: currstack = stacks[tid] if currstack[0] <= address and address <= currstack[1]: stackref = " (Thread 0x%08x, Stack Base : 0x%08x, Stack Top : 0x%08x)" % (tid,currstack[0],currstack[1]) break dbg.log(" This address is in a stack segment %s" % stackref) if modinfo: dbg.log(" Address is part of a module:") dbg.log(" %s" % modinfo.__str__()) if rva != 0: dbg.log(" Offset from module base: 0x%x" % rva) if modinfo: eatlist = modinfo.getEAT() if address in eatlist: dbg.log(" Address is start of function '%s' in %s" % (eatlist[address],modname)) else: iatlist = modinfo.getIAT() if address in iatlist: iatentry = iatlist[address] dbg.log(" Address is part of IAT, and contains pointer to '%s'" % iatentry) else: output = "" if ptr.isInHeap(): dbg.log(" This address resides in the heap") dbg.log("") ptr.showHeapBlockInfo() else: dbg.log(" Module: None") try: dbg.log("") dbg.log("[+] Disassembly:") op = dbg.disasm(address) opstring=getDisasmInstruction(op) dbg.log(" Instruction at %s : %s" % (toHex(address),opstring)) except: pass if __DEBUGGERAPP__ == "WinDBG": dbg.log("") dbg.log("Output of !address 0x%08x:" % address) output = dbg.nativeCommand("!address 0x%08x" % address) dbg.logLines(output) dbg.log("") # ----- dump: Dump some memory to a file ----- # def procDump(args): filename = "" if "f" not in args: dbg.log("Missing mandatory argument -f filename", highlight=1) return filename = args["f"] address = None if "s" not in args: dbg.log("Missing mandatory argument -s address", highlight=1) return startaddress = str(args["s"]).replace("0x","").replace("0X","") if not isAddress(startaddress): dbg.log("You have specified an invalid start address", highlight=1) return address = addrToInt(startaddress) size = 0 if "n" in args: size = int(args["n"]) elif "e" in args: endaddress = str(args["e"]).replace("0x","").replace("0X","") if not isAddress(endaddress): dbg.log("You have specified an invalid end address", highlight=1) return end = addrToInt(endaddress) if end < address: dbg.log("end address %s is before start address %s" % (args["e"],args["s"]), highlight=1) return size = end - address else: dbg.log("you need to specify either the size of the copy with -n or the end address with -e ", highlight=1) return dumpMemoryToFile(address,size,filename) # ----- compare : Compare a file created by msfvenom/gdb/hex/xxd/hexdump/ollydbg or just a file with raw bytes with a copy in memory, indicate bad chars / corruption ----- # def procCompare(args): startpos = 0 filename = "" skipmodules = False findunicode = False allregs = dbg.getRegs() if "f" in args: filename = getAbsolutePath(args["f"].replace('"',"").replace("'","")) #see if we can read the file if not os.path.isfile(filename): dbg.log("Unable to find/read file %s" % filename,highlight=1) return else: dbg.log("You must specify a valid filename using parameter -f", highlight=1) return if "a" in args: startpos,addyok = getAddyArg(args["a"]) if not addyok: dbg.log("%s is an invalid address" % args["a"], highlight=1) return if "s" in args: skipmodules = True if "unicode" in args: findunicode = True if "t" in args: format = args["t"] else: format = None compareFormattedFileWithMemory(filename,format,startpos,skipmodules,findunicode) # ----- offset: Calculate the offset between two addresses ----- # def procOffset(args): extratext1 = "" extratext2 = "" isReg_a1 = False isReg_a2 = False regs = dbg.getRegs() if "a1" not in args: dbg.log("Missing mandatory argument -a1
", highlight=1) return a1 = args["a1"] if "a2" not in args: dbg.log("Missing mandatory argument -a2
", highlight=1) return a2 = args["a2"] a1,addyok = getAddyArg(args["a1"]) if not addyok: dbg.log("0x%08x is not a valid address" % a1, highlight=1) return a2,addyok = getAddyArg(args["a2"]) if not addyok: dbg.log("0x%08x is not a valid address" % a2, highlight=1) return diff = a2 - a1 result=toHex(diff) negjmpbytes = "" if a1 > a2: ndiff = a1 - a2 result=toHex(4294967296-ndiff) negjmpbytes="\\x"+ result[6]+result[7]+"\\x"+result[4]+result[5]+"\\x"+result[2]+result[3]+"\\x"+result[0]+result[1] regaction="sub" dbg.log("Offset from 0x%08x to 0x%08x : %d (0x%s) bytes" % (a1,a2,diff,result)) if a1 > a2: dbg.log("Negative jmp offset : %s" % negjmpbytes) else: dbg.log("Jmp offset : %s" % negjmpbytes) return # ----- bp: Set a breakpoint on read/write/exe access ----- # def procBp(args): isReg_a = False regs = dbg.getRegs() thistype = "" if "a" not in args: dbg.log("Missing mandatory argument -a address", highlight=1) dbg.log("The address can be an absolute address, a register, or a modulename!functionname") return a = str(args["a"]) for reg in regs: if reg.upper() == a.upper(): a=toHex(regs[reg]) isReg_a = True break a = a.upper().replace("0X","").lower() if not isAddress(str(a)): # maybe it's a modulename!function if str(a).find("!") > -1: modparts = str(a).split("!") modname = modparts[0] if not modname.lower().endswith(".dll"): modname += ".dll" themodule = MnModule(modname) if themodule != None and len(modparts) > 1: eatlist = themodule.getEAT() funcname = modparts[1].lower() addyfound = False for eatentry in eatlist: if eatlist[eatentry].lower() == funcname: a = "%08x" % (eatentry) addyfound = True break if not addyfound: # maybe it's just a symbol, try to resolve if __DEBUGGERAPP__ == "WinDBG": symboladdress = dbg.resolveSymbol(a) if symboladdress != "" : a = symboladdress addyfound = True if not addyfound: dbg.log("Please specify a valid address/register/modulename!functionname (-a)", highlight=1) return else: dbg.log("Please specify a valid address/register/modulename!functionname (-a)", highlight=1) return else: dbg.log("Please specify a valid address/register/modulename!functionname (-a)", highlight=1) return valid_types = ["READ", "WRITE", "SFX", "EXEC"] if "t" not in args: dbg.log("Missing mandatory argument -t type", highlight=1) dbg.log("Valid types are: %s" % ", ".join(valid_types)) return else: thistype = args["t"].upper() if not thistype in valid_types: dbg.log("Invalid type : %s" % thistype) return if thistype == "EXEC": thistype = "SFX" a = hexStrToInt(a) dbg.setMemBreakpoint(a,thistype[0]) dbg.log("Breakpoint set on %s of 0x%s" % (thistype,toHex(a)),highlight=1) # ----- ct: calltrace ---- # def procCallTrace(args): modulecriteria={} criteria={} criteria["accesslevel"] = "X" modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) modulestosearch = getModulesToQuery(modulecriteria) hooks = [] rethooks = [] showargs = 0 hookrets = False if not "m" in args: dbg.log(" ** Please specify what module(s) you want to include in the trace, using argument -m **",highlight=1) return if "a" in args: if args["a"] != "": try: showargs = int(args["a"]) except: showargs = 0 if "r" in args: hookrets = True toignore = [] limit_scope = True if not "all" in args: # fill up array toignore.append("PeekMessage") toignore.append("GetParent") toignore.append("GetFocus") toignore.append("EnterCritical") toignore.append("LeaveCritical") toignore.append("GetWindow") toignore.append("CallnextHook") toignore.append("TlsGetValue") toignore.append("DefWindowProc") toignore.append("SetTextColor") toignore.append("DrawText") toignore.append("TranslateAccel") toignore.append("TranslateMessage") toignore.append("DispatchMessage") toignore.append("isChild") toignore.append("GetSysColor") toignore.append("SetBkColor") toignore.append("GetDlgCtrl") toignore.append("CallWindowProc") toignore.append("HideCaret") toignore.append("MessageBeep") toignore.append("SetWindowText") toignore.append("GetDlgItem") toignore.append("SetFocus") toignore.append("SetCursor") toignore.append("LoadCursor") toignore.append("SetEvent") toignore.append("SetDlgItem") toignore.append("SetWindowPos") toignore.append("GetDC") toignore.append("ReleaseDC") toignore.append("GetDeviceCaps") toignore.append("GetClientRect") toignore.append("etLastError") else: limit_scope = False if len( modulestosearch) > 0: dbg.log("[+] Initializing log file") logfile = MnLog("calltrace.txt") thislog = logfile.reset() dbg.log("[+] Number of CALL arguments to display : %d" % showargs) dbg.log("[+] Finding instructions & placing hooks") for thismod in modulestosearch: dbg.updateLog() objMod = dbg.getModule(thismod) if not objMod.isAnalysed: dbg.log(" Analysing code...") objMod.Analyse() themod = MnModule(thismod) modcodebase = themod.moduleCodebase modcodetop = themod.moduleCodetop dbg.setStatusBar("Placing hooks in %s..." % thismod) dbg.log(" * %s (0x%08x - 0x%08x)" % (thismod,modcodebase,modcodetop)) ccnt = 0 rcnt = 0 thisaddr = modcodebase allfuncs = dbg.getAllFunctions(modcodebase) for func in allfuncs: thisaddr = func thisfunc = dbg.getFunction(thisaddr) instrcnt = 0 while thisfunc.hasAddress(thisaddr): try: if instrcnt == 0: thisopcode = dbg.disasm(thisaddr) else: thisopcode = dbg.disasmForward(thisaddr,1) thisaddr = thisopcode.getAddress() instruction = getDisasmInstruction(thisopcode) if instruction.startswith("CALL "): ignore_this_instruction = False for ignores in toignore: if instruction.lower().find(ignores.lower()) > -1: ignore_this_instruction = True break if not ignore_this_instruction: if not thisaddr in hooks: hooks.append(thisaddr) myhook = MnCallTraceHook(thisaddr,showargs,instruction,thislog) myhook.add("HOOK_CT_%s" % thisaddr , thisaddr) ccnt += 1 if hookrets and instruction.startswith("RETN"): if not thisaddr in rethooks: rethooks.append(thisaddr) myhook = MnCallTraceHook(thisaddr,showargs,instruction,thislog) myhook.add("HOOK_CT_%s" % thisaddr , thisaddr) except: #dbg.logLines(traceback.format_exc(),highlight=True) break instrcnt += 1 dbg.log("[+] Total number of CALL hooks placed : %d" % len(hooks)) if hookrets: dbg.log("[+] Total number of RETN hooks placed : %d" % len(rethooks)) else: dbg.log("[!] No modules selected or found",highlight=1) return "Done" # ----- bu: set a deferred breakpoint ---- # def procBu(args): if not "a" in args: dbg.log("No targets defined. (-a)",highlight=1) return else: allargs = args["a"] bpargs = allargs.split(",") breakpoints = {} dbg.log("") dbg.log("Received %d addresses//functions to process" % len(bpargs)) # set a breakpoint right away for addresses and functions that are mapped already for tbparg in bpargs: bparg = tbparg.replace(" ","") # address or module.function ? if bparg.find(".") > -1: functionaddress = dbg.getAddress(bparg) if functionaddress > 0: # module.function is already mapped, we can set a bp right away dbg.setBreakpoint(functionaddress) breakpoints[bparg] = True dbg.log("Breakpoint set at 0x%08x (%s), was already mapped" % (functionaddress,bparg), highlight=1) else: breakpoints[bparg] = False # no breakpoint set yet elif bparg.find("+") > -1: ptrparts = bparg.split("+") modname = ptrparts[0] if not modname.lower().endswith(".dll"): modname += ".dll" themodule = getModuleObj(modname) if themodule != None and len(ptrparts) > 1: address = themodule.getBase() + int(ptrparts[1],16) if address > 0: dbg.log("Breakpoint set at %s (0x%08x), was already mapped" % (bparg,address),highlight=1) dbg.setBreakpoint(address) breakpoints[bparg] = True else: breakpoints[bparg] = False else: breakpoints[bparg] = False if bparg.find(".") == -1 and bparg.find("+") == -1: # address, see if it is mapped, by reading one byte from that location address = -1 try: address = int(bparg,16) except: pass thispage = dbg.getMemoryPageByAddress(address) if thispage != None: dbg.setBreakpoint(address) dbg.log("Breakpoint set at 0x%08x, was already mapped" % address, highlight=1) breakpoints[bparg] = True else: breakpoints[bparg] = False # get the correct addresses to put hook on loadlibraryA = dbg.getAddress("kernel32.LoadLibraryA") loadlibraryW = dbg.getAddress("kernel32.LoadLibraryW") if loadlibraryA > 0 and loadlibraryW > 0: # find end of function for each endAfound = False endWfound = False cnt = 1 while not endAfound: objInstr = dbg.disasmForward(loadlibraryA, cnt) strInstr = getDisasmInstruction(objInstr) if strInstr.startswith("RETN"): endAfound = True loadlibraryA = objInstr.getAddress() cnt += 1 cnt = 1 while not endWfound: objInstr = dbg.disasmForward(loadlibraryW, cnt) strInstr = getDisasmInstruction(objInstr) if strInstr.startswith("RETN"): endWfound = True loadlibraryW = objInstr.getAddress() cnt += 1 # if addresses/functions are left, throw them into their own hooks, # one for each LoadLibrary type. hooksplaced = False for bptarget in breakpoints: if not breakpoints[bptarget]: myhookA = MnDeferredHook(loadlibraryA, bptarget) myhookA.add("HOOK_A_%s" % bptarget, loadlibraryA) myhookW = MnDeferredHook(loadlibraryW, bptarget) myhookW.add("HOOK_W_%s" % bptarget, loadlibraryW) dbg.log("Hooks for %s installed" % bptarget) hooksplaced = True if not hooksplaced: dbg.log("No hooks placed") else: dbg.log("** Unable to place hooks, make sure kernel32.dll is loaded",highlight=1) return "Done" # ----- bf: Set a breakpoint on exported functions of a module ----- # def procBf(args): funcfilter = "" mode = "" type = "export" modes = ["add","del","list"] types = ["import","export","iat","eat"] modulecriteria={} criteria={} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) if "s" in args: try: funcfilter = args["s"].lower() except: dbg.log("No functions selected. (-s)",highlight=1) return else: dbg.log("No functions selected. (-s)",highlight=1) return if "t" in args: try: mode = args["t"].lower() except: pass if "f" in args: try: type = args["f"].lower() except: pass if not type in types: dbg.log("No valid function type selected (-f )",highlight=1) return if not mode in modes or mode=="": dbg.log("No valid action defined. (-t add|del|list)") doManageBpOnFunc(modulecriteria,criteria,funcfilter,mode,type) return # ----- Show info about modules -------# def procModInfoS(args): modulecriteria = {} criteria = {} modulecriteria["safeseh"] = False dbg.log("Safeseh unprotected modules :") modulestosearch = getModulesToQuery(modulecriteria) showModuleTable("",modulestosearch) return def procModInfoSA(args): modulecriteria = {} criteria = {} modulecriteria["safeseh"] = False modulecriteria["aslr"] = False modulecriteria["rebase"] = False dbg.log("Safeseh unprotected, no aslr & no rebase modules :") modulestosearch = getModulesToQuery(modulecriteria) showModuleTable("",modulestosearch) return def procModInfoA(args): modulecriteria = {} criteria = {} modulecriteria["aslr"] = False modulecriteria["rebase"] = False dbg.log("No aslr & no rebase modules :") modulestosearch = getModulesToQuery(modulecriteria) showModuleTable("",modulestosearch) return # ----- Print byte array ----- # def procByteArray(args): badchars = "" bytesperline = 32 startval = 0 endval = 255 # kept for legacy if "r" in args: startval = 255 endval = 0 # handle start argument if "s" in args: startval = hex2int(cleanHex(args['s'])) # handle end argument if "e" in args: endval = hex2int(cleanHex(args['e'])) if "b" in args: dbg.log(" *** Note: parameter -b has been deprecated and replaced with -cpb ***") if type(args["b"]).__name__.lower() != "bool": if not "cpb" in args: args["cpb"] = args["b"] if "cpb" in args: badchars = args["cpb"] badchars = cleanHex(badchars) # see if we need to expand .. bpos = 0 newbadchars = "" while bpos < len(badchars): curchar = badchars[bpos]+badchars[bpos+1] if curchar == "..": pos = bpos if pos > 1 and pos <= len(badchars)-4: # get byte before and after .. bytebefore = badchars[pos-2] + badchars[pos-1] byteafter = badchars[pos+2] + badchars[pos+3] bbefore = int(bytebefore,16) bafter = int(byteafter,16) insertbytes = "" bbefore += 1 while bbefore < bafter: insertbytes += "%02x" % bbefore bbefore += 1 newbadchars += insertbytes else: newbadchars += curchar bpos += 2 badchars = newbadchars cnt = 0 strb = "" while cnt < len(badchars): strb=strb+binascii.a2b_hex(badchars[cnt]+badchars[cnt+1]) cnt=cnt+2 dbg.log("Generating table, excluding %d bad chars..." % len(strb)) arraytable = [] binarray = "" # handle range() last value if endval > startval: increment = 1 endval += 1 else: endval += -1 increment = -1 # create bytearray for thisval in range(startval,endval,increment): hexbyte = hex(thisval)[2:] binbyte = hex2bin(toHexByte(thisval)) if len(hexbyte) == 1: hexbyte = "0" + hexbyte hexbyte2 = binascii.a2b_hex(hexbyte) if not hexbyte2 in strb: arraytable.append(hexbyte) binarray += binbyte dbg.log("Dumping table to file") output = "" cnt = 0 outputline = '"' totalbytes = len(arraytable) tablecnt = 0 while tablecnt < totalbytes: if (cnt < bytesperline): outputline += "\\x" + arraytable[tablecnt] else: outputline += '"\n' cnt = 0 output += outputline outputline = '"\\x' + arraytable[tablecnt] tablecnt += 1 cnt += 1 if (cnt-1) < bytesperline: outputline += '"\n' output += outputline global ignoremodules ignoremodules = True arrayfilename="bytearray.txt" objarrayfile = MnLog(arrayfilename) arrayfile = objarrayfile.reset() binfilename = arrayfile.replace("bytearray.txt","bytearray.bin") objarrayfile.write(output,arrayfile) ignoremodules = False dbg.logLines(output) dbg.log("") binfile = open(binfilename,"wb") binfile.write(binarray) binfile.close() dbg.log("Done, wrote %d bytes to file %s" % (len(arraytable),arrayfile)) dbg.log("Binary output saved in %s" % binfilename) return #----- Read binary file, print 'nice' header -----# def procPrintHeader(args): alltypes = ["ruby","rb","python","py"] thistype = "ruby" filename = "" typewrong = False stopnow = False if "f" in args: if type(args["f"]).__name__.lower() != "bool": filename = getAbsolutePath(args["f"]) if "t" in args: if type(args["t"]).__name__.lower() != "bool": if args["t"] in alltypes: thistype = args["t"] else: typewrong = True else: typewrong = True if typewrong: dbg.log("Invalid type specified with option -t. Valid types are: %s" % alltypes,highlight=1) stopnow = True else: if thistype == "rb": thistype = "ruby" if thistype == "py": thistype = "python" if filename == "": dbg.log("Missing argument -f ",highlight=1) stopnow = True if stopnow: return filename = filename.replace("'","").replace('"',"") content = "" try: file = open(filename,"rb") content = file.read() file.close() except: dbg.log("Unable to read file %s" % filename,highlight=1) return dbg.log("Read %d bytes from %s" % (len(content),filename)) dbg.log("Output type: %s" % thistype) cnt = 0 linecnt = 0 output = "" thisline = "" max = len(content) addchar = "<<" if thistype == "python": addchar = "+=" # keep it easy, initialize header as an empty string output = "header = \"\"\n" while cnt < max: # first check for unicode if cnt < max-1: thisline = "header %s \"" % addchar thiscnt = cnt while cnt < max-1 and isAscii2(ord(content[cnt])) and ord(content[cnt+1]) == 0: if content[cnt] == "\\": thisline += "\\" if content[cnt] == "\"": thisline += "\\" thisline += "%s\\x00" % content[cnt] cnt += 2 if thiscnt != cnt: output += thisline + "\"" + "\n" linecnt += 1 thisline = "header %s \"" % addchar thiscnt = cnt # ascii repetitions reps = 1 startval = content[cnt] if isAscii(ord(content[cnt])): while cnt < max-1: if startval == content[cnt+1]: reps += 1 cnt += 1 else: break if reps > 1: if startval == "\\": startval += "\\" if startval == "\"": startval = "\\" + "\"" output += thisline + startval + "\" * " + str(reps) + "\n" cnt += 1 linecnt += 1 continue thisline = "header %s \"" % addchar thiscnt = cnt # check for just ascii while cnt < max and isAscii2(ord(content[cnt])): if cnt < max-1 and ord(content[cnt+1]) == 0: break if content[cnt] == "\\": thisline += "\\" if content[cnt] == "\"": thisline += "\\" thisline += content[cnt] cnt += 1 if thiscnt != cnt: output += thisline + "\"" + "\n" linecnt += 1 #check others : repetitions if cnt < max: thisline = "header %s \"" % addchar thiscnt = cnt while cnt < max: if isAscii2(ord(content[cnt])): break if cnt < max-1 and isAscii2(ord(content[cnt])) and ord(content[cnt+1]) == 0: break #check repetitions reps = 1 startval = ord(content[cnt]) while cnt < max-1: if startval == ord(content[cnt+1]): reps += 1 cnt += 1 else: break if reps > 1: if len(thisline) > 12: output += thisline + "\"" + "\n" thisline = "header %s \"\\x" % addchar thisline += "%02x\" * %d" % (startval,reps) output += thisline + "\n" thisline = "header %s \"" % addchar linecnt += 1 else: thisline += "\\x" + "%02x" % ord(content[cnt]) cnt += 1 if thiscnt != cnt: if len(thisline) > 12: output += thisline + "\"" + "\n" linecnt += 1 global ignoremodules ignoremodules = True headerfilename="header.txt" objheaderfile = MnLog(headerfilename) headerfile = objheaderfile.reset() objheaderfile.write(output,headerfile) ignoremodules = False if not silent: dbg.log("-" * 30) dbg.logLines(output) dbg.log("-" * 30) dbg.log("Wrote header to %s" % headerfile) return #----- Update -----# def procUpdate(args): """ Function to update mona and optionally windbglib to the latest version Arguments : none Returns : new version of mona/windbglib (if available) """ updateproto = "https" #debugger version imversion = __IMM__ #url dbg.setStatusBar("Running update process...") dbg.updateLog() updateurl = "https://github.com/corelan/mona/raw/master/mona.py" currentversion,currentrevision = getVersionInfo(inspect.stack()[0][1]) u = "" try: u = urllib.urlretrieve(updateurl) newversion,newrevision = getVersionInfo(u[0]) if newversion != "" and newrevision != "": dbg.log("[+] Version compare :") dbg.log(" Current Version : %s, Current Revision : %s" % (currentversion,currentrevision)) dbg.log(" Latest Version : %s, Latest Revision : %s" % (newversion,newrevision)) else: dbg.log("[-] Unable to check latest version (corrupted file ?), try again later",highlight=1) return except: dbg.log("[-] Unable to check latest version (download error). Try again later",highlight=1) dbg.log(" Meanwhile, please check/confirm that you're running a recent version of python 2.7 (2.7.14 or higher)", highlight=1) return #check versions doupdate = False if newversion != "" and newrevision != "": if currentversion != newversion: doupdate = True else: if int(currentrevision) < int(newrevision): doupdate = True if doupdate: dbg.log("[+] New version available",highlight=1) dbg.log(" Updating to %s r%s" % (newversion,newrevision),highlight=1) try: shutil.copyfile(u[0],inspect.stack()[0][1]) dbg.log(" Done") except: dbg.log(" ** Unable to update mona.py",highlight=1) currentversion,currentrevision = getVersionInfo(inspect.stack()[0][1]) dbg.log("[+] Current version : %s r%s" % (currentversion,currentrevision)) else: dbg.log("[+] You are running the latest version") # update windbglib if needed if __DEBUGGERAPP__ == "WinDBG": dbg.log("[+] Locating windbglib path") paths = sys.path filefound = False libfile = "" for ppath in paths: libfile = ppath + "\\windbglib.py" if os.path.isfile(libfile): filefound=True break if not filefound: dbg.log(" ** Unable to find windbglib.py ! **") else: dbg.log("[+] Checking if %s needs an update..." % libfile) updateurl = "https://github.com/corelan/windbglib/raw/master/windbglib.py" currentversion,currentrevision = getVersionInfo(libfile) u = "" try: u = urllib.urlretrieve(updateurl) newversion,newrevision = getVersionInfo(u[0]) if newversion != "" and newrevision != "": dbg.log("[+] Version compare :") dbg.log(" Current Version : %s, Current Revision : %s" % (currentversion,currentrevision)) dbg.log(" Latest Version : %s, Latest Revision : %s" % (newversion,newrevision)) else: dbg.log("[-] Unable to check latest version (corrupted file ?), try again later",highlight=1) return except: dbg.log("[-] Unable to check latest version (download error). Try again later",highlight=1) dbg.log(" Meanwhile, please check/confirm that you're running a recent version of python 2.7 (2.7.14 or higher)", highlight=1) return #check versions doupdate = False if newversion != "" and newrevision != "": if currentversion != newversion: doupdate = True else: if int(currentrevision) < int(newrevision): doupdate = True if doupdate: dbg.log("[+] New version available",highlight=1) dbg.log(" Updating to %s r%s" % (newversion,newrevision),highlight=1) try: shutil.copyfile(u[0],libfile) dbg.log(" Done") except: dbg.log(" ** Unable to update windbglib.py",highlight=1) currentversion,currentrevision = getVersionInfo(libfile) dbg.log("[+] Current version : %s r%s" % (currentversion,currentrevision)) else: dbg.log("[+] You are running the latest version") dbg.setStatusBar("Done.") return #----- GetPC -----# def procgetPC(args): r32 = "" output = "" if "r" in args: if type(args["r"]).__name__.lower() != "bool": r32 = args["r"].lower() if r32 == "" or not "r" in args: dbg.log("Missing argument -r ",highlight=1) return opcodes = {} opcodes["eax"] = "\\x58" opcodes["ecx"] = "\\x59" opcodes["edx"] = "\\x5a" opcodes["ebx"] = "\\x5b" opcodes["esp"] = "\\x5c" opcodes["ebp"] = "\\x5d" opcodes["esi"] = "\\x5e" opcodes["edi"] = "\\x5f" calls = {} calls["eax"] = "\\xd0" calls["ecx"] = "\\xd1" calls["edx"] = "\\xd2" calls["ebx"] = "\\xd3" calls["esp"] = "\\xd4" calls["ebp"] = "\\xd5" calls["esi"] = "\\xd6" calls["edi"] = "\\xd7" output = "\n" + r32 + "| jmp short back:\n\"\\xeb\\x03" + opcodes[r32] + "\\xff" + calls[r32] + "\\xe8\\xf8\\xff\\xff\\xff\"\n" output += r32 + "| call + 4:\n\"\\xe8\\xff\\xff\\xff\\xff\\xc3" + opcodes[r32] + "\"\n" output += r32 + "| fstenv:\n\"\\xd9\\xeb\\x9b\\xd9\\x74\\x24\\xf4" + opcodes[r32] + "\"\n" global ignoremodules ignoremodules = True getpcfilename="getpc.txt" objgetpcfile = MnLog(getpcfilename) getpcfile = objgetpcfile.reset() objgetpcfile.write(output,getpcfile) ignoremodules = False dbg.logLines(output) dbg.log("") dbg.log("Wrote to file %s" % getpcfile) return #----- Egghunter -----# def procEgg(args): filename = "" egg = "w00t" usechecksum = False usewow64 = False useboth = False egg_size = 0 win_ver = "10" win_vers = ["7","10"] checksumbyte = "" extratext = "" global silent oldsilent = silent silent = True if "f" in args: if type(args["f"]).__name__.lower() != "bool": filename = args["f"] filename = getAbsolutePath(filename.replace("'", "").replace("\"", "")) if "winver" in args: if str(args["winver"]) in win_vers: win_ver = str(args["winver"]) else: dbg.log("[-] Didn't recognize windows version, using Win10 as the default", highlight=True) #Set egg if "t" in args: if type(args["t"]).__name__.lower() != "bool": egg = args["t"] if "wow64" in args: usewow64 = True # placeholder for later if "both" in args: useboth = True if len(egg) != 4: egg = 'w00t' dbg.log("[+] Egg set to %s" % egg) if "c" in args: if filename != "": usechecksum = True dbg.log("[+] Hunter will include checksum routine") else: dbg.log("Option -c only works in conjunction with -f ",highlight=1) return startreg = "" if "startreg" in args: if isReg(args["startreg"]): startreg = args["startreg"].lower() dbg.log("[+] Egg will start search at %s" % startreg) depmethods = ["virtualprotect","copy","copy_size"] depreg = "esi" depsize = 0 freeregs = [ "ebx","ecx","ebp","esi" ] regsx = {} # 0 : mov xX # 1 : push xX # 2 : mov xL # 3 : mov xH # regsx["eax"] = ["\x66\xb8","\x66\x50","\xb0","\xb4"] regsx["ebx"] = ["\x66\xbb","\x66\x53","\xb3","\xb7"] regsx["ecx"] = ["\x66\xb9","\x66\x51","\xb1","\xb5"] regsx["edx"] = ["\x66\xba","\x66\x52","\xb2","\xb6"] regsx["esi"] = ["\x66\xbe","\x66\x56"] regsx["edi"] = ["\x66\xbf","\x66\x57"] regsx["ebp"] = ["\x66\xbd","\x66\x55"] regsx["esp"] = ["\x66\xbc","\x66\x54"] addreg = {} addreg["eax"] = "\x83\xc0" addreg["ebx"] = "\x83\xc3" addreg["ecx"] = "\x83\xc1" addreg["edx"] = "\x83\xc2" addreg["esi"] = "\x83\xc6" addreg["edi"] = "\x83\xc7" addreg["ebp"] = "\x83\xc5" addreg["esp"] = "\x83\xc4" depdest = "" depmethod = "" getpointer = "" getsize = "" getpc = "" jmppayload = "\xff\xe7" #jmp edi if "depmethod" in args: if args["depmethod"].lower() in depmethods: depmethod = args["depmethod"].lower() dbg.log("[+] Hunter will include routine to bypass DEP on found shellcode") # other DEP related arguments ? # depreg # depdest # depsize if "depreg" in args: if isReg(args["depreg"]): depreg = args["depreg"].lower() if "depdest" in args: if isReg(args["depdest"]): depdest = args["depdest"].lower() if "depsize" in args: try: depsize = int(args["depsize"]) except: dbg.log(" ** Invalid depsize",highlight=1) return #read payload file data = "" if filename != "": try: f = open(filename, "rb") data = f.read() f.close() dbg.log("[+] Read payload file (%d bytes)" % len(data)) except: dbg.log("Unable to read file %s" %filename, highlight=1) return #let's start egghunter = "" if not usewow64: #Basic version of egghunter dbg.log("[+] Generating traditional 32bit egghunter code") egghunter = "" egghunter += ( "\x66\x81\xca\xff\x0f"+ #or dx,0xfff "\x42"+ #INC EDX "\x52" #push edx "\x6a\x02" #push 2 (NtAccessCheckAndAuditAlarm syscall) "\x58" #pop eax "\xcd\x2e" #int 0x2e "\x3c\x05" #cmp al,5 "\x5a" #pop edx "\x74\xef" #je "or dx,0xfff" "\xb8"+egg+ #mov eax, egg "\x8b\xfa" #mov edi,edx "\xaf" #scasd "\x75\xea" #jne "inc edx" "\xaf" #scasd "\x75\xe7" #jne "inc edx" ) incedxoffset = 5 # The offset in the egghunter to reach the #INC EDX if usewow64: dbg.log("[+] Generating egghunter for wow64, Windows %s" % win_ver) egghunter = "" if win_ver == "7": egghunter += ( # 64 stub needed before loop "\x31\xdb" #xor ebx,ebx "\x53" #push ebx "\x53" #push ebx "\x53" #push ebx "\x53" #push ebx "\xb3\xc0" #mov bl,0xc0 # 64 Loop "\x66\x81\xCA\xFF\x0F" #OR DX,0FFF "\x42" #INC EDX "\x52" #PUSH EDX "\x6A\x26" #PUSH 26 "\x58" #POP EAX "\x33\xC9" #XOR ECX,ECX "\x8B\xD4" #MOV EDX,ESP "\x64\xff\x13" #CALL DWORD PTR FS:[ebx] "\x5e" #POP ESI "\x5a" #POP EDX "\x3C\x05" #CMP AL,5 "\x74\xe9" #JE SHORT "\xB8"+egg+ #MOV EAX,74303077 w00t "\x8B\xFA" #MOV EDI,EDX "\xAF" #SCAS DWORD PTR ES:[EDI] "\x75\xe4" #JNZ "inc edx" "\xAF" #SCAS DWORD PTR ES:[EDI] "\x75\xe1" #JNZ "inc edx" "") incedxoffset = 13 # The offset in the egghunter to reach the #INC EDX elif win_ver == "10": egghunter += ( # _start: # "\x8c\xcb" #MOV EBX,CS # "\x80\xfb\x23" #CMP BL,0x23 "\x33\xD2" #XOR EDX,EDX # invalid_page: "\x66\x81\xCA\xFF\x0F" #OR DX,0FFF # valid_page: "\x33\xDB" #XOR EBX,EBX "\x42" #INC EDX "\x53" #PUSH EBX "\x53" #PUSH EBX "\x52" #PUSH EDX "\x53" #PUSH EBX "\x53" #PUSH EBX "\x53" #PUSH EBX "\x6A\x29" #PUSH 29 "\x58" #POP EAX "\xB3\xC0" #MOV BL,0C0 "\x64\xFF\x13" #CALL DWORD PTR FS:[EBX] "\x83\xC4\x0c" #ADD ESP,0xc "\x5A" #POP EDX "\x83\xc4\x08" #ADD ESP,0x8 "\x3C\x05" #CMP AL,5 "\x74\xDF" #JE SHORT invalid_page "\xB8" + egg + #MOV EAX, "\x8B\xFA" #MOV EDI,EDX "\xAF" #SCAS DWORD PTR ES:[EDI] "\x75\xDA" #JNZ SHORT valid_page "\xAF" #SCAS DWORD PTR ES:[EDI] "\x75\xD7" #JNZ SHORT valid_page ) incedxoffset = 9 # The offset in the egghunter to reach the #INC EDX if usechecksum: dbg.log("[+] Generating checksum routine") extratext = "+ checksum routine" egg_size = "" if len(data) < 256: cmp_reg = "\x80\xf9" #cmp cl,value egg_size = hex2bin("%02x" % len(data)) offset1 = "\xf7" elif len(data) < 65536: cmp_reg = "\x66\x81\xf9" #cmp cx,value #avoid nulls egg_size_normal = "%04X" % len(data) while egg_size_normal[0:2] == "00" or egg_size_normal[2:4] == "00": data += "\x90" egg_size_normal = "%04X" % len(data) egg_size = hex2bin(egg_size_normal[2:4]) + hex2bin(egg_size_normal[0:2]) offset1 = "\xf5" else: dbg.log("Cannot use checksum code with this payload size (way too big)",highlight=1) return sum = 0 for byte in data: sum += ord(byte) sumstr= toHex(sum) checksumbyte = sumstr[len(sumstr)-2:len(sumstr)] sizeOfjnzincedx = 2 # The number of bytes needed for the the jnz "inc edx" instruction below sizeOfChecksumRoutine = 15 # The number of static bytes in the checksum routine below offset2 = shortJump(sizeOfjnzincedx, - (len(egghunter) - incedxoffset + sizeOfChecksumRoutine + len(cmp_reg) + len(egg_size))) egghunter += ( "\x51" #push ecx "\x31\xc9" #xor ecx,ecx "\x31\xc0" #xor eax,eax "\x02\x04\x0f" #add al,byte [edi+ecx] "\x41"+ #inc ecx cmp_reg + egg_size + #cmp cx/cl, value "\x75" + offset1 + #jnz "add al,byte [edi+ecx] "\x3a\x04\x39" + #cmp al,byte [edi+ecx] "\x59" + #pop ecx "\x75" + offset2 #jnz "inc edx" ) #dep bypass ? if depmethod != "": dbg.log("[+] Generating dep bypass routine") if not depreg in freeregs: getpointer += "mov " + freeregs[0] +"," + depreg + "#" depreg = freeregs[0] freeregs.remove(depreg) if depmethod == "copy" or depmethod == "copy_size": if depdest != "": if not depdest in freeregs: getpointer += "mov " + freeregs[0] + "," + depdest + "#" depdest = freeregs[0] else: getpc = "\xd9\xee" # fldz getpc += "\xd9\x74\xe4\xf4" # fstenv [esp-0c] depdest = freeregs[0] getpc += hex2bin(assemble("pop "+depdest)) freeregs.remove(depdest) sizereg = freeregs[0] if depsize == 0: # set depsize to payload * 2 if we are using a file depsize = len(data) * 2 if depmethod == "copy_size": depsize = len(data) if depsize == 0: dbg.log("** Please specify a valid -depsize when you are not using -f **",highlight=1) return else: if depsize <= 127: #simply push it to the stack getsize = "\x6a" + hex2bin("\\x" + toHexByte(depsize)) else: #can we do it with 16bit reg, no nulls ? if depsize <= 65535: sizeparam = toHex(depsize)[4:8] getsize = hex2bin(assemble("xor "+sizereg+","+sizereg)) if not (sizeparam[0:2] == "00" or sizeparam[2:4] == "00"): #no nulls, hooray, write to xX getsize += regsx[sizereg][0]+hex2bin("\\x" + sizeparam[2:4] + "\\x" + sizeparam[0:2]) else: # write the non null if we can if len(regsx[sizereg]) > 2: if not (sizeparam[0:2] == "00"): # write to xH getsize += regsx[sizereg][3] + hex2bin("\\x" + sizeparam[0:2]) if not (sizeparam[2:4] == "00"): # write to xL getsize += regsx[sizereg][2] + hex2bin("\\x" + sizeparam[2:4]) else: #we have to write the full value to sizereg blockcnt = 0 vpsize = 0 blocksize = depsize while blocksize >= 127: blocksize = blocksize / 2 blockcnt += 1 if blockcnt > 0: getsize += addreg[sizereg] + hex2bin("\\x" + toHexByte(blocksize)) vpsize = blocksize depblockcnt = 0 while depblockcnt < blockcnt: getsize += hex2bin(assemble("add "+sizereg+","+sizereg)) vpsize += vpsize depblockcnt += 1 delta = depsize - vpsize if delta > 0: getsize += addreg[sizereg] + hex2bin("\\x" + toHexByte(delta)) else: getsize += addreg[sizereg] + hex2bin("\\x" + toHexByte(depsize)) # finally push getsize += hex2bin(assemble("push "+ sizereg)) else: dbg.log("** Shellcode size (depsize) is too big",highlight=1) return #finish it off if depmethod == "virtualprotect": jmppayload = "\x54\x6a\x40" jmppayload += getsize jmppayload += hex2bin(assemble("#push edi#push edi#push "+depreg+"#ret")) elif depmethod == "copy": jmppayload = hex2bin(assemble("push edi\push "+depdest+"#push "+depdest+"#push "+depreg+"#mov edi,"+depdest+"#ret")) elif depmethod == "copy_size": jmppayload += getsize jmppayload += hex2bin(assemble("push edi#push "+depdest+"#push " + depdest + "#push "+depreg+"#mov edi,"+depdest+"#ret")) #jmp to payload egghunter += getpc egghunter += jmppayload startat = "" skip = "" #start at a certain reg ? if startreg != "": if startreg != "edx": startat = hex2bin(assemble("mov edx," + startreg)) skip = "\xeb\x05" egghunter = skip + egghunter #pickup pointer for DEP bypass ? egghunter = hex2bin(assemble(getpointer)) + egghunter egghunter = startat + egghunter silent = oldsilent #Convert binary to printable hex format egghunter_hex = toniceHex(egghunter.strip().replace(" ",""),16) global ignoremodules ignoremodules = True hunterfilename="egghunter.txt" objegghunterfile = MnLog(hunterfilename) egghunterfile = objegghunterfile.reset() dbg.log("[+] Egghunter %s (%d bytes): " % (extratext,len(egghunter.strip().replace(" ","")))) dbg.logLines("%s" % egghunter_hex) objegghunterfile.write("Egghunter " + extratext + ", tag " + egg + " : ",egghunterfile) objegghunterfile.write(egghunter_hex,egghunterfile) if filename == "": objegghunterfile.write("Put this tag in front of your shellcode : " + egg + egg,egghunterfile) else: dbg.log("[+] Shellcode, with tag : ") block = "\"" + egg + egg + "\"\n" cnt = 0 flip = 1 thisline = "\"" while cnt < len(data): thisline += "\\x%s" % toHexByte(ord(data[cnt])) if (flip == 32) or (cnt == len(data)-1): if cnt == len(data)-1 and checksumbyte != "": thisline += "\\x%s" % checksumbyte thisline += "\"" flip = 0 block += thisline block += "\n" thisline = "\"" cnt += 1 flip += 1 dbg.logLines(block) objegghunterfile.write("\nShellcode, with tag :\n",egghunterfile) objegghunterfile.write(block,egghunterfile) ignoremodules = False return #----- Find MSP ------ # def procFindMSP(args): distance = 0 if "distance" in args: try: distance = int(args["distance"]) except: distance = 0 if distance < 0: dbg.log("** Please provide a positive number as distance",highlight=1) return mspresults = {} mspresults = goFindMSP(distance,args) return def procSuggest(args): modulecriteria={} criteria={} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) isEIP = False isSEH = False isEIPUnicode = False isSEHUnicode = False initialoffsetSEH = 0 initialoffsetEIP = 0 shellcodesizeSEH = 0 shellcodesizeEIP = 0 nullsallowed = True global ignoremodules global noheader global ptr_to_get global silent global ptr_counter targetstr = "" exploitstr = "" originalauthor = "" url = "" #are we attached to an application ? if dbg.getDebuggedPid() == 0: dbg.log("** You don't seem to be attached to an application ! **",highlight=1) return exploittype = "" skeletonarg = "" usecliargs = False validstypes ={} validstypes["tcpclient"] = "network client (tcp)" validstypes["udpclient"] = "network client (udp)" validstypes["fileformat"] = "fileformat" exploittypes = [ "fileformat","network client (tcp)","network client (udp)" ] if __DEBUGGERAPP__ == "WinDBG" or "t" in args: if "t" in args: if type(args["t"]).__name__.lower() != "bool": skeltype = args["t"].lower() skelparts = skeltype.split(":") if skelparts[0] in validstypes: exploittype = validstypes[skelparts[0]] if len(skelparts) > 1: skeletonarg = skelparts[1] else: dbg.log(" ** Please specify the skeleton type AND an argument. **") return usecliargs = True else: dbg.log(" ** Please specify a valid skeleton type and an argument. **") return else: dbg.log(" ** Please specify a skeletontype using -t **",highlight=1) return else: dbg.log(" ** Please specify a skeletontype using -t **",highlight=1) return mspresults = {} mspresults = goFindMSP(100,args) #create metasploit skeleton file exploitfilename="exploit.rb" objexploitfile = MnLog(exploitfilename) #ptr_to_get = 5 noheader = True ignoremodules = True exploitfile = objexploitfile.reset() ignoremodules = False noheader = False dbg.log(" ") dbg.log("[+] Preparing payload...") dbg.log(" ") dbg.updateLog() #what options do we have ? # 0 : pointer # 1 : offset # 2 : type if "registers" in mspresults: for reg in mspresults["registers"]: if reg.upper() == "EIP": isEIP = True eipval = mspresults["registers"][reg][0] ptrx = MnPointer(eipval) initialoffsetEIP = mspresults["registers"][reg][1] # 0 : pointer # 1 : offset # 2 : type # 3 : size if "seh" in mspresults: if len(mspresults["seh"]) > 0: isSEH = True for seh in mspresults["seh"]: if mspresults["seh"][seh][2] == "unicode": isSEHUnicode = True if not isSEHUnicode: initialoffsetSEH = mspresults["seh"][seh][1] else: initialoffsetSEH = mspresults["seh"][seh][1] shellcodesizeSEH = mspresults["seh"][seh][3] if isSEH: ignoremodules = True noheader = True exploitfilename_seh="exploit_seh.rb" objexploitfile_seh = MnLog(exploitfilename_seh) exploitfile_seh = objexploitfile_seh.reset() ignoremodules = False noheader = False # start building exploit structure if not isEIP and not isSEH: dbg.log(" ** Unable to suggest anything useful. You don't seem to control EIP or SEH ** ",highlight=1) return # ask for type of module if not usecliargs: dbg.log(" ** Please select a skeleton exploit type from the dropdown list **",highlight=1) exploittype = dbg.comboBox("Select msf exploit skeleton to build :", exploittypes).lower().strip() if not exploittype in exploittypes: dbg.log("Boo - invalid exploit type, try again !",highlight=1) return portnr = 0 extension = "" if exploittype.find("network") > -1: if usecliargs: portnr = skeletonarg else: portnr = dbg.inputBox("Remote port number : ") try: portnr = int(portnr) except: portnr = 0 if exploittype.find("fileformat") > -1: if usecliargs: extension = skeletonarg else: extension = dbg.inputBox("File extension :") extension = extension.replace("'","").replace('"',"").replace("\n","").replace("\r","") if not extension.startswith("."): extension = "." + extension dbg.createLogWindow() dbg.updateLog() url = "" badchars = "" if "badchars" in criteria: badchars = criteria["badchars"] if "nonull" in criteria: if not '\x00' in badchars: badchars += '\x00' skeletonheader,skeletoninit,skeletoninit2 = getSkeletonHeader(exploittype,portnr,extension,url,badchars) regsto = "" if isEIP: dbg.log("[+] Attempting to create payload for saved return pointer overwrite...") #where can we jump to - get the register that has the largest buffer size largestreg = "" largestsize = 0 offsetreg = 0 regptr = 0 # register_to # 0 : pointer # 1 : offset # 2 : size # 3 : type eipcriteria = criteria modulecriteria["aslr"] = False modulecriteria["rebase"] = False modulecriteria["os"] = False jmp_pointers = {} jmppointer = 0 instrinfo = "" if isEIPUnicode: eipcriteria["unicode"] = True eipcriteria["nonull"] = False if "registers_to" in mspresults: for reg in mspresults["registers_to"]: regsto += reg+"," thissize = mspresults["registers_to"][reg][2] thisreg = reg thisoffset = mspresults["registers_to"][reg][1] thisregptr = mspresults["registers_to"][reg][0] if thisoffset < initialoffsetEIP: #fix the size, which will end at offset to EIP thissize = initialoffsetEIP - thisoffset if thissize > largestsize: # can we find a jmp to that reg ? silent = True ptr_counter = 0 ptr_to_get = 1 jmp_pointers = findJMP(modulecriteria,eipcriteria,reg.lower()) if len( jmp_pointers ) == 0: ptr_counter = 0 ptr_to_get = 1 modulecriteria["os"] = True jmp_pointers = findJMP(modulecriteria,eipcriteria,reg.lower()) modulecriteria["os"] = False if len( jmp_pointers ) > 0: largestsize = thissize largestreg = thisreg offsetreg = thisoffset regptr = thisregptr silent = False regsto = regsto.rstrip(",") if largestreg == "": dbg.log(" Payload is referenced by at least one register (%s), but I couldn't seem to find" % regsto,highlight=1) dbg.log(" a way to jump to that register",highlight=1) else: #build exploit for ptrtype in jmp_pointers: jmppointer = jmp_pointers[ptrtype][0] instrinfo = ptrtype break ptrx = MnPointer(jmppointer) modname = ptrx.belongsTo() targetstr = " 'Targets' =>\n" targetstr += " [\n" targetstr += " [ '',\n" targetstr += " {\n" if not isEIPUnicode: targetstr += " 'Ret' => 0x" + toHex(jmppointer) + ", # " + instrinfo + " - " + modname + "\n" targetstr += " 'Offset' => " + str(initialoffsetEIP) + "\n" else: origptr = toHex(jmppointer) #real unicode ? unicodeptr = "" transforminfo = "" if origptr[0] == "0" and origptr[1] == "0" and origptr[4] == "0" and origptr[5] == "0": unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" else: #transform transform = UnicodeTransformInfo(origptr) transformparts = transform.split(",") transformsubparts = transformparts[0].split(" ") origptr = transformsubparts[len(transformsubparts)-1] transforminfo = " #unicode transformed to 0x" + toHex(jmppointer) unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" targetstr += " 'Ret' => " + unicodeptr + "," + transforminfo + "# " + instrinfo + " - " + modname + "\n" targetstr += " 'Offset' => " + str(initialoffsetEIP) + " #Unicode\n" targetstr += " }\n" targetstr += " ],\n" targetstr += " ],\n" exploitstr = " def exploit\n\n" if exploittype.find("network") > -1: if exploittype.find("tcp") > -1: exploitstr += "\n connect\n\n" elif exploittype.find("udp") > -1: exploitstr += "\n connect_udp\n\n" if initialoffsetEIP < offsetreg: # eip is before shellcode exploitstr += " buffer = rand_text(target['Offset']) \n" if not isEIPUnicode: exploitstr += " buffer << [target.ret].pack('V') \n" else: exploitstr += " buffer << target['Ret'] #Unicode friendly jump\n\n" if offsetreg > initialoffsetEIP+2: if not isEIPUnicode: if (offsetreg - initialoffsetEIP - 4) > 0: exploitstr += " buffer << rand_text(" + str(offsetreg - initialoffsetEIP - 4) + ") #junk\n" else: if ((offsetreg - initialoffsetEIP - 4)/2) > 0: exploitstr += " buffer << rand_text(" + str((offsetreg - initialoffsetEIP - 4)/2) + ") #unicode junk\n" stackadjust = 0 if largestreg.upper() == "ESP": if not isEIPUnicode: exploitstr += " buffer << Metasm::Shellcode.assemble(Metasm::Ia32.new, 'add esp,-1500').encode_string # avoid GetPC shellcode corruption\n" stackadjust = 6 exploitstr += " buffer << payload.encoded #max " + str(largestsize - stackadjust) + " bytes\n" if isEIPUnicode: exploitstr += " # Metasploit requires double encoding for unicode : Use alpha_xxxx encoder in the payload section\n" exploitstr += " # and then manually encode with unicode inside the exploit section :\n\n" exploitstr += " enc = framework.encoders.create('x86/unicode_mixed')\n\n" exploitstr += " register_to_align_to = '" + largestreg.upper() + "'\n\n" if largestreg.upper() == "ESP": exploitstr += " # Note : since you are using ESP as bufferregister, make sure EBP points to a writeable address !\n" exploitstr += " # or patch the unicode decoder yourself\n" exploitstr += " enc.datastore.import_options_from_hash({ 'BufferRegister' => register_to_align_to })\n\n" exploitstr += " unicodepayload = enc.encode(payload.encoded, nil, nil, platform)\n\n" exploitstr += " buffer << unicodepayload" else: # EIP -> jump to location before EIP beforeEIP = initialoffsetEIP - offsetreg if beforeEIP > 0: if offsetreg > 0: exploitstr += " buffer = rand_text(" + str(offsetreg)+") #offset to " + largestreg+"\n" exploitstr += " buffer << payload.encoded #max " + str(initialoffsetEIP - offsetreg) + " bytes\n" exploitstr += " buffer << rand_text(target['Offset'] - payload.encoded.length)\n" exploitstr += " buffer << [target.ret].pack('V') \n" else: exploitstr += " buffer = payload.encoded #max " + str(initialoffsetEIP - offsetreg) + " bytes\n" exploitstr += " buffer << rand_text(target['Offset'] - payload.encoded.length)\n" exploitstr += " buffer << [target.ret].pack('V') \n" if exploittype.find("network") > -1: exploitstr += "\n print_status(\"Trying target #{target.name}...\")\n" if exploittype.find("tcp") > -1: exploitstr += " sock.put(buffer)\n" exploitstr += "\n handler\n" elif exploittype.find("udp") > -1: exploitstr += " udp_sock.put(buffer)\n" exploitstr += "\n handler(udp_sock)\n" if exploittype == "fileformat": exploitstr += "\n file_create(buffer)\n\n" if exploittype.find("network") > -1: exploitstr += " disconnect\n\n" exploitstr += " end\n" dbg.log("Metasploit 'Targets' section :") dbg.log("------------------------------") dbg.logLines(targetstr.replace(" "," ")) dbg.log("") dbg.log("Metasploit 'exploit' function :") dbg.log("--------------------------------") dbg.logLines(exploitstr.replace(" "," ")) #write skeleton objexploitfile.write(skeletonheader+"\n",exploitfile) objexploitfile.write(skeletoninit+"\n",exploitfile) objexploitfile.write(targetstr,exploitfile) objexploitfile.write(skeletoninit2,exploitfile) objexploitfile.write(exploitstr,exploitfile) objexploitfile.write("end",exploitfile) if isSEH: dbg.log("[+] Attempting to create payload for SEH record overwrite...") sehcriteria = criteria modulecriteria["safeseh"] = False modulecriteria["rebase"] = False modulecriteria["aslr"] = False modulecriteria["os"] = False sehptr = 0 instrinfo = "" if isSEHUnicode: sehcriteria["unicode"] = True if "nonull" in sehcriteria: sehcriteria.pop("nonull") modulecriteria["safeseh"] = False #get SEH pointers silent = True ptr_counter = 0 ptr_to_get = 1 seh_pointers = findSEH(modulecriteria,sehcriteria) jmpback = False silent = False if not isSEHUnicode: #did we find a pointer ? if len(seh_pointers) == 0: #did we try to avoid nulls ? dbg.log("[+] No non-null pointers found, trying 'jump back' layout now...") if "nonull" in sehcriteria: if sehcriteria["nonull"] == True: sehcriteria.pop("nonull") silent = True ptr_counter = 0 ptr_to_get = 1 seh_pointers = findSEH(modulecriteria,sehcriteria) silent = False jmpback = True if len(seh_pointers) != 0: for ptrtypes in seh_pointers: sehptr = seh_pointers[ptrtypes][0] instrinfo = ptrtypes break else: if len(seh_pointers) == 0: sehptr = 0 else: for ptrtypes in seh_pointers: sehptr = seh_pointers[ptrtypes][0] instrinfo = ptrtypes break if sehptr != 0: ptrx = MnPointer(sehptr) modname = ptrx.belongsTo() mixin = "" if not jmpback: mixin += "#Don't forget to include the SEH mixin !\n" mixin += "include Msf::Exploit::Seh\n\n" skeletonheader += " include Msf::Exploit::Seh\n" targetstr = " 'Targets' =>\n" targetstr += " [\n" targetstr += " [ '',\n" targetstr += " {\n" if not isSEHUnicode: targetstr += " 'Ret' => 0x" + toHex(sehptr) + ", # " + instrinfo + " - " + modname + "\n" targetstr += " 'Offset' => " + str(initialoffsetSEH) + "\n" else: origptr = toHex(sehptr) #real unicode ? unicodeptr = "" transforminfo = "" if origptr[0] == "0" and origptr[1] == "0" and origptr[4] == "0" and origptr[5] == "0": unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" else: #transform transform = UnicodeTransformInfo(origptr) transformparts = transform.split(",") transformsubparts = transformparts[0].split(" ") origptr = transformsubparts[len(transformsubparts)-1] transforminfo = " #unicode transformed to 0x" + toHex(sehptr) unicodeptr = "\"\\x" + origptr[6] + origptr[7] + "\\x" + origptr[2] + origptr[3] + "\"" targetstr += " 'Ret' => " + unicodeptr + "," + transforminfo + " # " + instrinfo + " - " + modname + "\n" targetstr += " 'Offset' => " + str(initialoffsetSEH) + " #Unicode\n" targetstr += " }\n" targetstr += " ],\n" targetstr += " ],\n" exploitstr = " def exploit\n\n" if exploittype.find("network") > -1: exploitstr += "\n connect\n\n" if not isSEHUnicode: if not jmpback: exploitstr += " buffer = rand_text(target['Offset']) #junk\n" exploitstr += " buffer << generate_seh_record(target.ret)\n" exploitstr += " buffer << payload.encoded #" + str(shellcodesizeSEH) +" bytes of space\n" exploitstr += " # more junk may be needed to trigger the exception\n" else: exploitstr += " jmp_back = Rex::Arch::X86.jmp_short(-payload.encoded.length-5)\n\n" exploitstr += " buffer = rand_text(target['Offset'] - payload.encoded.length - jmp_back.length) #junk\n" exploitstr += " buffer << payload.encoded\n" exploitstr += " buffer << jmp_back #jump back to start of payload.encoded\n" exploitstr += " buffer << '\\xeb\\xf9\\x41\\x41' #nseh, jump back to jmp_back\n" exploitstr += " buffer << [target.ret].pack('V') #seh\n" else: exploitstr += " nseh = \n" exploitstr += " align = \n\n" exploitstr += " padding = \n\n" exploitstr += " # Metasploit requires double encoding for unicode : Use alpha_xxxx encoder in the payload section\n" exploitstr += " # and then manually encode with unicode inside the exploit section :\n\n" exploitstr += " enc = framework.encoders.create('x86/unicode_mixed')\n\n" exploitstr += " register_to_align_to = \n\n" exploitstr += " enc.datastore.import_options_from_hash({ 'BufferRegister' => register_to_align_to })\n\n" exploitstr += " unicodepayload = enc.encode(payload.encoded, nil, nil, platform)\n\n" exploitstr += " buffer = rand_text(target['Offset']) #unicode junk\n" exploitstr += " buffer << nseh #Unicode walkover friendly dword\n" exploitstr += " buffer << target['Ret'] #Unicode friendly p/p/r\n" exploitstr += " buffer << align\n" exploitstr += " buffer << padding\n" exploitstr += " buffer << unicodepayload\n" if exploittype.find("network") > -1: exploitstr += "\n print_status(\"Trying target #{target.name}...\")\n" exploitstr += " sock.put(buffer)\n\n" exploitstr += " handler\n" if exploittype == "fileformat": exploitstr += "\n file_create(buffer)\n\n" if exploittype.find("network") > -1: exploitstr += " disconnect\n\n" exploitstr += " end\n" if mixin != "": dbg.log("Metasploit 'include' section :") dbg.log("------------------------------") dbg.logLines(mixin) dbg.log("Metasploit 'Targets' section :") dbg.log("------------------------------") dbg.logLines(targetstr.replace(" "," ")) dbg.log("") dbg.log("Metasploit 'exploit' function :") dbg.log("--------------------------------") dbg.logLines(exploitstr.replace(" "," ")) #write skeleton objexploitfile_seh.write(skeletonheader+"\n",exploitfile_seh) objexploitfile_seh.write(skeletoninit+"\n",exploitfile_seh) objexploitfile_seh.write(targetstr,exploitfile_seh) objexploitfile_seh.write(skeletoninit2,exploitfile_seh) objexploitfile_seh.write(exploitstr,exploitfile_seh) objexploitfile_seh.write("end",exploitfile_seh) else: dbg.log(" Unable to suggest a buffer layout because I couldn't find any good pointers",highlight=1) return #-----stacks-----# def procStacks(args): stacks = getStacks() if len(stacks) > 0: dbg.log("Stacks :") dbg.log("--------") for threadid in stacks: dbg.log("Thread %s : Stack : 0x%s - 0x%s (size : 0x%s)" % (str(threadid),toHex(stacks[threadid][0]),toHex(stacks[threadid][1]),toHex(stacks[threadid][1]-stacks[threadid][0]))) else: dbg.log("No threads/stacks found !",highlight=1) return #------heapstuff-----# def procHeap(args): os = dbg.getOsVersion() heapkey = 0 #first, print list of heaps allheaps = [] try: allheaps = dbg.getHeapsAddress() except: allheaps = [] dbg.log("Peb : 0x%08x, NtGlobalFlag : 0x%08x" % (dbg.getPEBAddress(),getNtGlobalFlag())) dbg.log("Heaps:") dbg.log("------") if len(allheaps) > 0: for heap in allheaps: segments = getSegmentList(heap) segmentlist = [] for segment in segments: segmentlist.append(segment) if not win7mode: segmentlist.sort() segmentinfo = "" for segment in segmentlist: segmentinfo = segmentinfo + "0x%08x" % segment + "," segmentinfo = segmentinfo.strip(",") segmentinfo = " : " + segmentinfo defheap = "" lfhheap = "" keyinfo = "" if heap == getDefaultProcessHeap(): defheap = "* Default process heap" if win7mode: iHeap = MnHeap(heap) if iHeap.usesLFH(): lfhheapaddress = iHeap.getLFHAddress() lfhheap = "[LFH enabled, _LFH_HEAP at 0x%08x]" % lfhheapaddress if iHeap.getEncodingKey() > 0: keyinfo = "Encoding key: 0x%08x" % iHeap.getEncodingKey() dbg.log("0x%08x (%d segment(s)%s) %s %s %s" % (heap,len(segments),segmentinfo,defheap,lfhheap,keyinfo)) else: dbg.log(" ** No heaps found") dbg.log("") heapbase = 0 searchtype = "" searchtypes = ["lal","lfh","all","segments", "chunks", "layout", "fea", "bea"] error = False filterafter = "" showdata = False findvtablesize = True expand = False minstringlength = 32 if len(allheaps) > 0: if "h" in args and type(args["h"]).__name__.lower() != "bool": hbase = args["h"].replace("0x","").replace("0X","") if not (isAddress(hbase) or hbase.lower() == "default"): dbg.log("%s is an invalid address" % args["h"], highlight=1) return else: if hbase.lower() == "default": heapbase = getDefaultProcessHeap() else: heapbase = hexStrToInt(hbase) if "t" in args: if type(args["t"]).__name__.lower() != "bool": searchtype = args["t"].lower().replace('"','').replace("'","") if searchtype == "blocks": dbg.log("** Note : type 'blocks' has been replaced with 'chunks'",highlight=1) dbg.log("") searchtype = "chunks" if not searchtype in searchtypes: searchtype = "" else: searchtype = "" if "after" in args: if type(args["after"]).__name__.lower() != "bool": filterafter = args["after"].replace('"','').replace("'","") if "v" in args: showdata = True if "expand" in args: expand = True if "fast" in args: findvtablesize = False showdata = False if searchtype == "" and not "stat" in args: dbg.log("Please specify a valid searchtype -t",highlight=1) dbg.log("Valid values are :",highlight=1) for val in searchtypes: if val != "blocks": dbg.log(" %s" % val,highlight=1) error = True if "h" in args and heapbase == 0: dbg.log("Please specify a valid heap base address -h",highlight=1) error = True if "size" in args: if type(args["size"]).__name__.lower() != "bool": size = args["size"].lower() if size.startswith("0x"): minstringlength = hexStrToInt(size) else: minstringlength = int(size) else: dbg.log("Please provide a valid size -size",highlight=1) error = True if "clearcache" in args: dbg.forgetKnowledge("vtableCache") dbg.log("[+] vtableCache cleared.") else: dbg.log("No heaps found",highlight=1) return heap_to_query = [] heapfound = False if "h" in args: for heap in allheaps: if heapbase == heap: heapfound = True heap_to_query = [heapbase] if not heapfound: error = True dbg.log("0x%08x is not a valid heap base address" % heapbase,highlight=1) else: #show all heaps for heap in allheaps: heap_to_query.append(heap) if error: return else: statinfo = {} logfile_b = "" thislog_b = "" logfile_l = "" logfile_l = "" if searchtype == "chunks" or searchtype == "all": logfile_b = MnLog("heapchunks.txt") thislog_b = logfile_b.reset() if searchtype == "layout" or searchtype == "all": logfile_l = MnLog("heaplayout.txt") thislog_l = logfile_l.reset() for heapbase in heap_to_query: mHeap = MnHeap(heapbase) heapbase_extra = "" frontendinfo = [] frontendheapptr = 0 frontendheaptype = 0 if win7mode: heapkey = mHeap.getEncodingKey() if mHeap.usesLFH(): frontendheaptype = 0x2 heapbase_extra = " [LFH] " frontendheapptr = mHeap.getLFHAddress() frontendinfo = [frontendheaptype,frontendheapptr] dbg.log("") dbg.log("[+] Processing heap 0x%08x%s" % (heapbase,heapbase_extra)) if searchtype == "fea": if win7mode: searchtype = "lfh" else: searchtype = "lal" if searchtype == "bea": searchtype = "freelist" # LookAsideList if searchtype == "lal" or (searchtype == "all" and not win7mode): lalindex = 0 if win7mode: dbg.log(" !! This version of the OS doesn't have a LookAside List !!") else: dbg.log("[+] FrontEnd Allocator : LookAsideList") dbg.log("[+] Getting LookAsideList for heap 0x%08x" % heapbase) # do we have a LAL for this heap ? FrontEndHeap = mHeap.getFrontEndHeap() if FrontEndHeap > 0: dbg.log(" FrontEndHeap: 0x%08x" % FrontEndHeap) fea_lal = mHeap.getLookAsideList() dbg.log(" Nr of (non-empty) LookAside Lists : %d" % len(fea_lal)) dbg.log("") for lal_table_entry in sorted(fea_lal.keys()): expectedsize = lal_table_entry * 8 nr_of_chunks = len(fea_lal[lal_table_entry]) lalhead = struct.unpack(' 1 and int(bitmapstr[flindex]) != flindicator: dbg.log(" ** FreeListsInUseBitmap mismatch for index %d! **" % flindex, highlight = True) flindex += 1 if searchtype == "layout" or searchtype == "all": segments = getSegmentsForHeap(heapbase) sortedsegments = [] global vtableCache # read vtableCache from knowledge vtableCache = dbg.getKnowledge("vtableCache") if vtableCache is None: vtableCache = {} for seg in segments: sortedsegments.append(seg) if not win7mode: sortedsegments.sort() segmentcnt = 0 minstringlen = minstringlength blockmem = [] nr_filter_matches = 0 vablocks = [] # VirtualAllocdBlocks vachunks = mHeap.getVirtualAllocdBlocks() infoblocks = {} infoblocks["segments"] = sortedsegments if expand: infoblocks["virtualallocdblocks"] = [vachunks] for infotype in infoblocks: heapdata = infoblocks[infotype] for thisdata in heapdata: if infotype == "segments": seg = thisdata segmentcnt += 1 segstart = segments[seg][0] segend = segments[seg][1] FirstEntry = segments[seg][2] LastValidEntry = segments[seg][3] datablocks = walkSegment(FirstEntry,LastValidEntry,heapbase) tolog = "----- Heap 0x%08x%s, Segment 0x%08x - 0x%08x (%d/%d) -----" % (heapbase,heapbase_extra,segstart,segend,segmentcnt,len(sortedsegments)) if infotype == "virtualallocdblocks": datablocks = heapdata[0] tolog = "----- Heap 0x%08x%s, VirtualAllocdBlocks : %d" % (heapbase,heapbase_extra,len(datablocks)) logfile_l.write(" ",thislog_l) dbg.log(tolog) logfile_l.write(tolog,thislog_l) sortedblocks = [] for block in datablocks: sortedblocks.append(block) sortedblocks.sort() # for each block, try to get info # object ? # BSTR ? # str ? for block in sortedblocks: showinlog = False thischunk = datablocks[block] unused = thischunk.unused headersize = thischunk.headersize flags = getHeapFlag(thischunk.flag) userptr = block + headersize psize = thischunk.prevsize * 8 blocksize = thischunk.size * 8 selfsize = blocksize usersize = selfsize - unused usersize = blocksize - unused extratxt = "" if infotype == "virtualallocdblocks": selfsize = thischunk.commitsize * 8 blocksize = selfsize usersize = selfsize - unused nextblock = thischunk.flink # read block into memory blockmem = dbg.readMemory(block,blocksize) # first, find all strings (ascii, unicode and BSTR) asciistrings = {} unicodestrings = {} bstr = {} objects = {} asciistrings = getAllStringOffsets(blockmem,minstringlen) # determine remaining subsets of the original block remaining = {} curpos = 0 for stringpos in asciistrings: if stringpos > curpos: remaining[curpos] = stringpos - curpos curpos = asciistrings[stringpos] if curpos < blocksize: remaining[curpos] = blocksize # search for unicode in remaining subsets only - tx for the regex help Turboland ! for remstart in remaining: remend = remaining[remstart] thisunicodestrings = getAllUnicodeStringOffsets(blockmem[remstart:remend],minstringlen,remstart) # append results to master list for tus in thisunicodestrings: unicodestrings[tus] = thisunicodestrings[tus] # check each unicode, maybe it's a BSTR tomove = [] for unicodeoffset in unicodestrings: delta = unicodeoffset size = (unicodestrings[unicodeoffset] - unicodeoffset)/2 if delta >= 4: maybesize = struct.unpack(' -1 and line.find("vftable") > -1: parts = line.split(" ") objconstr = "" if len(parts) > 3: objectptr = hexStrToInt(parts[0]) cnt = 2 objectinfo = "" while cnt < len(parts): objectinfo += parts[cnt] + " " cnt += 1 parts2 = line.split("::") parts2name = "" pcnt = 0 while pcnt < len(parts2)-1: parts2name = parts2name + "::" + parts2[pcnt] pcnt += 1 parts3 = parts2name.split(" ") if len(parts3) > 3: objconstr = parts3[3] if not objectptr in objects: objects[objectptr-block] = [objectinfo,objconstr] objsize = 0 if findvtablesize: if not objconstr in vtableCache: cmd2run = "u %s::CreateElement L 12" % objconstr objoutput = dbg.nativeCommand(cmd2run) if not "HeapAlloc" in objoutput: cmd2run = "x %s::operator*" % objconstr oplist = dbg.nativeCommand(cmd2run) oplines = oplist.split("\n") oppat = "%s::operator" % objconstr for opline in oplines: if oppat in opline and not "del" in opline: lineparts = opline.split(" ") cmd2run = "uf %s" % lineparts[0] objoutput = dbg.nativeCommand(cmd2run) break if "HeapAlloc" in objoutput: objlines = objoutput.split("\n") lineindex = 0 for objline in objlines: if "HeapAlloc" in objline: if lineindex >= 3: sizeline = objlines[lineindex-3] if "push" in sizeline: sizelineparts = sizeline.split("push") if len(sizelineparts) > 1: sizevalue = sizelineparts[len(sizelineparts)-1].replace(" ","").replace("h","") try: objsize = hexStrToInt(sizevalue) # adjust allocation granulariy remainsize = objsize - ((objsize / 8) * 8) while remainsize != 0: objsize += 1 remainsize = objsize - ((objsize / 8) * 8) except: #print traceback.format_exc() objsize = 0 break lineindex += 1 vtableCache[objconstr] = objsize else: objsize = vtableCache[objconstr] # remove object entries that belong to the same object allobjects = [] objectstodelete = [] for optr in objects: allobjects.append(optr) allobjects.sort() skipuntil = 0 for optr in allobjects: if optr < skipuntil: objectstodelete.append(optr) else: objname = objects[optr][1] objsize = 0 try: objsize = vtableCache[objname] except: objsize = 0 skipuntil = optr + objsize # remove vtable lines that are too close to each other minvtabledistance = 0x0c prevvname = "" prevptr = 0 thisvname = "" for optr in allobjects: thisvname = objects[optr][1] if thisvname == prevvname and (optr - prevptr) <= minvtabledistance: if not optr in objectstodelete: objectstodelete.append(optr) else: prevptr = optr prevvname = thisvname for vtableptr in objectstodelete: del objects[vtableptr] for obj in objects: orderedobj.append(obj) for ascstring in asciistrings: orderedobj.append(ascstring) for unicodestring in unicodestrings: orderedobj.append(unicodestring) for bstrobj in bstr: orderedobj.append(bstrobj) orderedobj.sort() # print out details for this chunk chunkprefix = "" fieldname1 = "Usersize" fieldname2 = "ChunkSize" if infotype == "virtualallocdblocks": chunkprefix = "VA " fieldname1 = "CommitSize" tolog = "%sChunk 0x%08x (%s 0x%x, %s 0x%x) : %s" % (chunkprefix,block,fieldname1,usersize,fieldname2,usersize+unused,flags) if showdata: dbg.log(tolog) logfile_l.write(tolog,thislog_l) previousptr = block previoussize = 0 showinlog = False for ptr in orderedobj: ptrtype = "" ptrinfo = "" data = "" alldata = "" blockinfo = "" ptrbytes = 0 endptr = 0 datasize = 0 ptrchars = 0 infoptr = block + ptr endptr = 0 if ptr in asciistrings: ptrtype = "String" dataend = asciistrings[ptr] data = blockmem[ptr:dataend] alldata = data ptrbytes = len(data) ptrchars = ptrbytes datasize = ptrbytes if ptrchars > 100: data = data[0:100]+"..." blockinfo = "%s (Data : 0x%x/%d bytes, 0x%x/%d chars) : %s" % (ptrtype,ptrbytes,ptrbytes,ptrchars,ptrchars,data) infoptr = block + ptr endptr = infoptr + ptrchars - 1 # need -1 elif ptr in bstr: ptrtype = "BSTR" dataend = bstr[ptr] data = blockmem[ptr:dataend].replace("\x00","") alldata = data ptrchars = len(data) ptrbytes = ptrchars*2 datasize = ptrbytes+6 infoptr = block + ptr - 3 if ptrchars > 100: data = data[0:100]+"..." blockinfo = "%s 0x%x/%d bytes (Data : 0x%x/%d bytes, 0x%x/%d chars) : %s" % (ptrtype,ptrbytes+6,ptrbytes+6,ptrbytes,ptrbytes,ptrchars,ptrchars,data) endptr = infoptr + ptrbytes + 6 elif ptr in unicodestrings: ptrtype = "Unicode" dataend = unicodestrings[ptr] data = blockmem[ptr:dataend].replace("\x00","") alldata = "" ptrchars = len(data) ptrbytes = ptrchars * 2 datasize = ptrbytes if ptrchars > 100: data = data[0:100]+"..." blockinfo = "%s (0x%x/%d bytes, 0x%x/%d chars) : %s" % (ptrtype,ptrbytes,ptrbytes,ptrchars,ptrchars,data) endptr = infoptr + ptrbytes + 2 elif ptr in objects: ptrtype = "Object" data = objects[ptr][0] vtablename = objects[ptr][1] datasize = 0 if vtablename in vtableCache: datasize = vtableCache[vtablename] alldata = data if datasize > 0: blockinfo = "%s (0x%x bytes): %s" % (ptrtype,datasize,data) else: blockinfo = "%s : %s" % (ptrtype,data) endptr = infoptr + datasize # calculate delta slackspace = infoptr - previousptr if endptr > 0 and not ptrtype=="Object": if slackspace >= 0: tolog = " +%04x @ %08x->%08x : %s" % (slackspace,infoptr,endptr,blockinfo) else: tolog = " @ %08x->%08x : %s" % (infoptr,endptr,blockinfo) else: if slackspace >= 0: if endptr != infoptr: tolog = " +%04x @ %08x->%08x : %s" % (slackspace,infoptr,endptr,blockinfo) else: tolog = " +%04x @ %08x : %s" % (slackspace,infoptr,blockinfo) else: tolog = " @ %08x : %s" % (infoptr,blockinfo) if filterafter == "" or (filterafter != "" and filterafter in alldata): showinlog = True # keep this for the entire block if (filterafter != ""): nr_filter_matches += 1 if showinlog: if showdata: dbg.log(tolog) logfile_l.write(tolog,thislog_l) previousptr = endptr previoussize = datasize # save vtableCache again if filterafter != "": tolog = "Nr of filter matches: %d" % nr_filter_matches if showdata: dbg.log("") dbg.log(tolog) logfile_l.write("",thislog_l) logfile_l.write(tolog,thislog_l) dbg.addKnowledge("vtableCache",vtableCache) if searchtype in ["segments","all","chunks"] or "stat" in args: segments = getSegmentsForHeap(heapbase) dbg.log("Segment List for heap 0x%08x:" % (heapbase)) dbg.log("---------------------------------") sortedsegments = [] for seg in segments: sortedsegments.append(seg) if not win7mode: sortedsegments.sort() vablocks = [] # VirtualAllocdBlocks vachunks = mHeap.getVirtualAllocdBlocks() infoblocks = {} infoblocks["segments"] = sortedsegments if searchtype in ["all","chunks"]: infoblocks["virtualallocdblocks"] = [vachunks] for infotype in infoblocks: heapdata = infoblocks[infotype] for thisdata in heapdata: tolog = "" if infotype == "segments": # 0 : segmentstart # 1 : segmentend # 2 : firstentry # 3 : lastentry seg = thisdata segstart = segments[seg][0] segend = segments[seg][1] segsize = segend-segstart FirstEntry = segments[seg][2] LastValidEntry = segments[seg][3] tolog = "Segment 0x%08x - 0x%08x (FirstEntry: 0x%08x - LastValidEntry: 0x%08x): 0x%08x bytes" % (segstart,segend,FirstEntry,LastValidEntry, segsize) if infotype == "virtualallocdblocks": vablocks = heapdata tolog = "Heap : 0x%08x%s : VirtualAllocdBlocks : %d " % (heapbase,heapbase_extra,len(vachunks)) #dbg.log("") dbg.log(tolog) if searchtype == "chunks" or "stat" in args: try: logfile_b.write("Heap: 0x%08x%s" % (heapbase,heapbase_extra),thislog_b) #logfile_b.write("",thislog_b) logfile_b.write(tolog,thislog_b) except: pass if infotype == "segments": datablocks = walkSegment(FirstEntry,LastValidEntry,heapbase) else: datablocks = heapdata[0] tolog = " Nr of chunks : %d " % len(datablocks) dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass if len(datablocks) > 0: tolog = " _HEAP_ENTRY psize size unused UserPtr UserSize" dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass sortedblocks = [] for block in datablocks: sortedblocks.append(block) sortedblocks.sort() nextblock = 0 segstatinfo = {} for block in sortedblocks: showinlog = False thischunk = datablocks[block] unused = thischunk.unused headersize = thischunk.headersize flagtxt = getHeapFlag(thischunk.flag) if not infotype == "virtualallocdblocks" and "virtallocd" in flagtxt.lower(): flagtxt += " (LFH)" flagtxt = flagtxt.replace("Virtallocd","Internal") userptr = block + headersize psize = thischunk.prevsize * 8 blocksize = thischunk.size * 8 selfsize = blocksize usersize = selfsize - unused usersize = blocksize - unused extratxt = "" if infotype == "virtualallocdblocks": nextblock = thischunk.flink extratxt = " (0x%x bytes committed)" % (thischunk.commitsize * 8) else: nextblock = block + blocksize if not "stat" in args: tolog = " %08x %05x %05x %05x %08x %08x (%d) (%s) %s" % (block,psize,selfsize,unused,block+headersize,usersize,usersize,flagtxt,extratxt) dbg.log(tolog) logfile_b.write(tolog,thislog_b) else: if not usersize in segstatinfo: segstatinfo[usersize] = 1 else: segstatinfo[usersize] += 1 if nextblock > 0 and nextblock < LastValidEntry: if not "stat" in args: nextblock -= headersize restbytes = LastValidEntry - nextblock tolog = " 0x%08x - 0x%08x (end of segment) : 0x%x (%d) uncommitted bytes" % (nextblock,LastValidEntry,restbytes,restbytes) dbg.log(tolog) logfile_b.write(tolog,thislog_b) if "stat" in args: statinfo[segstart] = segstatinfo # show statistics orderedsizes = [] totalalloc = 0 for thissize in segstatinfo: orderedsizes.append(thissize) totalalloc += segstatinfo[thissize] orderedsizes.sort(reverse=True) tolog = " Segment Statistics:" dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass for thissize in orderedsizes: nrblocks = segstatinfo[thissize] percentage = (float(nrblocks) / float(totalalloc)) * 100 tolog = " Size : 0x%x (%d) : %d chunks (%.2f %%)" % (thissize,thissize,nrblocks,percentage) dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass tolog = " Total chunks : %d" % totalalloc dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass tolog = "" try: logfile_b.write(tolog,thislog_b) except: pass dbg.log("") dbg.log("") if "stat" in args and len(statinfo) > 0: tolog = "Global statistics" dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass globalstats = {} allalloc = 0 for seginfo in statinfo: segmentstats = statinfo[seginfo] for size in segmentstats: allalloc += segmentstats[size] if not size in globalstats: globalstats[size] = segmentstats[size] else: globalstats[size] += segmentstats[size] orderedstats = [] for size in globalstats: orderedstats.append(size) orderedstats.sort(reverse=True) for thissize in orderedstats: nrblocks = globalstats[thissize] percentage = (float(nrblocks) / float(allalloc)) * 100 tolog = " Size : 0x%x (%d) : %d chunks (%.2f %%)" % (thissize,thissize,nrblocks,percentage) dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass tolog = " Total chunks : %d" % allalloc dbg.log(tolog) try: logfile_b.write(tolog,thislog_b) except: pass #dbg.log("%s" % "*" * 90) return def procGetIAT(args): return procGetxAT(args,"iat") def procGetEAT(args): return procGetxAT(args,"eat") def procFwptr(args): modulecriteria = {} criteria = {} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) modulestosearch = getModulesToQuery(modulecriteria) allpages = dbg.getMemoryPages() orderedpages = [] for page in allpages.keys(): orderedpages.append(page) orderedpages.sort() pagestoquery = {} fwptrs = {} objwptr = MnLog("wptr.txt") wptrfile = objwptr.reset() setbps = False dopatch = False dofreelist = False if "bp" in args: setbps = True if "patch" in args: dopatch = True if "freelist" in args: dofreelist = True chunksize = 0 offset = 0 if "chunksize" in args: if type(args["chunksize"]).__name__.lower() != "bool": try: if str(args["chunksize"]).lower().startswith("0x"): chunksize = int(args["chunksize"],16) else: chunksize = int(args["chunksize"]) except: chunksize = 0 if chunksize == 0 or chunksize > 0xffff: dbg.log("[!] Invalid chunksize specified") if chunksize > 0xffff: dbg.log("[!] Chunksize must be <= 0xffff") chunksize == 0 return else: dbg.log("[+] Will filter on chunksize 0x%0x" % chunksize ) if dofreelist: if "offset" in args: if type(args["offset"]).__name__.lower() != "bool": try: if str(args["offset"]).lower().startswith("0x"): offset = int(args["offset"],16) else: offset = int(args["offset"]) except: offset = 0 if offset == 0: dbg.log("[!] Invalid offset specified") else: dbg.log("[+] Will add 0x%0x bytes between flink/blink and fwptr" % offset ) if not silent: if setbps: dbg.log("[+] Will set breakpoints on found CALL/JMP") if dopatch: dbg.log("[+] Will patch target for CALL/JMP with 0x41414141") dbg.log("[+] Extracting .text/.code sections from %d modules" % len(modulestosearch)) dbg.updateLog() if len(modulestosearch) > 0: for thismodule in modulestosearch: # find text section for thispage in orderedpages: page = allpages[thispage] pagestart = page.getBaseAddress() pagesize = page.getSize() ptr = MnPointer(pagestart) mod = "" sectionname = "" try: mod = ptr.belongsTo() if mod == thismodule: sectionname = page.getSection() if sectionname == ".text" or sectionname == ".code": pagestoquery[mod] = [pagestart,pagestart+pagesize] break except: pass if len(pagestoquery) > 0: if not silent: dbg.log("[+] Analysing .text/.code sections") dbg.updateLog() for modname in pagestoquery: tmodcnt = 0 nr_sizematch = 0 pagestart = pagestoquery[modname][0] pageend = pagestoquery[modname][1] if not silent: dbg.log(" - Carving through %s (0x%08x - 0x%08x)" % (modname,pagestart,pageend)) dbg.updateLog() loc = pagestart while loc < pageend: try: thisinstr = dbg.disasm(loc) instrbytes = thisinstr.getDump() if thisinstr.isJmp() or thisinstr.isCall(): # check if it's reading a pointer from somewhere instrtext = getDisasmInstruction(thisinstr) opcodepart = instrbytes.upper()[0:4] if opcodepart == "FF15" or opcodepart == "FF25": if "[" in instrtext and "]" in instrtext: parts1 = instrtext.split("[") if len(parts1) > 1: parts2 = parts1[1].split("]") addy = parts2[0] # get the actual value and check if it's writeable if "(" in addy and ")" in addy: parts1 = addy.split("(") parts2 = parts1[1].split(")") addy = parts2[0] if isHexValue(addy): addyval = hexStrToInt(addy) access = getPointerAccess(addyval) if "WRITE" in access: if meetsCriteria(addyval,criteria): savetolog = False sizeinfo = "" if chunksize == 0: savetolog = True else: # check if this location could acts as a heap chunk for a certain size # the size field would be placed at the current location - 8 bytes # and is 2 bytes large sizeval = 0 if not dofreelist: sizeval = struct.unpack('= chunksize: savetolog = True nr_sizematch += 1 sizeinfo = " Chunksize: %d (0x%02x) - " % ((sizeval*8),(sizeval*8)) else: sizeval = struct.unpack(' 0: loc = loc + len(instrbytes)/2 else: loc = loc + 1 except: loc = loc + 1 if not silent: dbg.log(" Found %d pointers" % tmodcnt) if chunksize > 0: dbg.log(" %d pointers with size match" % nr_sizematch) return def procGetxAT(args,mode): keywords = [] keywordstring = "" modulecriteria = {} criteria = {} thisxat = {} entriesfound = 0 if "s" in args: if type(args["s"]).__name__.lower() != "bool": keywordstring = args["s"].replace("'","").replace('"','') keywords = keywordstring.split(",") modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) modulestosearch = getModulesToQuery(modulecriteria) if not silent: dbg.log("[+] Querying %d modules" % len(modulestosearch)) if len(modulestosearch) > 0: xatfilename="%ssearch.txt" % mode objxatfilename = MnLog(xatfilename) xatfile = objxatfilename.reset() for thismodule in modulestosearch: thismod = MnModule(thismodule) if mode == "iat": thisxat = thismod.getIAT() else: thisxat = thismod.getEAT() thismodule = thismod.getShortName() for thisfunc in thisxat: thisfuncname = thisxat[thisfunc].lower() origfuncname = thisfuncname firstindex = thisfuncname.find(".") if firstindex > 0: thisfuncname = thisfuncname[firstindex+1:len(thisfuncname)] addtolist = False iatptr_modname = "" modinfohr = "" theptr = 0 if mode == "iat": theptr = struct.unpack(' 0: for keyword in keywords: keyword = keyword.lower().strip() if ((keyword.startswith("*") and keyword.endswith("*")) or keyword.find("*") < 0): keyword = keyword.replace("*","") if thisfuncname.find(keyword) > -1: addtolist = True break if keyword.startswith("*") and not keyword.endswith("*"): keyword = keyword.replace("*","") if thisfuncname.endswith(keyword): addtolist = True break if keyword.endswith("*") and not keyword.startswith("*"): keyword = keyword.replace("*","") if thisfuncname.startswith(keyword): addtolist = True break else: addtolist = True if addtolist: entriesfound += 1 # add info about the module if mode == "iat": thedelta = thisfunc - thismod.moduleBase logentry = "At 0x%s in %s (base + 0x%s) : 0x%s (ptr to %s) %s" % (toHex(thisfunc),thismodule.lower(),toHex(thedelta),toHex(theptr),origfuncname,modinfohr) else: thedelta = thisfunc - thismod.moduleBase logentry = "0x%08x : %s!%s (0x%08x+0x%08x)" % (thisfunc,thismodule.lower(),origfuncname,thismod.moduleBase,thedelta) dbg.log(logentry,address = thisfunc) objxatfilename.write(logentry,xatfile) if not silent: dbg.log("") dbg.log("%d entries found" % entriesfound) return #-----Metasploit module skeleton-----# def procSkeleton(args): cyclicsize = 5000 if "c" in args: if type(args["c"]).__name__.lower() != "bool": try: cyclicsize = int(args["c"]) except: cyclicsize = 5000 exploittype = "" skeletonarg = "" usecliargs = False validstypes ={} validstypes["tcpclient"] = "network client (tcp)" validstypes["udpclient"] = "network client (udp)" validstypes["fileformat"] = "fileformat" exploittypes = [ "fileformat","network client (tcp)","network client (udp)" ] errorfound = False if __DEBUGGERAPP__ == "WinDBG" or "t" in args: if "t" in args: if type(args["t"]).__name__.lower() != "bool": skeltype = args["t"].lower() skelparts = skeltype.split(":") if skelparts[0] in validstypes: exploittype = validstypes[skelparts[0]] if len(skelparts) > 1: skeletonarg = skelparts[1] else: errorfound = True usecliargs = True else: errorfound = True else: errorfound = True else: errorfound = True # ask for type of module else: dbg.log(" ** Please select a skeleton exploit type from the dropdown list **",highlight=1) exploittype = dbg.comboBox("Select msf exploit skeleton to build :", exploittypes).lower().strip() if errorfound: dbg.log(" ** Please specify a valid skeleton type and argument **",highlight=1) dbg.log(" Valid types are : tcpclient:argument, udpclient:argument, fileformat:argument") dbg.log(" Example : skeleton for a pdf file format exploit: -t fileformat:pdf") dbg.log(" skeleton for tcp client against port 123: -t tcpclient:123") return if not exploittype in exploittypes: dbg.log("Boo - invalid exploit type, try again !",highlight=1) return portnr = 0 extension = "" if exploittype.find("network") > -1: if usecliargs: portnr = skeletonarg else: portnr = dbg.inputBox("Remote port number : ") try: portnr = int(portnr) except: portnr = 0 if exploittype.find("fileformat") > -1: if usecliargs: extension = skeletonarg else: extension = dbg.inputBox("File extension :") extension = extension.replace("'","").replace('"',"").replace("\n","").replace("\r","") if not extension.startswith("."): extension = "." + extension exploitfilename="msfskeleton.rb" objexploitfile = MnLog(exploitfilename) global ignoremodules global noheader noheader = True ignoremodules = True exploitfile = objexploitfile.reset() ignoremodules = False noheader = False modulecriteria = {} criteria = {} modulecriteria,criteria = args2criteria(args,modulecriteria,criteria) badchars = "" if "badchars" in criteria: badchars = criteria["badchars"] if "nonull" in criteria: if not '\x00' in badchars: badchars += '\x00' skeletonheader,skeletoninit,skeletoninit2 = getSkeletonHeader(exploittype,portnr,extension,"",badchars) targetstr = " 'Targets' =>\n" targetstr += " [\n" targetstr += " [ '',\n" targetstr += " {\n" targetstr += " 'Ret' => 0x00000000,\n" targetstr += " 'Offset' => 0\n" targetstr += " }\n" targetstr += " ],\n" targetstr += " ],\n" exploitstr = " def exploit\n\n" if exploittype.find("network") > -1: if exploittype.find("tcp") > -1: exploitstr += "\n connect\n\n" elif exploittype.find("udp") > -1: exploitstr += "\n connect_udp\n\n" exploitstr += " buffer = Rex::Text.pattern_create(" + str(cyclicsize) + ")\n" if exploittype.find("network") > -1: exploitstr += "\n print_status(\"Trying target #{target.name}...\")\n" if exploittype.find("tcp") > -1: exploitstr += " sock.put(buffer)\n" exploitstr += "\n handler\n" elif exploittype.find("udp") > -1: exploitstr += " udp_sock.put(buffer)\n" exploitstr += "\n handler(udp_sock)\n" if exploittype == "fileformat": exploitstr += "\n file_create(buffer)\n\n" if exploittype.find("network") > -1: exploitstr += " disconnect\n\n" exploitstr += " end\n" objexploitfile.write(skeletonheader+"\n",exploitfile) objexploitfile.write(skeletoninit+"\n",exploitfile) objexploitfile.write(targetstr,exploitfile) objexploitfile.write(skeletoninit2,exploitfile) objexploitfile.write(exploitstr,exploitfile) objexploitfile.write("end",exploitfile) return def procFillChunk(args): reference = "" fillchar = "A" allregs = dbg.getRegs() origreference = "" deref = False refreg = "" offset = 0 signstuff = 1 customsize = 0 if "s" in args: if type(args["s"]).__name__.lower() != "bool": sizearg = args["s"] if sizearg.lower().startswith("0x"): sizearg = sizearg.lower().replace("0x","") customsize = int(sizearg,16) else: customsize = int(sizearg) if "r" in args: if type(args["r"]).__name__.lower() != "bool": # break into pieces reference = args["r"].upper() origreference = reference if reference.find("[") > -1 and reference.find("]") > -1: refregtmp = reference.replace("[","").replace("]","").replace(" ","") if reference.find("+") > -1 or reference.find("-") > -1: # deref with offset refregtmpparts = [] if reference.find("+") > -1: refregtmpparts = refregtmp.split("+") signstuff = 1 if reference.find("-") > -1: refregtmpparts = refregtmp.split("-") signstuff = -1 if len(refregtmpparts) > 1: offset = int(refregtmpparts[1].replace("0X",""),16) * signstuff deref = True refreg = refregtmpparts[0] if not refreg in allregs: dbg.log("** Please provide a valid reference using -r reg/reference **") return else: dbg.log("** Please provide a valid reference using -r reg/reference **") return else: # only deref refreg = refregtmp deref = True else: # no deref, maybe offset if reference.find("+") > -1 or reference.find("-") > -1: # deref with offset refregtmpparts = [] refregtmp = reference.replace(" ","") if reference.find("+") > -1: refregtmpparts = refregtmp.split("+") signstuff = 1 if reference.find("-") > -1: refregtmpparts = refregtmp.split("-") signstuff = -1 if len(refregtmpparts) > 1: offset = int(refregtmpparts[1].replace("0X",""),16) * signstuff refreg = refregtmpparts[0] if not refreg in allregs: dbg.log("** Please provide a valid reference using -r reg/reference **") return else: dbg.log("** Please provide a valid reference using -r reg/reference **") return else: # only deref refregtmp = reference.replace(" ","") refreg = refregtmp deref = False else: dbg.log("** Please provide a valid reference using -r reg/reference **") return else: dbg.log("** Please provide a valid reference using -r reg/reference **") return if not refreg in allregs: dbg.log("** Please provide a valid reference using -r reg/reference **") return dbg.log("Ref : %s" % refreg) dbg.log("Offset : %d (0x%s)" % (offset,toHex(int(str(offset).replace("-",""))))) dbg.log("Deref ? : %s" % deref) if "b" in args: if type(args["b"]).__name__.lower() != "bool": if args["b"].find("\\x") > -1: fillchar = hex2bin(args["b"])[0] else: fillchar = args["b"][0] # see if we can read the reference refvalue = 0 if deref: refref = 0 try: refref = allregs[refreg]+offset except: dbg.log("** Unable to read from %s (0x%08x)" % (origreference,allregs[refreg]+offset)) try: refvalue = struct.unpack(' 0x%08x" % (origreference,allregs[reference]+offset,refref)) return else: try: refvalue = allregs[refreg]+offset except: dbg.log("** Unable to read from %s (0x%08x)" % (reference,allregs[refreg]+offset)) dbg.log("Reference : %s: 0x%08x" % (origreference,refvalue)) dbg.log("Fill char : \\x%s" % bin2hex(fillchar)) cmd2run = "!heap -p -a 0x%08x" % refvalue output = dbg.nativeCommand(cmd2run) outputlines = output.split("\n") heapinfo = "" for line in outputlines: if line.find("[") > -1 and line.find("]") > -1 and line.find("(") > -1 and line.find(")") > -1: heapinfo = line break if heapinfo == "": dbg.log("Address is not part of a heap chunk") if customsize > 0: dbg.log("Filling memory location starting at 0x%08x with \\x%s" % (refvalue,bin2hex(fillchar))) dbg.log("Number of bytes to write : %d (0x%08x)" % (customsize,customsize)) data = fillchar * customsize dbg.writeMemory(refvalue,data) dbg.log("Done") else: dbg.log("Please specify a custom size with -s to fill up the memory location anyway") else: infofields = [] cnt = 0 charseen = False thisfield = "" while cnt < len(heapinfo): if heapinfo[cnt] == " " and charseen and thisfield != "": infofields.append(thisfield) thisfield = "" else: if not heapinfo[cnt] == " ": thisfield += heapinfo[cnt] charseen = True cnt += 1 if thisfield != "": infofields.append(thisfield) if len(infofields) > 7: chunkptr = hexStrToInt(infofields[0]) userptr = hexStrToInt(infofields[4]) size = hexStrToInt(infofields[5]) dbg.log("Heap chunk found at 0x%08x, size 0x%08x (%d) bytes" % (chunkptr,size,size)) dbg.log("Filling chunk with \\x%s, starting at 0x%08x" % (bin2hex(fillchar),userptr)) data = fillchar * size dbg.writeMemory(userptr,data) dbg.log("Done") return def procInfoDump(args): allpages = dbg.getMemoryPages() filename = "infodump.xml" xmldata = '\n' xmldata += "\n" if len(g_modules) == 0: populateModuleInfo() modulestoquery=[] for thismodule,modproperties in g_modules.iteritems(): xmldata += " \n" % thismodule thisbase = getModuleProperty(thismodule,"base") thissize = getModuleProperty(thismodule,"size") xmldata += " 0x%08x\n" % thisbase xmldata += " 0x%08x\n" % thissize xmldata += " \n" xmldata += "\n" orderedpages = [] for tpage in allpages.keys(): orderedpages.append(tpage) orderedpages.sort() if len(orderedpages) > 0: xmldata += "\n" # first dump module info to file objfile = MnLog(filename) infofile = objfile.reset(clear=True,showheader=False) f = open(infofile,"wb") for line in xmldata.split("\n"): if line != "": f.write(line + "\n") tolog = "Dumping the following pages to file:" dbg.log(tolog) tolog = "Start End Size ACL" dbg.log(tolog) for thispage in orderedpages: page = allpages[thispage] pagestart = page.getBaseAddress() pagesize = page.getSize() ptr = MnPointer(pagestart) mod = "" sectionname = "" ismod = False isstack = False isheap = False try: mod = ptr.belongsTo() if mod != "": ismod = True except: mod = "" if not ismod: if ptr.isOnStack(): isstack = True if not ismod and not isstack: if ptr.isInHeap(): isheap = True if not ismod and not isstack and not isheap: acl = page.getAccess(human=True) if not "NOACCESS" in acl: tolog = "0x%08x - 0x%08x (0x%08x) %s" % (pagestart,pagestart + pagesize,pagesize,acl) dbg.log(tolog) # add page contents to xml thispage = dbg.readMemory(pagestart,pagesize) f.write(" \n" % pagestart) f.write(" 0x%08x\n" % pagesize) f.write(" %s\n" % acl) f.write(" ") memcontents = "" for thisbyte in thispage: memcontents += bin2hex(thisbyte) f.write(memcontents) f.write("\n") f.write(" \n") f.write("\n") f.write("") dbg.log("") f.close() dbg.log("Done") return def procPEB(args): """ Show the address of the PEB """ pebaddy = dbg.getPEBAddress() dbg.log("PEB is located at 0x%08x" % pebaddy,address=pebaddy) return def procTEB(args): """ Show the address of the TEB for the current thread """ tebaddy = dbg.getCurrentTEBAddress() dbg.log("TEB is located at 0x%08x" % tebaddy,address=tebaddy) return def procPageACL(args): global silent silent = True findaddy = 0 if "a" in args: findaddy,addyok = getAddyArg(args["a"]) if not addyok: dbg.log("%s is an invalid address" % args["a"], highlight=1) return if findaddy > 0: dbg.log("Displaying page information around address 0x%08x" % findaddy) allpages = dbg.getMemoryPages() dbg.log("Total of %d pages : "% len(allpages)) filename="pageacl.txt" orderedpages = [] for tpage in allpages.keys(): orderedpages.append(tpage) orderedpages.sort() # find indexes to show in case we have specified an address toshow = [] previouspage = 0 nextpage = 0 pagefound = False if findaddy > 0: for thispage in orderedpages: page = allpages[thispage] pagestart = page.getBaseAddress() pagesize = page.getSize() pageend = pagestart + pagesize if findaddy >= pagestart and findaddy < pageend: toshow.append(thispage) pagefound = True if pagefound and previouspage > 0: if not previouspage in toshow: toshow.append(previouspage) if not thispage in toshow: toshow.append(thispage) # nextpage break previouspage = thispage if len(toshow) > 0: toshow.sort() orderedpages = toshow dbg.log("Showing %d pages" % len(orderedpages)) if len(orderedpages) > 0: objfile = MnLog(filename) aclfile = objfile.reset() tolog = "Start End Size ACL" dbg.log(tolog) objfile.write(tolog,aclfile) for thispage in orderedpages: page = allpages[thispage] pagestart = page.getBaseAddress() pagesize = page.getSize() ptr = MnPointer(pagestart) mod = "" sectionname = "" try: mod = ptr.belongsTo() if not mod == "": mod = "(" + mod + ")" sectionname = page.getSection() except: #print traceback.format_exc() pass if mod == "": if ptr.isOnStack(): mod = "(Stack)" elif ptr.isInHeap(): mod = "(Heap)" acl = page.getAccess(human=True) tolog = "0x%08x - 0x%08x (0x%08x) %s %s %s" % (pagestart,pagestart + pagesize,pagesize,acl,mod, sectionname) objfile.write(tolog,aclfile) dbg.log(tolog) silent = False return def procMacro(args): validcommands = ["run","set","list","del","add","show"] validcommandfound = False selectedcommand = "" for command in validcommands: if command in args: validcommandfound = True selectedcommand = command break dbg.log("") if not validcommandfound: dbg.log("*** Please specify a valid command. Valid commands are :") for command in validcommands: dbg.log(" -%s" % command) return macroname = "" if "set" in args: if type(args["set"]).__name__.lower() != "bool": macroname = args["set"] if "show" in args: if type(args["show"]).__name__.lower() != "bool": macroname = args["show"] if "add" in args: if type(args["add"]).__name__.lower() != "bool": macroname = args["add"] if "del" in args: if type(args["del"]).__name__.lower() != "bool": macroname = args["del"] if "run" in args: if type(args["run"]).__name__.lower() != "bool": macroname = args["run"] filename = "" index = -1 insert = False iamsure = False if "index" in args: if type(args["index"]).__name__.lower() != "bool": index = int(args["index"]) if index < 0: dbg.log("** Please use a positive integer as index",highlight=1) if "file" in args: if type(args["file"]).__name__.lower() != "bool": filename = args["file"] if filename != "" and index > -1: dbg.log("** Please either provide an index or a filename, not both",highlight=1) return if "insert" in args: insert = True if "iamsure" in args: iamsure = True argcommand = "" if "cmd" in args: if type(args["cmd"]).__name__.lower() != "bool": argcommand = args["cmd"] dbg.setKBDB("monamacro.db") macros = dbg.getKnowledge("macro") if macros is None: macros = {} if selectedcommand == "list": for macro in macros: thismacro = macros[macro] macronametxt = "Macro : '%s' : %d command(s)" % (macro,len(thismacro)) dbg.log(macronametxt) dbg.log("") dbg.log("Number of macros : %d" % len(macros)) if selectedcommand == "show": if macroname != "": if not macroname in macros: dbg.log("** Macro %s does not exist !" % macroname) return else: macro = macros[macroname] macronametxt = "Macro : %s" % macroname macroline = "-" * len(macronametxt) dbg.log(macronametxt) dbg.log(macroline) thismacro = macro macrolist = [] for macroid in thismacro: macrolist.append(macroid) macrolist.sort() nr_of_commands = 0 for macroid in macrolist: macrocmd = thismacro[macroid] if macrocmd.startswith("#"): dbg.log(" [%04d] File:%s" % (macroid,macrocmd[1:])) else: dbg.log(" [%04d] %s" % (macroid,macrocmd)) nr_of_commands += 1 dbg.log("") dbg.log("Nr of commands in this macro : %d" % nr_of_commands) else: dbg.log("** Please specify the macroname to show !",highlight=1) return if selectedcommand == "run": if macroname != "": if not macroname in macros: dbg.log("** Macro %s does not exist !" % macroname) return else: macro = macros[macroname] macronametxt = "Running macro : %s" % macroname macroline = "-" * len(macronametxt) dbg.log(macronametxt) dbg.log(macroline) thismacro = macro macrolist = [] for macroid in thismacro: macrolist.append(macroid) macrolist.sort() for macroid in macrolist: macrocmd = thismacro[macroid] if macrocmd.startswith("#"): dbg.log("Executing script %s" % macrocmd[1:]) output = dbg.nativeCommand("$<%s" % macrocmd[1:]) dbg.logLines(output) dbg.log("-" * 40) else: dbg.log("Index %d : %s" % (macroid,macrocmd)) dbg.log("") output = dbg.nativeCommand(macrocmd) dbg.logLines(output) dbg.log("-" * 40) dbg.log("") dbg.log("[+] Done.") else: dbg.log("** Please specify the macroname to run !",highlight=1) return if selectedcommand == "set": if macroname != "": if not macroname in macros: dbg.log("** Macro %s does not exist !" % macroname) return if argcommand == "" and filename == "": dbg.log("** Please enter a valid command with parameter -cmd",highlight=1) return thismacro = macros[macroname] if index == -1: for i in thismacro: thiscmd = thismacro[i] if thiscmd.startswith("#"): dbg.log("** You cannot edit a macro that uses a scriptfile.",highlight=1) dbg.log(" Edit file %s instead" % thiscmd[1:],highlight=1) return if filename == "": # append to end of the list # find the next index first nextindex = 0 for macindex in thismacro: if macindex >= nextindex: nextindex = macindex+1 if thismacro.__class__.__name__ == "dict": thismacro[nextindex] = argcommand else: thismacro = {} thismacro[nextindex] = argcommand else: thismacro = {} nextindex = 0 thismacro[0] = "#%s" % filename macros[macroname] = thismacro dbg.addKnowledge("macro",macros) dbg.log("[+] Done, saved new command at index %d." % nextindex) else: # user has specified an index if index in thismacro: if argcommand == "#": # remove command at this index del thismacro[index] else: # if macro already contains a file entry, bail out for i in thismacro: thiscmd = thismacro[i] if thiscmd.startswith("#"): dbg.log("** You cannot edit a macro that uses a scriptfile.",highlight=1) dbg.log(" Edit file %s instead" % thiscmd[1:],highlight=1) return # index exists - overwrite unless -insert was provided too # remove or insert ? #print sys.argv if not insert: thismacro[index] = argcommand else: # move things around # get ordered list of existing indexes indexes = [] for macindex in thismacro: indexes.append(macindex) indexes.sort() thismacro2 = {} cmdadded = False for i in indexes: if i < index: thismacro2[i] = thismacro[i] elif i == index: thismacro2[i] = argcommand thismacro2[i+1] = thismacro[i] elif i > index: thismacro2[i+1] = thismacro[i] thismacro = thismacro2 else: # index does not exist, add new command to this index for i in thismacro: thiscmd = thismacro[i] if thiscmd.startswith("#"): dbg.log("** You cannot edit a macro that uses a scriptfile.",highlight=1) dbg.log(" Edit file %s instead" % thiscmd[1:],highlight=1) return if argcommand != "#": thismacro[index] = argcommand else: dbg.log("** Index %d does not exist, unable to remove the command at that position" % index,highlight=1) macros[macroname] = thismacro dbg.addKnowledge("macro",macros) if argcommand != "#": dbg.log("[+] Done, saved new command at index %d." % index) else: dbg.log("[+] Done, removed command at index %d." % index) else: dbg.log("** Please specify the macroname to edit !",highlight=1) return if selectedcommand == "add": if macroname != "": if macroname in macros: dbg.log("** Macro '%s' already exists !" % macroname,highlight=1) return else: macros[macroname] = {} dbg.log("[+] Adding macro '%s'" % macroname) dbg.addKnowledge("macro",macros) dbg.log("[+] Done.") else: dbg.log("** Please specify the macroname to add !",highlight=1) return if selectedcommand == "del": if not macroname in macros: dbg.log("** Macro '%s' doesn't exist !" % macroname,highlight=1) else: if not iamsure: dbg.log("** To delete macro '%s', please add the -iamsure flag to the command" % macroname) return else: dbg.forgetKnowledge("macro",macroname) dbg.log("[+] Done, deleted macro '%s'" % macroname) return def procEnc(args): validencoders = ['alphanum'] encodertyperror = True byteerror = True encodertype = "" bytestoencodestr = "" bytestoencode = "" badbytes = "" if "t" in args: if type(args["t"]).__name__.lower() != "bool": encodertype = args["t"] encodertyperror = False if "s" in args: if type(args["s"]).__name__.lower() != "bool": bytestoencodestr = args["s"] byteerror = False if "f" in args: if type(args["f"]).__name__.lower() != "bool": binfile = getAbsolutePath(args["f"]) if os.path.exists(binfile): if not silent: dbg.log("[+] Reading bytes from %s" % binfile) try: f = open(binfile,"rb") content = f.readlines() f.close() for c in content: for a in c: bytestoencodestr += "\\x%02x" % ord(a) byteerror = False except: dbg.log("*** Error - unable to read bytes from %s" % binfile) dbg.logLines(traceback.format_exc(),highlight=True) byteerror = True else: byteerror = True else: byteerror = True if "cpb" in args: if type(args["cpb"]).__name__.lower() != "bool": badbytes = hex2bin(args["cpb"]) if not encodertype in validencoders: encodertyperror = True if bytestoencodestr == "": byteerror = True else: bytestoencode = hex2bin(bytestoencodestr) if encodertyperror: dbg.log("*** Please specific a valid encodertype with parameter -t.",highlight=True) dbg.log("*** Valid types are: %s" % validencoders,highlight=True) if byteerror: dbg.log("*** Please specify a valid series of bytes with parameter -s",highlight=True) dbg.log("*** or specify a valid path with parameter -f",highlight=True) if encodertyperror or byteerror: return else: cEncoder = MnEncoder(bytestoencode) encodedbytes = "" if encodertype == "alphanum": encodedbytes = cEncoder.encodeAlphaNum(badchars = badbytes) # determine correct sequence of dictionary if len(encodedbytes) > 0: logfile = MnLog("encoded_%s.txt" % encodertype) thislog = logfile.reset() if not silent: dbg.log("") dbg.log("Results:") dbg.log("--------") logfile.write("",thislog) logfile.write("Results:",thislog) logfile.write("--------",thislog) encodedindex = [] fulllist_str = "" fulllist_bin = "" for i in encodedbytes: encodedindex.append(i) for i in encodedindex: thisline = encodedbytes[i] # 0 = bytes # 1 = info thislinebytes = "\\x" + "\\x".join(bin2hex(a) for a in thisline[0]) logline = " %s : %s : %s" % (thisline[0],thislinebytes,thisline[1]) if not silent: dbg.log("%s" % logline) logfile.write(logline,thislog) fulllist_str += thislinebytes fulllist_bin += thisline[0] if not silent: dbg.log("") dbg.log("Full encoded string:") dbg.log("--------------------") dbg.log("%s" % fulllist_bin) logfile.write("",thislog) logfile.write("Full encoded string:",thislog) logfile.write("--------------------",thislog) logfile.write("%s" % fulllist_bin,thislog) logfile.write("",thislog) logfile.write("Full encoded hex:",thislog) logfile.write("-----------------",thislog) logfile.write("%s" % fulllist_str,thislog) return def procString(args): mode = "" useunicode = False terminatestring = True addy = 0 regs = dbg.getRegs() stringtowrite = "" # read or write ? if not "r" in args and not "w" in args: dbg.log("*** Error: you must indicate if you want to read (-r) or write (-w) ***",highlight=True) return addresserror = False if not "a" in args: addresserror = True else: if type(args["a"]).__name__.lower() != "bool": # check if it's a register or not if str(args["a"]).upper() in regs: addy = regs[str(args["a"].upper())] else: addy = int(args["a"],16) else: addresserror = True if addresserror: dbg.log("*** Error: you must specify a valid address with -a ***",highlight=True) return if "w" in args: mode = "write" if "r" in args: # read wins, because it's non destructive mode = "read" if "u" in args: useunicode = True stringerror = False if "w" in args and not "s" in args: stringerror = True if "s" in args: if type(args["s"]).__name__.lower() != "bool": stringtowrite = args["s"] else: stringerror = True if "noterminate" in args: terminatestring = False if stringerror: dbg.log("*** Error: you must specify a valid string with -s ***",highlight=True) return if mode == "read": stringinmemory = "" extra = " " try: if not useunicode: stringinmemory = dbg.readString(addy) else: stringinmemory = dbg.readWString(addy) extra = " (unicode) " dbg.log("String%sat 0x%08x:" % (extra,addy)) dbg.log("%s" % stringinmemory) except: dbg.log("Unable to read string at 0x%08x" % addy) if mode == "write": origstring = stringtowrite writtendata = "" try: if not useunicode: if terminatestring: stringtowrite += "\x00" byteswritten = "" for c in stringtowrite: byteswritten += " %s" % bin2hex(c) dbg.writeMemory(addy,stringtowrite) writtendata = dbg.readString(addy) dbg.log("Wrote string (%d bytes) to 0x%08x:" % (len(stringtowrite),addy)) dbg.log("%s" % byteswritten) else: newstring = "" for c in stringtowrite: newstring += "%s%s" % (c,"\x00") if terminatestring: newstring += "\x00\x00" dbg.writeMemory(addy,newstring) dbg.log("Wrote unicode string (%d bytes) to 0x%08x" % (len(newstring),addy)) writtendata = dbg.readWString(addy) byteswritten = "" for c in newstring: byteswritten += " %s" % bin2hex(c) dbg.log("%s" % byteswritten) if not writtendata.startswith(origstring): dbg.log("Write operation succeeded, but the string in memory doesn't appear to be there",highlight=True) except: dbg.log("Unable to write the string to 0x%08x" % addy) dbg.logLines(traceback.format_exc(),highlight=True) return def procKb(args): validcommands = ['set','list','del'] validcommandfound = False selectedcommand = "" selectedid = "" selectedvalue = "" for command in validcommands: if command in args: validcommandfound = True selectedcommand = command break dbg.log("") if not validcommandfound: dbg.log("*** Please specify a valid command. Valid commands are :") for command in validcommands: dbg.log(" -%s" % command) return if "id" in args: if type(args["id"]).__name__.lower() != "bool": selectedid = args["id"] if "value" in args: if type(args["value"]).__name__.lower() != "bool": selectedvalue = args["value"] dbg.log("Knowledgebase database : %s" % dbg.getKBDB()) kb = dbg.listKnowledge() if selectedcommand == "list": dbg.log("Number of IDs in Knowledgebase : %d" % len(kb)) if len(kb) > 0: if selectedid == "": dbg.log("IDs :") dbg.log("-----") for kbid in kb: dbg.log(kbid) else: if selectedid in kb: kbid = dbg.getKnowledge(selectedid) kbtype = kbid.__class__.__name__ kbtitle = "Entries for ID %s (type %s) :" % (selectedid,kbtype) dbg.log(kbtitle) dbg.log("-" * (len(kbtitle)+2)) if selectedvalue != "": dbg.log(" (Filter : %s)" % selectedvalue) nrentries = 0 if kbtype == "dict": for dictkey in kbid: if selectedvalue == "" or selectedvalue in dictkey: logline = "" if kbid[dictkey].__class__.__name__ == "int" or kb[dictkey].__class__.__name__ == "long": logline = " %s : %d (0x%x)" % (str(dictkey),kbid[dictkey],kbid[dictkey]) else: logline = " %s : %s" % (str(dictkey),kbid[dictkey]) dbg.log(logline) nrentries += 1 if kbtype == "list": cnt = 0 for entry in kbid: dbg.log(" %d : %s" % (cnt,kbid[entry])) cnt += 1 nrentries += 1 if kbtype == "str": dbg.log(" %s" % kbid) nrentries += 1 if kbtype == "int" or kbtype == "long": dbg.log(" %d (0x%08x)" % (kbid,kbid)) nrentries += 1 dbg.log("") filtertxt = "" if selectedvalue != "": filtertxt="filtered " dbg.log("Number of %sentries for ID %s : %d" % (filtertxt,selectedid,nrentries)) else: dbg.log("ID %s was not found in the Knowledgebase" % selectedid) if selectedcommand == "set": # we need an ID and a value argument if selectedid == "": dbg.log("*** Please enter a valid ID with -id",highlight=1) return if selectedvalue == "": dbg.log("*** Please enter a valid value",highlight=1) return if selectedid in kb: # vtableCache if selectedid == "vtableCache": # split on command valueparts = selectedvalue.split(",") if len(valueparts) == 2: vtablename = valueparts[0].strip(" ") vtablevalue = 0 if "0x" in valueparts[1].lower(): vtablevalue = int(valueparts[1],16) else: vtablevalue = int(valueparts[1]) kbadd = {} kbadd[vtablename] = vtablevalue dbg.addKnowledge(selectedid,kbadd) else: dbg.log("*** Please provide a valid value for -value") dbg.log("*** KB %s contains a list, please use a comma") dbg.log("*** to separate entries. First entry should be a string,") dbg.log("*** Second entry should be an integer.") return else: dbg.addKnowledge(selectedid,selectedvalue) dbg.log(" ") dbg.log("ID %s updated." % selectedid) else: dbg.log("ID %s was not found in the Knowledgebase" % selectedid) if selectedcommand == "del": if selectedid == "" or selectedid not in kb: dbg.log("*** Please enter a valid ID with -id",highlight=1) return else: dbg.forgetKnowledge(selectedid,selectedvalue) if selectedvalue == "": dbg.log("*** Entire ID %s removed from Knowledgebase" % selectedid) else: dbg.log("*** Object %s in ID %s removed from Knowledgebase" % (selectedvalue,selectedid)) return def procBPSeh(self): sehchain = dbg.getSehChain() dbg.log("Nr of SEH records : %d" % len(sehchain)) if len(sehchain) > 0: dbg.log("SEH Chain :") dbg.log("-----------") dbg.log("Address Next SEH Handler") for sehrecord in sehchain: address = sehrecord[0] sehandler = sehrecord[1] nseh = "" try: nsehvalue = struct.unpack(' 0: dbg.log("Start of chain (TEB FS:[0]) : 0x%08x" % sehchain[0][0]) dbg.log("Address Next SEH Handler") dbg.log("------- -------- -------") for sehrecord in sehchain: recaddress = sehrecord[0] sehandler = sehrecord[1] nseh = "" try: nsehvalue = struct.unpack(' 0: ptr = MnPointer(sehandler) funcinfo = ptr.getPtrFunction() else: funcinfo = " (corrupted record)" if str(nseh).startswith("0x"): nseh = "0x%08x" % int(nseh,16) else: nseh = "0x%08x" % int(nseh) if len(overwritedata) > 0: handlersoverwritten[recaddress] = overwritedata smashoffset = int(overwritedata[1]) typeinfo = "" if overwritedata[0] == "unicode": smashoffset += 2 typeinfo = " [unicode]" overwritemark = " (record smashed at offset %d%s)" % (smashoffset,typeinfo) dbg.log("0x%08x %s 0x%08x %s%s" % (recaddress,nseh,sehandler,funcinfo, overwritemark), recaddress) if len(handlersoverwritten) > 0: dbg.log("") dbg.log("Payload structure suggestion(s):") for overwrittenhandler in handlersoverwritten: overwrittendata = handlersoverwritten[overwrittenhandler] overwrittentype = overwrittendata[0] overwrittenoffset = int(overwrittendata[1]) if not overwrittentype == "unicode": dbg.log("[Junk * %d]['\\xeb\\x06\\x41\\x41'][p/p/r][shellcode][more junk if needed]" % (overwrittenoffset)) else: overwrittenoffset += 2 dbg.log("[Junk * %d][nseh - walkover][unicode p/p/r][venetian alignment][shellcode][more junk if needed]" % overwrittenoffset) return def procDumpLog(args): logfile = "" levels = 0 nestedsize = 0x28 filtersize = 0 ignorefree = False if "f" in args: if type(args["f"]).__name__.lower() != "bool": logfile = getAbsolutePath(args["f"]) if "nofree" in args: ignorefree = True if "l" in args: if type(args["l"]).__name__.lower() != "bool": if str(args["l"]).lower().startswith("0x"): try: levels = int(args["l"],16) except: levels = 0 else: try: levels = int(args["l"]) except: levels = 0 if "m" in args: if type(args["m"]).__name__.lower() != "bool": if str(args["m"]).lower().startswith("0x"): try: nestedsize = int(args["m"],16) except: nestedsize = 0x28 else: try: nestedsize = int(args["m"]) except: nestedsize = 0x28 if "s" in args: if type(args["s"]).__name__.lower() != "bool": if str(args["s"]).lower().startswith("0x"): try: filtersize = int(args["s"],16) except: filtersize = 0 else: try: filtersize = int(args["s"]) except: filtersize = 0 if logfile == "": dbg.log(" *** Error: please specify a valid logfile with argument -f ***",highlight=1) return allocs = 0 frees = 0 # open logfile and record all objects & sizes logdata = {} try: dbg.log("[+] Parsing logfile %s" % logfile) f = open(logfile,"rb") contents = f.readlines() f.close() for tline in contents: line = str(tline) if line.startswith("alloc("): size = "" addy = "" lineparts = line.split("(") if len(lineparts) > 1: sizeparts = lineparts[1].split(")") size = sizeparts[0].replace(" ","") lineparts = line.split("=") if len(lineparts) > 1: linepartaddy = lineparts[1].split(" ") for lpa in linepartaddy: if addy != "": break if lpa != "": addy = lpa if size != "" and addy != "": size = size.lower() addy = addy.lower() if not addy in logdata: if filtersize == 0: logdata[addy] = size allocs += 1 else: try: isize = int(size,16) if isize == filtersize: logdata[addy] = size allocs += 1 except: continue if line.startswith("free(") and not ignorefree: addy = "" lineparts = line.split("(") if len(lineparts) > 1: addyparts = lineparts[1].split(")") addy = addyparts[0].replace(" ","") if addy != "": addy = addy.lower() if addy in logdata: del logdata[addy] frees += 1 if ignorefree: dbg.log("[+] Ignoring all free() events, showing all allocations") dbg.log("[+] Logfile parsed, %d objects found" % len(logdata)) if filtersize > 0: dbg.log(" Only showing alloc chunks of size 0x%08x" % filtersize) dbg.log(" Total allocs: %d, total free: %d" % (allocs,frees)) dbg.log("[+] Dumping objects") logfile = MnLog("dump_alloc_free.txt") thislog = logfile.reset() logfile.write("Addresses to dump:", thislog) allocsizegroups = {} allocsizes = [] heapgranularity = 8 for addy in logdata: logfile.write("%s (%s)" % (addy, logdata[addy]), thislog) allocsize = getHeapAllocSize(logdata[addy], heapgranularity) if not allocsize in allocsizegroups: allocsizegroups[allocsize] = [addy] else: allocsizegroups[allocsize].append(addy) if not allocsize in allocsizes: allocsizes.append(allocsize) logfile.write("", thislog); logfile.write("(Allocated) Size groups, heap granularity %d bytes" % heapgranularity, thislog) allocsizes.sort() for allocsize in allocsizes: logfile.write("Size 0x%02x" % allocsize, thislog) for allocsizeaddy in allocsizegroups[allocsize]: logfile.write(" %s (%s)" % (allocsizeaddy, logdata[allocsizeaddy]), thislog) for addy in logdata: asize = logdata[addy] ptrx = MnPointer(int(addy,16)) size = int(asize,16) dumpdata = ptrx.dumpObjectAtLocation(size,levels,nestedsize,thislog,logfile) except: dbg.log(" *** Unable to open logfile %s ***" % logfile,highlight=1) dbg.log(traceback.format_exc()) return return def procDumpObj(args): addy = 0 levels = 0 size = 0 nestedsize = 0x28 regs = dbg.getRegs() if "a" in args: if type(args["a"]).__name__.lower() != "bool": addy,addyok = getAddyArg(args["a"]) if "s" in args: if type(args["s"]).__name__.lower() != "bool": if str(args["s"]).lower().startswith("0x"): try: size = int(args["s"],16) except: size = 0 else: try: size = int(args["s"]) except: size = 0 if "l" in args: if type(args["l"]).__name__.lower() != "bool": if str(args["l"]).lower().startswith("0x"): try: levels = int(args["l"],16) except: levels = 0 else: try: levels = int(args["l"]) except: levels = 0 if "m" in args: if type(args["m"]).__name__.lower() != "bool": if str(args["m"]).lower().startswith("0x"): try: nestedsize = int(args["m"],16) except: nestedsize = 0 else: try: nestedsize = int(args["m"]) except: nestedsize = 0 errorsfound = False if addy == 0: errorsfound = True dbg.log("*** Please specify a valid address to argument -a ***",highlight=1) else: ptrx = MnPointer(addy) osize = size if size == 0: # no size specified if addy > 0: dbg.log("[+] No size specified, checking if address is part of known heap chunk") if ptrx.isInHeap(): heapinfo = ptrx.getHeapInfo() heapaddy = heapinfo[0] chunkobj = heapinfo[3] if not heapaddy == None: if heapaddy > 0: chunkaddy = chunkobj.chunkptr size = chunkobj.usersize dbg.log(" Address found in chunk 0x%08x, heap 0x%08x, (user)size 0x%02x" % (chunkaddy, heapaddy, size)) addy = chunkobj.userptr if size > 0xfff: dbg.log(" I'll only dump 0xfff bytes from the object, for performance reasons") size = 0xfff if size > 0xfff and osize > 0: errorsfound = True dbg.log("*** Please keep the size below 0xfff (argument -s) ***",highlight=1) if size == 0: size = 0x28 if levels > 0 and nestedsize == 0: errorsfound = True dbg.log("*** Please specify a valid size to argument -m ***",highlight=1) if not errorsfound: ptrx = MnPointer(addy) dumpdata = ptrx.dumpObjectAtLocation(size,levels,nestedsize) return # routine to copy bytes from one location to another def procCopy(args): src = 0 dst = 0 nrbytes = 0 regs = dbg.getRegs() if "src" in args: if type(args["src"]).__name__.lower() != "bool": src,addyok = getAddyArg(args["src"]) if "dst" in args: if type(args["dst"]).__name__.lower() != "bool": dst,addyok = getAddyArg(args["dst"]) if "n" in args: if type(args["n"]).__name__.lower() != "bool": if "+" in str(args['n']) or "-" in str(args['n']): nrbytes,bytesok = getAddyArg(args['n']) if not bytesok: errorsfound = True else: if str(args['n']).lower().startswith("0x"): try: nrbytes = int(args["n"],16) except: nrbytes = 0 else: try: nrbytes = int(args["n"]) except: nrbytes = 0 errorsfound = False if src == 0: errorsfound = True dbg.log("*** Please specify a valid source address to argument -src ***",highlight=1) if dst == 0: errorsfound = True dbg.log("*** Please specify a valid destination address to argument -dst ***",highlight=1) if nrbytes == 0: errorsfound = True dbg.log("*** Please specify a valid number of bytes to argument -n ***",highlight=1) if not errorsfound: dbg.log("[+] Attempting to copy 0x%08x bytes from 0x%08x to 0x%08x" % (nrbytes, src, dst)) sourcebytes = dbg.readMemory(src,nrbytes) try: dbg.writeMemory(dst,sourcebytes) dbg.log(" Done.") except: dbg.log(" *** Copy failed, check if both locations are accessible/mapped",highlight=1) return # unicode alignment routines written by floyd (http://www.floyd.ch, twitter: @floyd_ch) def procUnicodeAlign(args): leaks = False address = 0 alignresults = {} bufferRegister = "eax" #we will put ebp into the buffer register timeToRun = 15 registers = {"eax":0, "ebx":0, "ecx":0, "edx":0, "esp":0, "ebp":0,} showerror = False regs = dbg.getRegs() if "l" in args: leaks = True if "a" in args: if type(args["a"]).__name__.lower() != "bool": address,addyok = getAddyArg(args["a"]) else: address = regs["EIP"] if leaks: address += 1 if address == 0: dbg.log("Please enter a valid address with argument -a",highlight=1) dbg.log("This address must be the location where the alignment code will be placed/start") dbg.log("(without leaking zero byte). Don't worry, the script will only use") dbg.log("it to calculate the offset from the address to EBP.") showerror=True if "b" in args: if args["b"].lower().strip() == "eax": bufferRegister = 'eax' elif args["b"].lower().strip() == "ebx": bufferRegister = 'ebx' elif args["b"].lower().strip() == "ecx": bufferRegister = 'ecx' elif args["b"].lower().strip() == "edx": bufferRegister = 'edx' else: dbg.log("Please enter a valid register with argument -b") dbg.log("Valid registers are: eax, ebx, ecx, edx") showerror = True if "t" in args and args["t"] != "": try: timeToRun = int(args["t"]) if timeToRun < 0: timeToRun = timeToRun * (-1) except: dbg.log("Please enter a valid integer for -t",highlight=1) showerror=True if "ebp" in args and args["ebp"] != "": try: registers["ebp"] = int(args["ebp"],16) except: dbg.log("Please enter a valid value for ebp",highlight=1) showerror=True dbg.log("[+] Start address for venetian alignment routine: 0x%08x" % address) dbg.log("[+] Will prepend alignment with null byte compensation? %s" % str(leaks).lower()) # ebp must be writeable for this routine to work value_of_ebp = regs["EBP"] dbg.log("[+] Checking if ebp (0x%08x) is writeable" % value_of_ebp) ebpaccess = getPointerAccess(value_of_ebp) if not "WRITE" in ebpaccess: dbg.log("[!] Warning! ebp does not appear to be writeable!",highlight = 1) dbg.log(" You will have to run some custom instructions first to make ebp writeable") dbg.log(" and at that point, run this mona command again.") dbg.log(" Hints: maybe you can pop something off the stack into ebp,") dbg.log(" or push esp and pop it into ebp.") showerror = True else: dbg.log(" OK (%s)" % ebpaccess) if not showerror: alignresults = prepareAlignment(leaks, address, bufferRegister, timeToRun, registers) # write results to file if len(alignresults) > 0: if not silent: dbg.log("[+] Alignment generator finished, %d results" % len(alignresults)) logfile = MnLog("venetian_alignment.txt") thislog = logfile.reset() for resultnr in alignresults: resulttitle = "Alignment routine %d:" % resultnr logfile.write(resulttitle,thislog) logfile.write("-" * len(resulttitle),thislog) theseresults = alignresults[resultnr] for resultinstructions in theseresults: logfile.write("Instructions:",thislog) resultlines = resultinstructions.split(";") for resultline in resultlines: logfile.write(" %s" % resultline.strip(),thislog) logfile.write("Hex:",thislog) logfile.write("'%s'" % theseresults[resultinstructions],thislog) logfile.write("",thislog) return alignresults def prepareAlignment(leaks, address, bufferRegister, timeToRun, registers): def getRegister(registerName): registerName = registerName.upper() regs = dbg.getRegs() if registerName in regs: return regs[registerName] def calculateNewXregister(x,h,l): return ((x>>16)<<16)+(h<<8)+l prefix = "" postfix = "" additionalLength = 0 #Length of the prefix+postfix instructions in after-unicode-conversion bytes code_to_get_rid_of_zeros = "add [ebp],ch; " #\x6d --> \x00\x6d\x00 buf_sig = bufferRegister[1] registers_to_fill = ["ah", "al", "bh", "bl", "ch", "cl", "dh", "dl"] #important: h's first! registers_to_fill.remove(buf_sig+"h") registers_to_fill.remove(buf_sig+"l") leadingZero = leaks for name in registers: if not registers[name]: registers[name] = getRegister(name) #256 values with only 8276 instructions (bruteforced), best found so far: #values_to_generate_all_255_values = [71, 87, 15, 251, 162, 185] #but to be on the safe side, let's take only A-Za-z values (in 8669 instructions): values_to_generate_all_255_values = [86, 85, 75, 109, 121, 99] new_values = zip(registers_to_fill, values_to_generate_all_255_values) if leadingZero: prefix += code_to_get_rid_of_zeros additionalLength += 2 leadingZero = False #prefix += "mov bl,0; mov bh,0; mov cl,0; mov ch,0; mov dl,0; mov dh,0; " #additionalLength += 12 for name, value in zip(registers_to_fill, values_to_generate_all_255_values): padding = "" if value < 16: padding = "0" if "h" in name: prefix += "mov e%sx,0x4100%s%s00; " % (name[0], padding, hex(value)[2:]) prefix += "add [ebp],ch; " additionalLength += 8 if "l" in name: prefix += "mov e%sx,0x4100%s%s00; " % (buf_sig, padding, hex(value)[2:]) prefix += "add %s,%sh; " % (name, buf_sig) prefix += "add [ebp],ch; " additionalLength += 10 leadingZero = False new_values_dict = dict(new_values) for new in registers_to_fill[::2]: n = new[0] registers['e%sx'%n] = calculateNewXregister(registers['e%sx'%n], new_values_dict['%sh'%n], new_values_dict['%sl'%n]) if leadingZero: prefix += code_to_get_rid_of_zeros additionalLength += 2 leadingZero = False #Let's push the value of ebp into the BufferRegister prefix += "push ebp; %spop %s; " % (code_to_get_rid_of_zeros, bufferRegister) leadingZero = True additionalLength += 6 registers[bufferRegister] = registers["ebp"] if not leadingZero: #We need a leading zero for the ADD operations prefix += "push ebp; " #something 1 byte, doesn't matter what leadingZero = True additionalLength += 2 #The last ADD command will leak another zero to the next instruction #Therefore append (postfix) a last instruction to get rid of it #so the shellcode is nicely aligned postfix += code_to_get_rid_of_zeros additionalLength += 2 alignresults = generateAlignment(address, bufferRegister, registers, timeToRun, prefix, postfix, additionalLength) return alignresults def generateAlignment(alignment_code_loc, bufferRegister, registers, timeToRun, prefix, postfix, additionalLength): import copy, random, time alignresults = {} def sanitiseZeros(originals, names): for index, i in enumerate(originals): if i == 0: warn("Your %s register is zero. That's bad for the heuristic." % names[index]) warn("In general this means there will be no result or they consist of more bytes.") def checkDuplicates(originals, names): duplicates = len(originals) - len(set(originals)) if duplicates > 0: warn("""Some of the 2 byte registers seem to be the same. There is/are %i duplicate(s):""" % duplicates) warn("In general this means there will be no result or they consist of more bytes.") warn(", ".join(names)) warn(", ".join(hexlist(originals))) def checkHigherByteBufferRegisterForOverflow(g1, name, g2): overflowDanger = 0x100-g1 max_instructions = overflowDanger*256-g2 if overflowDanger <= 3: warn("Your BufferRegister's %s register value starts pretty high (%s) and might overflow." % (name, hex(g1))) warn("Therefore we only look for solutions with less than %i bytes (%s%s until overflow)." % (max_instructions, hex(g1), hex(g2)[2:])) warn("This makes our search space smaller, meaning it's harder to find a solution.") return max_instructions def randomise(values, maxValues): for index, i in enumerate(values): if random.random() <= MAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS: values[index] += 1 values[index] = values[index] % maxValues[index] def check(as1, index_for_higher_byte, ss, gs, xs, ys, M, best_result): g1, g2 = gs s1, s2 = ss sum_of_instructions = 2*sum(xs) + 2*sum(ys) + M if best_result > sum_of_instructions: res0 = s1 res1 = s2 for index, _ in enumerate(as1): res0 += as1[index]*xs[index] % 256 res0 = res0 - ((g2+sum_of_instructions)/256) as2 = copy.copy(as1) as2[index_for_higher_byte] = (g1 + ((g2+sum_of_instructions)/256)) % 256 for index, _ in enumerate(as2): res1 += as2[index]*ys[index] % 256 res1 = res1 - sum_of_instructions if g1 == res0 % 256 and g2 == res1 % 256: return sum_of_instructions return 0 def printNicely(names, buffer_registers_4_byte_names, xs, ys, additionalLength=0, prefix="", postfix=""): thisresult = {} resulting_string = prefix sum_bytes = 0 for index, x in enumerate(xs): for k in range(0, x): resulting_string += "add "+buffer_registers_4_byte_names[0]+","+names[index]+"; " sum_bytes += 2 for index, y in enumerate(ys): for k in range(y): resulting_string += "add "+buffer_registers_4_byte_names[1]+","+names[index]+"; " sum_bytes += 2 resulting_string += postfix sum_bytes += additionalLength if not silent: info("[+] %i resulting bytes (%i bytes injection) of Unicode code alignment. Instructions:"%(sum_bytes,sum_bytes/2)) info(" ", resulting_string) hex_string = metasm(resulting_string) if not silent: info(" Unicode safe opcodes without zero bytes:") info(" ", hex_string) thisresult[resulting_string] = hex_string return thisresult def metasm(inputInstr): #the immunity and metasm assembly differ a lot: #immunity add [ebp],ch "\x00\xad\x00\x00\x00\x00" #metasm add [ebp],ch "\x00\x6d\x00" --> we want this! #Therefore implementing our own "metasm" mapping here #same problem for things like mov eax,0x41004300 ass_operation = {'add [ebp],ch': '\\x00\x6d\\x00', 'pop ebp': ']', 'pop edx': 'Z', 'pop ecx': 'Y', 'push ecx': 'Q', 'pop ebx': '[', 'push ebx': 'S', 'pop eax': 'X', 'push eax': 'P', 'push esp': 'T', 'push ebp': 'U', 'push edx': 'R', 'pop esp': '\\', 'add dl,bh': '\\x00\\xfa', 'add dl,dh': '\\x00\\xf2', 'add dl,ah': '\\x00\\xe2', 'add ah,al': '\\x00\\xc4', 'add ah,ah': '\\x00\\xe4', 'add ch,bl': '\\x00\\xdd', 'add ah,cl': '\\x00\\xcc', 'add bl,ah': '\\x00\\xe3', 'add bh,dh': '\\x00\\xf7', 'add bl,cl': '\\x00\\xcb', 'add ah,ch': '\\x00\\xec', 'add bl,al': '\\x00\\xc3', 'add bh,dl': '\\x00\\xd7', 'add bl,ch': '\\x00\\xeb', 'add dl,cl': '\\x00\\xca', 'add dl,bl': '\\x00\\xda', 'add al,ah': '\\x00\\xe0', 'add bh,ch': '\\x00\\xef', 'add al,al': '\\x00\\xc0', 'add bh,cl': '\\x00\\xcf', 'add al,ch': '\\x00\\xe8', 'add dh,bl': '\\x00\\xde', 'add ch,ch': '\\x00\\xed', 'add cl,dl': '\\x00\\xd1', 'add al,cl': '\\x00\\xc8', 'add dh,bh': '\\x00\\xfe', 'add ch,cl': '\\x00\\xcd', 'add cl,dh': '\\x00\\xf1', 'add ch,ah': '\\x00\\xe5', 'add cl,bl': '\\x00\\xd9', 'add dh,al': '\\x00\\xc6', 'add ch,al': '\\x00\\xc5', 'add cl,bh': '\\x00\\xf9', 'add dh,ah': '\\x00\\xe6', 'add dl,dl': '\\x00\\xd2', 'add dh,cl': '\\x00\\xce', 'add dh,dl': '\\x00\\xd6', 'add ah,dh': '\\x00\\xf4', 'add dh,dh': '\\x00\\xf6', 'add ah,dl': '\\x00\\xd4', 'add ah,bh': '\\x00\\xfc', 'add ah,bl': '\\x00\\xdc', 'add bl,bh': '\\x00\\xfb', 'add bh,al': '\\x00\\xc7', 'add bl,dl': '\\x00\\xd3', 'add bl,bl': '\\x00\\xdb', 'add bh,ah': '\\x00\\xe7', 'add bl,dh': '\\x00\\xf3', 'add bh,bl': '\\x00\\xdf', 'add al,bl': '\\x00\\xd8', 'add bh,bh': '\\x00\\xff', 'add al,bh': '\\x00\\xf8', 'add al,dl': '\\x00\\xd0', 'add dl,ch': '\\x00\\xea', 'add dl,al': '\\x00\\xc2', 'add al,dh': '\\x00\\xf0', 'add cl,cl': '\\x00\\xc9', 'add cl,ch': '\\x00\\xe9', 'add ch,bh': '\\x00\\xfd', 'add cl,al': '\\x00\\xc1', 'add ch,dh': '\\x00\\xf5', 'add cl,ah': '\\x00\\xe1', 'add dh,ch': '\\x00\\xee', 'add ch,dl': '\\x00\\xd5', 'add ch,ah': '\\x00\\xe5', 'mov dh,0': '\\xb6\\x00', 'add dl,ah': '\\x00\\xe2', 'mov dl,0': '\\xb2\\x00', 'mov ch,0': '\\xb5\\x00', 'mov cl,0': '\\xb1\\x00', 'mov bh,0': '\\xb7\\x00', 'add bl,ah': '\\x00\\xe3', 'mov bl,0': '\\xb3\\x00', 'add dh,ah': '\\x00\\xe6', 'add cl,ah': '\\x00\\xe1', 'add bh,ah': '\\x00\\xe7'} for example_instr, example_op in [("mov eax,0x41004300", "\\xb8\\x00\\x43\\x00\\x41"), ("mov ebx,0x4100af00", "\\xbb\\x00\\xaf\\x00\\x41"), ("mov ecx,0x41004300", "\\xb9\\x00\\x43\\x00\\x41"), ("mov edx,0x41004300", "\\xba\\x00\\x43\\x00\\x41")]: for i in range(0,256): padding ="" if i < 16: padding = "0" new_instr = example_instr[:14]+padding+hex(i)[2:]+example_instr[16:] new_op = example_op[:10]+padding+hex(i)[2:]+example_op[12:] ass_operation[new_instr] = new_op res = "" for instr in inputInstr.split("; "): if instr in ass_operation: res += ass_operation[instr].replace("\\x00","") elif instr.strip(): warn(" Couldn't find metasm assembly for %s" % str(instr)) warn(" You have to manually convert it in the metasm shell") res += "<"+instr+">" return res def getCyclic(originals): cyclic = [0 for i in range(0,len(originals))] for index, orig_num in enumerate(originals): cycle = 1 num = orig_num while True: cycle += 1 num += orig_num num = num % 256 if num == orig_num: cyclic[index] = cycle break return cyclic def hexlist(lis): return [hex(i) for i in lis] def theX(num): res = (num>>16)<<16 ^ num return res def higher(num): res = num>>8 return res def lower(num): res = ((num>>8)<<8) ^ num return res def info(*text): dbg.log(" ".join(str(i) for i in text)) def warn(*text): dbg.log(" ".join(str(i) for i in text), highlight=1) def debug(*text): if False: dbg.log(" ".join(str(i) for i in text)) buffer_registers_4_byte_names = [bufferRegister[1]+"h", bufferRegister[1]+"l"] buffer_registers_4_byte_value = theX(registers[bufferRegister]) MAGIC_PROBABILITY_OF_ADDING_AN_ELEMENT_FROM_INPUTS=0.25 MAGIC_PROBABILITY_OF_RESETTING=0.04 MAGIC_MAX_PROBABILITY_OF_RESETTING=0.11 originals = [] ax = theX(registers["eax"]) ah = higher(ax) al = lower(ax) bx = theX(registers["ebx"]) bh = higher(bx) bl = lower(bx) cx = theX(registers["ecx"]) ch = higher(cx) cl = lower(cx) dx = theX(registers["edx"]) dh = higher(dx) dl = lower(dx) start_address = theX(buffer_registers_4_byte_value) s1 = higher(start_address) s2 = lower(start_address) alignment_code_loc_address = theX(alignment_code_loc) g1 = higher(alignment_code_loc_address) g2 = lower(alignment_code_loc_address) names = ['ah', 'al', 'bh', 'bl', 'ch', 'cl', 'dh', 'dl'] originals = [ah, al, bh, bl, ch, cl, dh, dl] sanitiseZeros(originals, names) checkDuplicates(originals, names) best_result = checkHigherByteBufferRegisterForOverflow(g1, buffer_registers_4_byte_names[0], g2) xs = [0 for i in range(0,len(originals))] ys = [0 for i in range(0,len(originals))] cyclic = getCyclic(originals) mul = 1 for i in cyclic: mul *= i if not silent: dbg.log("[+] Searching for random solutions for code alignment code in at least %i possibilities..." % mul) dbg.log(" Bufferregister: %s" % bufferRegister) dbg.log(" Max time: %d seconds" % timeToRun) dbg.log("") #We can't even know the value of AH yet (no, it's NOT g1 for high instruction counts) cyclic2 = copy.copy(cyclic) cyclic2[names.index(buffer_registers_4_byte_names[0])] = 9999999 number_of_tries = 0.0 beginning = time.time() resultFound = False resultcnt = 0 while time.time()-beginning < timeToRun: #Run only timeToRun seconds! randomise(xs, cyclic) randomise(ys, cyclic2) #[Extra constraint!] #not allowed: all operations with the bufferRegister, #because we can not rely on it's values, e.g. #add al, al #add al, ah #add ah, ah #add ah, al xs[names.index(buffer_registers_4_byte_names[0])] = 0 xs[names.index(buffer_registers_4_byte_names[1])] = 0 ys[names.index(buffer_registers_4_byte_names[0])] = 0 ys[names.index(buffer_registers_4_byte_names[1])] = 0 tmp = check(originals, names.index(buffer_registers_4_byte_names[0]), [s1, s2], [g1, g2], xs, ys, additionalLength, best_result) if tmp > 0: best_result = tmp #we got a new result resultFound = True alignresults[resultcnt] = printNicely(names, buffer_registers_4_byte_names, xs, ys, additionalLength, prefix, postfix) resultcnt += 1 if not silent: dbg.log(" Time elapsed so far: %s seconds" % (time.time()-beginning)) dbg.log("") #Slightly increases probability of resetting with time probability = MAGIC_PROBABILITY_OF_RESETTING+number_of_tries/(10**8) if probability < MAGIC_MAX_PROBABILITY_OF_RESETTING: number_of_tries += 1.0 if random.random() <= probability: xs = [0 for i in range(0,len(originals))] ys = [0 for i in range(0,len(originals))] if not silent: dbg.log("") dbg.log(" Done. Total time elapsed: %s seconds" % (time.time()-beginning)) if not resultFound: dbg.log("") dbg.log("No results. Please try again (you might want to increase -t)") dbg.log("") dbg.log("If you are unsatisfied with the result, run the command again and use the -t option") dbg.log("") return alignresults # end unicode alignment routines def procHeapCookie(args): # first find all writeable pages allpages = dbg.getMemoryPages() filename="heapcookie.txt" orderedpages = [] cookiemonsters = [] for tpage in allpages.keys(): orderedpages.append(tpage) orderedpages.sort() for thispage in orderedpages: page = allpages[thispage] page_base = page.getBaseAddress() page_size = page.getSize() page_end = page_base + page_size acl = page.getAccess(human=True) if "WRITE" in acl: processpage = True # don't even bother if page belongs to module that is ASLR/Rebased pageptr = MnPointer(page_base) thismodulename = pageptr.belongsTo() if thismodulename != "": thismod = MnModule(thismodulename) if thismod.isAslr or thismod.isRebase: processpage = False if processpage: dbg.log("[+] Walking page 0x%08x - 0x%08x (%s)" % (page_base,page_end,acl)) startptr = page_base # we need to start here while startptr < page_end-16: # pointer needs to pass 3 tests try: heap_entry = startptr userptr = heap_entry + 0x8 cookieptr = heap_entry + 5 raw_heapcookie = dbg.readMemory(cookieptr,1) heapcookie = struct.unpack(" 0: # write to log dbg.log("Found %s (fake) UserPtr pointers." % len(cookiemonsters)) all_ptrs = {} all_ptrs[""] = cookiemonsters logfile = MnLog(filename) thislog = logfile.reset() processResults(all_ptrs,logfile,thislog) else: dbg.log("Bad luck, no results.") return def procFlags(args): currentflag = getNtGlobalFlag() dbg.log("[+] NtGlobalFlag: 0x%08x" % currentflag) flagvalues = getNtGlobalFlagValues(currentflag) if len(flagvalues) == 0: dbg.log(" No GFlags set") else: for flagvalue in flagvalues: dbg.log(" 0x%08x : %s" % (flagvalue,getNtGlobalFlagValueName(flagvalue))) return def procEval(args): # put all args together argline = "" if len(currentArgs) > 1: if __DEBUGGERAPP__ == "WinDBG": for a in currentArgs[2:]: argline += a else: for a in currentArgs[1:]: argline += a argline = argline.replace(" ","") if argline.replace(" ","") != "": dbg.log("[+] Evaluating expression '%s'" % argline) val,valok = getAddyArg(argline) if valok: dbg.log(" Result: 0x%08x" % val) else: dbg.log(" *** Unable to evaluate expression ***") else: dbg.log(" *** No expression found***") return def procDiffHeap(args): global ignoremodules filenamebefore = "heapstate_before.db" filenameafter = "heapstate_after.db" ignoremodules = True statefilebefore = MnLog(filenamebefore) thisstatefilebefore = statefilebefore.reset(clear=False) statefileafter = MnLog(filenameafter) thisstatefileafter = statefileafter.reset(clear=False) ignoremodules = False beforestate = {} afterstate = {} #do we want to save states, or diff them? if not "before" in args and not "after" in args and not "diff" in args: dbg.log("*** Missing mandatory argument -before, -after or -diff ***", highlight=1) return if "diff" in args: # check if before and after state file exists if os.path.exists(thisstatefilebefore) and os.path.exists(thisstatefileafter): # read contents from both states into dict dbg.log("[+] Reading 'before' state from %s" % thisstatefilebefore) beforestate = readPickleDict(thisstatefilebefore) dbg.log("[+] Reading 'after' state from %s" % thisstatefileafter) afterstate = readPickleDict(thisstatefileafter) # compare dbg.log("[+] Diffing heap states...") else: if not os.path.exists(thisstatefilebefore): dbg.log("[-] Oops, unable to find 'before' state file %s" % thisstatefilebefore) if not os.path.exists(thisstatefileafter): dbg.log("[-] Oops, unable to find 'after' state file %s" % thisstatefileafter) return elif "before" in args: thisstatefilebefore = statefilebefore.reset(showheader=False) dbg.log("[+] Enumerating current heap layout, please wait...") currentstate = getCurrentHeapState() dbg.log("[+] Saving current heap layout to 'before' heap state file %s" % thisstatefilebefore) # save dict to file try: writePickleDict(thisstatefilebefore, currentstate) dbg.log("[+] Done") except: dbg.log("[-] Error while saving current state to file") return elif "after" in args: thisstatefileafter = statefileafter.reset(showheader=False) dbg.log("[+] Enumerating current heap layout, please wait...") currentstate = getCurrentHeapState() dbg.log("[+] Saving current heap layout to 'after' heap state file %s" % thisstatefileafter) try: writePickleDict(thisstatefileafter, currentstate) dbg.log("[+] Done") except: dbg.log("[-] Error while saving current state to file") return return def procFlow(args): srplist = [] endlist = [] cregs = [] cregsc = [] avoidlist = [] endloc = 0 rellist = {} funcnamecache = {} branchstarts = {} maxinstr = 60 maxcalllevel = 3 callskip = 0 instrcnt = 0 regs = dbg.getRegs() aregs = getAllRegs() addy = regs["EIP"] addyerror = False eaddy = 0 showfuncposition = False if "cl" in args: if type(args["cl"]).__name__.lower() != "bool": try: maxcalllevel = int(args["cl"]) except: pass if "cs" in args: if type(args["cs"]).__name__.lower() != "bool": try: callskip = int(args["cs"]) except: pass if "avoid" in args: if type(args["avoid"]).__name__.lower() != "bool": try: avoidl = args["avoid"].replace("'","").replace('"',"").replace(" ","").split(",") for aa in avoidl: a,aok = getAddyArg(aa) if aok: if not a in avoidlist: avoidlist.append(a) except: pass if "cr" in args: if type(args["cr"]).__name__.lower() != "bool": crdata = args["cr"] crdata = crdata.replace("'","").replace('"',"").replace(" ","") crlist = crdata.split(",") for c in crlist: c1 = c.upper() if c1 in aregs: cregs.append(c1) csmall = getSmallerRegs(c1) for cs in csmall: cregs.append(cs) if "crc" in args: if type(args["crc"]).__name__.lower() != "bool": crdata = args["crc"] crdata = crdata.replace("'","").replace('"',"").replace(" ","") crlist = crdata.split(",") for c in crlist: c1 = c.upper() if c1 in aregs: cregsc.append(c1) csmall = getSmallerRegs(c1) for cs in csmall: cregsc.append(cs) cregs = list(set(cregs)) cregsc = list(set(cregsc)) if "n" in args: if type(args["n"]).__name__.lower() != "bool": try: maxinstr = int(args["n"]) except: pass if "func" in args: showfuncposition = True if "a" in args: if type(args["a"]).__name__.lower() != "bool": addy,addyok = getAddyArg(args["a"]) if not addyok: dbg.log(" ** Please provide a valid start location with argument -a **") return if "e" in args: if type(args["e"]).__name__.lower() != "bool": eaddy,eaddyok = getAddyArg(args["e"]) if not eaddyok: dbg.log(" ** Please provide a valid end location with argument -e **") return dbg.log("[+] Max nr of instructions per branch: %d" % maxinstr) dbg.log("[+] Maximum CALL level: %d" % maxcalllevel) if len(avoidlist) > 0: dbg.log("[+] Only showing flows that don't contains these pointer(s):") for a in avoidlist: dbg.log(" 0x%08x" % a) if callskip > 0: dbg.log("[+] Skipping details of the first %d child functions" % callskip) if eaddy > 0: dbg.log("[+] Searching all possible paths between 0x%08x and 0x%08x" % (addy,eaddy)) else: dbg.log("[+] Searching all possible paths from 0x%08x" % (addy)) if len(cregs) > 0: dbg.log("[+] Controlled registers: %s" % cregs) if len(cregsc) > 0: dbg.log("[+] Controlled register contents: %s" % cregsc) # first, get SRPs at this point if addy == regs["EIP"]: cmd2run = "k" srpdata = dbg.nativeCommand(cmd2run) for line in srpdata.split("\n"): linedata = line.split(" ") if len(linedata) > 1: childebp = linedata[0] srp = linedata[1] if isAddress(childebp) and isAddress(srp): srplist.append(hexStrToInt(srp)) branchstarts[addy] = [0,srplist,0] curlocs = [addy] # create relations while len(curlocs) > 0: curloc = curlocs.pop(0) callcnt = 0 #dbg.log("New start location: 0x%08x" % curloc) prevloc = curloc instrcnt = branchstarts[curloc][0] srplist = branchstarts[curloc][1] currcalllevel = branchstarts[curloc][2] while instrcnt < maxinstr: beforeloc = prevloc prevloc = curloc try: thisopcode = dbg.disasm(curloc) instruction = getDisasmInstruction(thisopcode) instructionbytes = thisopcode.getBytes() instructionsize = thisopcode.opsize opupper = instruction.upper() if opupper.startswith("RET"): if currcalllevel > 0: currcalllevel -= 1 if len(srplist) > 0: newloc = srplist.pop(0) rellist[curloc] = [newloc] curloc = newloc else: break elif opupper.startswith("JMP"): if "(" in opupper and ")" in opupper: ipartsa = opupper.split(")") ipartsb = ipartsa[0].split("(") if len(ipartsb) > 0: jmptarget = ipartsb[1] if isAddress(jmptarget): newloc = hexStrToInt(jmptarget) rellist[curloc] = [newloc] curloc = newloc elif opupper.startswith("J"): if "(" in opupper and ")" in opupper: ipartsa = opupper.split(")") ipartsb = ipartsa[0].split("(") if len(ipartsb) > 0: jmptarget = ipartsb[1] if isAddress(jmptarget): newloc = hexStrToInt(jmptarget) if not newloc in curlocs: curlocs.append(newloc) branchstarts[newloc] = [instrcnt,srplist,currcalllevel] newloc2 = prevloc + instructionsize rellist[curloc] = [newloc,newloc2] curloc = newloc2 #dbg.log(" Added 0x%08x as alternative branch start" % newloc) elif opupper.startswith("CALL"): if ("(" in opupper and ")" in opupper) and currcalllevel < maxcalllevel and callcnt > callskip: ipartsa = opupper.split(")") ipartsb = ipartsa[0].split("(") if len(ipartsb) > 0: jmptarget = ipartsb[1] if isAddress(jmptarget): newloc = hexStrToInt(jmptarget) rellist[curloc] = [newloc] curloc = newloc newretptr = prevloc + instructionsize srplist.insert(0,newretptr) currcalllevel += 1 else: # don't show the function details, simply continue after the call newloc = curloc+instructionsize rellist[curloc] = [newloc] curloc = newloc callcnt += 1 else: curloc += instructionsize rellist[prevloc] = [curloc] except: #dbg.log("Unable to disasm at 0x%08x, past: 0x%08x" % (curloc,beforeloc)) if not beforeloc in endlist: endlist.append(beforeloc) instrcnt = maxinstr break #dbg.log("%d 0x%08x : %s -> 0x%08x" % (instrcnt,prevloc,instruction,curloc)) instrcnt += 1 if not curloc in endlist: endlist.append(curloc) dbg.log("[+] Found total of %d possible flows" % len(endlist)) if eaddy > 0: if eaddy in rellist: endlist = [eaddy] dbg.log("[+] Limit flows to cases that contain 0x%08x" % eaddy) else: dbg.log(" ** Unable to reach 0x%08x ** " % eaddy) dbg.log(" Try increasing max nr of instructions with parameter -n") return filename = "flows.txt" logfile = MnLog(filename) thislog = logfile.reset() dbg.log("[+] Processing %d endings" % len(endlist)) endingcnt = 1 processedresults = [] for endaddy in endlist: dbg.log("[+] Creating all paths between 0x%08x and 0x%08x" % (addy,endaddy)) allpaths = findAllPaths(rellist,addy,endaddy) if len(allpaths) == 0: #dbg.log(" *** No paths from 0x%08x to 0x%08x *** " % (addy,endaddy)) continue dbg.log("[+] Ending: 0x%08x (%d/%d), %d paths" % (endaddy,endingcnt,len(endlist), len(allpaths))) endingcnt += 1 for p in allpaths: if p in processedresults: dbg.log(" > Skipping duplicate path from 0x%08x to 0x%08x" % (addy,endaddy)) else: processedresults.append(p) skipthislist = False logl = "Path from 0x%08x to 0x%08x (%d instructions) :" % (addy,endaddy,len(p)) if len(avoidlist) > 0: for a in avoidlist: if a in p: dbg.log(" > Skipping path, contains 0x%08x (which should be avoided)"%a) skipthislist = True break if not skipthislist: logfile.write("\n",thislog) logfile.write(logl,thislog) logfile.write("-" * len(logl),thislog) dbg.log(" > Simulating path from 0x%08x to 0x%08x (%d instructions)" % (addy,endaddy,len(p))) cregsb = [] for c in cregs: cregsb.append(c) cregscb = [] for c in cregsc: cregscb.append(c) prevfname = "" fname = "" foffset = "" previnstruction = "" for thisaddy in p: if showfuncposition: if previnstruction == "" or previnstruction.startswith("RET") or previnstruction.startswith("J") or previnstruction.startswith("CALL"): if not thisaddy in funcnamecache: fname,foffset = getFunctionName(thisaddy) funcnamecache[thisaddy] = [fname,foffset] else: fname = funcnamecache[thisaddy][0] foffset = funcnamecache[thisaddy][1] if fname != prevfname: prevfname = fname locname = fname if foffset != "": locname += "+%s" % foffset logfile.write("#--- %s ---" % locname,thislog) #dbg.log("%s" % locname) thisopcode = dbg.disasm(thisaddy) instruction = getDisasmInstruction(thisopcode) previnstruction = instruction clist = [] clistc = [] for c in cregsb: combins = [] combins.append(" %s" % c) combins.append("[%s" % c) combins.append(",%s" % c) combins.append("%s]" % c) combins.append("%s-" % c) combins.append("%s+" % c) combins.append("-%s" % c) combins.append("+%s" % c) for comb in combins: if comb in instruction and not c in clist: clist.append(c) for c in cregscb: combins = [] combins.append(" %s" % c) combins.append("[%s" % c) combins.append(",%s" % c) combins.append("%s]" % c) combins.append("%s-" % c) combins.append("%s+" % c) combins.append("-%s" % c) combins.append("+%s" % c) for comb in combins: if comb in instruction and not c in clistc: clistc.append(c) rsrc,rdst = getSourceDest(instruction) csource = False cdest = False if rsrc in cregsb or rsrc in cregscb: csource = True if rdst in cregsb or rdst in cregscb: cdest = True destructregs = ["MOV","XOR","OR"] writeregs = ["INC","DEC","AND"] ocregsb = copy.copy(cregsb) if not instruction.startswith("TEST") and not instruction.startswith("CMP"): for d in destructregs: if instruction.startswith(d): sourcefound = False sourcereg = "" destfound = False destreg = "" for s in clist: for sr in rsrc: if s in sr and not sourcefound: sourcefound = True sourcereg = s for sr in rdst: if s in sr and not destfound: destfound = True destreg = s if sourcefound and destfound: if not destreg in cregsb: cregsb.append(destreg) if destfound and not sourcefound: sregs = getSmallerRegs(destreg) if destreg in cregsb: cregsb.remove(destreg) for s in sregs: if s in cregsb: cregsb.remove(s) break #else: #dbg.log(" Control: %s" % ocregsb) logfile.write("0x%08x : %s" % (thisaddy,instruction),thislog) #if len(cregs) > 0 or len(cregsb) > 0: # if cmp(ocregsb,cregsb) == -1: # dbg.log(" Before: %s" % ocregsb) # dbg.log(" After : %s" % cregsb) return def procChangeACL(args): size = 1 addy = 0 acl = "" addyerror = False aclerror = False if "a" in args: if type(args["a"]).__name__.lower() != "bool": addy,addyok = getAddyArg(args["a"]) if not addyok: addyerror = True if "acl" in args: if type(args["acl"]).__name__.lower() != "bool": if args["acl"].upper() in memProtConstants: acl = args["acl"].upper() else: aclerror = True else: aclerror = True if addyerror: dbg.log(" *** Please specify a valid address to argument -a ***") if aclerror: dbg.log(" *** Please specify a valid memory protection constant with -acl ***") dbg.log(" *** Valid values are :") for acltype in memProtConstants: dbg.log(" %s (%s = 0x%02x)" % (toSize(acltype,10),memProtConstants[acltype][0],memProtConstants[acltype][1])) if not addyerror and not aclerror: pageacl = memProtConstants[acl][1] pageaclname = memProtConstants[acl][0] dbg.log("[+] Current ACL: %s" % getPointerAccess(addy)) dbg.log("[+] Desired ACL: %s (0x%02x)" % (pageaclname,pageacl)) retval = dbg.rVirtualAlloc(addy,1,0x1000,pageacl) return def procToBp(args): """ Generate WinDBG syntax to create a logging breakpoint on a given location """ addy = 0 addyerror = False executenow = False locsyntax = "" regsyntax = "" poisyntax = "" dmpsyntax = "" instructionparts = [] global silent oldsilent = silent regs = dbg.getRegs() silent = True if "a" in args: if type(args["a"]).__name__.lower() != "bool": addy,addyok = getAddyArg(args["a"]) if not addyok: addyerror = True else: addy = regs["EIP"] if "e" in args: executenow = True if addyerror: dbg.log(" *** Please provide a valid address with argument -a ***",highlight=1) return # get RVA for addy (or absolute address if addy is not part of a module) bpdest = "0x%08x" % addy instruction = "" ptrx = MnPointer(addy) modname = ptrx.belongsTo() if not modname == "": mod = MnModule(modname) m = mod.moduleBase rva = addy - m bpdest = "%s+0x%02x" % (modname,rva) thisopcode = dbg.disasm(addy) instruction = getDisasmInstruction(thisopcode) locsyntax = "bp %s" % bpdest instructionparts = multiSplit(instruction,[" ",","]) usedregs = [] for reg in regs: for ipart in instructionparts: if reg.upper() in ipart.upper(): usedregs.append(reg) if len(usedregs) > 0: regsyntax = '.printf \\"' argsyntax = "" for ipart in instructionparts: for reg in regs: if reg.upper() in ipart.upper(): if "[" in ipart: regsyntax += ipart.replace("[","").replace("]","") regsyntax += ": 0x%p, " argsyntax += "%s," % ipart.replace("[","").replace("]","") regsyntax += ipart regsyntax += ": 0x%p, " argsyntax += "%s," % ipart.replace("[","poi(").replace("]",")") iparttxt = ipart.replace("[","").replace("]","") dmpsyntax += ".echo;.echo %s:;dps %s L 0x24/4;" % (iparttxt,iparttxt) else: regsyntax += ipart regsyntax += ": 0x%p, " argsyntax += "%s," % ipart argsyntax = argsyntax.strip(",") regsyntax = regsyntax.strip(", ") regsyntax += '\\",%s;' % argsyntax if "CALL" in instruction.upper(): dmpsyntax += '.echo;.printf \\"Stack (esp: 0x%p):\\",esp;.echo;dps esp L 0x4;' if instruction.upper().startswith("RET"): dmpsyntax += '.echo;.printf \\"EAX: 0x%p, Ret To: 0x%p, Arg1: 0x%p, Arg2: 0x%p, Arg3: 0x%p, Arg4: 0x%p\\",eax,poi(esp),poi(esp+4),poi(esp+8),poi(esp+c),poi(esp+10);' bpsyntax = locsyntax + ' ".echo ---------------;u eip L 1;' + regsyntax + dmpsyntax + ".echo;g" + '"' filename = "logbps.txt" logfile = MnLog(filename) thislog = logfile.reset(False,False) with open(thislog, "a") as fh: fh.write(bpsyntax + "\n") silent = oldsilent dbg.log("%s" % bpsyntax) dbg.log("Updated %s" % thislog) if executenow: dbg.nativeCommand(bpsyntax) dbg.log("> Breakpoint set at 0x%08x" % addy) return def procAllocMem(args): size = 0x1000 addy = 0 sizeerror = False addyerror = False byteerror = False fillup = False writemore = False fillbyte = "A" acl = "RWX" if "s" in args: if type(args["s"]).__name__.lower() != "bool": sval = args["s"] if sval.lower().startswith("0x"): try: size = int(sval,16) except: sizeerror = True else: try: size = int(sval) except: sizeerror = True else: sizeerror = True if "b" in args: if type(args["b"]).__name__.lower() != "bool": try: fillbyte = hex2bin(args["b"])[0] except: dbg.log(" *** Invalid byte specified with -b ***") byteerror = True if size < 0x1: sizeerror = True dbg.log(" *** Minimum size is 0x1 bytes ***",highlight=1) if "a" in args: if type(args["a"]).__name__.lower() != "bool": addy,addyok = getAddyArg(args["a"]) if not addyok: addyerror = True if "fill" in args: fillup = True if "force" in args: writemore = True aclerror = False if "acl" in args: if type(args["acl"]).__name__.lower() != "bool": if args["acl"].upper() in memProtConstants: acl = args["acl"].upper() else: aclerror = True dbg.log(" *** Please specify a valid memory protection constant with -acl ***") dbg.log(" *** Valid values are :") for acltype in memProtConstants: dbg.log(" %s (%s = 0x%02x)" % (toSize(acltype,10),memProtConstants[acltype][0],memProtConstants[acltype][1])) if addyerror: dbg.log(" *** Please specify a valid address with -a ***",highlight=1) if sizeerror: dbg.log(" *** Please specify a valid size with -s ***",highlight = 1) if not addyerror and not sizeerror and not byteerror and not aclerror: dbg.log("[+] Requested allocation size: 0x%08x (%d) bytes" % (size,size)) if addy > 0: dbg.log("[+] Desired target location : 0x%08x" % addy) pageacl = memProtConstants[acl][1] pageaclname = memProtConstants[acl][0] if addy > 0: dbg.log(" Current page ACL: %s" % getPointerAccess(addy)) dbg.log(" Desired page ACL: %s (0x%02x)" % (pageaclname,pageacl)) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) allocat = dbg.rVirtualAlloc(addy,size,0x1000,pageacl) if addy == 0 and allocat > 0: retval = dbg.rVirtualProtect(allocat,1,pageacl) else: retval = dbg.rVirtualProtect(addy,1,pageacl) dbg.log("[+] Allocated memory at 0x%08x" % allocat) #if allocat > 0: # dbg.log(" ACL 0x%08x: %s" % (allocat,getPointerAccess(allocat))) #else: # dbg.log(" ACL 0x%08x: %s" % (addy,getPointerAccess(addy))) if allocat == 0 and fillup and not writemore: dbg.log("[+] It looks like the page was already mapped. Use the -force argument") dbg.log(" to make me write to 0x%08x anyway" % addy) if (allocat > 0 and fillup) or (writemore and fillup): loc = 0 written = 0 towrite = size while loc < towrite: try: dbg.writeMemory(addy+loc,fillbyte) written += 1 except: pass loc += 1 dbg.log("[+] Wrote %d times \\x%s to chunk at 0x%08x" % (written,bin2hex(fillbyte),addy)) return def procHideDebug(args): peb = dbg.getPEBAddress() dbg.log("[+] Patching PEB (0x%08x)" % peb) if peb == 0: dbg.log("** Unable to find PEB **") return isdebugged = struct.unpack(' 0x%x" % (isdebugged,0)) dbg.writeMemory(peb + 0x02, '\x00') dbg.log(" Patching PEB.ProcessHeap.Flag : 0x%x -> 0x%x" % (processheapvalue,0)) dbg.writeLong(processheapflag,0) dbg.log(" Patching PEB.NtGlobalFlag : 0x%x -> 0x%x" % (ntglobalflag,0)) dbg.writeLong(peb + 0x68, 0) dbg.log(" Patching PEB.LDR_DATA Fill pattern") a = dbg.readLong(peb + 0xc) while a != 0: a += 1 try: b = dbg.readLong(a) c = dbg.readLong(a + 4) if (b == 0xFEEEFEEE) and (c == 0xFEEEFEEE): dbg.writeLong(a,0) dbg.writeLong(a + 4,0) a += 7 except: break uef = dbg.getAddress("kernel32.UnhandledExceptionFilter") if uef > 0: dbg.log("[+] Patching kernel32.UnhandledExceptionFilter (0x%08x)" % uef) uef += 0x86 dbg.writeMemory(uef, dbg.assemble(" \ PUSH EDI \ ")) else: dbg.log("[-] Failed to hook kernel32.UnhandledExceptionFilter (0x%08x)") remdebpres = dbg.getAddress("kernel32.CheckRemoteDebuggerPresent") if remdebpres > 0: dbg.log("[+] Patching CheckRemoteDebuggerPresent (0x%08x)" % remdebpres) dbg.writeMemory( remdebpres, dbg.assemble( " \ MOV EDI, EDI \n \ PUSH EBP \n \ MOV EBP, ESP \n \ MOV EAX, [EBP + C] \n \ PUSH 0 \n \ POP [EAX] \n \ XOR EAX, EAX \n \ POP EBP \n \ RET 8 \ " ) ) else: dbg.log("[-] Unable to patch CheckRemoteDebuggerPresent") gtc = dbg.getAddress("kernel32.GetTickCount") if gtc > 0: dbg.log("[+] Patching GetTickCount (0x%08x)" % gtc) patch = dbg.assemble("MOV EDX, 0x7FFE0000") + Poly_ReturnDW(0x0BADF00D) + dbg.assemble("Ret") while len(patch) > 0x0F: patch = dbg.assemble("MOV EDX, 0x7FFE0000") + Poly_ReturnDW(0x0BADF00D) + dbg.assemble("Ret") dbg.writeMemory( gtc, patch ) else: dbg.log("[-] Unable to pach GetTickCount") zwq = dbg.getAddress("ntdll.ZwQuerySystemInformation") if zwq > 0: dbg.log("[+] Patching ZwQuerySystemInformation (0x%08x)" % zwq) isPatched = False a = 0 s = 0 while a < 3: a += 1 s += dbg.disasmSizeOnly(zwq + s).opsize FakeCode = dbg.readMemory(zwq, 1) + "\x78\x56\x34\x12" + dbg.readMemory(zwq + 5, 1) if FakeCode == dbg.assemble("PUSH 0x12345678\nRET"): isPatched = True a = dbg.readLong(zwq+1) i = 0 s = 0 while i < 3: i += 1 s += dbg.disasmSizeOnly(a+s).opsize if isPatched: dbg.log(" Function was already patched.") else: a = dbg.remoteVirtualAlloc(size=0x1000) if a > 0: dbg.log(" Writing instructions to 0x%08x" % a) dbg.writeMemory(a, dbg.readMemory(zwq,s)) pushCode = dbg.assemble("PUSH 0x%08x" % (zwq + s)) patchCode = "\x83\x7c\x24\x08\x07" # CMP [ESP+8],7 patchCode += "\x74\x06" patchCode += pushCode patchCode += "\xC3" # RETN patchCode += "\x8B\x44\x24\x0c" # MOV EAX,[ESP+0x0c] patchCode += "\x6a\x00" # PUSH 0 patchCode += "\x8f\x00" # POP [EAX] patchCode += "\x33\xC0" # XOR EAX,EAX patchCode += "\xC2\x14\x00" # RETN 14 dbg.writeMemory( a + s, patchCode) # redirect function dbg.writeMemory( zwq, dbg.assemble( "PUSH 0x%08X\nRET" % a) ) else: dbg.log(" ** Unable to allocate memory in target process **") else: dbg.log("[-] Unable to patch ZwQuerySystemInformation") return # ----- Finally, some main stuff ----- # # All available commands and their Usage : sehUsage = """Default module criteria : non safeseh, non aslr, non rebase This function will retrieve all stackpivot pointers that will bring you back to nseh in a seh overwrite exploit Optional argument: -all : also search outside of loaded modules""" configUsage = """Change config of mona.py Available options are : -get , -set or -add Valid parameters are : workingfolder, excluded_modules, author""" jmpUsage = """Default module criteria : non aslr, non rebase Mandatory argument : -r where reg is a valid register""" ropfuncUsage = """Default module criteria : non aslr, non rebase, non os Output will be written to ropfunc.txt""" modulesUsage = """Shows information about the loaded modules""" ropUsage="""Default module criteria : non aslr,non rebase,non os Optional parameters : -offset : define the maximum offset for RET instructions (integer, default : 40) -distance : define the minimum distance for stackpivots (integer, default : 8). If you want to specify a min and max distance, set the value to min,max -depth : define the maximum nr of instructions (not ending instruction) in each gadget (integer, default : 6) -split : write gadgets to individual files, grouped by the module the gadget belongs to -fast : skip the 'non-interesting' gadgets -end : specify one or more instructions that will be used as chain end. (Separate instructions with #). Default ending is RETN -f \"file1,file2,..filen\" : use mona generated rop files as input instead of searching in memory -rva : use RVA's in rop chain -s : only create a ROP chain for the selected technique (options: virtualalloc, virtualprotect) -sort : sort the output in rop.txt (sort on pointer value)""" jopUsage="""Default module criteria : non aslr,non rebase,non os Optional parameters : -depth : define the maximum nr of instructions (not ending instruction) in each gadget (integer, default : 8)""" stackpivotUsage="""Default module criteria : non aslr,non rebase,non os Optional parameters : -offset : define the maximum offset for RET instructions (integer, default : 40) -distance : define the minimum distance for stackpivots (integer, default : 8) If you want to specify a min and max distance, set the value to min,max -depth : define the maximum nr of instructions (not ending instruction) in each gadget (integer, default : 6)""" filecompareUsage="""Compares 2 or more files created by mona using the same output commands Make sure to use files that are created with the same version of mona and contain the output of the same mona command. Mandatory argument : -f \"file1,file2,...filen\" Put all filenames between one set of double quotes, and separate files with comma's. You can specify a foldername as well with -f, all files in the root of that folder will be part of the compare. Output will be written to filecompare.txt and filecompare_not.txt (not matching pointers) Optional parameters : -contains \"INSTRUCTION\" (will only list if instruction is found) -nostrict (will also list pointer is instructions don't match in all files) -range : find overlapping ranges for all pointers + range. When using -range, the -contains and -nostrict options will be ignored -ptronly : only show matching pointers (slightly faster). Doesn't work when 'range' is used""" patcreateUsage="""Create a cyclic pattern of a given size. Output will be written to pattern.txt in ascii, hex and unescape() javascript format Mandatory argument : size (numberic value) Optional arguments : -extended : extend the 3rd characterset (numbers) with punctuation marks etc -c1 : set the first charset to this string of characters -c2 : set the second charset to this string of characters -c3 : set the third charset to this string of characters""" patoffsetUsage="""Find the location of 4 bytes in a cyclic pattern Mandatory argument : the 4 bytes to look for Note : you can also specify a register Optional arguments : -extended : extend the 3rd characterset (numbers) with punctuation marks etc -c1 : set the first charset to this string of characters -c2 : set the second charset to this string of characters -c3 : set the third charset to this string of characters Note : the charset must match the charset that was used to create the pattern ! """ findwildUsage = """Find instructions in memory, accepts wildcards : Mandatory arguments : -s (separate instructions with #) Optional arguments : -b
: base/bottom address of the search range -t
: top address of the search range -depth : number of instructions to go deep -all : show all instruction chains, even if it contains something that might break the chain -distance min=nr,max=nr : you can use a numeric offset wildcard (a single *) in the first instruction of the search the distance parameter allows you to specify the range of the offset Inside the instructions string, you can use the following wildcards : * = any instruction r32 = any register Example : pop r32#*#xor eax,eax#*#pop esi#ret """ findUsage= """Find a sequence of bytes in memory. Mandatory argument : -s : the sequence to search for. If you specified type 'file', then use -s to specify the file. This file needs to be a file created with mona.py, containing pointers at the begin of each line. Optional arguments: -type : Type of pattern to search for : bin,asc,ptr,instr,file -b
: base/bottom address of the search range -t
: top address of the search range -c : skip consecutive pointers but show length of the pattern instead -p2p : show pointers to pointers to the pattern (might take a while !) this setting equals setting -level to 1 -level : do recursive (p2p) searches, specify number of levels deep if you want to look for pointers to pointers, set level to 1 -offset : subtract a value from a pointer at a certain level -offsetlevel : level to subtract a value from a pointer -r : if p2p is used, you can tell the find to also find close pointers by specifying -r with a value. This value indicates the number of bytes to step backwards for each search -unicode : used in conjunction with search type asc, this will convert the search pattern to unicode first -ptronly : Only show the pointers, skip showing info about the pointer (slightly faster)""" assembleUsage = """Convert instructions to opcode. Separate multiple instructions with #. Mandatory argument : -s : the sequence of instructions to assemble to opcode""" infoUsage = """Show information about a given address in the context of the loaded application Mandatory argument : -a
: the address to query""" dumpUsage = """Dump the specified memory range to a file. Either the end address or the size of buffer needs to be specified. Mandatory arguments : -s
: start address -f : the name of the file where to write the bytes Optional arguments: -n : the number of bytes to copy (size of the buffer) -e
: the end address of the copy""" # compareUsage = """Compares contents of a binary file with locations in memory. # Mandatory argument : # -f : full path to binary file # Optional argument : # -a
: the exact address of the bytes in memory (address or register). # If you don't specify an address, I will try to locate the bytes in memory # by looking at the first 8 bytes. # -s : skip locations that belong to a module # -unicode : perform unicode search. Note: input should *not* be unicode, it will be expanded automatically""" compareUsage = """Compare a file created by mona's bytearray/msfvenom/gdb/hex/xxd/hexdump/ollydbg with a copy in memory. Mandatory argument : -f : full path to input file Optional argument : -a
: the exact address of the bytes in memory (address or register). If you don't specify an address, I will try to locate the bytes in memory by looking at the first 8 bytes. -s : skip locations that belong to a module -unicode : perform unicode search. Note: input should *not* be unicode, it will be expanded automatically -t : input file type format. If no file type format is specified, I will try to guess the input file type format. Available formats: 'raw', 'hexdump', 'js-unicode', 'dword', 'xxd', 'byte-array', 'hexstring', 'hexdump-C', 'classic-hexdump', 'escaped-hexes', 'msfvenom-powershell', 'gdb', 'ollydbg', 'msfvenom-ruby', 'msfvenom-c', 'msfvenom-carray', 'msfvenom-python' """ offsetUsage = """Calculate the number of bytes between two addresses. You can use registers instead of addresses. Mandatory arguments : -a1
: the first address/register -a2
: the second address/register""" bpUsage = """Set a breakpoint when a given address is read from, written to or executed Mandatory arguments : -a
: the address where to set the breakpoint (absolute address / register / modulename!functionname) -t : type of the breakpoint, can be READ, WRITE or SFX""" bfUsage = """Set a breakpoint on exported or imported function(s) of the selected modules. Mandatory argument : -t : type of breakpoint action. Can be 'add', 'del' or 'list' Optional arguments : -f : set to 'import' or 'export' to read IAT or EAT. Default : export -s : specify function names. If you want a bp on all functions, set -s to *""" nosafesehUsage = """Show modules that are not safeseh protected""" nosafesehaslrUsage = """Show modules that are not safeseh protected, not subject to ASLR, and won't get rebased either""" noaslrUsage = """Show modules that are not subject to ASLR and won't get rebased""" findmspUsage = """Finds begin of a cyclic pattern in memory, looks if one of the registers contains (is overwritten) with a cyclic pattern or points into a cyclic pattern. findmsp will also look if a SEH record is overwritten and finally, it will look for cyclic patterns on the stack, and pointers to cyclic pattern on the stack. Optional argument : -distance : distance from ESP, applies to search on the stack. Default : search entire stack Note : you can use the same options as with pattern_create and pattern_offset in terms of defining the character set to use""" suggestUsage = """Suggests an exploit buffer structure based on pointers to a cyclic pattern Note : you can use the same options as with pattern_create and pattern_offset in terms of defining the character set to use Mandatory argument in case you are using WinDBG: -t : skeletontype. Valid types are : tcpclient:port, udpclient:port, fileformat:extension Examples : -t tcpclient:21 -t fileformat:pdf""" bytearrayUsage = """Creates a byte array, can be used to find bad characters Optional arguments : -cpb : bytes to exclude from the array. Example : '\\x00\\x0a\\x0d' Note: you can specify wildcards using .. Example: '\\x00\\x0a..\\x20\\x32\\x7f..\\xff' -s : optional starting hex, example: '\\x7f' -e : optional ending hex, example: '\\xff' Example: -s \\x01 -e \\x7f to have all bytes from 0x01 to 0x7f -s \\xff -e \\x7f to have all bytes from 0xff to 0x7f in reverse -r : show array backwards (reversed), starting at \\xff Output will be written to bytearray.txt, and binary output will be written to bytearray.bin""" headerUsage = """Convert contents of a binary file to code that can be run to produce the file Mandatory argument : -f : source filename Optional argument: -t : specify type of output. Valid choices are 'ruby' (default) or 'python' """ updateUsage = """Update mona to the latest version""" getpcUsage = """Find getpc routine for specific register Mandatory argument : -r : register (ex: eax)""" eggUsage = """Creates an egghunter routine Optional arguments : -t : tag (ex: w00t). Default value is w00t -c : enable checksum routine. Only works in conjunction with parameter -f -f : file containing the shellcode -startreg : start searching at the address pointed by this reg -wow64 : generate wow64 egghunter (Win7 and Win10). Default is traditional 32bit egghunter -winver : indicate Windows version for wow64 egghunter. Default is Windows 10. valid values are 7 and 10. DEP Bypass options : -depmethod : method can be "virtualprotect", "copy" or "copy_size" -depreg : sets the register that contains a pointer to the API function to bypass DEP. By default this register is set to ESI -depsize : sets the size for the dep bypass routine -depdest : this register points to the location of the egghunter itself. When bypassing DEP, the egghunter is already marked as executable. So when using the copy or copy_size methods, the DEP bypass in the egghunter would do a "copy 2 self". In order to be able to do so, it needs a register where it can copy the shellcode to. If you leave this empty, the code will contain a GetPC routine.""" stacksUsage = """Shows all stacks for each thread in the running application""" skeletonUsage = """Creates a Metasploit exploit module skeleton for a specific type of exploit Mandatory argument in case you are using WinDBG: -t : skeletontype. Valid types are : tcpclient:port, udpclient:port, fileformat:extension Examples : -t tcpclient:21 -t fileformat:pdf Optional arguments : -s : size of the cyclic pattern (default : 5000) """ heapUsage = """Show information about various heap chunk lists Mandatory arguments : -h
: base address of the heap to query -t : where type is 'segments', 'chunks', 'layout', 'fea' (let mona determine the frontend allocator), 'lal' (force display of LAL FEA, only on XP/2003), 'lfh' (force display of LFH FEA (Vista/Win7/...)), 'bea' (backend allocator, mona will automatically determine what it is), 'all' (show all information) Note: 'layout' will show all heap chunks and their vtables & strings. Use on WinDBG for maximum results. Optional arguments : -expand : Works only in combination with 'layout', will include VA/LFH/... chunks in the search. VA/LFH chunks may be very big, so this might slow down the search. -stat : show statistics (also works in combination with -h heap, -t segments or -t chunks -size : only show strings of at least the specified size. Works in combination with 'layout' -after : only show current & next chunk layout entries when an entry contains this data (Only works in combination with 'layout') -v : show data / write verbose info to the Log window""" getiatUsage = """Show IAT entries from selected module(s) Optional arguments : -s : only show IAT entries that contain one of these keywords""" geteatUsage = """Show EAT entries from selected module(s) Optional arguments : -s : only show EAT entries that contain one of these keywords""" deferUsage = """Set a deferred breakpoint Mandatory arguments : -a ,,... target can be an address, a modulename.functionname or module.dll+offset (hex value) Warning, modulename.functionname is case sensitive ! """ calltraceUsage = """Logs all CALL instructions Mandatory arguments : -m module : specify what module to search for CALL instructions (global option) Optional arguments : -a : number of arguments to show for each CALL -r : also trace RETN instructions (will slow down process!)""" fillchunkUsage = """Fills a heap chunk, referenced by a register, with A's (or another character) Mandatory arguments : -r : reference to heap chunk to fill Optional arguments : -b -s : if the referenced chunk is not found, and a size is defined with -s, memory will be filled anyway, up to the specified size""" getpageACLUsage = """List all mapped pages and show the ACL associated with each page Optional arguments : -a
: only show page information around this address. (Page before, current page and page after will be displayed)""" bpsehUsage = """Sets a breakpoint on all current SEH Handler function pointers""" kbUsage = """Manage knowledgebase data Mandatory arguments: - : type can be 'list', 'set' or 'del' To 'set' ( = add / update ) a KB entry, or 'del' an entry, you will need to specify 2 additional arguments: -id : the Knowledgebase ID -value : the value to add/update. In case of lists, use a comma to separate entries. The -list parameter will show all current ID's To see the contents of a specific ID, use the -id parameter.""" macroUsage = """Manage macros for WinDBG Arguments: -run : run the commands defined in the specified macro -show : show all commands defined in the specified macro -add : create a new macro -set -index -cmd : edit a macro If you set the -command value to #, the command at the specified index will be removed. If you have specified an existing index, the command at that position will be replaced, unless you've also specified the -insert parameter. If you have not specified an index, the command will be appended to he list. -set -file : will tell this macro to execute all instructions in the specified file. You can only enter one file per macro. -del -iamsure: remove the specified macro. Use with care, I won't ask if you're sure.""" sehchainUsage = """Displays the SEH chain for the current thread. This command will also attempt to display offsets and suggest a payload structure in case a cyclic pattern was used to overwrite the chain.""" heapCookieUsage = """Will attempt to find reliable writeable pointers that can help avoiding a heap cookie check during an arbitrary free on Windows XP""" hidedebugUsage = """Will attempt to hide the debugger from the process""" gflagsUsage = """Will show the currently set GFlags, based on the PEB.NtGlobalFlag value""" fwptrUsage = """Search for calls to pointers in a writeable location, will assist with finding a good target for 4byte arbitrary writes Optional arguments: -bp : Set breakpoints on all found CALL instructions -patch : Patch the target of each CALL with 0x41414141 -chunksize : only list the pointer if location-8 bytes contains a size value larger than (size in blocks, not bytes) -offset : add bytes of offset within chunk, after flink/blink pointer (use in combination with -freelist and -chunksize ) -freelist : Search for fwptr that are preceeded by 2 readable pointers that can act as flink/blink""" allocmemUsage = """Allocate RWX memory in the debugged process. Optional arguments: -s : desired size of allocated chunk. VirtualAlloc will allocate at least 0x1000 bytes, but this size argument is only useful when used in combination with -fill. -a
: desired target location for allocation, set to start of chunk to allocate. -acl : overrule default RWX memory protection. -fill : fill 'size' bytes (-s) of memory at specified address (-a) with A's. -force : use in combination with -fill, in case page was already mapped but you still want to fill the chunk at the desired location. -b : Specify what byte to write to the desired location. Defaults to '\\x41' """ changeaclUsage = """Change the ACL of a given page. Arguments: -a
: Address belonging to the page that needs to be changed -acl : New ACL. Valid values are R,RW,RXW,RX,N,GUARD,NOCACHE,WC""" infodumpUsage = """Dumps contents of memory to file. Contents will include all pages that don't belong to stack, heap or loaded modules. Output will be written to infodump.xml""" pebUsage = """Show the address of the Process Environment Block (PEB)""" tebUsage = """Show the address of the Thread Environment Block (TEB) for the current thread""" jsehUsage = """(look for jmp/call dword ptr[ebp/esp+nn and ebp-nn] + add esp,8+ret) Only addresses outside address range of modules will be listed unless parameter '-all' is given. In that case, all addresses will be listed. TRY THIS ONE !""" encUsage = """Encode a series of bytes Arguments: -t : Type of encoder to use. Allowed value(s) are alphanum -s : The bytes to encode (or use -f instead) -f : The full path to the binary file that contains the bytes to encode""" stringUsage = """Read a string from memory or write a string to memory Arguments: -r : Read a string, use in combination with -a -w : Write a string, use in combination with -a and -s -noterminate : Do not terminate the string (using in combination with -w) -u : use UTF-16 (Unicode) mode -s : The string to write -a
: The location to read from or write to""" unicodealignUsage = """Generates a venetian shellcode alignment stub which can be placed directly before unicode shellcode. Arguments: -a
: Specify the address where the alignment code will start/be placed : If -a is not specified, the current value in EIP will be used. -l : Prepend alignment with a null byte compensating nop equivalent (Use this if the last instruction before the alignment routine 'leaks' a null byte) -b : Set the bufferregister, defaults to eax -t : Time in seconds to run heuristics (defaults to 15) -ebp : Overrule the use of the 'current' value of ebp, ebp/address will be used to calculate offset to shellcode""" copyUsage = """Copies bytes from one location to another. Arguments: -src
: The source address -dst
: The destination address -n : The number of bytes to copy""" dumpobjUsage = """Dump the contents of an object. Arguments: -a
: Address of object -s : Size of object (default value: 0x28 or size of chunk) Optional arguments: -l : Recursively dump objects -m : Size for recursive objects (default value: 0x28) """ dumplogUsage = """Dump all objects recorded in an alloc/free log Note: dumplog will only dump objects that have not been freed in the same logfile. Expected syntax for log entries: Alloc : 'alloc(size in hex) = address' Free : 'free(address)' Additional text after the alloc & free info is fine. Just make sure the syntax matches exactly with the examples above. Arguments: -f : Full path to the logfile Optional arguments: -l : Recursively dump objects -m : Size for recursive objects (default value: 0x28) -s : Only take allocated chunks of this exact size into consideration -nofree : Ignore all free() events, show all allocations (including those that were freed)""" tobpUsage = """Generate WinDBG syntax to set a logging breakpoint at a given location Arguments: -a
: Location (address, register) for logging breakpoint Optional arguments: -e : Execute breakpoint command right away""" flowUsage = """Simulates execution flows from current location (EIP), tries all conditional jump combinations Optional arguments: -e
: Show execution flows that will reach specified address -avoid : Only show paths that don't contain any of the pointers to avoid -n : Max nr of instructions, default: 60 -cl : Max level of CALL to follow in detail, default: 3 -cs : Don't show details of first CALL/child functions. default: 0 -func : Show function names (slows down process).""" evalUsage = """Evaluates an expression Arguments: Accepted syntax includes: hex values, decimal values (prefixed with 0n), registers, module names, 'heap' ( = address of default process heap), module!functionname simple math operations""" diffheapUsage = """Compare current heap layout with previously saved state Arguments: -save : save current state to disk -diff : compare current state with previously saved state""" commands["seh"] = MnCommand("seh", "Find pointers to assist with SEH overwrite exploits",sehUsage, procFindSEH) commands["config"] = MnCommand("config","Manage configuration file (mona.ini)",configUsage,procConfig,"conf") commands["jmp"] = MnCommand("jmp","Find pointers that will allow you to jump to a register",jmpUsage,procFindJMP, "j") commands["ropfunc"] = MnCommand("ropfunc","Find pointers to pointers (IAT) to interesting functions that can be used in your ROP chain",ropfuncUsage,procFindROPFUNC) commands["rop"] = MnCommand("rop","Finds gadgets that can be used in a ROP exploit and do ROP magic with them",ropUsage,procROP) commands["jop"] = MnCommand("jop","Finds gadgets that can be used in a JOP exploit",jopUsage,procJOP) commands["jseh"] = MnCommand("jseh", "Finds gadgets that can be used to bypass SafeSEH", jsehUsage, procJseh) commands["stackpivot"] = MnCommand("stackpivot","Finds stackpivots (move stackpointer to controlled area)",stackpivotUsage,procStackPivots) commands["modules"] = MnCommand("modules","Show all loaded modules and their properties",modulesUsage,procShowMODULES,"mod") commands["filecompare"] = MnCommand("filecompare","Compares 2 or more files created by mona using the same output commands",filecompareUsage,procFileCOMPARE,"fc") commands["pattern_create"] = MnCommand("pattern_create","Create a cyclic pattern of a given size",patcreateUsage,procCreatePATTERN,"pc") commands["pattern_offset"] = MnCommand("pattern_offset","Find location of 4 bytes in a cyclic pattern",patoffsetUsage,procOffsetPATTERN,"po") commands["find"] = MnCommand("find", "Find bytes in memory", findUsage, procFind,"f") commands["findwild"] = MnCommand("findwild", "Find instructions in memory, accepts wildcards", findwildUsage, procFindWild,"fw") commands["assemble"] = MnCommand("assemble", "Convert instructions to opcode. Separate multiple instructions with #",assembleUsage,procAssemble,"asm") commands["info"] = MnCommand("info", "Show information about a given address in the context of the loaded application",infoUsage,procInfo) commands["dump"] = MnCommand("dump", "Dump the specified range of memory to a file", dumpUsage,procDump) commands["offset"] = MnCommand("offset", "Calculate the number of bytes between two addresses", offsetUsage, procOffset) #commands["compare"] = MnCommand("compare","Compare contents of a binary file with a copy in memory", compareUsage, procCompare,"cmp") commands["compare"] = MnCommand("compare","Compare a file created by msfvenom/gdb/hex/xxd/hexdump/ollydbg with a copy in memory", compareUsage, procCompare,"cmp") commands["breakpoint"] = MnCommand("bp","Set a memory breakpoint on read/write or execute of a given address", bpUsage, procBp,"bp") commands["nosafeseh"] = MnCommand("nosafeseh", "Show modules that are not safeseh protected", nosafesehUsage, procModInfoS) commands["nosafesehaslr"] = MnCommand("nosafesehaslr", "Show modules that are not safeseh protected, not aslr and not rebased", nosafesehaslrUsage, procModInfoSA) commands["noaslr"] = MnCommand("noaslr", "Show modules that are not aslr or rebased", noaslrUsage, procModInfoA) commands["findmsp"] = MnCommand("findmsp","Find cyclic pattern in memory", findmspUsage,procFindMSP,"findmsf") commands["suggest"] = MnCommand("suggest","Suggest an exploit buffer structure", suggestUsage,procSuggest) commands["bytearray"] = MnCommand("bytearray","Creates a byte array, can be used to find bad characters",bytearrayUsage,procByteArray,"ba") commands["header"] = MnCommand("header","Read a binary file and convert content to a nice 'header' string",headerUsage,procPrintHeader) commands["update"] = MnCommand("update","Update mona to the latest version",updateUsage,procUpdate,"up") commands["getpc"] = MnCommand("getpc","Show getpc routines for specific registers",getpcUsage,procgetPC) commands["egghunter"] = MnCommand("egghunter","Create egghunter code",eggUsage,procEgg,"egg") commands["stacks"] = MnCommand("stacks","Show all stacks for all threads in the running application",stacksUsage,procStacks) commands["skeleton"] = MnCommand("skeleton","Create a Metasploit module skeleton with a cyclic pattern for a given type of exploit",skeletonUsage,procSkeleton) commands["breakfunc"] = MnCommand("breakfunc","Set a breakpoint on an exported function in on or more dll's",bfUsage,procBf,"bf") commands["heap"] = MnCommand("heap","Show heap related information",heapUsage,procHeap) commands["getiat"] = MnCommand("getiat","Show IAT of selected module(s)",getiatUsage,procGetIAT,"iat") commands["geteat"] = MnCommand("geteat","Show EAT of selected module(s)",geteatUsage,procGetEAT,"eat") commands["pageacl"] = MnCommand("pageacl","Show ACL associated with mapped pages",getpageACLUsage,procPageACL,"pacl") commands["bpseh"] = MnCommand("bpseh","Set a breakpoint on all current SEH Handler function pointers",bpsehUsage,procBPSeh,"sehbp") commands["kb"] = MnCommand("kb","Manage Knowledgebase data",kbUsage,procKb,"kb") commands["encode"] = MnCommand("encode","Encode a series of bytes",encUsage,procEnc,"enc") commands["unicodealign"] = MnCommand("unicodealign","Generate venetian alignment code for unicode stack buffer overflow",unicodealignUsage,procUnicodeAlign,"ua") #commands["heapcookie"] = MnCommand("heapcookie","Looks for writeable pointers that can help avoiding cookie check during arbitrary free",heapCookieUsage,procHeapCookie,"hc") if __DEBUGGERAPP__ == "Immunity Debugger": commands["deferbp"] = MnCommand("deferbp","Set a deferred breakpoint",deferUsage,procBu,"bu") commands["calltrace"] = MnCommand("calltrace","Log all CALL instructions",calltraceUsage,procCallTrace,"ct") if __DEBUGGERAPP__ == "WinDBG": commands["fillchunk"] = MnCommand("fillchunk","Fill a heap chunk referenced by a register",fillchunkUsage,procFillChunk,"fchunk") commands["dumpobj"] = MnCommand("dumpobj","Dump the contents of an object",dumpobjUsage,procDumpObj,"do") commands["dumplog"] = MnCommand("dumplog","Dump objects present in alloc/free log file",dumplogUsage,procDumpLog,"dl") commands["changeacl"] = MnCommand("changeacl","Change the ACL of a given page",changeaclUsage,procChangeACL,"ca") commands["allocmem"] = MnCommand("allocmem","Allocate some memory in the process",allocmemUsage,procAllocMem,"alloc") commands["tobp"] = MnCommand("tobp","Generate WinDBG syntax to create a logging breakpoint at given location",tobpUsage,procToBp,"2bp") commands["flow"] = MnCommand("flow","Simulate execution flows, including all branch combinations",flowUsage,procFlow,"flw") #commands["diffheap"] = MnCommand("diffheap", "Compare current heap layout with previously saved state", diffheapUsage, procDiffHeap, "dh") commands["fwptr"] = MnCommand("fwptr", "Find Writeable Pointers that get called", fwptrUsage, procFwptr, "fwp") commands["sehchain"] = MnCommand("sehchain","Show the current SEH chain",sehchainUsage,procSehChain,"exchain") commands["hidedebug"] = MnCommand("hidedebug","Attempt to hide the debugger",hidedebugUsage,procHideDebug,"hd") commands["gflags"] = MnCommand("gflags", "Show current GFlags settings from PEB.NtGlobalFlag", gflagsUsage, procFlags, "gf") commands["infodump"] = MnCommand("infodump","Dumps specific parts of memory to file", infodumpUsage, procInfoDump,"if") commands["peb"] = MnCommand("peb","Show location of the PEB",pebUsage,procPEB,"peb") commands["teb"] = MnCommand("teb","Show TEB related information",tebUsage,procTEB,"teb") commands["string"] = MnCommand("string","Read or write a string from/to memory",stringUsage,procString,"str") commands["copy"] = MnCommand("copy","Copy bytes from one location to another",copyUsage,procCopy,"cp") commands["?"] = MnCommand("?","Evaluate an expression",evalUsage,procEval,"eval") # get the options opts = {} last = "" arguments = [] argcopy = copy.copy(args) aline = " ".join(a for a in argcopy) if __DEBUGGERAPP__ == "WinDBG": aline = "!py " + aline else: aline = "!mona " + aline dbg.log("[+] Command used:") dbg.log("%s" % aline) # in case we're not using Immunity if "-showargs" in args: dbg.log("-" * 50) dbg.log("args: %s" % args) if len(args) > 0: if args[0].lower().startswith("mona") or args[0].lower().endswith("mona") or args[0].lower().endswith("mona.py"): args.pop(0) if len(args) >= 2: arguments = args[1:] if "-showargs" in args: dbg.log("arguments: %s" % arguments) for word in arguments: if (word[0] == '-'): word = word.lstrip("-") opts[word] = True last = word else: if (last != ""): if str(opts[last]) == "True": opts[last] = word else: opts[last] = opts[last] + " " + word #last = "" # if a command only requires a value and not a switch ? # then we'll drop the value into dictionary with key "?" if len(args) > 1 and args[1][0] != "-": opts["?"] = args[1] if len(args) < 1: commands["help"].parseProc(opts) return("") command = args[0] if "-showargs" in args: dbg.log("command: %s" % command) dbg.log("-" * 50) args.remove("-showargs") arguments.remove("-showargs") # ----- execute the chosen command ----- # if command in commands: if command.lower().strip() == "help": commands[command].parseProc(args) else: commands[command].parseProc(opts) else: # maybe it's an alias aliasfound = False for cmd in commands: if commands[cmd].alias == command: commands[cmd].parseProc(opts) aliasfound = True if not aliasfound: commands["help"].parseProc(None) return("** Invalid command **") # ----- report ----- # endtime = datetime.datetime.now() delta = endtime - starttime dbg.log("") dbg.log("[+] This mona.py action took %s" % str(delta)) dbg.setStatusBar("Done") except: dbg.log("*" * 80,highlight=True) dbg.logLines(traceback.format_exc(),highlight=True) dbg.log("*" * 80,highlight=True) dbg.error(traceback.format_exc()) return "" if __name__ == "__main__": dbg.log("Hold on...") # do we need to profile ? doprofile = False if "-profile" in sys.argv: doprofile = True dbg.log("Starting profiler...") cProfile.run('main(sys.argv)', 'monaprofile') else: main(sys.argv) if doprofile: dbg.log("[+] Showing profile stats...") p = pstats.Stats('monaprofile') dbg.log(" ***** ALL *****") p.print_stats() dbg.log(" ***** CUMULATIVE *****") p.sort_stats('cumulative').print_stats(30) dbg.log(" ***** TIME *****") p.sort_stats('time', 'cum').print_stats(30) # clear memory if __DEBUGGERAPP__ == "WinDBG": dbglib.clearvars() try: # allvars = [var for var in globals() if var[0] != "_"] # for var in allvars: # del globals()[var] resetGlobals() dbg = None except: pass