def find(symbols): print(utils.make_bright("<find>")) matches = [] with sqlite3.connect(utils.get_libcs_db_filepath()) as conn: conn.row_factory = sqlite3.Row for libc in conn.execute("SELECT * FROM libcs"): libc_filepath = os.path.join(utils.get_libcs_dirpath(), libc["relpath"]) with open(libc_filepath, "rb") as f: elf = elftools.elf.elffile.ELFFile(f) dynsym_section = elf.get_section_by_name(".dynsym") for symbol, address in symbols: offset = address & 0xFFF try: libc_symbol = dynsym_section.get_symbol_by_name(symbol)[0] libc_offset = libc_symbol.entry.st_value & 0xFFF if libc_offset != offset: break except (IndexError, TypeError): break else: utils.dump(dict(libc)) matches.append(dict(libc)) print(utils.make_bright("</find>")) return matches
def rebuild(): print(utils.make_bright("<rebuild>")) with sqlite3.connect(utils.get_libcs_db_filepath()) as conn: conn.execute("DROP TABLE IF EXISTS libcs") conn.execute( "CREATE TABLE libcs" "(relpath text, architecture text, distro text, release text, version text, patch text, buildID text)" ) for filepath in glob.glob(f"{utils.get_libcs_dirpath()}/**", recursive=True): match = re.match( r"(?:.*?)libcs/(?:(?P<distro>.+?)/)?(?:(?P<release>.+?)/)?libc-(?P<architecture>i386|i686|amd64|x86_64|armel|armhf|arm64)-(?P<version>\d.\d+)(?:-(?P<patch>.+?))?\.so$", filepath, ) if match: relpath = os.path.relpath(filepath, utils.get_libcs_dirpath()) print(f"Importing: {utils.make_bright(relpath)}") conn.execute( "INSERT INTO libcs VALUES (?, ?, ?, ?, ?, ?, ?)", ( relpath, match.group("architecture"), match.group("distro"), match.group("release"), match.group("version"), match.group("patch"), utils.extract_buildID(filepath), ), ) print(utils.make_bright("</rebuild>"))
def extract(package_filepath): print(utils.make_bright("<extract>")) package_filename = os.path.basename(package_filepath) tmp_dirpath = tempfile.mkdtemp() shutil.copy2(package_filepath, tmp_dirpath) # extract the package subprocess.run( f"tar xf {shlex.quote(package_filename)}" if package_filepath.endswith("tar.xz") else f"ar x {shlex.quote(package_filename)}", cwd=tmp_dirpath, check=True, shell=True, stderr=subprocess.PIPE, ) # extract data.tar.?z if it exists subprocess.run( f"if [ -f data.tar.?z ]; then tar xf data.tar.?z; fi", cwd=tmp_dirpath, check=True, shell=True, ) print(f"Extracted: {utils.make_bright(tmp_dirpath)}") print(utils.make_bright("</extract>")) return tmp_dirpath
def bootstrap(ubuntu_only): print(utils.make_bright("<bootstrap>")) if not utils.query_yes_no( "This operation will download a bunch of libcs into" f" {utils.make_bright(utils.get_libcs_dirpath())}. Proceed?"): utils.abort("Aborted by user.") _add_ubuntu_libcs() if not ubuntu_only: _add_debian_libcs() _add_arch_linux_libcs() print(utils.make_bright("</bootstrap>"))
def dump(libc_filepath, symbols): print(utils.make_bright("<dump>")) with open(libc_filepath, "rb") as f: elf = elftools.elf.elffile.ELFFile(f) dynsym_section = elf.get_section_by_name(".dynsym") for symbol in symbols: try: libc_symbol = dynsym_section.get_symbol_by_name(symbol)[0] libc_offset = libc_symbol.entry.st_value & 0xFFF except TypeError: pass else: print(f"{symbol}={hex(libc_offset)}") print(utils.make_bright("</dump>"))
def identify(libc_filepath): print(utils.make_bright("<identify>")) matches = [] with sqlite3.connect(utils.get_libcs_db_filepath()) as conn: conn.row_factory = sqlite3.Row matches = [ dict(libc) for libc in conn.execute( "SELECT * FROM libcs where buildID=?", (utils.extract_buildID(libc_filepath),), ) ] for libc in matches: utils.dump(libc) print(utils.make_bright("</identify>")) return matches
def patch(binary_filepath, supplied_libc_filepath): print(utils.make_bright("<patch>")) binary_dirpath = os.path.dirname(binary_filepath) # identify the supplied libc matches = identify(supplied_libc_filepath) if not matches: utils.abort("The supplied libc is not in the local library.") # TODO pick the first for now libc = matches[0] libc_filepath = os.path.join(utils.get_libcs_dirpath(), libc["relpath"]) libc_architecture = libc["architecture"] libc_patch = libc["patch"] libc_version = libc["version"] ld_filepath = os.path.join( os.path.dirname(libc_filepath), os.path.basename(libc_filepath).replace("libc-", "ld-"), ) # if the dynamic loader does not exist, abort (don't care about race conditions) if not os.path.isfile(ld_filepath): utils.abort( "The dynamic loader corresponding to the libc to use cannot be found." f" It should reside at {utils.make_bright(ld_filepath)}" ) # copy the dynamic loader and the libc to the directory where the binary is located libs_dirpath = os.path.join( binary_dirpath, "libs", libc_architecture, libc_version, libc_patch ) ld_proper_filename = f"ld-{libc_version}.so" ld_proper_filepath = os.path.join(libs_dirpath, ld_proper_filename) libc_proper_filename = f"libc-{libc_version}.so" libc_proper_filepath = os.path.join(libs_dirpath, libc_proper_filename) if not utils.query_yes_no( "Copy:\n" f"- {utils.make_bright(ld_filepath)}\n" f"- {utils.make_bright(libc_filepath)}\n" "to:\n" f"- {utils.make_bright(ld_proper_filepath)}\n" f"- {utils.make_bright(libc_proper_filepath)}\n" "?" ): utils.abort("Aborted by user.") os.makedirs(libs_dirpath, exist_ok=True) shutil.copy2(ld_filepath, ld_proper_filepath) shutil.copy2(libc_filepath, libc_proper_filepath) print() # if debug symbols exist, copy them also libc_dbg_filepath = f"{libc_filepath}.debug" if os.path.isfile(libc_dbg_filepath): libs_debug_dirpath = os.path.join(libs_dirpath, ".debug") libc_dbg_proper_filename = utils.get_libc_dbg_proper_filename(libc_filepath) libc_dbg_proper_filepath = os.path.join( libs_debug_dirpath, libc_dbg_proper_filename ) if utils.query_yes_no( "Copy:\n" f"- {utils.make_bright(libc_dbg_filepath)}\n" "to:\n" f"- {utils.make_bright(libc_dbg_proper_filepath)}\n" "?" ): os.makedirs(libs_debug_dirpath, exist_ok=True) shutil.copy2(libc_dbg_filepath, libc_dbg_proper_filepath) print() # patch the binary to use the new dynamic loader and libc patched_binary_filepath = ( f"{binary_filepath}-{libc_architecture}-{libc_version}-{libc_patch}" ) if not utils.query_yes_no( "Copy:\n" f"- {utils.make_bright(binary_filepath)}\n" "to:\n" f"- {utils.make_bright(patched_binary_filepath)}\n" "and patch the latter?" ): utils.abort("Aborted by user.") shutil.copy2(binary_filepath, patched_binary_filepath) ld_basename = os.path.basename(ld_proper_filename) libc_basename = os.path.basename(libc_proper_filename) subprocess.run( ( f"patchelf --set-interpreter {shlex.quote(os.path.relpath(ld_proper_filepath, binary_dirpath))} {shlex.quote(patched_binary_filepath)}" f" && patchelf --add-needed {shlex.quote(os.path.relpath(libc_proper_filepath, binary_dirpath))} {shlex.quote(patched_binary_filepath)}" ), check=True, shell=True, ) print(utils.make_bright("</patch>"))
def add(package_filepath, dest_dirpath=utils.get_libcs_dirpath()): print(utils.make_bright("<add>")) match = utils.match(package_filepath) if not match: print( utils.make_warning( f"Skipping: the filename of the package did not match any supported patterns." )) return libc_architecture = match.group("architecture") libc_version = match.group("version") libc_patch = match.group("patch") try: tmp_dirpath = extract(package_filepath) except subprocess.CalledProcessError: print( utils.make_warning( f"Problems during the parsing of the package: {package_filepath}" )) print(utils.make_warning(f"Probably format not supported (yet)")) return # find and add ld ld_search_paths = [ os.path.join(tmp_dirpath, subpath) for subpath in ( "lib/aarch64-linux-gnu/ld-*.so", "lib/arm-linux-gnueabihf/ld-*.so", "lib/arm-linux-gnueabi/ld-*.so", "lib/i386-linux-gnu/ld-*.so", "lib/x86_64-linux-gnu/ld-*.so", "usr/lib/ld-*.so", ) ] new_ld_filename = f"ld-{libc_architecture}-{libc_version}-{libc_patch}.so" new_ld_filepath = _find_matching_file_and_add_to_db( ld_search_paths, dest_dirpath, new_ld_filename) # find and add libc libc_search_paths = [ os.path.join(tmp_dirpath, subpath) for subpath in ( "lib/aarch64-linux-gnu/libc-*.so", "lib/arm-linux-gnueabihf/libc-*.so", "lib/arm-linux-gnueabi/libc-*.so", "lib/i386-linux-gnu/libc-*.so", "lib/x86_64-linux-gnu/libc-*.so", "usr/lib/libc-*.so", ) ] new_libc_filename = f"libc-{libc_architecture}-{libc_version}-{libc_patch}.so" new_libc_filepath = _find_matching_file_and_add_to_db( libc_search_paths, dest_dirpath, new_libc_filename) # find and add libc symbols libc_symbols_search_paths = [ os.path.join(tmp_dirpath, subpath) for subpath in ( "usr/lib/debug/lib/i386-linux-gnu/libc-*.so", "usr/lib/debug/lib/x86_64-linux-gnu/libc-*.so", ) ] new_libc_symbols_filename = ( f"libc-{libc_architecture}-{libc_version}-{libc_patch}.so.debug") new_libc_symbols_filepath = _find_matching_file_and_add_to_db( libc_symbols_search_paths, dest_dirpath, new_libc_symbols_filename) if not any( (new_ld_filepath, new_libc_filepath, new_libc_symbols_filepath)): print( utils.make_warning( f"Skipping: the package seems to not contain a dynamic loader, libc or debug symbols." )) return # keep the package, it may be useful later shutil.copy2(package_filepath, dest_dirpath) print(utils.make_bright("</add>"))