def load_internal_db(self, force=False): """ Load the rop gadgets list from the internal db """ if self.blob_manager == None: return internal_repr = self.blob_manager["db"].split(";") if internal_repr == None: return for item in internal_repr: offset, ret_offset = item.split(':') # Reconstruct linear address based on binary base address and offset address = int(offset, 16) + idaapi.get_imagebase() ret_address = int(ret_offset, 16) + idaapi.get_imagebase() gadget = Gadget(address=address, ret_address=ret_address, instructions=list(), opcodes="", size=0) self.engine.rop.gadgets.append(gadget) if idaapi.wasBreak(): print("[IdaRopLoad] Load csv file interrupted.") break
def save_internal_db(self): """ store the found rop gadget in the default internal db """ if len(self.engine.rop.gadgets) == 0 or self.blob_manager == None: return cancel_flag = False internal_repr = list() for item in self.engine.rop.gadgets: address, ret_addres = item.address, item.ret_address offset = "0x%x" % (address - idaapi.get_imagebase()) ret_offset = "0x%x" % (ret_addres - idaapi.get_imagebase()) internal_repr.append((offset, ret_offset)) if idaapi.wasBreak(): cancel_flag = True print("[IdaRop] save internal db interrupted.") break # save only on success if not cancel_flag: txt_repr = ";".join("%s:%s" % (g[0], g[1]) for g in internal_repr) self.blob_manager["db"] = txt_repr
def ghidraaas_checkout(ghidra_server_url): """ That's all. Remove .bytes file from Ghidraaas server. """ if not GLOBAL_CHECKIN: return idaapi.show_wait_box( "Connecting to Ghidraaas. Removing temporary files...") try: md5_hash = idautils.GetInputFileMD5() aargs = (md5_hash, ghidra_server_url) t1 = threading.Thread(target=ghidraaas_checkout_thread, args=aargs) t1.start() counter = 0 stop = False while not stop: # print("waiting check-out 1 zzz") # idaapi.request_refresh(idaapi.IWID_DISASMS) time.sleep(0.1) if idaapi.wasBreak(): print("GhIDA:: [!] Check-out interrupted.") stop = True continue if counter > COUNTER_MAX * 10: print("GhIDA:: [!] Timeout reached.") stop = True continue if not t1.isAlive(): stop = True print("GhIDA:: [DEBUG] Thread terminated.") continue print("GhIDA:: [DEBUG] Joining check-out thread.") t1.join(0) print("GhIDA:: [DEBUG] Thread joined") idaapi.hide_wait_box() return except Exception: idaapi.hide_wait_box() print("GhIDA:: [!] Check-out error") idaapi.warning("GhIDA check-out error") return
def ghidraaas_checkin(bin_file_path, filename, ghidra_server_url): """ Upload the .bytes files in ghidraaas. One time only (until IDA is restarted...) """ idaapi.show_wait_box("Connecting to Ghidraaas. Sending bytes file...") try: md5_hash = idautils.GetInputFileMD5() queue = Queue.Queue() my_args = (bin_file_path, filename, ghidra_server_url, md5_hash, queue) t1 = threading.Thread(target=ghidraaas_checkin_thread, args=my_args) t1.start() counter = 0 stop = False while not stop: time.sleep(0.1) # User terminated action if idaapi.wasBreak(): stop = True print("GhIDA:: [!] Check-in interrupted.") continue # Reached TIIMEOUT if counter > COUNTER_MAX * 10: stop = True print("GhIDA:: [!] Timeout reached.") continue # Thread terminated if not t1.isAlive(): stop = True print("GhIDA:: [DEBUG] Thread terminated.") continue print("GhIDA:: [DEBUG] Joining check-in thread.") t1.join(0) q_result = queue.get_nowait() print("GhIDA:: [DEBUG] Thread joined. Got queue result.") idaapi.hide_wait_box() return q_result except Exception: idaapi.hide_wait_box() print("GhIDA:: [!] Check-in error.") idaapi.warning("GhIDA check-in error") return False
def ghidraaas_decompile(address, xml_file_path, bin_file_path, ghidra_server_url): """ Send the xml file to ghidraaas and ask to decompile a function """ global GLOBAL_CHECKIN # Filename without the .xml extension filename = GLOBAL_FILENAME if not GLOBAL_CHECKIN: if ghidraaas_checkin(bin_file_path, filename, ghidra_server_url): GLOBAL_CHECKIN = True else: raise Exception("[!] Ghidraaas Check-in error") idaapi.show_wait_box("Connecting to Ghidraaas. Decompiling function %s" % address) try: md5_hash = idautils.GetInputFileMD5() queue = Queue.Queue() aargs = (address, xml_file_path, bin_file_path, ghidra_server_url, filename, md5_hash, queue) t1 = threading.Thread(target=ghidraaas_decompile_thread, args=aargs) t1.start() counter = 0 stop = False while not stop: # idaapi.request_refresh(idaapi.IWID_DISASMS) # print("waiting decompile 1 zzz") time.sleep(0.1) if idaapi.wasBreak(): print("GhIDA:: [!] decompilation interrupted.") stop = True continue if counter > COUNTER_MAX * 10: print("GhIDA:: [!] Timeout reached.") stop = True continue if not t1.isAlive(): stop = True print("GhIDA:: [DEBUG] Thread terminated.") continue print("GhIDA:: [DEBUG] Joining decompilation thread.") t1.join(0) q_result = queue.get_nowait() print("GhIDA:: [DEBUG] Thread joined. Got queue result.") idaapi.hide_wait_box() return q_result except Exception: idaapi.hide_wait_box() print("GhIDA:: [!] Unexpected decompilation error") idaapi.warning("GhIDA decompilation error") return None
def ghidra_headless(address, xml_file_path, bin_file_path, ghidra_headless_path, ghidra_plugins_path): """ Call Ghidra in headless mode and run the plugin FunctionDecompile.py to decompile the code of the function. """ try: if not os.path.isfile(ghidra_headless_path): print("GhIDA:: [!] ghidra analyzeHeadless not found.") raise Exception("analyzeHeadless not found") decompiled_code = None idaapi.show_wait_box("Ghida decompilation started") prefix = "%s_" % address output_temp = tempfile.NamedTemporaryFile(prefix=prefix, delete=False) output_path = output_temp.name # print("GhIDA:: [DEBUG] output_path: %s" % output_path) output_temp.close() cmd = [ ghidra_headless_path, ".", "Temp", "-import", xml_file_path, '-scriptPath', ghidra_plugins_path, '-postScript', 'FunctionDecompile.py', address, output_path, "-noanalysis", "-deleteProject" ] # Options to 'safely' terminate the process if os.name == 'posix': kwargs = {'preexec_fn': os.setsid} else: kwargs = { 'creationflags': subprocess.CREATE_NEW_PROCESS_GROUP, 'shell': True } p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) stop = False counter = 0 print("GhIDA:: [INFO] Ghidra headless (timeout: %ds)" % TIMEOUT) print("GhIDA:: [INFO] Waiting Ghidra headless analysis to finish...") while not stop: time.sleep(0.1) counter += 1 subprocess.Popen.poll(p) # Process terminated if p.returncode is not None: stop = True print("GhIDA:: [INFO] Ghidra analysis completed!") continue # User terminated action if idaapi.wasBreak(): # Termiante the process! if os.name == 'posix': os.killpg(os.getpgid(p.pid), signal.SIGTERM) else: os.kill(p.pid, -9) stop = True print("GhIDA:: [!] Ghidra analysis interrupted.") continue # Process timeout if counter > COUNTER_MAX * 10: os.killpg(os.getpgid(p.pid), signal.SIGTERM) stop = True print("GhIDA:: [!] Decompilation error - timeout reached") continue # Check if JSON response is available if os.path.isfile(output_path): with open(output_path) as f_in: j = json.load(f_in) if j['status'] == "completed": decompiled_code = j['decompiled'] else: print("GhIDA:: [!] Decompilation error -", " JSON response is malformed") # Remove the temporary JSON response file os.remove(output_path) else: print("GhIDA:: [!] Decompilation error - JSON response not found") idaapi.warning("Ghidra headless decompilation error") except Exception as e: print("GhIDA:: [!] %s" % e) print("GhIDA:: [!] Ghidra headless analysis failed") idaapi.warning("Ghidra headless analysis failed") decompiled_code = None finally: idaapi.hide_wait_box() return decompiled_code
def search_pointers(self): # HACK: A separate flag is used to track user canceling the search, # because multiple calls to idaapi.wasBreak() do not properly # detect cancellations. breakFlag = False # Show wait dialog idaapi.show_wait_box("Searching writable function pointers...") for m in self.modules: ################################################################### # Locate all of the CALL and JMP instructions in the current module # which use an immediate operand. # List of call/jmp pointer calls in a given module ptr_calls = list() # Iterate over segments in the module # BUG: Iterating over all loaded segments is more stable than looking up by address for n in xrange(idaapi.get_segm_qty()): seg = idaapi.getnseg(n) # Segment in a selected modules if seg and seg.startEA >= m.addr and seg.endEA <= (m.addr + m.size): # Locate executable segments # NOTE: Each module may have multiple executable segments # TODO: Search for "MOV REG, PTR # CALL REG" if seg.perm & idaapi.SEGPERM_EXEC: # Search all instances of CALL /2 imm32/64 - FF 15 # TODO: Alternative pointer calls using SIB: FF 14 E5 11 22 33 44 - call dword/qword ptr [0x44332211] # FF 14 65 11 22 33 44 # FF 14 25 11 22 33 44 call_ea = seg.startEA while True: call_ea = idaapi.find_binary(call_ea + 1, seg.endEA, "FF 15", 16, idaapi.SEARCH_DOWN) if call_ea == idaapi.BADADDR: break ptr_calls.append(call_ea) # Search all instances of JMP /2 imm32/64 - FF 25 # TODO: Alternative pointer calls using SIB: FF 24 E5 11 22 33 44 - jmp dword/qword ptr [0x44332211] # FF 24 65 11 22 33 44 # FF 24 25 11 22 33 44 call_ea = seg.startEA while True: call_ea = idaapi.find_binary(call_ea + 1, seg.endEA, "FF 25", 16, idaapi.SEARCH_DOWN) if call_ea == idaapi.BADADDR: break ptr_calls.append(call_ea) ################################################################### # Extract all of the function pointers and make sure they are # are writable. # List of writable function pointer objects in a given module ptrs = list() for call_ea in ptr_calls: # Decode CALL/JMP instruction # NOTE: May result in invalid disassembly of split instructions insn_size = idaapi.decode_insn(call_ea) if insn_size: insn = idaapi.cmd insn_op1 = insn.Operands[0].type # Verify first operand is a direct memory reference if insn.Operands[0].type == idaapi.o_mem: # Get operand address ptr_ea = insn.Operands[0].addr # Apply pointer offset ptr_ea -= self.ptrOffset # Locate segment where the pointer is located ptr_seg = idaapi.getseg(ptr_ea) # Make sure a valid segment writeable segment was found if ptr_seg and ptr_seg.perm & idaapi.SEGPERM_WRITE: # Get pointer charset ptr_charset = self.sploiter.get_ptr_charset(ptr_ea) # Filter the pointer if not self.filterP2P: if ptr_charset == None: continue if self.ptrNonull and not "nonull" in ptr_charset: continue if self.ptrUnicode and not "unicode" in ptr_charset: continue if self.ptrAscii and not "ascii" in ptr_charset: continue if self.ptrAsciiPrint and not "asciiprint" in ptr_charset: continue if self.ptrAlphaNum and not "alphanum" in ptr_charset: continue if self.ptrNum and not "numeric" in ptr_charset: continue if self.ptrAlpha and not "alpha" in ptr_charset: continue # Increment the fptr counter # Get pointer disassembly insn_disas = idc.GetDisasmEx(call_ea, idaapi.GENDSM_FORCE_CODE) # Add pointer to the list ptr = Ptr(m.file, ptr_ea, self.ptrOffset, ptr_charset, call_ea, insn_disas) ptrs.append(ptr) ################################################################### # Cache Pointers to Pointers ptr_ea_prefix_cache = dict() if self.searchP2P: # CACHE: Running repeated searches over the entire memory space is # very expensive. Let's cache all of the addresses containing # bytes corresponding to discovered function pointers in a # single search and simply reference this cache for each # function pointer. Specifically running idaapi.find_binary() # is much more expensive than idaapi.dbg_read_memory(). # # NOTE: For performance considerations, the cache works on a per # module basis, but could be expanded for the entire memory # space. # # prefix_offset - how many bytes of discovered function # pointers to cache. # # Example: For function pointers 0x00401234, 0x00404321, 0x000405678 # we are going to use prefix_offset 2, so we will cache all of the # values located at addresses 0x0040XXXX if self.sploiter.addr64: pack_format = "<Q" addr_bytes = 8 prefix_offset = 6 else: pack_format = "<I" addr_bytes = 4 prefix_offset = 2 # Set of unique N-byte address prefixes to search in memory ea_prefix_set = set() for ptr in ptrs: ptr_ea = ptr.ptr_ea ptr_bytes = struct.pack(pack_format, ptr_ea) ptr_bytes = ptr_bytes[-prefix_offset:] ea_prefix_set.add(ptr_bytes) # Search the module for all bytes corresponding to the prefix # and use them as candidates for pointers-to-pointers for ea_prefix in ea_prefix_set: # NOTE: Make sure you search using 44 33 22 11 format and not 11223344 ea_prefix_str = " ".join(["%02x" % ord(b) for b in ea_prefix]) # Initialize search parameters for a given module ea = m.addr maxea = m.addr + m.size while True: ea = idaapi.find_binary(ea + 1, maxea, ea_prefix_str, 16, idaapi.SEARCH_DOWN) if ea == idaapi.BADADDR: break p2p_ea = ea - (addr_bytes - prefix_offset) dbg_mem = read_module_memory(p2p_ea, addr_bytes) ptr_ea_prefix = unpack(pack_format, dbg_mem)[0] if ptr_ea_prefix in ptr_ea_prefix_cache: ptr_ea_prefix_cache[ptr_ea_prefix].add(p2p_ea) else: ptr_ea_prefix_cache[ptr_ea_prefix] = set([p2p_ea, ]) # Detect search cancellation, but allow the loop below # to run to create already cached/found function pointers # Canceled if breakFlag or idaapi.wasBreak(): breakFlag = True break # Canceled if breakFlag or idaapi.wasBreak(): breakFlag = True break ################################################################### # Locate Pointer to Pointers for ptr in ptrs: ptr_ea = ptr.ptr_ea # Locate pointers-to-pointers for a given function pointer in the cache if self.searchP2P and ptr_ea in ptr_ea_prefix_cache: for p2p_ea in ptr_ea_prefix_cache[ptr_ea]: # Apply pointer-to-pointer offset p2p_ea -= self.p2pOffset p2p_charset = self.sploiter.get_ptr_charset(p2p_ea) # Filter the pointer if self.filterP2P: if p2p_charset == None: continue if self.ptrNonull and not "nonull" in p2p_charset: continue if self.ptrUnicode and not "unicode" in p2p_charset: continue if self.ptrAscii and not "ascii" in p2p_charset: continue if self.ptrAsciiPrint and not "asciiprint" in p2p_charset: continue if self.ptrAlphaNum and not "alphanum" in p2p_charset: continue if self.ptrNum and not "numeric" in p2p_charset: continue if self.ptrAlpha and not "alpha" in p2p_charset: continue # Copy existing pointer object to modify it for the particular p p2p = copy.copy(ptr) p2p.p2p_ea = p2p_ea p2p.p2p_offset = self.p2pOffset p2p.p2p_charset = p2p_charset # Apppend p2p specific pointer object to the global list self.ptrs.append(p2p) # Exceeded maximum number of pointers if self.maxPtrs and len(self.ptrs) >= self.maxPtrs: breakFlag = True print "[idasploiter] Maximum number of pointers exceeded." break # Simply append pointer object to the global list else: self.ptrs.append(ptr) # Exceeded maximum number of pointers if self.maxPtrs and len(self.ptrs) >= self.maxPtrs: breakFlag = True print "[idasploiter] Maximum number of pointers exceeded." break if breakFlag or idaapi.wasBreak(): breakFlag = True break # Canceled # NOTE: Only works when started from GUI not script. if breakFlag or idaapi.wasBreak(): breakFlag = True print "[idasploiter] Canceled." break print "[idasploiter] Found %d total pointers." % len(self.ptrs) idaapi.hide_wait_box()
def search_gadgets(self): count_total = len(self.retns) count_notify = 0 count_curr = 0 # BUG: A separate flag is used to track user canceling the search, # because multiple calls to idaapi.wasBreak() do not properly # detect cancellations. breakFlag = False # Show wait dialog if not self.debug: idaapi.show_wait_box("Searching gadgets: 00%%%%") for (ea_end, module) in self.retns: # Flush the gadgets cache for each new retn pointer self.gadgets_cache = dict() # Flush memory cache for each new retn pointer self.dbg_mem_cache = None # CACHE: It is faster to read as much memory in one blob than to make incremental reads backwards. # Try to read and cache self.maxRopOffset bytes back. In cases where it is not possible, # then simply try to read the largest chunk. # NOTE: Read a bit extra to cover correct decoding of RETN, RETN imm16, CALL /2, and JMP /4 instructions. for i in range(self.maxRopOffset): self.dbg_mem_cache = read_module_memory(ea_end - self.maxRopOffset + i, self.maxRopOffset - i + self.dbg_read_extra) if self.dbg_mem_cache != None: break # Check to make sure we have actual data to work with. if self.dbg_mem_cache == None: continue # Search all possible gadgets up to maxoffset bytes back # NOTE: Try all byte combinations to capture longer/more instructions # even with bad bytes in the middle. for i in range(1, len(self.dbg_mem_cache) - self.dbg_read_extra): ea = ea_end - i # Get pointer charset ptr_charset = self.sploiter.get_ptr_charset(ea) # Filter the pointer if ptr_charset == None: continue if self.ptrNonull and not "nonull" in ptr_charset: continue if self.ptrUnicode and not "unicode" in ptr_charset: continue if self.ptrAscii and not "ascii" in ptr_charset: continue if self.ptrAsciiPrint and not "asciiprint" in ptr_charset: continue if self.ptrAlphaNum and not "alphanum" in ptr_charset: continue if self.ptrNum and not "numeric" in ptr_charset: continue if self.ptrAlpha and not "alpha" in ptr_charset: continue # Try to build a gadget at the pointer gadget = self.build_gadget(ea, ea_end) # Successfully built the gadget if gadget: # Populate gadget object with more data gadget.address = ea gadget.module = module gadget.ptr_charset = ptr_charset # Filter gadgets with too many instruction if gadget.size > self.maxRopSize: break # Append newly built gadget self.gadgets.append(gadget) self.gadgets_cache[ea] = gadget # Exceeded maximum number of gadgets if self.maxRops and len(self.gadgets) >= self.maxRops: breakFlag = True print "[idasploiter] Maximum number of gadgets exceeded." break else: self.gadgets_cache[ea] = None if breakFlag or idaapi.wasBreak(): breakFlag = True break # Canceled # NOTE: Only works when started from GUI not script. if breakFlag or idaapi.wasBreak(): breakFlag = True print "[idasploiter] Canceled." break # Progress report if not self.debug and count_curr >= count_notify: # NOTE: Need to use %%%% to escape both Python and IDA's format strings idaapi.replace_wait_box("Searching gadgets: %02d%%%%" % (count_curr * 100 / count_total)) count_notify += 0.10 * count_total count_curr += 1 print "[idasploiter] Found %d gadgets." % len(self.gadgets) if not self.debug: idaapi.hide_wait_box()
def search_gadgets(self): count_total = len(self.retns) count_notify = 0 count_curr = 0 # BUG: A separate flag is used to track user canceling the search, # because multiple calls to idaapi.wasBreak() do not properly # detect cancellations. breakFlag = False # Show wait dialog if not self.debug: idaapi.show_wait_box("Searching gadgets: 00 %") try : for ea_end in self.retns: # Flush the gadgets cache for each new retn pointer self.gadgets_cache = dict() # Flush memory cache for each new retn pointer self.dbg_mem_cache = None # CACHE: It is faster to read as much memory in one blob than to make incremental reads backwards. # Try to read and cache self.maxRopOffset bytes back. In cases where it is not possible, # then simply try to read the largest chunk. # NOTE: Read a bit extra to cover correct decoding of RETN, RETN imm16, CALL /2, and JMP /4 instructions. # Bug on end of segments : self.dbg_read_extra must be 0 dbg_read_extra = self.dbg_read_extra seg_start, seg_end = idc.SegStart(ea_end), idc.SegEnd(ea_end) if ea_end + dbg_read_extra > seg_end: dbg_read_extra = 0 for i in range(self.maxRopOffset): self.dbg_mem_cache = idc.GetManyBytes(ea_end - self.maxRopOffset + i, self.maxRopOffset - i + self.dbg_read_extra) if self.dbg_mem_cache != None: break # Error while reading memory (Ida sometimes does not want to read uninit data) if self.dbg_mem_cache == None: for backward_size in range(self.maxRopOffset, 0, -1): self.dbg_mem_cache = idc.GetManyBytes(ea_end - backward_size, backward_size) if self.dbg_mem_cache != None: break # Big problem ahead if self.dbg_mem_cache == None: logging.error("[Ida Search Error] could not read bytes [0x%x, 0x%x]" % (ea_end - self.maxRopOffset + i, ea_end - self.maxRopOffset + i + self.maxRopOffset - i + self.dbg_read_extra)) # Search all possible gadgets up to maxoffset bytes back # NOTE: Try all byte combinations to capture longer/more instructions # even with bad bytes in the middle. for i in range(1, len(self.dbg_mem_cache) - self.dbg_read_extra): ea = ea_end - i # Try to build a gadget at the pointer gadget = self.build_gadget(ea, ea_end) # Successfully built the gadget if gadget: # Filter gadgets with too many instruction if gadget.size > self.maxRopSize: break # Append newly built gadget self.gadgets.append(gadget) self.gadgets_cache[ea] = gadget # Exceeded maximum number of gadgets if self.maxRops and len(self.gadgets) >= self.maxRops: breakFlag = True print("[Ida Rop] Maximum number of gadgets exceeded.") break else: self.gadgets_cache[ea] = None if breakFlag or idaapi.wasBreak(): breakFlag = True break # Canceled # NOTE: Only works when started from GUI not script. if breakFlag or idaapi.wasBreak(): breakFlag = True print ("[IdaRopSearch] Canceled.") break # Progress report if not self.debug and count_curr >= count_notify: # NOTE: Need to use %%%% to escape both Python and IDA's format strings percent_progression = count_curr*100/count_total progression_str = """Searching gadgets: {progression:02d} %""".format(progression = percent_progression) idaapi.replace_wait_box(progression_str) count_notify += 0.10 * count_total count_curr += 1 print ("[IdaRopSearch] Found %d gadgets." % len(self.gadgets)) except: logging.error ("[IdaRopSearch] Exception raised while search for gadgets : %s." % sys.exc_info()) pass finally: if not self.debug: idaapi.hide_wait_box()