def get_args(self): """Try to get the args of the all XREFS of the function""" # load the pattern args_pattern = open(self.cfg["call_pattern"], "r").read() args_pattern = args_pattern.replace("FILL_ADDR", str(hex(self.func_addr))) #print Color.step("Searching for arguments of each XREFS to the function 0x{:X}".format(self.func_addr)) # match the pattern args_matches = pygrap.match_graph(args_pattern, self.test_graph) for match in args_matches.items()[0][1]: self.args.append(match)
def get_xrefs(self): """Try to get the xrefs of the matched function""" # generate our pattern for xrefs xrefs_pattern = XREFS_PATTERN.replace("FILL_ADDR", str(hex(self.func_addr))) #print Color.step("Searching for the XREFS to the function 0x{:X}".format(self.func_addr)) # match the pattern call_matches = pygrap.match_graph(xrefs_pattern, self.test_graph) # creation of the list of xrefs if len(call_matches) == 0: print Color.error("No XREFS to the function 0x{:X} was found".format(self.func_addr)) else: for match in call_matches["xrefs"]: self.xrefs.append(int(match["call"][0].info.address))
def decrypt_strings(pe, pe_baseaddr, pe_cfg): r = parse_decrypt_function(pe, pe_baseaddr, pe_cfg) if r is None: print("ERROR: unable to find the decryption function") sys.exit(1) func_addr, xorkey_addr, cipher_addr, cipher_size = r pattern_call_final = pattern_call.replace("FILL_ADDRESS", hex(func_addr)) matches = pygrap.match_graph(pattern_call_final, pe_cfg) print("\nStrings:") if "push_call_func" in matches: for match in matches["push_call_func"]: push_inst = match["PUSH"][0] push_addr = push_inst.info.address offset = pygrap.parse_first_immediate(push_inst.info.arg1) if offset: dec = decrypt_string(pe, pe_baseaddr, offset, xorkey_addr, cipher_addr, cipher_size) print(hex(push_addr), hex(offset), dec)
def parse_decrypt_function(pe, pe_baseaddr, pe_cfg): matches = pygrap.match_graph(pattern_function, pe_cfg) if "qakbot_strings_parsing_func" in matches: for match in matches["qakbot_strings_parsing_func"]: func_addr = match["0_EP"][0].info.address size1 = pygrap.parse_first_immediate(match["1_CIPHERSIZE1"][0].info.arg1) cipher_addr = pygrap.parse_first_immediate(match["2_CIPHER"][0].info.arg1) size2 = pygrap.parse_first_immediate(match["3_CIPHERSIZE2"][0].info.arg2) xorkey_addr = pygrap.parse_first_address(match["5_XORKEY"][0].info.arg2) if size1 != size2 + 1: print("ERROR: size1 and size2 do not match") return print("Decryption function at:", hex(func_addr)) print("XOR key at:", hex(xorkey_addr)) print("XOR key:", pe.get_data(xorkey_addr - pe_baseaddr, 0x40).hex()) print("Cipher block at:", hex(cipher_addr)) print("Cipher block size:", hex(size1)) return func_addr, xorkey_addr, cipher_addr, size1
def get_func_ep_grap(self): """Try to get the entrypoint of the matched function""" first_inst = self.match["first_inst"][0] first_addr = first_inst.info.address # Find the beginning of the function: # It is a node with at least 2 fathers which address a fulfills: address - 0x7 <= a <= address address_cond = "address >= " + str(hex(int(first_addr - 0x7))) + " and address <= " + str(hex(int(first_addr))) ep_pattern = EP_PATTERN.replace("FILL_ADDR_COND", address_cond) #print Color.step("Searching for the function entrypoint near 0x{:X}".format(first_addr)) # search for the EP ep_matches = pygrap.match_graph(ep_pattern, self.test_graph) if len(ep_matches) != 1 or len(ep_matches["ep_func"]) != 1: print Color.error("Entrypoint not found or the function was not call for the address 0x{:X}".format(first_addr)) else: self.func_addr = int(ep_matches["ep_func"][0]["ep"][0].info.address)
def __init__(self, cfg, test_graph): """Initialization Arguments: - cfg(Dict): Config for a given pattern - test_graph(pygrap.graph_t): Grap representation of the binary """ self.cfg = cfg self.test_graph = test_graph self.matches = [] # search for patterns matches = pygrap.match_graph(self.cfg["func_pattern"], self.test_graph).items()[0][1] print Color.info("The graph {name} was found {size} time(s)".format(name=self.cfg["name"], size=len(matches))) # process matches for match in matches: try: self.matches.append(Match(match, self.test_graph, self.cfg)) except: continue
def main(): if len(sys.argv) <= 1: print_usage() sys.exit(1) elif len(sys.argv) <= 2: verbose = False bin_path = sys.argv[1] dot_path = sys.argv[1] + ".grapcfg" else: verbose = True bin_path = sys.argv[2] dot_path = sys.argv[2] + ".grapcfg" if len(sys.argv) == 2 and (sys.argv[1] == "-h" or sys.argv[1] == "--help"): print_usage() sys.exit(1) if bin_path[-8:] == ".grapcfg": print "The argument should be a binary and not a .grapcfg file" sys.exit(1) # use_existing specifies wether an existing dot file should be used unchanged or overwritten pygrap.disassemble_file(bin_path=bin_path, dot_path=dot_path, use_existing=True) test_graph = pygrap.getGraphFromPath(dot_path) if verbose: print "Analyzing", bin_path if not os.path.isfile(bin_path) or not os.path.isfile(dot_path): print "Error: binary or dot file doesn't exist, exiting." sys.exit(1) pattern_decrypt = "backspace_decrypt_algos.grapp" matches_decrypt = pygrap.match_graph(pattern_decrypt, test_graph) if len(matches_decrypt) >= 2: print "Error: two or more decryption algorithms matched, exiting." sys.exit(1) for algorithm_name in matches_decrypt: if verbose: print "Matched algorithm:", algorithm_name first_match = matches_decrypt[algorithm_name][0] first_group = first_match["A"] first_instruction = first_group[0] description = first_instruction.info.inst_str address = first_instruction.info.address if verbose: print "Found decryption pattern at address", hex(int(address)) # Find the beginning of the function: # It is a node with at least 5 fathers which address a fulfills: address - 30 <= a <= address address_cond = "address >= " + str(hex( int(address - 30))) + " and address <= " + str(hex(int(address))) entrypoint_pattern = """ digraph decrypt_fun_begin{ ep [label="ep", cond="nfathers >= 5 and FILL_ADDR_COND", getid="ep"] } """.replace("FILL_ADDR_COND", address_cond) if verbose: print "Looking for entrypoint with pattern:" print entrypoint_pattern matches_entrypoint = pygrap.match_graph(entrypoint_pattern, test_graph) if len(matches_entrypoint) != 1 or len( matches_entrypoint["decrypt_fun_begin"]) != 1: print "Error: Entrypoint not found, exiting" sys.exit(1) entrypoint = hex( int(matches_entrypoint["decrypt_fun_begin"][0]["ep"] [0].info.address)) if verbose: print "Found decryption function at", entrypoint push_call_pattern = """ digraph push_call_decrypt{ push [label="push", cond="opcode is push", repeat=2, getid=push] call [label="call", cond="opcode is call"] entrypoint [label="entrypoint", cond="address == FILL_ADDR"] push -> call call -> entrypoint [childnumber=2] } """.replace("FILL_ADDR", entrypoint) if verbose: print "Looking for calls to decrypt function with pattern:" print push_call_pattern matches_calls = pygrap.match_graph(push_call_pattern, test_graph) if len(matches_calls) == 0: print "error: No call found, exiting" sys.exit(1) if verbose: print len(matches_calls["push_call_decrypt"] ), "calls to decrypt function found." str_tuple = [] for m in matches_calls["push_call_decrypt"]: # Work on matches with immediate arguments such as: # PUSH (between 2 and 5) with hex arguments (for instance: 9, 0x12 or 0x4012a3) # CALL entrypoint if len(m["push"] [-2].info.arg1) == 1 or "0x" in m["push"][-2].info.arg1: str_tuple.append((int(m["push"][-2].info.arg1, 16), int(m["push"][-1].info.arg1, 16))) decrypted_strings = decrypt_strings(algorithm_name, str_tuple, bin_path) print "Decrypted strings in", bin_path + ":" for d in decrypted_strings: print d print "" pygrap.graph_free(test_graph, True)
def _analyzing(self): cfg = self.graph # Clear the list del self._analyzed_patterns[:] # # Control flow graph extraction # if not cfg.graph: print "[I] Creation of the Control Flow Graph (can take few seconds)" # Get the CFG of the binary cfg.extract() # # Pattern searching # print "[I] Searching for patterns." # Update "Test" patterns MODULES["Test"]["Misc"] = get_test_misc() # Group for grp_name, grp in MODULES.iteritems(): #print "Group: " + grp_name # Group->Type for tp_name, tp in grp.iteritems(): #print "\tType: " + tp_name if grp_name == "Test" and tp_name == "Misc": patterns_path_list = [] for algo in tp: for patterns in algo.get_patterns(): for pattern in patterns.get_patterns(): path = pattern.get_file() #print "\t\tAdded patterns from " + path print "Added patterns from " + path patterns_path_list.append(path) #print "\t\tMatching patterns against binary... this may take a few seconds" print "Matching patterns against binary... this may take a few seconds" matches = match_graph(patterns_path_list, cfg.graph, print_all_matches=True) #print "\t\t", len(matches), "patterns found." print len(matches), "patterns found." for pattern_name in matches: pattern = Pattern(name=pattern_name) patterns_t = Patterns(patterns=[pattern], name=pattern_name, perform_analysis=False, matches=matches[pattern_name]) pattern._matches = [] for c_match in matches[pattern_name]: match = Match(c_match, pattern_name) pattern._matches.append(match) ana = PatternsAnalysis(patterns_t, None) ana.filter_patterns() self._analyzed_patterns.append(ana) else: for algo in tp: # print algo print "\t\tAlgorithm: %s" % algo.get_name() # List of Patterns for patterns in algo.get_patterns(): print "\t\t\tFunction: %s" % patterns.get_name() # List of Pattern for pattern in patterns.get_patterns(): print "\t\t\t\t[I] Searching for " + pattern.get_name( ) pattern.parcourir(cfg.graph) print "\t\t\t\t[I] %d %s pattern found" % (len( pattern.get_matches()), pattern.get_name()) # # Analyzing # if patterns._perform_analysis: print "\t\t\t\t[I] Linking the patterns matches which are in the same area" ana = PatternsAnalysis(patterns, algo) print "\t\t\t\t[I] Filtering those patterns" ana.filter_patterns() # Add the analyzed patterns to the list self._analyzed_patterns.append(ana)
def match(self, graph): path_list = [p.get_file() for p in self._patterns] self._matches = match_graph(path_list, graph)