def collare_export(self): project_path = cutter.cmd("e prj.file") if ".collare_projects" in project_path: base_addr = int(json.loads(cutter.cmd("iSj"))[0]["vaddr"]) changes = {"function_names": {}, "comments": {}, "base": base_addr} functions = cutter.cmd("afij @@F") functions = functions.replace("]\n[", ",") functions = json.loads(functions) for function in functions: if hex(function["offset"])[2:] not in function["name"]: # Non-default function names changes["function_names"][function["offset"]] = { "name": function["name"], "end": function["offset"] + function["size"] } comments = cutter.cmd("CCfj @@F") comments = comments.replace("[]\n", "").replace("]\n[", ",") comments = json.loads(comments) for comment in comments: changes["comments"][comment["offset"]] = comment["name"] with open( os.path.join(os.path.dirname(project_path), "changes.json"), "w") as changes_file: json.dump(changes, changes_file) QMessageBox.information(self.main, "CollaRE", "Export Done!", QMessageBox.Ok) else: QMessageBox.warning(self.main, "CollaRE", "Not a CollaRE project!", QMessageBox.Ok)
def generate_local_data_references(self): # aga function_name = self.get_output_name("data_references", function=True) command = "aga" + self.get_output_format_cmd(self.combo.currentIndex()) command += " > " command += function_name cutter.cmd(command)
def generate_xrefs(self): # agx function_name = self.get_output_name("xrefs", function=True) command = "agx" + self.get_output_format_cmd(self.combo.currentIndex()) command += " > " command += function_name cutter.cmd(command)
def generate_local_callgraph(self): # agc function_name = self.get_output_name("callgraph", function=True) command = "agc" + self.get_output_format_cmd(self.combo.currentIndex()) command += " > " command += function_name cutter.cmd(command)
def print_data(data, offset): cleaned = "" for c in data: if (ord(c) <= 0x7f and ord(c) >= 0x20): cleaned += c if len(cleaned) >= 3: print('%d - Hidden String: "%s"' % (offset, cleaned)) cutter.cmd('CC Hidden String: \"%s\" @ %d' % (cleaned, offset))
def setAvoidAddr(self): offset = int(self.avoidAddrAction.data()) if offset in self.avoidAddrs or offset in self.findAddrs or offset in self.symAddrs: print("[angr-cutter] Address %s was already set" % hex(offset)) return self.avoidAddrs.append(offset) self.updateAvoidAddrLine() cutter.cmd("ecHi yellow @ %d" % offset)
def radare_crawl(): funcs = json.loads(cutter.cmd('aflj')) for func in funcs: try: instructions = json.loads( cutter.cmd('pdfj %s @ %s' % (func['size'], func['offset']))) except ValueError as e: continue mov_hunt(instructions)
def setAvoidAddr(self): offset = int(self.avoidAddrAction.data()) if offset in self.avoidAddrs or offset in self.findAddrs or offset in self.symAddrs: printMessage("Address %s was already set" % hex(offset), LogLevel.WARNING) return self.avoidAddrs.append(offset) self.updateAvoidAddrLine() cutter.cmd("ecHi yellow @ %d" % offset)
def runSomethingInThread(self): ## you see print only on console if you start cutter from console print('send from threadd') ### next line freeze cutter, not everytime for the first time, ### sometimes it runs 2,3 times ,then freezes curr_pos = cutter.cmd('s') time.sleep(5) curr_pos = cutter.cmd('s') print('send from thread after time') self.resultReady.emit()
def unsetAddr(self): offset = self.unsetAddrAction.data() if offset in self.findAddrs: self.findAddrs.remove(offset) self.updateFindAddrLine() if offset in self.avoidAddrs: self.avoidAddrs.remove(offset) self.updateAvoidAddrLine() if offset in self.symAddrs: del self.symAddrs[offset] self.updateSymAddrLine() cutter.cmd("ecH- @ %d" % offset)
def setSymAddr(self): offset = int(self.symAddrAction.data()) if offset in self.avoidAddrs or offset in self.findAddrs or offset in self.symAddrs: printMessage("Address %s was already set" % hex(offset), LogLevel.WARNING) return dialog = SymAddrDialog() dialog.exec_() size = dialog.getSize() self.symAddrs[offset] = size self.updateSymAddrLine() cutter.cmd("ecHi black @ %d" % offset)
def update_content(self): current_line = cutter.cmdj("pdj 1")[0] # Running "ij" during DockWidget init causes a crash if not self.cutterref: info = cutter.cmdj("ij")["bin"] arch = info["arch"] self.cutterref = CutterRef(info["arch"] + "-" + str(info["bits"])) try: inst = current_line["disasm"].split()[0] except: return # Don't update the text box for the same instruction if inst != self.previous_inst: doc = self.cutterref.get_instruction_doc(inst) # Use r2's documentation if the instruction wasn't found in the loaded manual if not doc: doc = cutter.cmd("aod @ " + str(current_line["offset"])) self.view.setHtml(doc) self.previous_inst = inst return
def setSymAddr(self): offset = int(self.symAddrAction.data()) if offset in self.avoidAddrs or offset in self.findAddrs or offset in self.symAddrs: print("[angr-cutter] Address %s was already set" % hex(offset)) return text, ok = QInputDialog.getText(self, "Symbolize address", "Size(bytes):") if ok: size = int(text) else: size = 8 self.symAddrs[offset] = size self.updateSymAddrLine() cutter.cmd("ecHi black @ %d" % offset)
def output_callgraph(self): output = 'deep_callgraph_' + cutter.cmd( 'afi.') + self.get_output_format_extension( self.combo.currentIndex()) file = open(output, "w") file.write(self.graph_dot) file.close()
def send_to_mixto(self): if self.mixto_entry_id is not None: arg = self.command[0:70] out = cutter.cmd(self.command).strip() url = urljoin( self.mixto_host, "/api/entry/{}/{}/commit".format(self.workspace, self.mixto_entry_id), ) req = Request( method="POST", url=url, data=dumps({ "type": "tool", "title": "(Cutter) - " + arg, "data": out, "meta": {}, }).encode(), headers={ "x-api-key": self.mixto_api, "Content-Type": "application/json", }, ) try: res = urlopen(req) status = res.getcode() if status > 300: self.message.setText("{} error".format(status)) else: self.message.setText("OK!") except HTTPError as e: self.message.setText(getattr(e, "message", repr(e))) else: self.message.setText("Entry ID not provided")
def export_db(self): filename = self.file_dialog("Open new file for export", True) if not filename: return cutter.message("[x64dbg-cutter]: Exporting database to %s" % filename) db = {} base_addr = cutter.cmdj("elJ bin.baddr")[0]["value"] cutter.message("[x64dbg-cutter]: baddr is %d" % base_addr) # We can only export items from the main binary currently module = os.path.basename(cutter.cmdj("ij")["core"]["file"]).lower() # ref: {"addr":5368778842,"size":1,"prot":"--x","hw":false,"trace":false,"enabled":true,"data":"","cond":""} db["breakpoints"] = [{ "address": hex(bp["addr"] - base_addr), # Comment address relative to the base of the module "enabled": bp["enabled"], # Whether the breakpoint is enabled "type": BPHARDWARE if bp["hw"] else BPNORMAL, # see https://github.com/x64dbg/x64dbg/blob/development/src/dbg/breakpoint.h#L13 "module": module, # The module that the comment is in } for bp in cutter.cmdj("dbj")] cutter.message("[x64dbg-cutter]: %d breakpoint(s) exported" % len(db["breakpoints"])) # ref: {"offset":5368713216,"type":"CCu","name":"[00] -rwx section size 65536 named .textbss"} db["comments"] = [{ "module": module, # The module that the comment is in "address": hex(c["offset"] - base_addr), # Comment address relative to the base of the module "manual": True, # Whether the comment was created by the user - set to True to show it in the comments window "text": c["name"], # Comment text } for c in cutter.cmdj("CCj")] cutter.message("[x64dbg-cutter]: %d comment(s) exported" % len(db["comments"])) # Set flagspace to all before iterating over fj to show all of the flags cutter.cmd("fs *") # ref: {"name":"fcn.1400113de","size":5,"offset":5368779742} db["labels"] = [{ "module": module, # The module that the label is in "address": hex(label["offset"] - base_addr), # Label address relative to the base of the module "manual": False, # Whether the label was created by the user "text": label["name"], # Label text } for label in cutter.cmdj("fj") if (label["offset"] - base_addr) >= 0] cutter.message("[x64dbg-cutter]: %d labels(s) exported" % len(db["labels"])) with open(filename, "w") as outfile: json.dump(db, outfile, indent=1)
def get_reg(self, name): if name == "efl": name = "eflags" reg = cutter.cmd("dr " + name) # Large regs(over 80bits) are returned as hex if "0x" in reg: val = int(reg, 16) else: val = int(reg) return val
def startExplore(self): if len(self.findAddrs) == 0: printMessage( "You have to set a find address to explore to", LogLevel.WARNING) return self.stateMgr = StateManager() self.simMgr = self.stateMgr.simulation_manager() # Configure symbolic memory addresses and registers for addr in self.symAddrs: self.stateMgr.sim(addr, self.symAddrs[addr]) for reg in self.viewRegisters.symRegs: self.stateMgr.sim( self.stateMgr[reg], self.viewRegisters.symRegs[reg]) # Start exploration printMessage("Starting exploration with find (%s) and avoid (%s)" % (self.findAddrs, self.avoidAddrs,), LogLevel.INFO) printMessage("Symbolics are: " + str(self.stateMgr.symbolics), LogLevel.INFO) self.simMgr.explore(find=self.findAddrs, avoid=self.avoidAddrs) # Attempt to print the results if len(self.simMgr.found): printMessage("Found: " + str(self.simMgr.found[0]), LogLevel.INFO) conc = self.stateMgr.concretize(self.simMgr.found[0]) for addr in conc: printMessage("0x%x ==> %s" % (addr, repr(conc[addr])), LogLevel.INFO) self.applySimButton.setDisabled(False) else: printMessage("Failed to find a state", LogLevel.ERROR) self.applySimButton.setDisabled(True) # Synchronize displays cutter.core().refreshAll.emit() # Return to the previous seek cutter.cmd("s %d" % cutter.core().getProgramCounterValue())
def smart_rename_function(location, name): # Get function info function_info = cutter.cmdj('afij @%s' % location) if not len(function_info): # no function at location, trigger analysis # and refetch function info cutter.cmd('af @ %s' % location) function_info = cutter.cmdj('afij @%s' % location) old_fn_name = function_info[0]['name'] # Sometimes the vivisect feature extractor identifies # adresses in the middle of functions as function starts # for some reason. We get the actual addr with r2. actual_offset = function_info[0]['offset'] if old_fn_name.startswith('fcn.'): # Function has default name, replace all of it. cutter.cmd('afn {} @ {}'.format(name, actual_offset)) else: # Function does not have generic name keep old name as prefix name = f'{old_fn_name}__{name}' cutter.cmd('afn {} @ {}'.format(name, actual_offset))
def collare_import(self): project_path = cutter.cmd("e prj.file") if ".collare_projects" in project_path: with open( os.path.join(os.path.dirname(project_path), "changes.json"), "r") as changes_file: changes = json.load(changes_file) base = changes["base"] if base != int(json.loads(cutter.cmd("iSj"))[0]["vaddr"]): base = int(json.loads( cutter.cmd("iSj"))[0]["vaddr"]) - base else: base = 0 for comment in changes["comments"]: comment_address = int(comment, 10) + base current_comment = self.get_comment_at(comment_address) if current_comment: if self.get_comment_at(comment_address) in changes[ "comments"][comment]: self.set_comment_at(comment_address, changes["comments"][comment]) elif changes["comments"][comment] in current_comment: pass else: self.set_comment_at( comment_address, current_comment + "; " + changes["comments"][comment]) else: self.set_comment_at(comment_address, changes["comments"][comment]) for function in changes["function_names"]: self.rename_function( int(function, 10) + base, changes["function_names"][function]["name"]) QMessageBox.information(self.main, "CollaRE", "Import Done!", QMessageBox.Ok) else: QMessageBox.warning(self.main, "CollaRE", "Not a CollaRE project!", QMessageBox.Ok)
def get_output_name(self, graph_name, function=False): if not function: name = graph_name + "." + self.get_output_format_extension( self.combo.currentIndex()) return self.append_to_path(name) else: function_name = cutter.cmd('afi.') function_name = function_name.replace("\n", "") function_name += "_" function_name += graph_name function_name += "." function_name += self.get_output_format_extension( self.combo.currentIndex()) return self.append_to_path(function_name)
def generate_callgraph(self): # TODO: get user settings for the .dot graph instead of using this hardcoded settings self.graph_dot = """digraph code { rankdir=LR; outputorder=edgesfirst; graph [bgcolor=azure fontname="Courier" splines="curved"]; node [fillcolor=white style=filled fontname="Courier New Bold" fontsize=14 shape=box]; edge [arrowhead="normal" style=bold weight=2];""" self.used_nodes = [] function_name = cutter.cmd('afi.') function_name = function_name.replace("\n", "") self.functions_list = cutter.cmdj('aflmj') self.get_calls(function_name) self.graph_dot += '\n}'
def decodeAll(self): # Start with analysis cutter.cmd('aa') # Build the decoder table self.buildDecoderTable() # Dump all the strings passed to decoder function for xref in cutter.cmdj("axtj %d" % self.decoder['fcn']): xref_addr = xref['from'] arg_len, arg_offsets = cutter.cmdj("pdj -2 @ %d" % xref_addr) if not 'val' in arg_len: continue indexes = cutter.cmdj("pxj %d @ %d" % (arg_len['val'] * 2, arg_offsets['val'])) decoded_str = self.decode(indexes) #print("%s @ %s" % (decoded_str, hex(xref_addr))) cutter.cmd("CC Decoded: %s @ %d" % (decoded_str, xref_addr)) # Refresh interface cutter.refresh()
def resolve_name(self, name): try: modules = cutter.cmdj("dmmj") for m in modules[1:]: addr = m["address"] lib = os.path.basename(m["name"]).split(".")[0].split("-")[0] o = cutter.cmd("dmi* %s %s" % (lib, name)) for line in o.split("\n"): line = line.split() if len(line) < 4: continue if line[1] == name or line[3] == "sym." + name: return int(line[3], 16) except: pass return None
def decode_strings(verbose=True): if verbose: print("\n%s\n\tStarting the decode of the encrypted strings\n%s\n\n" % ('~'*60, '~'*60)) # Declaration of decryption-table related variables decryption_table = 0x41BA3C decryption_table_end = 0x41BA77 decryption_table_len = decryption_table_end - decryption_table decryption_function = 0x4012A0 # Analyze the binary to better detect functions and x-refs cutter.cmd('aa') # Rename the decryption function cutter.cmd('afn decryption_function %d' % decryption_function) # Dump the decryption table to a variable decryption_table_content = cutter.cmdj( "pxj %d @ %d" % (decryption_table_len, decryption_table)) # Iterate x-refs to the decryption function for xref in cutter.cmdj('axtj %d' % decryption_function): # Get the arguments passed to the decryption function: length and encrypted string length_arg, offsets_arg = cutter.cmdj('pdj -2 @ %d' % (xref['from'])) # String variable to store the decrypted string decrypted_string = "" # Guard rail to avoid exception if (not 'val' in length_arg): continue # Manually decrypt the encrypted string for i in range(0, length_arg['val']): decrypted_string += chr(decryption_table_content[cutter.cmdj( 'pxj 1 @ %d' % (offsets_arg['val'] + (i*2)))[0]]) # Print the decrypted and the address it was referenced to the console if verbose: print(decrypted_string + " @ " + hex(xref['from'])) # Add comments to each call of the decryption function cutter.cmd('CC Decrypted: %s @ %d' % (decrypted_string, xref['from']))
def debugStateChanged(self): # Calculate the diff based on the previous baddr baddr = int(cutter.cmd("e bin.baddr").strip('\n'), 16) diff = baddr - self.baddr self.baddr = baddr if cutter.core().currentlyDebugging: disableUi = False else: del self.stateMgr self.stateMgr = None disableUi = True # applySim can be enabled only after startExplore self.applySimButton.setDisabled(True) # Enable exploration action when in debug mode self.startButton.setDisabled(disableUi) self.stopButton.setDisabled(disableUi) # Rebase addresses tmp = [] for addr in self.findAddrs: tmp.append(addr + diff) self.findAddrs = tmp tmp = [] for addr in self.avoidAddrs: tmp.append(addr + diff) self.avoidAddrs = tmp tmp = {} for addr in self.symAddrs: tmp[addr + diff] = self.symAddrs[addr] self.symAddrs = tmp self.update()
def generate_imports_graph(self): # agi name = self.get_output_name("imports_refs") command = "agi" + self.get_output_format_cmd(self.combo.currentIndex()) cutter.cmd(command + " > " + name)
def generate_global_refs(self): # agR name = self.get_output_name("global_references") command = "agR" + self.get_output_format_cmd(self.combo.currentIndex()) cutter.cmd(command + " > " + name)
def generate_global_callgraph(self): # agC name = self.get_output_name("global_callgraph") command = "agC" + self.get_output_format_cmd(self.combo.currentIndex()) cutter.cmd(command + " > " + name)
def generate_fortune(self): fortune = cutter.cmd("fo").replace("\n", "") res = cutter.core().cmdRaw(f"?E {fortune}") self.text.setText(res)