def build_build_image(args: dict, build_image: str = None) -> None: """Builds the build image. Args: args (dict): Parsed command-line arguments passed to the script. build_image (str): Optional; Build image name. Defaults to the value from config file. """ build_image = build_image if build_image else CONFIG['DOCKER']['build_image'] if not args['--rebuild'] and docker_utils.item_exists('image', build_image): utils.warn(f"Image '{build_image}' already exists, not building") return utils.log(f"Building '{build_image}' image") build_image_cmd = [ 'docker', 'build', '--tag', build_image, '--file', os.path.join('docker', 'Dockerfile-gradle'), '.', ] if args['--no-cache']: build_image_cmd.insert(2, '--no-cache') elif args['--cache-from']: for item in args['--cache-from']: build_image_cmd[2:2] = ['--cache-from', item] utils.execute_cmd(build_image_cmd)
def build_main_image(args: dict, main_image: str = None) -> None: """Builds the main image. Args: args (dict): Parsed command-line arguments passed to the script. main_image (str): Optional; Main image name. Defaults to the value from config file. """ main_image = main_image if main_image else CONFIG['DOCKER']['main_image'] if not args['--rebuild'] and docker_utils.item_exists('image', main_image): utils.warn(f"Image '{main_image}' already exists, not building") return utils.log(f"Building '{main_image}' image") main_image_cmd = [ 'docker', 'build', '--tag', main_image, '--file', os.path.join('docker', 'Dockerfile'), '.', ] if args['--no-cache']: main_image_cmd.insert(2, '--no-cache') elif args['--cache-from']: for item in args['--cache-from']: main_image_cmd[2:2] = ['--cache-from', item] if args['--suspend'] or args['--debug']: main_image_cmd[2:2] = ['--build-arg', 'suspend=true' if args['--suspend'] else 'debug=true'] utils.execute_cmd(main_image_cmd)
def main() -> None: parser = argparse.ArgumentParser( "Identifies matching functions by looking at function calls in matching functions" ) parser.add_argument("-f", "--fn", help="Functions to analyze", nargs="*") args = parser.parse_args() functions_to_analyze = set(args.fn) if args.fn else set() functions_by_addr: Dict[int, utils.FunctionInfo] = { fn.addr: fn for fn in utils.get_functions() } fn_checker = Checker() for fn in functions_by_addr.values(): if functions_to_analyze and fn.decomp_name not in functions_to_analyze: continue if fn.status != utils.FunctionStatus.Matching: continue base_fn = elf.get_fn_from_base_elf(fn.addr, fn.size) try: my_fn = elf.get_fn_from_my_elf(fn.decomp_name) except KeyError: utils.warn(f"could not find function {fn.decomp_name}") continue fn_checker.checking = fn.decomp_name fn_checker.check(base_fn, my_fn) if fn_checker.invalid_call_descriptions: for x in fn_checker.invalid_call_descriptions: utils.print_note(x) utils.fail("invalid calls detected") new_matches: Dict[int, str] = dict() calls = fn_checker.get_possible_calls().copy() for base_target, my_target in calls.items(): target_info = functions_by_addr.get(base_target) if target_info is None: continue if target_info.status != utils.FunctionStatus.NotDecompiled: continue base_fn = elf.get_fn_from_base_elf(target_info.addr, target_info.size) try: name = fn_checker.addr_to_symbol[my_target] my_fn = elf.get_fn_from_my_elf(name) except KeyError: continue if fn_checker.check(base_fn, my_fn): new_matches[base_target] = name utils.print_note( f"new match: {Fore.BLUE}{cxxfilt.demangle(name)}{Fore.RESET}") utils.add_decompiled_functions(new_matches)
def main() -> None: parser = argparse.ArgumentParser() parser.add_argument( "csv_path", help= "Path to a list of functions to identify (in the same format as the main function CSV)" ) parser.add_argument("candidates_path", help="Path to a list of candidates (names only)") args = parser.parse_args() csv_path = Path(args.csv_path) candidates_path = Path(args.candidates_path) candidates = read_candidates(candidates_path) new_matches: Dict[int, str] = dict() checker = util.checker.FunctionChecker() # Given a list L of functions to identify and a small list of candidates C, this tool will attempt to # automatically identify matches by checking each function in L against each function in C. # # This matching algorithm is quite naive (quadratic time complexity if both lists have about the same size) # but this should work well enough for short lists of candidates... for func in utils.get_functions(csv_path): if func.status != utils.FunctionStatus.NotDecompiled: continue match_name = "" for candidate_name, candidate in candidates.items(): if len(candidate.data) != func.size: continue if checker.check( util.elf.get_fn_from_base_elf(func.addr, func.size), candidate): match_name = candidate_name break if match_name: new_matches[func.addr] = match_name utils.print_note( f"found new match: {Fore.BLUE}{match_name}{Fore.RESET} ({func.addr | 0x71_00000000:#018x})" ) # This is no longer a candidate. del candidates[match_name] else: utils.warn( f"no match found for {Fore.BLUE}{func.name}{Fore.RESET} ({func.addr | 0x71_00000000:#018x})" ) # Output the modified function CSV. writer = csv.writer(sys.stdout, lineterminator="\n") for func in utils.get_functions(): if func.status == utils.FunctionStatus.NotDecompiled and func.addr in new_matches: func.raw_row[3] = new_matches[func.addr] writer.writerow(func.raw_row)
def main() -> None: failed = False nonmatching_fns_with_dump = { p.stem: util.elf.Function(p.read_bytes(), 0) for p in (utils.get_repo_root() / "expected").glob("*.bin") } checker = util.checker.FunctionChecker(log_mismatch_cause=True) for func in utils.get_functions(): if not func.decomp_name: continue try: util.elf.get_fn_from_my_elf(func.decomp_name) except KeyError: utils.warn( f"couldn't find {utils.format_symbol_name_for_msg(func.decomp_name)}" ) continue if func.status == utils.FunctionStatus.Matching: if not check_function(checker, func.addr, func.size, func.decomp_name): utils.print_error( f"function {utils.format_symbol_name_for_msg(func.decomp_name)} is marked as matching but does not match" ) a1, a2, reason = checker.get_mismatch() if a1 != -1: sys.stderr.write( f" at {a1 | 0x7100000000:#x} : {Fore.CYAN}{reason}{Fore.RESET}\n" ) failed = True elif func.status == utils.FunctionStatus.Equivalent or func.status == utils.FunctionStatus.NonMatching: if check_function(checker, func.addr, func.size, func.decomp_name): utils.print_note( f"function {utils.format_symbol_name_for_msg(func.decomp_name)} is marked as non-matching but matches" ) fn_dump = nonmatching_fns_with_dump.get(func.decomp_name, None) if fn_dump is not None and not check_function( checker, func.addr, len(fn_dump), func.decomp_name, fn_dump): utils.print_error( f"function {utils.format_symbol_name_for_msg(func.decomp_name)} does not match expected output" ) failed = True if failed: sys.exit(1)
def create_main_container(args: dict, main_image: str = None) -> None: """Creates main Docker container. Args: args (dict): Parsed command-line arguments passed to the script. main_image (str): Optional; Main image name. Defaults to the value from config file. """ main_image = main_image if main_image else CONFIG['DOCKER']['main_image'] main_container = main_image if not args['--rebuild'] and docker_utils.item_exists('container', main_container): utils.warn(f"Container '{main_container}' already exists, not creating") return utils.log(f"Creating '{main_container}' container") spring_port = CONFIG['SPRING']['port'] main_container_cmd = [ 'docker', 'create', '--publish', f"{spring_port}:{spring_port}", '--name', main_container, '--network', CONFIG['DOCKER']['network'], '--env', f"SPRING_DATASOURCE_URL={os.environ.get('SPRING_DATASOURCE_URL')}", '--env', f"SPRING_DATASOURCE_USERNAME={os.environ.get('SPRING_DATASOURCE_USERNAME')}", '--env', f"SPRING_DATASOURCE_PASSWORD={os.environ.get('SPRING_DATASOURCE_PASSWORD')}", main_image, ] if args['--suspend'] or args['--debug']: debug_port = CONFIG['SPRING']['debug_port'] main_container_cmd[2:2] = ['--publish', f"{debug_port}:{debug_port}"] if not args['--detach']: main_container_cmd[2:2] = ['--interactive', '--tty'] utils.execute_cmd(main_container_cmd) utils.log(f"Copying JAR into '{main_container}'") utils.execute_cmd([ 'docker', 'cp', os.path.join('build', 'libs', 'app.jar'), f"{main_container}:/home/project/app.jar", ])
def run_build_image(args: dict, build_image: str = None) -> None: """Runs the build image and copies the compiled JAR file out of the container. Args: args (dict): Parsed command-line arguments passed to the script. build_image (str): Optional; Build image name. Defaults to the value from config file. """ build_image = build_image if build_image else CONFIG['DOCKER']['build_image'] build_container = build_image if not args['--rebuild'] and docker_utils.item_exists('container', build_container): utils.warn(f"Container '{build_container}' already exists, not running") return utils.log(f"Running '{build_image}' image") build_container_cmd = [ 'docker', 'run', '--name', build_container, '--volume', f"{CONFIG['DOCKER']['cache_volume']}:/home/gradle/.gradle", '--user', 'gradle', build_image, ] build_container_cmd.extend(CONFIG['DOCKER']['build_command'].split(' ')) if not args['--detach']: build_container_cmd[2:2] = ['--interactive', '--tty'] utils.execute_cmd(build_container_cmd) utils.log(f"Copying JAR from '{build_container}' container") shutil.rmtree(os.path.join('build', 'libs'), ignore_errors=True) pathlib.Path(os.path.join('build')).mkdir(parents=True, exist_ok=True) utils.execute_cmd([ 'docker', 'cp', f"{build_container}:/home/gradle/project/build/libs", os.path.join('build'), ]) for file in glob.glob(os.path.join('build', 'libs', '*.jar')): if file.endswith('.jar'): os.rename(file, os.path.join('build', 'libs', 'app.jar')) break
if info is not None: if not info.decomp_name: utils.fail(f"{args.function} has not been decompiled") print( f"diffing: {Style.BRIGHT}{Fore.BLUE}{cxxfilt.demangle(info.decomp_name)}{Style.RESET_ALL} {Style.DIM}({info.decomp_name}){Style.RESET_ALL}" ) addr_end = info.addr + info.size subprocess.call([ "tools/asm-differ/diff.py", "-I", "-e", info.decomp_name, "0x%016x" % info.addr, "0x%016x" % addr_end ] + unknown) if info.status == utils.FunctionStatus.NonMatching: utils.warn( f"{info.decomp_name} is marked as non-matching and possibly NOT functionally equivalent" ) elif info.status == utils.FunctionStatus.Equivalent: utils.warn( f"{info.decomp_name} is marked as functionally equivalent but non-matching" ) else: if find_wip: utils.fail("no WIP function") utils.fail( f"unknown function '{args.function}'\nfor constructors and destructors, list the complete object constructor (C1) or destructor (D1)" )
def main() -> None: new_matches: Dict[int, str] = dict() functions_by_addr: Dict[int, utils.FunctionInfo] = { fn.addr: fn for fn in utils.get_functions() } md = cs.Cs(cs.CS_ARCH_ARM64, cs.CS_MODE_ARM) md.detail = True decomp_addr_to_symbol = elf.build_addr_to_symbol_table(elf.my_symtab) decomp_glob_data_table = elf.build_glob_data_table(elf.my_elf) processed: Set[int] = set() for fn in functions_by_addr.values(): if fn.status != utils.FunctionStatus.Matching: continue if fn.size != 0x5C or ( not fn.decomp_name.endswith("8getRuntimeTypeInfoEv") and not fn.name.endswith("rtti2")): continue base_fn = elf.get_fn_from_base_elf(fn.addr, fn.size) try: my_fn = elf.get_fn_from_my_elf(fn.decomp_name) except KeyError: utils.warn(f"could not find function {fn.decomp_name}") continue assert len(base_fn.data) == len(my_fn.data) vtable_ptr1 = 0 vtable_ptr2 = 0 for j, (i1, i2) in enumerate( zip(md.disasm(base_fn.data, base_fn.addr), md.disasm(my_fn.data, my_fn.addr))): assert i1.mnemonic == i2.mnemonic if j == 10: assert i1.mnemonic == "adrp" assert i1.operands[0].reg == i2.operands[0].reg vtable_ptr1 = i1.operands[1].imm vtable_ptr2 = i2.operands[1].imm elif j == 11: assert i1.mnemonic == "ldr" assert i1.operands[0].reg == i2.operands[0].reg assert i1.operands[1].value.mem.base == i2.operands[ 1].value.mem.base vtable_ptr1 += i1.operands[1].value.mem.disp vtable_ptr2 += i2.operands[1].value.mem.disp break assert vtable_ptr1 != 0 and vtable_ptr2 != 0 if vtable_ptr1 in processed: continue processed.add(vtable_ptr1) ptr1, = struct.unpack("<Q", elf.read_from_elf(elf.base_elf, vtable_ptr1, 8)) ptr2 = decomp_glob_data_table[vtable_ptr2] vtable1 = elf.get_vtable_fns_from_base_elf(ptr1 + 0x10, num_entries=1) vtable2 = elf.unpack_vtable_fns(elf.read_from_elf(elf.my_elf, addr=ptr2 + 0x10, size=8), num_entries=1) if functions_by_addr[ vtable1[0]].status == utils.FunctionStatus.Matching: continue decomp_derive_fn_addr = vtable2[0] if decomp_derive_fn_addr == 0: decomp_derive_fn_addr = decomp_glob_data_table.get(ptr2 + 0x10, 0) if decomp_derive_fn_addr == 0: raise RuntimeError( f"Derive virtual function pointer is null " f"(fn: {fn.decomp_name}, decomp vtable at {ptr2:#x})") name = decomp_addr_to_symbol[decomp_derive_fn_addr] new_matches[vtable1[0]] = name utils.print_note( f"new match: {Fore.BLUE}{cxxfilt.demangle(name)}{Fore.RESET} (from {fn.decomp_name})" ) # overwrite the original names because they are likely to be incorrect utils.add_decompiled_functions(new_matches, new_orig_names=new_matches)