def main(conversion_type, input_pyc, output_pyc): """Convert Python bytecode from one version to another. INPUT_PYC contains the input bytecode path name OUTPUT_PYC contians the output bytecode path name if supplied The --conversion type option specifies what conversion to do. Note: there are a very limited set of conversions currently supported. Help out and write more!""" shortname = osp.basename(input_pyc) if shortname.endswith('.pyc'): shortname = shortname[:-4] src_version = conversion_to_version(conversion_type, is_dest=False) dest_version = conversion_to_version(conversion_type, is_dest=True) if output_pyc is None: output_pyc = "%s-%s.pyc" % (shortname, dest_version) if conversion_type in UPWARD_COMPATABLE: copy_magic_into_pyc(input_pyc, output_pyc, src_version, dest_version) return temp_asm = NamedTemporaryFile('w', suffix='.pyasm', prefix=shortname, delete=False) (filename, co, version, timestamp, magic_int) = disassemble_file(input_pyc, temp_asm, asm_format=True) temp_asm.close() assert version == float(src_version), ( "Need Python %s bytecode; got bytecode for version %s" % (src_version, version)) asm = asm_file(temp_asm.name) new_asm = transform_asm(asm, conversion_type, src_version, dest_version) os.unlink(temp_asm.name) write_pycfile(output_pyc, new_asm)
def check_for_password_file(self): self.potential_keys = [] if hasattr(self, "archive_path"): dir_of_pyz = self.archive_path.parent else: dir_of_pyz = Path.cwd() key_file = dir_of_pyz / "pyimod00_crypto_key.pyc" if key_file.exists(): self.encrypted = True logger.debug( f"[+] Found ZlibArchive encryption key file at path {key_file}" ) crypto_key_filename: str # full path of try: ( crypto_key_filename, crypto_key_co, crypto_key_python_version, crypto_key_compilation_timestamp, crypto_key_magic_int, crypto_key_is_pypy, crypto_key_source_size, crypto_key_sip_hash, ) = disassemble_file(str(key_file), outstream=open(os.devnull, "w")) except Exception as e: logger.warning( f"[!] Could not disassemble file {key_file}. Received error: {e}" ) else: self.compilation_time = datetime.fromtimestamp( crypto_key_compilation_timestamp) for const_string in crypto_key_co.co_consts: if const_string and len(const_string) == 16: self.potential_keys.append(const_string) # If we couldn't decompile the file to see the consts, lets just search the raw bytes of the file # for the password if not self.potential_keys: with key_file.open("rb") as file_ptr: file_strings = utils.parse_for_strings(file_ptr.read()) s: str for s in file_strings: if len(s) >= 16 and "pyimod00_crypto_key" not in s: while len(s) >= 16: self.potential_keys.append(s[0:16]) s = s[1:] logger.info( f"[*] Found these potential PyInstaller PYZ Archive encryption keys: {self.potential_keys}" ) if not self.potential_keys: logger.error( f"[*] Encryption key file detected, however no password was able to be retrieved." )
def main(asm, show_bytes, header, files): """Disassembles a Python bytecode file. We handle bytecode for virtually every release of Python and some releases of PyPy. The version of Python in the bytecode doesn't have to be the same version as the Python interpreter used to run this program. For example, you can disassemble Python 3.6.1 bytecode from Python 2.7.13 and vice versa. """ Usage_short = """usage: %s [--asm] -i FILE... %s --version Type -h for for full help.""" % (program, program) if not (2.5 <= PYTHON_VERSION <= 3.7): sys.stderr( print("This works on Python version 2.5..3.7; have %s" % PYTHON_VERSION)) if not len(files): sys.stderr.write("No file(s) given..\n") print(Usage_short, file=sys.stderr) sys.exit(1) for path in files: # Some sanity checks if not osp.exists(path): sys.stderr.write("File name: '%s' doesn't exist\n" % path) continue elif not osp.isfile(path): sys.stderr.write("File name: '%s' isn't a file\n" % path) continue elif osp.getsize(path) < 50: sys.stderr.write( "File name: '%s (%d bytes)' is too short to be a valid pyc file\n" % (path, osp.getsize(path))) continue disassemble_file(path, sys.stdout, asm, header, show_bytes) return
def main(asm, show_bytes, header, files): """Disassembles a Python bytecode file. We handle bytecode for virtually every release of Python and some releases of PyPy. The version of Python in the bytecode doesn't have to be the same version as the Python interpreter used to run this program. For example, you can disassemble Python 3.6.9 bytecode from Python 2.7.15 and vice versa. """ if not (2.7 <= PYTHON_VERSION <= 3.9): if 2.4 <= PYTHON_VERSION <= 2.6: sys.stderr.write( "This code works on 2.7..3.8. code that works for %s can be found in the python-2.4 branch\n" % PYTHON_VERSION ) sys.exit(1) sys.stderr.write( "This works on Python version 2.7..3.8; have %s.\n" % PYTHON_VERSION ) sys.exit(2) for path in files: # Some sanity checks if not osp.exists(path): sys.stderr.write("File name: '%s' doesn't exist\n" % path) continue elif not osp.isfile(path): sys.stderr.write("File name: '%s' isn't a file\n" % path) continue elif osp.getsize(path) < 50: sys.stderr.write( "File name: '%s (%d bytes)' is too short to be a valid pyc file\n" % (path, osp.getsize(path)) ) continue disassemble_file(path, sys.stdout, asm, header, show_bytes) return
def main(asm, show_bytes, header, files): """Disassembles a Python bytecode file. We handle bytecode for virtually every release of Python and some releases of PyPy. The version of Python in the bytecode doesn't have to be the same version as the Python interpreter used to run this program. For example, you can disassemble Python 3.6.1 bytecode from Python 2.7.13 and vice versa. """ Usage_short = """usage: %s [--asm] -i FILE... %s --version Type -h for for full help.""" % (program, program) if not (2.5 <= PYTHON_VERSION <= 3.8): sys.stderr(print("This works on Python version 2.5..3.8; have %s" % PYTHON_VERSION)) if not len(files): sys.stderr.write("No file(s) given..\n") print(Usage_short, file=sys.stderr) sys.exit(1) for path in files: # Some sanity checks if not osp.exists(path): sys.stderr.write("File name: '%s' doesn't exist\n" % path) continue elif not osp.isfile(path): sys.stderr.write("File name: '%s' isn't a file\n" % path) continue elif osp.getsize(path) < 50: sys.stderr.write("File name: '%s (%d bytes)' is too short to be a valid pyc file\n" % (path, osp.getsize(path))) continue disassemble_file(path, sys.stdout, asm, header, show_bytes) return
def do_tests( src_dir, patterns, target_dir, start_with=None, do_verify=False, max_files=800, do_compile=False, verbose=False, ): def visitor(files, dirname, names): files.extend([ os.path.normpath(os.path.join(dirname, n)) for n in names for pat in patterns if fnmatch(n, pat) ]) def file_matches(files, root, basenames, patterns): files.extend([ os.path.normpath(os.path.join(root, n)) for n in basenames for pat in patterns if fnmatch(n, pat) ]) files = [] if do_compile: for root, dirs, basenames in os.walk(src_dir): file_matches(files, root, basenames, PY) for sfile in files: py_compile.compile(sfile) pass pass files = [] pass cwd = os.getcwd() os.chdir(src_dir) if PYTHON3: for root, dirname, names in os.walk(os.curdir): files.extend([ os.path.normpath(os.path.join(root, n)) for n in names for pat in patterns if fnmatch(n, pat) ]) pass pass else: os.path.walk(os.curdir, visitor, files) files.sort() if start_with: try: start_with = files.index(start_with) files = files[start_with:] print(">>> starting with file", files[0]) except ValueError: pass if len(files) > max_files: files = [file for file in files if not "site-packages" in file] files = [file for file in files if not "test" in file] if len(files) > max_files: files = files[:max_files] pass elif len(files) == 0: print("No files found\n") os.chdir(cwd) return output = open(os.devnull, "w") # output = sys.stdout start_time = time.time() print(time.ctime()) for i, bc_file in enumerate(files): if verbose: print(os.path.join(src_dir, bc_file)) if sys.version_info >= (3, 4, 0) and bc_file.endswith(".py"): check_bc_file = check_object_path(bc_file) if not osp.exists(check_bc_file): basename = osp.basename(bc_file)[0:-3] new_bc_file = tempfile.mkstemp(prefix=basename + "-", suffix=".pyc", text=False)[1] py_compile.compile(bc_file, cfile=new_bc_file, doraise=True) bc_file = new_bc_file bc_filename, co, version, ts, magic = main.disassemble_file( bc_file, output) if do_verify: file = co.co_filename verify_file(file, bc_filename) if i % 100 == 0 and i > 0: print("Processed %d files" % (i)) print("Processed %d files, total" % (i + 1)) print(time.ctime()) elapsed_time = time.time() - start_time print("%g seconds" % elapsed_time) os.chdir(cwd)
def do_tests(src_dir, obj_patterns, target_dir, opts): def file_matches(files, root, basenames, patterns): files.extend( [os.path.normpath(os.path.join(root, n)) for n in basenames for pat in patterns if fnmatch(n, pat)]) files = [] # Change directories so use relative rather than # absolute paths. This speeds up things, and allows # main() to write to a relative-path destination. cwd = os.getcwd() os.chdir(src_dir) if opts['do_compile']: compiled_version = opts['compiled_version'] if compiled_version and PYTHON_VERSION != compiled_version: sys.stderr.write("Not compiling: desired Python version is %s " "but we are running %s\n" % (compiled_version, PYTHON_VERSION)) else: for root, dirs, basenames in os.walk(src_dir): file_matches(files, root, basenames, PY) for sfile in files: py_compile.compile(sfile) pass pass files = [] pass pass for root, dirs, basenames in os.walk('.'): # Turn root into a relative path dirname = root[2:] # 2 = len('.') + 1 file_matches(files, dirname, basenames, obj_patterns) if not files: sys.stderr.write("Didn't come up with any files to test! Try with --compile?\n") exit(1) os.chdir(cwd) files.sort() if opts['start_with']: try: start_with = files.index(opts['start_with']) files = files[start_with:] print('>>> starting with file', files[0]) except ValueError: pass output = open(os.devnull,"w") # output = sys.stdout print(time.ctime()) print('Source directory: ', src_dir) cwd = os.getcwd() os.chdir(src_dir) try: for infile in files: main.disassemble_file(infile, output) if opts['do_verify']: pass # print("Need to do something here to verify %s" % infile) # msg = verify.verify_file(infile, outfile) # if failed_files != 0: # exit(2) # elif failed_verify != 0: # exit(3) except (KeyboardInterrupt, OSError): print() exit(1) os.chdir(cwd)
def do_tests(src_dir, patterns, target_dir, start_with=None, do_verify=False, max_files=800, do_compile=False, verbose=False): def visitor(files, dirname, names): files.extend( [os.path.normpath(os.path.join(dirname, n)) for n in names for pat in patterns if fnmatch(n, pat)]) def file_matches(files, root, basenames, patterns): files.extend( [os.path.normpath(os.path.join(root, n)) for n in basenames for pat in patterns if fnmatch(n, pat)]) files = [] if do_compile: for root, dirs, basenames in os.walk(src_dir): file_matches(files, root, basenames, PY) for sfile in files: py_compile.compile(sfile) pass pass files = [] pass cwd = os.getcwd() os.chdir(src_dir) if PYTHON3: for root, dirname, names in os.walk(os.curdir): files.extend( [os.path.normpath(os.path.join(root, n)) for n in names for pat in patterns if fnmatch(n, pat)]) pass pass else: os.path.walk(os.curdir, visitor, files) files.sort() if start_with: try: start_with = files.index(start_with) files = files[start_with:] print('>>> starting with file', files[0]) except ValueError: pass if len(files) > max_files: files = [file for file in files if not 'site-packages' in file] files = [file for file in files if not 'test' in file] if len(files) > max_files: files = files[:max_files] pass elif len(files) == 0: print("No files found\n") os.chdir(cwd) return output = open(os.devnull,"w") # output = sys.stdout start_time = time.time() print(time.ctime()) for i, bc_file in enumerate(files): if verbose: print(os.path.join(src_dir, bc_file)) if sys.version_info >= (3, 4, 0) and bc_file.endswith('.py'): check_bc_file = check_object_path(bc_file) if not osp.exists(check_bc_file): basename = osp.basename(bc_file)[0:-3] new_bc_file = tempfile.mkstemp(prefix=basename + '-', suffix='.pyc', text=False)[1] py_compile.compile(bc_file, cfile=new_bc_file, doraise=True) bc_file = new_bc_file bc_filename, co, version, ts, magic = main.disassemble_file(bc_file, output) if do_verify: file = co.co_filename verify_file(file, bc_filename) if i % 100 == 0 and i > 0: print("Processed %d files" % (i)) print("Processed %d files, total" % (i+1)) print(time.ctime()) elapsed_time = time.time() - start_time print("%g seconds" % elapsed_time) os.chdir(cwd)
def do_tests(src_dir, obj_patterns, target_dir, opts): def file_matches(files, root, basenames, patterns): files.extend([ os.path.normpath(os.path.join(root, n)) for n in basenames for pat in patterns if fnmatch(n, pat) ]) files = [] # Change directories so use relative rather than # absolute paths. This speeds up things, and allows # main() to write to a relative-path destination. cwd = os.getcwd() os.chdir(src_dir) if opts['do_compile']: compiled_version = opts['compiled_version'] if compiled_version and PYTHON_VERSION != compiled_version: sys.stderr.write("Not compiling: desired Python version is %s " "but we are running %s\n" % (compiled_version, PYTHON_VERSION)) else: for root, dirs, basenames in os.walk(src_dir): file_matches(files, root, basenames, PY) for sfile in files: py_compile.compile(sfile) pass pass files = [] pass pass for root, dirs, basenames in os.walk('.'): # Turn root into a relative path dirname = root[2:] # 2 = len('.') + 1 file_matches(files, dirname, basenames, obj_patterns) if not files: sys.stderr.write( "Didn't come up with any files to test! Try with --compile?\n") exit(1) os.chdir(cwd) files.sort() if opts['start_with']: try: start_with = files.index(opts['start_with']) files = files[start_with:] print('>>> starting with file', files[0]) except ValueError: pass output = open(os.devnull, "w") # output = sys.stdout print(time.ctime()) print('Source directory: ', src_dir) cwd = os.getcwd() os.chdir(src_dir) try: for infile in files: main.disassemble_file(infile, output) if opts['do_verify']: pass # print("Need to do something here to verify %s" % infile) # msg = verify.verify_file(infile, outfile) # if failed_files != 0: # exit(2) # elif failed_verify != 0: # exit(3) except (KeyboardInterrupt, OSError): print() exit(1) os.chdir(cwd)
def test_get_jump_targets(): my_dir = osp.dirname(osp.abspath(__file__)) # Python 2.7 code code = bug708901.__code__ # FIXME: Consider loading from a file like below to remove # dependence on running interpreter if PYTHON_VERSION == 2.7: # 19: 0 SETUP_LOOP 23 (to 26) # 3 LOAD_GLOBAL 0 (range) # 6 LOAD_CONST 1 (1) # # 20: 9 LOAD_CONST 2 (10) # 12 CALL_FUNCTION 2 (2 positional, 0 keyword pair) # 15 GET_ITER # >> 16 FOR_ITER 6 (to 25) # 19 STORE_FAST 0 (res) # # 21: 22 JUMP_ABSOLUTE 16 (to 16) # >> 25 POP_BLOCK # >> 26 LOAD_CONST 0 (None) # 29 RETURN_VALUE offset_map = opcode_27.get_jump_target_maps(code, opcode_27) expected = { 3: [0], 6: [3], 9: [6], 12: [9], 15: [12], 16: [15, 22], 19: [16], 22: [19], 25: [16], 26: [0, 25], 29: [26] } assert expected == offset_map offsets = opcode_27.get_jump_targets(code, opcode_27) assert offsets == [26, 25, 16] test_pyc = my_dir + '/../test/bytecode_2.7/01_dead_code.pyc' (version, timestamp, magic_int, co, pypy, source_size) = load_module(test_pyc) code = co.co_consts[0] offsets = opcode_27.get_jump_targets(code, opcode_27) assert [10] == offsets # from xdis.main import disassemble_file # print('\n') # disassemble_file(test_pyc) # 2: 0 LOAD_FAST 0 (a) # 3 POP_JUMP_IF_FALSE 10 (to 10) # # 3: 6 LOAD_CONST 1 (5) # 9 RETURN_VALUE # # 5: >> 10 LOAD_CONST 2 (6) # 13 RETURN_VALUE # 14 LOAD_CONST 0 (None) # 17 RETURN_VALUE offset_map = opcode_27.get_jump_target_maps(code, opcode_27) # print(offset_map) expect = {3: [0], 6: [3], 9: [6], 10: [3], 13: [10], 17: [14]} assert expect == offset_map # Python 3.6 code wordcode # ------------------------ test_pyc = my_dir + '/../test/bytecode_3.6/01_dead_code.pyc' (version, timestamp, magic_int, co, pypy, source_size) = load_module(test_pyc) code = co.co_consts[0] # 2: 0 LOAD_FAST 0 (a) # 2 POP_JUMP_IF_FALSE 8 (to 8) # # 3: 4 LOAD_CONST 1 (5) # 6 RETURN_VALUE # # 5: 8 LOAD_CONST 2 (6) # 10 RETURN_VALUE # 12 LOAD_CONST 0 (None) # 14 RETURN_VALUE offsets = opcode_36.get_jump_targets(code, opcode_36) assert offsets == [8] from xdis.main import disassemble_file print('\n') disassemble_file(test_pyc) offset_map = opcode_36.get_jump_target_maps(code, opcode_36) expect = {2: [0], 4: [2], 6: [4], 8: [2], 10: [8], 14: [12]} assert expect == offset_map
def test_get_jump_targets(): my_dir = osp.dirname(osp.abspath(__file__)) # Python 2.7 code code = bug708901.__code__ # FIXME: Consider loading from a file like below to remove # dependence on running interpreter if PYTHON_VERSION == 2.7: # 19: 0 SETUP_LOOP 23 (to 26) # 3 LOAD_GLOBAL 0 (range) # 6 LOAD_CONST 1 (1) # # 20: 9 LOAD_CONST 2 (10) # 12 CALL_FUNCTION 2 (2 positional, 0 keyword pair) # 15 GET_ITER # >> 16 FOR_ITER 6 (to 25) # 19 STORE_FAST 0 (res) # # 21: 22 JUMP_ABSOLUTE 16 (to 16) # >> 25 POP_BLOCK # >> 26 LOAD_CONST 0 (None) # 29 RETURN_VALUE offset_map = opcode_27.get_jump_target_maps(code, opcode_27) expected = { 3: [0], 6: [3], 9: [6], 12: [9], 15: [12], 16: [15, 22], 19: [16], 22: [19], 25: [16], 26: [0, 25], 29: [26]} assert expected == offset_map offsets = opcode_27.get_jump_targets(code, opcode_27) assert offsets == [26, 25, 16] test_pyc = my_dir +'/../test/bytecode_2.7/01_dead_code.pyc' (version, timestamp, magic_int, co, pypy, source_size) = load_module(test_pyc) code = co.co_consts[0] offsets = opcode_27.get_jump_targets(code, opcode_27) assert [10] == offsets # from xdis.main import disassemble_file # print('\n') # disassemble_file(test_pyc) # 2: 0 LOAD_FAST 0 (a) # 3 POP_JUMP_IF_FALSE 10 (to 10) # # 3: 6 LOAD_CONST 1 (5) # 9 RETURN_VALUE # # 5: >> 10 LOAD_CONST 2 (6) # 13 RETURN_VALUE # 14 LOAD_CONST 0 (None) # 17 RETURN_VALUE offset_map = opcode_27.get_jump_target_maps(code, opcode_27) # print(offset_map) expect = {3: [0], 6: [3], 9: [6], 10: [3], 13: [10], 17: [14]} assert expect == offset_map # Python 3.6 code wordcode # ------------------------ test_pyc = my_dir +'/../test/bytecode_3.6/01_dead_code.pyc' (version, timestamp, magic_int, co, pypy, source_size) = load_module(test_pyc) code = co.co_consts[0] # 2: 0 LOAD_FAST 0 (a) # 2 POP_JUMP_IF_FALSE 8 (to 8) # # 3: 4 LOAD_CONST 1 (5) # 6 RETURN_VALUE # # 5: 8 LOAD_CONST 2 (6) # 10 RETURN_VALUE # 12 LOAD_CONST 0 (None) # 14 RETURN_VALUE offsets = opcode_36.get_jump_targets(code, opcode_36) assert offsets == [8] from xdis.main import disassemble_file print('\n') disassemble_file(test_pyc) offset_map = opcode_36.get_jump_target_maps(code, opcode_36) expect = {2: [0], 4: [2], 6: [4], 8: [2], 10: [8], 14: [12]} assert expect == offset_map
def do_tests(src_dir, patterns, target_dir, start_with=None, do_verify=False, max_files=800, do_compile=False, verbose=False): def visitor(files, dirname, names): files.extend([ os.path.normpath(os.path.join(dirname, n)) for n in names for pat in patterns if fnmatch(n, pat) ]) def file_matches(files, root, basenames, patterns): files.extend([ os.path.normpath(os.path.join(root, n)) for n in basenames for pat in patterns if fnmatch(n, pat) ]) files = [] if do_compile: for root, dirs, basenames in os.walk(src_dir): file_matches(files, root, basenames, PY) for sfile in files: py_compile.compile(sfile) pass pass files = [] pass cwd = os.getcwd() os.chdir(src_dir) if PYTHON3: for root, dirname, names in os.walk(os.curdir): files.extend([ os.path.normpath(os.path.join(root, n)) for n in names for pat in patterns if fnmatch(n, pat) ]) pass pass else: os.path.walk(os.curdir, visitor, files) files.sort() if start_with: try: start_with = files.index(start_with) files = files[start_with:] print('>>> starting with file', files[0]) except ValueError: pass if len(files) > max_files: files = [file for file in files if not 'site-packages' in file] files = [file for file in files if not 'test' in file] if len(files) > max_files: files = files[:max_files] pass elif len(files) == 0: print("No files found\n") os.chdir(cwd) return output = open(os.devnull, "w") # output = sys.stdout start_time = time.time() print(time.ctime()) for i, bc_file in enumerate(files): if verbose: print(os.path.join(src_dir, bc_file)) bc_filename, co, version, ts, magic = main.disassemble_file( bc_file, output) if do_verify: file = co.co_filename verify_file(file, bc_filename) if i % 100 == 0 and i > 0: print("Processed %d files" % (i)) print("Processed %d files, total" % (i + 1)) print(time.ctime()) elapsed_time = time.time() - start_time print("%g seconds" % elapsed_time) os.chdir(cwd)