Skip to content

Instantly share code, notes, and snippets.

@deqline
Created April 21, 2022 08:25
Show Gist options
  • Save deqline/44b9e47b2360564a3aa6261d091dc261 to your computer and use it in GitHub Desktop.
Save deqline/44b9e47b2360564a3aa6261d091dc261 to your computer and use it in GitHub Desktop.
Easily decompile `.pyc` and `.py` files for the latest python version.
####
# 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