def run(self): os.putenv("LANG", "C") os.putenv("ASAN_SYMBOLIZER_PATH", self.asan_symbolizer_path) cmd = self.program print "Running %s" % cmd cmd_obj = TimeoutCommand(cmd) cmd_obj.run(self.timeout, get_output=True) buf = cmd_obj.stderr self.asan.parse_buffer(buf) if self.asan.reason is not None: crash_data = CCrashData(self.asan.pc, self.asan.reason) i = 0 for line in self.asan.stack_trace: crash_data.add_data("stack trace", "%d" % i, (line[0], line[1])) i += 1 crash_data.add_data("registers", "pc", self.asan.pc) crash_data.add_data("registers", "bp", self.asan.bp) crash_data.add_data("registers", "sp", self.asan.sp) crash_data.add_data("disassembly", int(self.asan.pc), "") j = 0 for line in self.asan.additional: crash_data.add_data("information", j, line) j += 1 crash_data.disasm = [self.asan.pc, ""] if not self.asan.reason.startswith("SIG"): crash_data.exploitable = "EXPLOITABLE" else: crash_data.exploitable = "UNKNOWN" crash_data.add_data("exploitability", "reason", self.asan.reason) crash_data_buf = crash_data.dump_json() crash_data_dict = crash_data.dump_dict() line = "Program received %s at PC 0x%x SP 0x%x BP 0x%x" print line % (self.asan.reason, self.asan.pc, self.asan.sp, self.asan.bp) print for i, line in enumerate(self.asan.stack_trace): if i > 10: break print "0x%08x %s" % (line[0], line[1]) print print "Yep, we got a crash! \o/" print return crash_data_dict return
def run(self): global buf os.putenv("LANG", "C") logfile = mkstemp()[1] try: cmd = '/bin/bash -c "/usr/bin/gdb -q --batch --command=%s --args %s" 2>/dev/null > %s' cmd %= (self.gdb_commands, self.program, logfile) print cmd print "Running %s" % cmd cmd_obj = TimeoutCommand(cmd) #cmd_obj.shell = True cmd_obj.run(self.timeout) buf = open(logfile, "rb").readlines() self.parse_dump(buf) if self.signal: crash_data = CCrashData(self.pc, self.signal) i = 0 for stack in self.stack: crash_data.add_data("stack trace", "%d" % i, stack) i += 1 for reg in self.registers: crash_data.add_data("registers", reg, self.registers[reg]) crash_data.add_data("disassembly", int(self.pc), self.disasm) for dis in self.disasm_around: if type(dis[0]) is int or dis[0].isdigit(): crash_data.add_data("disassembly", dis[0], dis[1]) crash_data.disasm = [self.pc, self.disasm] if self.exploitability is not None: crash_data.exploitable = self.exploitability if self.exploitability_reason is not None: crash_data.add_data("exploitability", "reason", self.exploitability_reason) crash_data_buf = crash_data.dump_json() crash_data_dict = crash_data.dump_dict() print print "Yep, we got a crash! \o/" print return crash_data_dict return finally: os.remove(logfile)
def main(args): crash_data_dict = None tr = vtrace.getTrace() global timeout if os.getenv("NIGHTMARE_TIMEOUT"): timeout = float(os.getenv("NIGHTMARE_TIMEOUT")) if args[0] in ["--attach", "-A"]: if len(args) == 1: usage() sys.exit(1) else: pid = int(args[1]) if timeout != 0: # Schedule a timer to detach from the process after some seconds timer = threading.Timer(timeout, kill_process, (tr, False, )) timer.start() tr.attach(pid) else: if timeout != 0: # Schedule a timer to kill the process after 5 seconds timer = threading.Timer(timeout, kill_process, (tr, True, )) timer.start() tr.execute(" ".join(args)) tr.run() signal = tr.getCurrentSignal() signal_name = signal_to_name(signal) ignore_list = ["Unknown", "SIGUSR1", "SIGUSR2", "SIGTTIN", "SIGPIPE", "SIGINT"] while signal is None or signal_name in ignore_list: signal = tr.getCurrentSignal() signal_name = signal_to_name(signal) try: tr.run() except: break if timeout != 0: timer.cancel() # Don't do anything else, the process is gone if os.name != "nt" and not tr.attached: return None if signal is not None: print tr, hex(signal) print " ".join(sys.argv) crash_name = os.getenv("NFP_INFO_CRASH") # Create the object to store all the crash data crash_data = CCrashData(tr.getProgramCounter(), sig2name(signal)) if crash_name is None: crash_name = "info.crash" #f = open(crash_name, "wb") exploitability_reason = None if os.name != "nt" and signal == 4: # Due to illegal instruction exploitable = EXPLOITABLE exploitability_reason = "Illegal instruction" elif os.name == "nt" and signal in [0xc0000096, 0xc000001d]: # Due to illegal or privileged instruction exploitable = EXPLOITABLE if signal == 0xc000001d: exploitability_reason = "Illegal instruction" else: exploitability_reason = "Privileged instruction" else: exploitable = NOT_EXPLOITABLE crash_data.add_data("process", "pid", tr.getPid()) if os.name == "nt": print "Process %d crashed with exception 0x%x (%s)" % (tr.getPid(), signal, win32_exc_to_name(signal)) else: print "Process %d crashed with signal %d (%s)" % (tr.getPid(), signal, signal_to_name(signal)) i = 0 for t in tr.getThreads(): i += 1 crash_data.add_data("threads", "%d" % i, t) stack_trace = tr.getStackTrace() total = len(stack_trace) i = 0 for x in stack_trace: i += 1 sym = tr.getSymByAddr(x[0], exact=False) if sym is None: sym = "" crash_data.add_data("stack trace", "%d" % i, [x[0], str(sym)]) total -= 1 regs = tr.getRegisterContext().getRegisters() for reg in regs: crash_data.add_data("registers", reg, regs[reg]) if reg.startswith("r"): line = reg.ljust(5) + "%016x" % regs[reg] try: mem = tr.readMemory(regs[reg], 32) mem = base64.b64encode(mem) crash_data.add_data("registers memory", reg, mem) line += "\t" + repr(mem) except: pass for reg in COMMON_REGS: if reg in regs: if reg in crash_data.data["registers memory"]: print reg, hex(regs[reg]), repr(base64.b64decode(crash_data.data["registers memory"][reg])) else: print reg, hex(regs[reg]) print total_around = 40 if 'rip' in regs or 'rsp' in regs or 'rbp' in regs: if len("%08x" % regs['rip']) > 8 or len("%08x" % regs['rsp']) > 8 or len("%08x" % regs['rbp']) > 8: mode = CS_MODE_64 else: mode = CS_MODE_32 else: mode = CS_MODE_32 md = Cs(CS_ARCH_X86, mode) md.skipdata = True pc = tr.getProgramCounter() crash_mnem = None crash_ops = None try: pc_mem = tr.readMemory(pc-total_around/2, total_around) offset = regs["rip"]-total_around/2 ret = [] found = False for x in md.disasm(pc_mem, 0): line = "%016x %s %s" % ((offset + x.address), x.mnemonic, x.op_str) crash_data.add_data("disassembly", offset + x.address, "%s %s" %(x.mnemonic, x.op_str)) if offset + x.address == pc: crash_data.disasm = [x.address + offset, "%s %s" %(x.mnemonic, x.op_str)] line += "\t\t<--------- CRASH" print line found = True ret.append(line) if not found: offset = pc = tr.getProgramCounter() pc_mem = tr.readMemory(pc, total_around) for x in md.disasm(pc_mem, 0): line = "%016x %s %s" % ((offset + x.address), x.mnemonic, x.op_str) if offset + x.address == pc: line += "\t\t<--------- CRASH" crash_data.disasm = [x.address + offset, "%s %s" % (x.mnemonic, x.op_str)] print line except: # Due to invalid memory at $PC if signal != 6: exploitable = True exploitability_reason = "Invalid memory at program counter" print "Exception:", sys.exc_info()[1] if crash_mnem: if crash_mnem in ["call", "jmp"] or \ crash_mnem.startswith("jmp") or \ crash_mnem.startswith("call"): if crash_ops.find("[") > -1: # Due to jump/call with a register that maybe controllable exploitable = EXPLOITABLE exploitability_reason = "Jump or call with a probably controllable register" elif crash_mnem.startswith(".byte"): # Due to illegal instruction exploitable = MAYBE_EXPLOITABLE exploitability_reason = "Illegal instruction" elif crash_mnem.startswith("in") or \ crash_mnem.startswith("out") or \ crash_mnem in ["hlt", "iret", "clts", "lgdt", "lidt", "lldt", "lmsw", "ltr", "cli", "sti"]: if crash_mnem != "int": # Due to privileged instruction (which makes no sense in user-land) exploitable = MAYBE_EXPLOITABLE exploitability_reason = "Privileged instruction" #print >>f #print >>f, "Maps:" i = 0 for m in tr.getMemoryMaps(): i += 1 line = "%016x %s %s %s" % (m[0], str(m[1]).rjust(8), get_permision_str(m[2]), m[3]) crash_data.add_data("memory maps", "%d" % i, m) #print >>f, line #print >>f if exploitable > 0: crash_data.exploitable = is_exploitable(exploitable) crash_data.add_data("exploitability", "reason", exploitability_reason) #print >>f, "Exploitable: %s. %s." % (is_exploitable(exploitable), exploitability_reason) else: #print >>f, "Exploitable: Unknown." pass crash_data_buf = crash_data.dump_json() crash_data_dict = crash_data.dump_dict() print "Yep, we got a crash! \o/" print #print "Dumping JSON...." #print crash_data_buf #print if tr.attached: try: tr.kill() except: pass try: tr.release() except: pass return crash_data_dict
class CWinDbgInterface(object): def __init__(self, program, mode=32, windbg_path=None, exploitable_path=None): global timeout self.id = None self.mode = mode self.program = program self.exploitable_path = None self.windbg_path = windbg_path self.exploitable_path = exploitable_path try: self.handler = ExceptionHandler() except: self.handler = None self.minidump_path = None #self.minidump_path = r"C:\minidumps\\" if windbg_path is None: self.resolve_windbg_path() self.regs = {} self.stack = [] self.pc_register = None self.disassembly = None self.exploitability = "Unknown" self.do_stop = False self.timer = None if os.getenv("NIGHTMARE_TIMEOUT"): timeout = float(os.getenv("NIGHTMARE_TIMEOUT")) self.timeout = timeout def resolve_windbg_path(self): try: reg = ConnectRegistry(None,HKEY_LOCAL_MACHINE) key = OpenKey(reg, r"SOFTWARE\Microsoft\Microsoft SDKs\Windows") if key: for i in range(QueryInfoKey(key)[0]): value = EnumKey(key, i) if value: full_key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\\" + value key2 = OpenKey(reg, full_key) if key2: name = QueryValueEx(key2, "ProductName") name = name[0] if name and name.startswith("Microsoft Windows SDK for Windows"): vals = QueryValueEx(key2, "InstallationFolder") val = vals[0] if val is not None: log("Found installation path at %s" % val) self.windbg_path = val break CloseKey(key2) CloseKey(key) except WindowsError: print "Cannot resolve Windows SDKs path:", sys.exc_info()[1] print "Did you install Windows SDKs for Windows?" except: print "Cannot resolve Windows SDKs path:", sys.exc_info()[1] def parse_registers(self, regs): ret = re.findall("([a-z]{1}[a-z0-9]{1,})=([a-f0-9]+)", regs) tmp_regs = [] for reg in ret: reg, val = reg self.regs[reg] = val tmp_regs.append([reg, val]) if "rip" in self.regs: self.pc = int(self.regs["rip"], 16) self.pc_register = "rip" elif "eip" in self.regs: self.pc = int(self.regs["eip"], 16) self.pc_register = "eip" elif "pc" in self.regs: self.pc = int(self.regs["pc"], 16) self.pc_register = "pc" else: raise Exception("Cannot find the program counter register!") # Create the CCrashData object and fill registers and disassembly # data self.crash_data = CCrashData(self.pc, self.signal) # Extract the disassembly line from the "r" command lines = regs.split("\n") last_line = lines[len(lines)-2] dis = re.findall("[a-z0-9]{1,} [a-z0-9]{2,} (.*)", last_line) self.disasm = dis[0].strip(" ") # ...and add it to the CCrashData object self.crash_data.disasm = [self.pc, self.disasm] # Add the registers in the order shown by WinDbg for reg in tmp_regs: reg, val = reg self.crash_data.add_data("registers", reg, int(val, 16)) def parse_stack(self, stack): lines = stack.split("\n") i = 0 for line in lines: fields = re.findall("([a-f0-9]+) ([a-f0-9]+) (.*)", line) if len(fields) > 0: addr, func = fields[0][1], fields[0][2] if addr.strip("-") == "": addr = "FFFFFFFF" if self.mode == 64: addr += addr addr = "0x%s" % addr self.stack.append([addr, func]) self.crash_data.add_data("stack trace", "%d" %i, [int(addr, 16), func]) i += 1 def disasm_around(self): lines = pykd.dbgCommand("u %s-c L12" % self.pc_register) for line in lines.split("\n"): tmp = re.findall("([a-f0-9]{1,}) ([a-f0-9]{2,}) (.*)", line) if len(tmp) > 0: line = tmp[0] addr = line[0] dis = line[2] self.crash_data.add_data("disassembly", int(addr, 16), dis) def create_crash_data(self, regs, stack, exploitable): regs = regs.replace("`", "") stack = stack.replace("`", "") self.signal = self.handler.exception_info[1] self.parse_registers(regs) self.parse_stack(stack) self.parse_exploitable(exploitable) self.disasm_around() def parse_exploitable(self, exploitable): self.exploitability = "Unknown" if exploitable is not None: s = "Exploitability Classification: " l = exploitable.split("\n") for line in l: pos = line.find(s) if pos > -1: self.exploitability = line[pos+len(s):] self.crash_data.exploitable = self.exploitability break last_line = l[len(l)-2] self.exploitability_reason = last_line self.crash_data.add_data("exploitability", "reason", \ self.exploitability_reason) def timeout_func(self): log("Timeout (%d seconds), killing the target..." % self.timeout) self.do_stop = True pykd.breakin() def run(self): if self.timeout != 0: self.timer = Timer(self.timeout, self.timeout_func) self.timer.start() self.do_stop = False self.id = pykd.startProcess(self.program, debugChildren=True) if self.handler is None: self.handler = ExceptionHandler() while not self.handler.exception_occurred and not self.do_stop: try: pykd.go() except: break if self.do_stop: try: pykd.dbgCommand(".kill") except: log("Exception killing target: %s" % str(sys.exc_info()[1])) return None if self.timer is not None: self.timer.cancel() ret = None if self.handler.exception_occurred: tmp = pykd.dbgCommand("k 1") if tmp.find("Wow64NotifyDebugger") > -1: pykd.dbgCommand(".effmach x86") stack_trace = pykd.dbgCommand("k") registers = pykd.dbgCommand("r") exploitable = None msec_path = None if self.exploitable_path is None: if self.mode == 32: msec_path = os.path.join(self.windbg_path, r"Debuggers\x86\winext") elif self.mode == 64: msec_path = os.path.join(self.windbg_path, r"Debuggers\x64\winext") elif self.mode == "arm": msec_path = os.path.join(self.windbg_path, r"Debuggers\arm\winext") else: raise Exception("Unknown mode %s, known ones are 32, 64 or 'arm'." % self.mode) else: msec_path = self.exploitable_path if msec_path is not None: full_msec_path = os.path.join(msec_path, r"msec.dll") if os.path.exists(full_msec_path): try: msec_handle = pykd.loadExt(full_msec_path) commandOutput = pykd.callExt(msec_handle, "exploitable", "") exploitable = commandOutput except: log("Error loading extension: " + str(sys.exc_info()[1])) try: if self.minidump_path is not None: pykd.dbgCommand(r".dump /m /u %s\\" % self.minidump_path) log("*** Minidump written at %s" % self.minidump_path) except: log("!!! Error saving minidump:" + str(sys.exc_info()[1])) ret = self.create_crash_data(registers, stack_trace, exploitable) print pykd.dbgCommand("k 10") print pykd.dbgCommand("r") print exploitable crash_data_buf = self.crash_data.dump_json() ret = self.crash_data.dump_dict() print print "Yep, we got a crash! \o/" print return ret
def main(args): crash_data_dict = None tr = vtrace.getTrace() global timeout if os.getenv("NIGHTMARE_TIMEOUT"): timeout = float(os.getenv("NIGHTMARE_TIMEOUT")) if args[0] in ["--attach", "-A"]: if len(args) == 1: usage() sys.exit(1) else: pid = int(args[1]) # Schedule a timer to detach from the process after some seconds timer = threading.Timer(timeout, kill_process, ( tr, False, )) timer.start() tr.attach(pid) else: # Schedule a timer to kill the process after 5 seconds timer = threading.Timer(timeout, kill_process, ( tr, True, )) timer.start() tr.execute(" ".join(args)) tr.run() signal = tr.getCurrentSignal() signal_name = signal_to_name(signal) ignore_list = [ "Unknown", "SIGUSR1", "SIGUSR2", "SIGTTIN", "SIGPIPE", "SIGINT" ] while signal is None or signal_name in ignore_list: signal = tr.getCurrentSignal() signal_name = signal_to_name(signal) try: tr.run() except: break timer.cancel() # Don't do anything else, the process is gone if os.name != "nt" and not tr.attached: return None if signal is not None: print tr, hex(signal) print " ".join(sys.argv) crash_name = os.getenv("NFP_INFO_CRASH") # Create the object to store all the crash data crash_data = CCrashData(tr.getProgramCounter(), sig2name(signal)) if crash_name is None: crash_name = "info.crash" #f = open(crash_name, "wb") exploitability_reason = None if os.name != "nt" and signal == 4: # Due to illegal instruction exploitable = EXPLOITABLE exploitability_reason = "Illegal instruction" elif os.name == "nt" and signal in [0xc0000096, 0xc000001d]: # Due to illegal or privileged instruction exploitable = EXPLOITABLE if signal == 0xc000001d: exploitability_reason = "Illegal instruction" else: exploitability_reason = "Privileged instruction" else: exploitable = NOT_EXPLOITABLE crash_data.add_data("process", "pid", tr.getPid()) if os.name == "nt": print "Process %d crashed with exception 0x%x (%s)" % ( tr.getPid(), signal, win32_exc_to_name(signal)) else: print "Process %d crashed with signal %d (%s)" % ( tr.getPid(), signal, signal_to_name(signal)) i = 0 for t in tr.getThreads(): i += 1 crash_data.add_data("threads", "%d" % i, t) stack_trace = tr.getStackTrace() total = len(stack_trace) i = 0 for x in stack_trace: i += 1 sym = tr.getSymByAddr(x[0], exact=False) if sym is None: sym = "" crash_data.add_data("stack trace", "%d" % i, [x[0], str(sym)]) total -= 1 regs = tr.getRegisterContext().getRegisters() for reg in regs: crash_data.add_data("registers", reg, regs[reg]) if reg.startswith("r"): line = reg.ljust(5) + "%016x" % regs[reg] try: mem = tr.readMemory(regs[reg], 32) mem = base64.b64encode(mem) crash_data.add_data("registers memory", reg, mem) line += "\t" + repr(mem) except: pass for reg in COMMON_REGS: if reg in regs: if reg in crash_data.data["registers memory"]: print reg, hex(regs[reg]), repr( base64.b64decode( crash_data.data["registers memory"][reg])) else: print reg, hex(regs[reg]) print total_around = 40 if 'rip' in regs: if len("%08x" % regs['rip']) > 8: decoder = Decode64Bits else: decoder = Decode32Bits else: decoder = Decode32Bits pc = tr.getProgramCounter() pc_line = None try: pc_mem = tr.readMemory(pc - total_around / 2, total_around) offset = regs["rip"] - total_around / 2 ret = [] found = False for x in Decode(0, pc_mem, decoder): line = "%016x %s" % ((offset + x.offset), str(x)) crash_data.add_data("disassembly", offset + x.offset, str(x)) if offset + x.offset == pc: crash_data.disasm = [x.offset + offset, str(x)] line += "\t\t<--------- CRASH" print line pc_line = x found = True ret.append(line) if found: #print "\n".join(ret) pass else: offset = pc = tr.getProgramCounter() pc_mem = tr.readMemory(pc, total_around) for x in Decode(0, pc_mem, decoder): line = "%016x %s" % ((offset + x.offset), str(x)) if offset + x.offset == pc: line += "\t\t<--------- CRASH" pc_line = x print line except: # Due to invalid memory at $PC if signal != 6: exploitable = True exploitability_reason = "Invalid memory at program counter" print "Exception:", sys.exc_info()[1] if pc_line: if str(pc_line.mnemonic) in ["CALL", "JMP"] or \ str(pc_line.mnemonic).startswith("JMP") or \ str(pc_line.mnemonic).startswith("CALL"): if str(pc_line.operands).find("[") > -1: # Due to jump/call with a register that maybe controllable exploitable = EXPLOITABLE exploitability_reason = "Jump or call with a probably controllable register" elif str(pc_line.mnemonic).startswith("DB "): # Due to illegal instruction exploitable = MAYBE_EXPLOITABLE exploitability_reason = "Illegal instruction" elif str(pc_line.mnemonic).startswith("IN") or \ str(pc_line.mnemonic).startswith("OUT") or \ str(pc_line.mnemonic) in ["HLT", "IRET", "CLTS", "LGDT", "LIDT", "LLDT", "LMSW", "LTR", "CLI", "STI"]: if str(pc_line.mnemonic) != "INT": # Due to privileged instruction (which makes no sense in user-land) exploitable = MAYBE_EXPLOITABLE exploitability_reason = "Privileged instruction" #print >>f #print >>f, "Maps:" i = 0 for m in tr.getMemoryMaps(): i += 1 line = "%016x %s %s %s" % (m[0], str( m[1]).rjust(8), get_permision_str(m[2]), m[3]) crash_data.add_data("memory maps", "%d" % i, m) #print >>f, line #print >>f if exploitable > 0: crash_data.exploitable = is_exploitable(exploitable) crash_data.add_data("exploitability", "reason", exploitability_reason) #print >>f, "Exploitable: %s. %s." % (is_exploitable(exploitable), exploitability_reason) else: #print >>f, "Exploitable: Unknown." pass crash_data_buf = crash_data.dump_json() crash_data_dict = crash_data.dump_dict() print "Yep, we got a crash! \o/" print #print "Dumping JSON...." #print crash_data_buf #print if tr.attached: try: tr.kill() except: pass try: tr.release() except: pass return crash_data_dict
class CWinDbgInterface(object): def __init__(self, program, mode=32, windbg_path=None, exploitable_path=None): global timeout self.id = None self.mode = mode self.program = program self.exploitable_path = None self.windbg_path = windbg_path self.exploitable_path = exploitable_path try: self.handler = ExceptionHandler() except: self.handler = None self.minidump_path = None #self.minidump_path = r"C:\minidumps\\" if windbg_path is None: self.resolve_windbg_path() self.regs = {} self.stack = [] self.pc_register = None self.disassembly = None self.exploitability = "Unknown" self.do_stop = False self.timer = None if os.getenv("NIGHTMARE_TIMEOUT"): timeout = float(os.getenv("NIGHTMARE_TIMEOUT")) self.timeout = timeout def resolve_windbg_path(self): try: reg = ConnectRegistry(None, HKEY_LOCAL_MACHINE) key = OpenKey(reg, r"SOFTWARE\Microsoft\Microsoft SDKs\Windows") if key: for i in range(QueryInfoKey(key)[0]): value = EnumKey(key, i) if value: full_key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\\" + value key2 = OpenKey(reg, full_key) if key2: name = QueryValueEx(key2, "ProductName") name = name[0] if name and name.startswith( "Microsoft Windows SDK for Windows"): vals = QueryValueEx(key2, "InstallationFolder") val = vals[0] if val is not None: log("Found installation path at %s" % val) self.windbg_path = val break CloseKey(key2) CloseKey(key) except WindowsError: print "Cannot resolve Windows SDKs path:", sys.exc_info()[1] print "Did you install Windows SDKs for Windows?" except: print "Cannot resolve Windows SDKs path:", sys.exc_info()[1] def parse_registers(self, regs): ret = re.findall("([a-z]{1}[a-z0-9]{1,})=([a-f0-9]+)", regs) tmp_regs = [] for reg in ret: reg, val = reg self.regs[reg] = val tmp_regs.append([reg, val]) if "rip" in self.regs: self.pc = int(self.regs["rip"], 16) self.pc_register = "rip" elif "eip" in self.regs: self.pc = int(self.regs["eip"], 16) self.pc_register = "eip" elif "pc" in self.regs: self.pc = int(self.regs["pc"], 16) self.pc_register = "pc" else: raise Exception("Cannot find the program counter register!") # Create the CCrashData object and fill registers and disassembly # data self.crash_data = CCrashData(self.pc, self.signal) # Extract the disassembly line from the "r" command lines = regs.split("\n") last_line = lines[len(lines) - 2] dis = re.findall("[a-z0-9]{1,} [a-z0-9]{2,} (.*)", last_line) self.disasm = dis[0].strip(" ") # ...and add it to the CCrashData object self.crash_data.disasm = [self.pc, self.disasm] # Add the registers in the order shown by WinDbg for reg in tmp_regs: reg, val = reg self.crash_data.add_data("registers", reg, int(val, 16)) def parse_stack(self, stack): lines = stack.split("\n") i = 0 for line in lines: fields = re.findall("([a-f0-9]+) ([a-f0-9]+) (.*)", line) if len(fields) > 0: addr, func = fields[0][1], fields[0][2] if addr.strip("-") == "": addr = "FFFFFFFF" if self.mode == 64: addr += addr addr = "0x%s" % addr self.stack.append([addr, func]) self.crash_data.add_data("stack trace", "%d" % i, [int(addr, 16), func]) i += 1 def disasm_around(self): lines = pykd.dbgCommand("u %s-c L12" % self.pc_register) for line in lines.split("\n"): tmp = re.findall("([a-f0-9]{1,}) ([a-f0-9]{2,}) (.*)", line) if len(tmp) > 0: line = tmp[0] addr = line[0] dis = line[2] self.crash_data.add_data("disassembly", int(addr, 16), dis) def create_crash_data(self, regs, stack, exploitable): regs = regs.replace("`", "") stack = stack.replace("`", "") self.signal = self.handler.exception_info[1] self.parse_registers(regs) self.parse_stack(stack) self.parse_exploitable(exploitable) self.disasm_around() def parse_exploitable(self, exploitable): self.exploitability = "Unknown" if exploitable is not None: s = "Exploitability Classification: " l = exploitable.split("\n") for line in l: pos = line.find(s) if pos > -1: self.exploitability = line[pos + len(s):] self.crash_data.exploitable = self.exploitability break last_line = l[len(l) - 2] self.exploitability_reason = last_line self.crash_data.add_data("exploitability", "reason", \ self.exploitability_reason) def timeout_func(self): log("Timeout (%d seconds), killing the target..." % self.timeout) self.do_stop = True pykd.breakin() def run(self): if self.timeout != 0: self.timer = Timer(self.timeout, self.timeout_func) self.timer.start() self.do_stop = False self.id = pykd.startProcess(self.program, debugChildren=True) if self.handler is None: self.handler = ExceptionHandler() while not self.handler.exception_occurred and not self.do_stop: try: pykd.go() except: break if self.do_stop: try: pykd.dbgCommand(".kill") except: log("Exception killing target: %s" % str(sys.exc_info()[1])) return None if self.timer is not None: self.timer.cancel() ret = None if self.handler.exception_occurred: tmp = pykd.dbgCommand("k 1") if tmp.find("Wow64NotifyDebugger") > -1: pykd.dbgCommand(".effmach x86") stack_trace = pykd.dbgCommand("k") registers = pykd.dbgCommand("r") exploitable = None msec_path = None if self.exploitable_path is None: if self.mode == 32: msec_path = os.path.join(self.windbg_path, r"Debuggers\x86\winext") elif self.mode == 64: msec_path = os.path.join(self.windbg_path, r"Debuggers\x64\winext") elif self.mode == "arm": msec_path = os.path.join(self.windbg_path, r"Debuggers\arm\winext") else: raise Exception( "Unknown mode %s, known ones are 32, 64 or 'arm'." % self.mode) else: msec_path = self.exploitable_path if msec_path is not None: full_msec_path = os.path.join(msec_path, r"msec.dll") if os.path.exists(full_msec_path): try: msec_handle = pykd.loadExt(full_msec_path) commandOutput = pykd.callExt(msec_handle, "exploitable", "") exploitable = commandOutput except: log("Error loading extension: " + str(sys.exc_info()[1])) try: if self.minidump_path is not None: pykd.dbgCommand(r".dump /m /u %s\\" % self.minidump_path) log("*** Minidump written at %s" % self.minidump_path) except: log("!!! Error saving minidump:" + str(sys.exc_info()[1])) ret = self.create_crash_data(registers, stack_trace, exploitable) print pykd.dbgCommand("k 10") print pykd.dbgCommand("r") print exploitable crash_data_buf = self.crash_data.dump_json() ret = self.crash_data.dump_dict() print print "Yep, we got a crash! \o/" print return ret
class CWinDbgInterface(object): def __init__(self, program, timeout, mode=32, windbg_path=None, exploitable_path=None): reload(pykd) self.id = None self.mode = mode self.program = program self.exploitable_path = None self.windbg_path = windbg_path self.exploitable_path = exploitable_path try: self.handler = ExceptionHandler() except: self.handler = None self.minidump_path = None #self.minidump_path = r"C:\minidumps\\" if windbg_path is None: self.resolve_windbg_path() self.regs = {} self.stack = [] self.pc_register = None self.disassembly = None self.exploitability = "Unknown" self.do_stop = False self.timer = None if str(timeout).lower() == "auto": self.timeout = timeout else: self.timeout = int(timeout) self.pykd_version = self.get_pykd_version() def resolve_windbg_path(self): try: reg = ConnectRegistry(None,HKEY_LOCAL_MACHINE) key = OpenKey(reg, r"SOFTWARE\Microsoft\Microsoft SDKs\Windows") if key: for i in range(QueryInfoKey(key)[0]): value = EnumKey(key, i) if value: full_key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\\" + value key2 = OpenKey(reg, full_key) if key2: name = QueryValueEx(key2, "ProductName") name = name[0] if name and name.startswith("Microsoft Windows SDK for Windows"): vals = QueryValueEx(key2, "InstallationFolder") val = vals[0] if val is not None: log("Found installation path at %s" % val) self.windbg_path = val break CloseKey(key2) CloseKey(key) except WindowsError: print "Cannot resolve Windows SDKs path:", sys.exc_info()[1] print "Did you install Windows SDKs for Windows?" except: print "Cannot resolve Windows SDKs path:", sys.exc_info()[1] def parse_registers(self, regs): ret = re.findall("([a-z]{1}[a-z0-9]{1,})=([a-f0-9]+)", regs) tmp_regs = [] for reg in ret: reg, val = reg self.regs[reg] = val tmp_regs.append([reg, val]) if "rip" in self.regs: self.pc = int(self.regs["rip"], 16) self.pc_register = "rip" elif "eip" in self.regs: self.pc = int(self.regs["eip"], 16) self.pc_register = "eip" elif "pc" in self.regs: self.pc = int(self.regs["pc"], 16) self.pc_register = "pc" else: raise Exception("Cannot find the program counter register!") # Create the CCrashData object and fill registers and disassembly # data self.crash_data = CCrashData(self.pc, self.signal) # Extract the disassembly line from the "r" command lines = regs.split("\n") last_line = lines[len(lines)-2] dis = re.findall("[a-z0-9]{1,} [a-z0-9]{2,} (.*)", last_line) self.disasm = None if dis is not None and len(dis) > 0: self.disasm = dis[0].strip(" ") # ...and add it to the CCrashData object self.crash_data.disasm = [self.pc, self.disasm] # Add the registers in the order shown by WinDbg for reg in tmp_regs: reg, val = reg self.crash_data.add_data("registers", reg, int(val, 16)) def parse_stack(self, stack): lines = stack.split("\n") i = 0 for line in lines: fields = re.findall("([a-f0-9]+) ([a-f0-9]+) (.*)", line) if len(fields) > 0: addr, func = fields[0][1], fields[0][2] if addr.strip("-") == "": addr = "FFFFFFFF" if self.mode == 64: addr += addr addr = "0x%s" % addr self.stack.append([addr, func]) self.crash_data.add_data("stack trace", "%d" %i, [int(addr, 16), func]) i += 1 def disasm_around(self): try: lines = pykd.dbgCommand("u %s-c L12" % self.pc_register) for line in lines.split("\n"): tmp = re.findall("([a-f0-9]{1,}) ([a-f0-9]{2,}) (.*)", line) if len(tmp) > 0: line = tmp[0] addr = line[0] dis = line[2] self.crash_data.add_data("disassembly", int(addr, 16), dis) except: log("Error in disasm_around: %s" % str(sys.exc_info()[1])) def create_crash_data(self, regs, stack, exploitable): regs = regs.replace("`", "") stack = stack.replace("`", "") self.signal = self.handler.exception_info[1] self.parse_registers(regs) self.parse_stack(stack) self.parse_exploitable(exploitable) self.disasm_around() def parse_exploitable(self, exploitable): self.exploitability = "Unknown" if exploitable is not None: s = "Exploitability Classification: " l = exploitable.split("\n") for line in l: pos = line.find(s) if pos > -1: self.exploitability = line[pos+len(s):] self.crash_data.exploitable = self.exploitability break last_line = l[len(l)-2] self.exploitability_reason = last_line self.crash_data.add_data("exploitability", "reason", \ self.exploitability_reason) def timeout_func(self): log("Timeout (%d seconds), killing the target..." % self.timeout) self.do_stop = True try: pykd.breakin() except: # A race condition might happen in the timeout function and in # such cases we must ignore the error. pass def check_cpu(self): while True: try: if self.pid is None: time.sleep(0.2) continue proc = psutil.Process(self.pid) if proc is None: break cpu = 0 l = [] for x in xrange(20): tmp = int(proc.cpu_percent(interval=0.1)) cpu += tmp l.append(tmp) if cpu is not None and (cpu <= 100 or l.count(0) > 10): log("CPU at 0%, killing") self.do_stop = True pykd.breakin() break else: time.sleep(0.5) except psutil.NoSuchProcess: self.do_stop = True break def get_pykd_version(self): """ Gets the pykd version number 2 or 3. Returns: pykd version number """ version = pykd.version version_number = int(version.replace(',', '.').replace(' ', '').split('.')[1]) if version_number == 3: return PYKD3 elif version_number == 2: return PYKD2 return None def get_pid(self): if self.pykd_version == PYKD3: return pykd.getProcessSystemID() return pykd.getCurrentProcessId() def start_process(self): if not "ProcessDebugOptions" in dir(pykd): self.id = pykd.startProcess(self.program, debugChildren=True) else: self.id = pykd.startProcess(self.program, pykd.ProcessDebugOptions.DebugChildren) return self.id def run(self): self.do_stop = False try: self.id = self.start_process() self.pid = self.get_pid() except: log("Error launching process! %s" % str(sys.exc_info()[1])) return None if self.handler is None: self.handler = ExceptionHandler() if self.timeout is not None: if str(self.timeout).lower() == "auto": self.thread = Thread(target=self.check_cpu) self.thread.start() else: self.timer = Timer(self.timeout, self.timeout_func) self.timer.start() while not self.handler.exception_occurred and not self.do_stop: try: pykd.go() except: break if self.do_stop: try: pykd.dbgCommand(".kill") except: log("Exception killing target: %s" % str(sys.exc_info()[1])) return None if self.timer is not None: self.timer.cancel() ret = None if self.handler.exception_occurred: try: pykd.breakin() pykd.breakin() except: pass tmp = pykd.dbgCommand("k 1") if tmp.find("Wow64NotifyDebugger") > -1: pykd.dbgCommand(".effmach x86") registers = pykd.dbgCommand("r") stack_trace = pykd.dbgCommand("k") exploitable = None msec_path = None if self.exploitable_path is None: if self.mode == 32: msec_path = os.path.join(self.windbg_path, r"Debuggers\x86\winext") elif self.mode == 64: msec_path = os.path.join(self.windbg_path, r"Debuggers\x64\winext") elif self.mode == "arm": msec_path = os.path.join(self.windbg_path, r"Debuggers\arm\winext") else: raise Exception("Unknown mode %s, known ones are 32, 64 or 'arm'." % self.mode) else: msec_path = self.exploitable_path if msec_path is not None: full_msec_path = os.path.join(msec_path, r"msec.dll") if os.path.exists(full_msec_path): try: msec_handle = pykd.loadExt(full_msec_path) commandOutput = pykd.callExt(msec_handle, "exploitable", "") exploitable = commandOutput except: log("Error loading extension: " + str(sys.exc_info()[1])) try: if self.minidump_path is not None: pykd.dbgCommand(r".dump /m /u %s\\" % self.minidump_path) log("*** Minidump written at %s" % self.minidump_path) except: log("!!! Error saving minidump:" + str(sys.exc_info()[1])) ret = self.create_crash_data(registers, stack_trace, exploitable) print pykd.dbgCommand("k 10") print pykd.dbgCommand("r") print exploitable try: pykd.killAllProcesses() except: log("Error killing processes: " + str(sys.exc_info()[1])) crash_data_buf = self.crash_data.dump_json() ret = self.crash_data.dump_dict() print print "Yep, we got a crash! \o/" print return ret