Skip to content

Instantly share code, notes, and snippets.

@motebaya
Created April 17, 2024 18:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save motebaya/8e2abd463294c52cb9889bab8a2643e9 to your computer and use it in GitHub Desktop.
Save motebaya/8e2abd463294c52cb9889bab8a2643e9 to your computer and use it in GitHub Desktop.
simple script that help me to RE python bytecode
#!/usr/bin/python3.11
# it help me for RE, @github.com/motebaya
# useless tool :) tools 17.04.2024
from typing import Dict
from xdis import magics
import re, os, types, marshal, argparse
class Asmtool:
def __init__(self) -> None:
self.magic = []
self.index = 1
def dump_magic(self) -> Dict[str, int]:
"""dump magic header from
:return Dict[str, int]: magics list
"""
m = magics.magics
for vers in map(lambda x: (str(x[0]),magics.magic2int(x[1])), zip(m.keys(), m.values())):
if re.sub(r'([\./])*?', str(), vers[0]).isdigit() and vers[0].startswith('3'):
self.magic.append(vers)
del m
self.magic = dict(self.magic)
return self.magic
def make_header(self, vers: str) -> bytes:
"""create magic headers by py version
it based from:
- https://github.com/python/cpython/blob/fda8cd1fd38a12e17b1db1775d54ff0fa4d886b8/Lib/py_compile.py#L79
- https://github.com/python/cpython/blob/fda8cd1fd38a12e17b1db1775d54ff0fa4d886b8/Lib/importlib/_bootstrap_external.py#L764
:param str vers: py version str
:return bytes: header
"""
pack = (lambda x: (int(x) & 0xFFFFFFFF).to_bytes(4, 'little'))
stat = os.stat(__file__)
if len(self.magic) == 0:
self.dump_magic()
data = bytearray(magics.int2magic(self.magic[str(vers)]))
data.extend(pack(0))
data.extend(pack(stat.st_ctime))
data.extend(pack(stat.st_size))
return bytes(data)
def quick_header(self, vers: str) -> bytes:
"""quick get created headers/
:param str vers: py str version
:return bytes: header
"""
match float(vers):
case 3.8:
return b'U\r\r\n\x00\x00\x00\x00:M?^\x99\x02\x00\x00'
case 3.9:
return b'a\r\r\n\x00\x00\x00\x00B4\x04`\xa8\x00\x00\x00'
case 3.10:
return b'o\r\r\n\x00\x00\x00\x00\xf0\xc1\xdaau\x00\x00\x00'
case 3.11:
return b'\xa7\r\r\n\x00\x00\x00\x00\x06Wie\xd3\x00\x00\x00'
def loadcode(self, pycfile: str) -> types.CodeType:
"""load python bytecode file to code object
:param str pycfile: pycfile
:raises FileNotFoundError: no file
:raises RuntimeError: potential bad marshal
:return types.CodeType: code
"""
if not os.path.exists(pycfile):
raise FileNotFoundError(f'no file {pycfile}')
try:
return marshal.loads(open(pycfile, 'rb').read().strip()[16:])
except ValueError as e:
exit(
f" {e}\n try change shebang on: {__file__} to current python version!"
)
def dump(self, code: types.CodeType, fpath: str) -> None:
"""save code object to pyc(bytecode) file's.
:param types.CodeType code: from consts attribute/pyc file.
:param str fpath: path for save
"""
for code in code.co_consts:
if isinstance(code, types.CodeType):
name = '{}/{}.pyc'.format(fpath, code.co_name)
print(f" -- ({self.index}) saving to -> {name}")
with open(name, 'wb') as f:
f.write(self.quick_header(3.11) + marshal.dumps(code))
self.index +=1
self.dump(code, fpath)
else:
continue
def dump_code(self, pycfile: str) -> None:
"""recursive save code object to pycfile.
:param str pycfile: pycfile
"""
fpath = 'object_{}'.format(pycfile[:pycfile.find('.pyc')])
if not os.path.exists(fpath):
os.mkdir(fpath)
self.dump(self.loadcode(pycfile), fpath)
if __name__=="__main__":
asm = Asmtool()
asm.dump_magic()
args = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
description="\n Util RE: gist.github.com/motebaya \n")
args.add_argument('-f','--func',help='dump all code object to pyc files',metavar='')
args.add_argument('--quick-header', help="get quick header pyc: 3.8,3.9,3.10,3.11", metavar="")
args.add_argument('--make-header', help='create pyc header based python version: [{}..{}]'.format(
list(asm.magic.keys())[0], list(asm.magic.keys())[-1]
), metavar="")
arg = args.parse_args()
if arg.func:
asm.dump_code(
arg.func
)
elif arg.quick_header:
print("```\n{}\n```".format(
asm.quick_header(
arg.quick_header
)
))
exit(1)
elif arg.make_header:
print("```\n{}\n```".format(
asm.make_header(
arg.make_header
)
))
exit(1)
else:
args.print_help()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment