Created
April 21, 2022 08:25
-
-
Save deqline/44b9e47b2360564a3aa6261d091dc261 to your computer and use it in GitHub Desktop.
Easily decompile `.pyc` and `.py` files for the latest python version.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#### | |
# Easily decompile `.pyc` and `.py` files for the latest python version. | |
# Basic structure taken from https://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html, fully updated and added functionality. | |
# Added complete disassembly: offsets, opcodes, opnames... | |
#### | |
import dis, marshal, struct, sys, time, types, py_compile | |
def to_int(bytes): | |
return struct.unpack("i",bytes)[0] | |
def bytes2str(byte_seq): | |
str_seq = '' | |
for b in byte_seq: | |
print(" %X" % b,end="") | |
return str_seq | |
def calc_hex_length(number): | |
return len(hex(number)[2:]) | |
def show_file(f): | |
magic = struct.unpack(">i",f.read(4))[0] | |
bitfield = to_int(f.read(4)) | |
if(bitfield != 0): exit("Cannot parse hash-based pyc") | |
moddate = to_int(f.read(4)) | |
modtime = time.asctime(time.localtime(moddate)) | |
filesize = to_int(f.read(4)) | |
indent = ' ' | |
print("HEADER") | |
print("%smagic %X" % (indent, magic)) | |
print("%smoddate %s" % (indent, modtime)) | |
print("%sfilesize %X" % (indent, filesize)) | |
code = marshal.load(f) | |
show_code(code) | |
def show_code(code, indent=''): | |
indent += ' ' | |
if(code.co_name == "<module>"): | |
print("FILE") | |
print("%sfilename %r" % (indent, code.co_filename)) | |
print("CODE") | |
print("%sname %r" % (indent, code.co_name)) | |
print("%sfirstlineno %d" % (indent, code.co_firstlineno)) | |
print("%sargcount %d" % (indent, code.co_argcount)) | |
print("%snlocals %d" % (indent, code.co_nlocals)) | |
print("%sstacksize %d" % (indent, code.co_stacksize)) | |
print("%sflags %04x" % (indent, code.co_flags)) | |
print("%sdissassembly" % indent) | |
else: | |
print("%sdissassembly" % indent) | |
pass | |
additional_indent = " " | |
ez = False | |
prev_indent = 1 | |
for instr in dis.Bytecode(code): | |
instructions_args = "" | |
zero = "" | |
if len(hex(instr.offset)[2:]) == 1: | |
zero += "0" | |
print("%s%s%X: " % (indent*2, zero ,instr.offset), end="") | |
if(instr.argval != None): | |
instructions_args = "" | |
if isinstance(instr.argval, int): | |
if(calc_hex_length(instr.opcode) == 1): | |
print("0%X" % instr.opcode,end="") | |
else: | |
print("%X" % instr.opcode,end="") | |
zero = "" | |
if len(hex(instr.argval)[2:]) == 1: | |
zero += "0" | |
if(calc_hex_length(instr.argval) > 2): | |
nibble_length = calc_hex_length(instr.argval) | |
byte_length = ( nibble_length + 1 ) // 2 | |
print(" ",end="") | |
for byte in (instr.argval).to_bytes(byte_length, "big"): | |
zero = "" | |
if len(hex(byte)[2:]) == 1: | |
zero += "0" | |
print("%s%X " % (zero,byte), end="") | |
print("%s %s %s" % ( ( (prev_indent-byte_length)*" "), instr.opname, instr.argval)) | |
continue | |
else: | |
print(" %s%X " % (zero,instr.argval), end="") | |
print("%s %s %s" % ( ( (prev_indent-1)*" "), instr.opname, instr.argval)) | |
elif(isinstance(instr.argval, str)): | |
print("%X" % instr.opcode,end="") | |
instructions_args = bytes2str(instr.argval.encode()) | |
temp = len(instr.argval.encode()) | |
if(temp < prev_indent): | |
print(" %s %s %s" % ((prev_indent-temp)*" ",instr.opname, instr.argval)) | |
else: | |
if(ez): | |
print(" %s %s %s" % (((temp-prev_indent))*" ", instr.opname, instr.argval)) | |
prev_indent = temp | |
else: | |
print(" %s %s %s" % ("", instr.opname, instr.argval)) | |
prev_indent = temp | |
ez = True | |
elif(type(instr.argval) == types.CodeType): | |
print(" '%s' {" % instr.argval.co_name) | |
show_code(instr.argval, indent+' ') | |
print("%s}" % (indent*2)) | |
else: | |
exit("%sfailure parsing instruction %s" % (indent, type(instr.argval))) | |
else: | |
zero = "" | |
if(calc_hex_length(instr.opcode) == 1): | |
zero = "0" | |
print("%s%X %s %s%s %s" % (zero, instr.opcode, instructions_args , (prev_indent)*" ", instr.opname, instr.argval)) | |
print("%sconsts" % indent) | |
# const var is a code object | |
for const in code.co_consts: | |
if type(const) == types.CodeType: | |
print("%s '%s'{" % (indent*2,const.co_name)) | |
show_code(const, indent+' ') | |
print("%s}," % (indent*2)) | |
pass | |
else: | |
print(" %s%r," % (indent, const)) | |
if(code.co_name == "<module>"): | |
print("%snames %r" % (indent, code.co_names)) | |
print("%svarnames %r" % (indent, code.co_varnames)) | |
print("%sfreevars %r" % (indent, code.co_freevars)) | |
print("%scellvars %r" % (indent, code.co_cellvars)) | |
else: | |
print("%svarnames %r" % (indent, code.co_varnames)) | |
print("%sfreevars %r" % (indent, code.co_freevars)) | |
def main(file_path): | |
if(file_path.lower().endswith('.py')): | |
dest_path = py_compile.compile(file_path) | |
elif(file_path.lower().endswith('.py')): | |
dest_path = file_path | |
else: | |
print("Usage: python dispyc.py .pyc/.py") | |
with open(dest_path, "rb") as f: | |
show_file(f) | |
main(sys.argv[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment