#!/usr/bin/env python3 import subprocess import sys import os import shutil import re import stat import tarfile run_script_linux = """ #!/bin/sh set -e script_path=`readlink -f $0` script_dir=`dirname $script_path` "$script_dir/$SO_LOADER" --library-path "$script_dir/libs" "$script_dir/$PROGRAM_NAME" "$@" """ def get_executable_dynamic_libraries(filepath): libs = [] process = subprocess.Popen(["ldd", filepath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = process.communicate() if process.returncode != 0: raise RuntimeError("Failed to execute ldd on executable %s, error:\n%s" % (filepath, stderr)) lines = stdout.splitlines() for line in lines: s = line.split() if len(s) >= 3 and b"=>" in s: lib_path = " ".join(x.decode("UTF-8") for x in s[2:-1]) if os.path.exists(lib_path): libs.append([os.path.realpath(lib_path), os.path.basename(lib_path)]) return libs def make_executable(filepath): mode = os.stat(filepath).st_mode os.chmod(filepath, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) def get_libnss_files(): libnss_pattern = re.compile("libnss.*\\.so.*") files = [] for filepath in os.listdir("/usr/lib"): if libnss_pattern.match(filepath): files.append(os.path.join("/usr/lib", filepath)) return files def main(): if len(sys.argv) <= 2: print("usage: %s executable_path destination_path" % sys.argv[0]) exit(1) os.makedirs(sys.argv[2], exist_ok=True) libs = get_executable_dynamic_libraries(sys.argv[1]) so_loader_pattern = re.compile("ld-linux-x86-64\\.so.*") so_loader = None for lib in libs: if so_loader_pattern.match(lib[1]): if so_loader != None: print("Unexpected error: found multiple so loaders, unable to recover") exit(2) so_loader = lib[1] if not so_loader: print("Unexpected error: no so loader found, unable to recover") exit(5) so_loader = os.path.join("libs", so_loader) libnss_files = get_libnss_files() for idx, libnss_file in enumerate(libnss_files): new_file_path = os.path.join(sys.argv[2], os.path.basename(libnss_file)) libnss_files[idx] = new_file_path if os.path.islink(libnss_file): target_name = os.path.basename(os.readlink(libnss_file)) os.symlink(target_name, new_file_path) else: shutil.copyfile(libnss_file, new_file_path) executable_filename = os.path.basename(sys.argv[1]) new_executable_path = os.path.join(sys.argv[2], executable_filename) shutil.copyfile(sys.argv[1], new_executable_path) make_executable(new_executable_path) print("Patching executable") process = subprocess.Popen(["patchelf", "--set-interpreter", so_loader, new_executable_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = process.communicate() if process.returncode != 0: print("Failed to execute patchelf --set-interpreter on executable %s, error: %s" % (new_executable_path, stderr)) exit(3) process = subprocess.Popen(["patchelf", "--set-rpath", "libs", new_executable_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = process.communicate() if process.returncode != 0: print("Failed to execute patchelf --set-rpath on executable %s, error: %s" % (new_executable_path, stderr)) exit(4) run_script_path = os.path.join(sys.argv[2], "run.sh") with open(run_script_path, "wb") as run_script: run_script.write(run_script_linux.replace("$SO_LOADER", so_loader).replace("$PROGRAM_NAME", "program").encode("UTF-8")) make_executable(run_script_path) package_name = new_executable_path + ".tar.gz" print("Creating archive %s" % os.path.basename(package_name)) with tarfile.open(package_name, "w:gz") as tar: for lib in libs: print("Adding shared library %s to package" % lib[0]) tar.add(lib[0], arcname=os.path.join("libs", lib[1])) for libnss_file in libnss_files: print("Adding libnss shared library %s to package" % libnss_file) libnss_name = os.path.basename(libnss_file) tar.add(libnss_file, arcname=os.path.join("libs", libnss_name)) print("Adding executable %s to package" % sys.argv[1]) tar.add(new_executable_path, arcname="program") print("Adding run script %s to package" % run_script_path) tar.add(run_script_path, arcname=executable_filename) print("Removing temporary files") os.remove(new_executable_path) os.remove(run_script_path) for libnss_file in libnss_files: os.remove(libnss_file) print("Package has been created at %s" % package_name) if __name__ == "__main__": main()