Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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",
    ])
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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)