def process_pyinstaller_archive(ar, prog): # See utils/cliutils/archive_viewer.py # If PyInstaller is not installed, do nothing try: from PyInstaller.archive.readers import CArchiveReader, NotAnArchiveError except ImportError: return # Attempt to open the program as PyInstaller archive try: pyi_ar = CArchiveReader(prog) except: # Silence all PyInstaller exceptions here return logging.info("Opened PyInstaller archive!") # Create a temporary directory # TODO PY3: Use tempfile.TemporaryDirectory and cleanup() tmpdir = tempfile.mkdtemp(prefix='staticx-pyi-') try: # Process the archive, looking at all shared libs for n, item in enumerate(pyi_ar.toc.data): (dpos, dlen, ulen, flag, typcd, name) = item # Only process binary files # See xformdict in PyInstaller.building.api.PKG if typcd != 'b': continue # Extract it to a temporary location x, data = pyi_ar.extract(n) tmppath = os.path.join(tmpdir, name) directory = os.path.dirname(tmppath) if not os.path.exists(directory): os.makedirs(directory) logging.debug("Extracting to {}".format(tmppath)) with open(tmppath, 'wb') as f: f.write(data) # Silence "you do not have execution permission" warning from ldd make_executable(tmppath) # Add any missing libraries to our archive for libpath in get_shobj_deps(tmppath): lib = os.path.basename(libpath) if lib in ar.libraries: logging.debug("{} already in staticx archive".format(lib)) continue if pyi_ar.toc.find(lib) != -1: logging.debug( "{} already in pyinstaller archive".format(lib)) continue ar.add_library(libpath) finally: shutil.rmtree(tmpdir)
def extract_pyc_header(self, arch: CArchiveReader) -> bytes: ndx = arch.toc.find('pyimod01_os_path') x, data = arch.extract(ndx) with io.BytesIO(data) as buffer: buffer.seek(0) float_version, _, _, _, _, _, _ = load_module_from_file_object( buffer) if float_version >= 3.7: return data[:16] if float_version >= 3.3: return data[:12] return data[:8]
def extract_to(self, extracted_dir: Path): extracted_dir.mkdir(parents=True, exist_ok=True) arch = CArchiveReader(self.path) pyc_header = self.extract_pyc_header(arch) for dpos, dlen, ulen, flag, typcd, nm in arch.toc.data: ndx = arch.toc.find(nm) x, data = arch.extract(ndx) """ 'm': 'PYMODULE', 's': 'PYSOURCE', 'b': 'EXTENSION', 'z': 'PYZ', 'a': 'PKG', 'x': 'DATA', 'b': 'BINARY', 'Z': 'ZIPFILE', 'b': 'EXECUTABLE', 'd': 'DEPENDENCY', 'o': 'OPTION', """ name = nm if typcd in ('s', 'm'): name += '.pyc' print(name) file_path = extracted_dir / name file_path.parent.mkdir(parents=True, exist_ok=True) with (file_path).open('wb+') as fp: if typcd == 's': fp.write(pyc_header) fp.write(data) if typcd in ('s', 'm'): self.extract_source(fp, extracted_dir / (nm + '.py'))