| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- #!/usr/bin/env python
- # Copyright Hagen Fritsch, 2012, License: GPL-2.0
- # Adaption and generalization for xdis use by Rocky Bernstein
- # Copyright 2019-2020, 2024
- # Dropbox Python bytecode decryption tool for Dropbox versions of 1.1x
- # (and possibly earlier) which uses Python bytecode 2.5.
- import struct
- import types
- from types import CodeType
- from typing import Dict
- import xdis.marsh as xmarshal
- # FIXME to use codeType2Portable
- # from xdis.codetype import codeType2Portable
- from xdis.codetype.code20 import Code2Compat
- from xdis.version_info import PYTHON3
- def rng(a: int, b: int) -> int:
- b = ((b << 13) ^ b) & 0xFFFFFFFF
- c = b ^ (b >> 17)
- c = c ^ (c << 5)
- return (a * 69069 + c + 0x6611CB3B) & 0xFFFFFFFF
- # This is replaced by Mersenne in newer versions.
- def get_keys(a, b) -> tuple[int, int, int, int]:
- ka = rng(a, b)
- kb = rng(ka, a)
- kc = rng(kb, ka)
- kd = rng(kc, kb)
- ke = rng(kd, kc)
- return (kb, kc, kd, ke)
- def MX(z, y, sum: int, key, p: int, e: int):
- return ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ (
- (sum ^ y) + (key[(p & 3) ^ e] ^ z)
- )
- def tea_decipher(v, key: tuple[int, int, int, int]):
- """
- Tiny Decryption Algorithm description (TEA)
- See https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
- """
- DELTA = 0x9E3779B9
- n = len(v)
- rounds = 6 + 52 // n
- sum = rounds * DELTA
- y = v[0]
- while sum != 0:
- e = (sum >> 2) & 3
- for p in range(n - 1, -1, -1):
- z = v[(n + p - 1) % n]
- v[p] = (v[p] - MX(z, y, sum, key, p, e)) & 0xFFFFFFFF
- y = v[p]
- sum -= DELTA
- return v
- def load_code(self) -> Code2Compat | CodeType:
- """
- Returns a Python code object like xdis.unmarshal.load_code(),
- but in we decrypt the data in self.bufstr.
- That is:
- * calculate the TEA key,
- * decrypt self.bufstr
- * create and return a Python code-object
- """
- a = self.load_int()
- b = self.load_int()
- key = get_keys(a, b)
- padsize = (b + 15) & ~0xF
- intsize = padsize / 4
- data = self.bufstr[self.bufpos : self.bufpos + padsize]
- # print("%d: %d (%d=%d)" % (self.bufpos, b, padsize, len(data)))
- data = list(struct.unpack("<%dL" % intsize, data))
- tea_decipher(data, key)
- self.bufpos += padsize
- obj = xmarshal._FastUnmarshaller(struct.pack("<%dL" % intsize, *data))
- code = obj.load_code()
- co_code = patch(code.co_code)
- if PYTHON3:
- return Code2Compat(
- code.co_argcount,
- code.co_nlocals,
- code.co_stacksize,
- code.co_flags,
- co_code,
- code.co_consts,
- code.co_names,
- code.co_varnames,
- code.co_filename,
- code.co_name,
- code.co_firstlineno,
- code.co_lnotab,
- code.co_freevars,
- code.co_cellvars,
- )
- else:
- return types.CodeType(
- code.co_argcount,
- code.co_nlocals,
- code.co_stacksize,
- code.co_flags,
- co_code,
- code.co_consts,
- code.co_names,
- code.co_varnames,
- code.co_filename,
- code.co_name,
- code.co_firstlineno,
- code.co_lnotab,
- code.co_freevars,
- code.co_cellvars,
- )
- try:
- a = bytearray()
- except:
- class bytearray(object):
- def __init__(self, s) -> None:
- self.l = map(ord, s)
- def __setitem__(self, idx, val) -> None:
- self.l[idx] = val
- def __getitem__(self, idx):
- return self.l[idx]
- def __str__(self) -> str:
- return "".join(map(chr, self.l))
- def __len__(self) -> int:
- return len(self.l)
- # Automatically generated opcode substitution table for v1
- # A different dropbox table for v.2 table is at
- # https://github.com/kholia/dedrop/blob/master/src/dedrop-v2/dedrop-v2.py
- # They are similar but not the same.
- table = {
- 0: 0,
- 1: 87,
- 2: 66,
- 4: 25,
- 6: 55,
- 7: 62,
- 9: 71,
- 10: 79,
- 12: 21,
- 13: 4,
- 14: 72,
- 15: 1,
- 16: 30,
- 17: 31,
- 18: 32,
- 19: 33,
- 22: 63,
- 26: 86,
- 29: 56,
- 31: 60,
- 33: 73,
- 34: 15,
- 35: 74,
- 36: 20,
- 38: 12,
- 39: 68,
- 40: 80,
- 41: 22,
- 42: 89,
- 43: 26,
- 50: 64,
- 51: 82,
- 52: 23,
- 54: 11,
- 55: 24,
- 56: 84,
- 59: 2,
- 60: 3,
- 61: 40,
- 62: 41,
- 63: 42,
- 64: 43,
- 65: 85,
- 66: 83,
- 67: 88,
- 68: 18,
- 69: 61,
- 70: 116,
- 71: 126,
- 72: 100,
- 73: 110,
- 74: 120,
- 75: 122,
- 76: 132,
- 77: 133,
- 78: 104,
- 79: 101,
- 80: 102,
- 81: 93,
- 82: 125,
- 83: 111,
- 84: 95,
- 85: 134,
- 86: 105,
- 88: 107,
- 89: 108,
- 90: 112,
- 91: 130,
- 92: 124,
- 93: 92,
- 94: 91,
- 95: 90,
- 97: 135,
- 99: 136,
- 100: 137,
- 101: 106,
- 102: 131,
- 103: 113,
- 104: 99,
- 105: 97,
- 106: 121,
- 107: 103,
- 111: 140,
- 112: 141,
- 113: 142,
- }
- # manual mapping of the rest
- table[37] = 81
- table[28] = 19
- table[87] = 96
- table[21] = 65
- table[96] = 119
- table[8] = 57
- table[32] = 28
- table[44] = 50
- table[45] = 51
- table[46] = 52
- table[47] = 53
- table[23] = 78
- table[24] = 77
- table[3] = 59
- table[11] = 75
- table[58] = 76
- misses: Dict[int, int] = {}
- def patch(code: bytes):
- code = bytearray(code)
- i = 0
- n = len(code)
- while i < n:
- op = code[i]
- if not op in table:
- print("missing opcode %d. code: " % op, repr(str(code)))
- misses[op] = misses.get(op, 0) + 1
- code[i] = table.get(op, op)
- i += 1
- if table.get(op, op) >= 90: # opcode.HAVE_ARGUMENT:
- i += 2
- return bytes(code) if PYTHON3 else str(code)
- try:
- from __pypy__ import builtinify
- except ImportError:
- builtinify = lambda f: f
- @builtinify
- def loads(s):
- """
- xdis.marshal.load() but with its dispatch load_code() function replaced
- with our decoding version.
- """
- um = xmarshal._FastUnmarshaller(s)
- um.dispatch[xmarshal.TYPE_CODE] = load_code
- return um.load()
- def fix_dropbox_pyc(fp):
- source_size = struct.unpack("I", fp.read(4))[0] # size mod 2**32
- ts = fp.read(4)
- timestamp = struct.unpack("I", ts)[0]
- b = fp.read()
- co = loads(b)
- return (2, 5, "0dropbox"), timestamp, 62131, co, False, source_size, None
- def fix_dir(path) -> None:
- import os
- for root, _, files in os.walk(path):
- for name in files:
- if not name.endswith("pyc"):
- continue
- name = os.path.join(root, name)
- print("fixing", name)
- data = open(name).read()
- try:
- c = xmarshal.loads(data[8:])
- except Exception as e:
- print("error", e, repr(e))
- # print repr(data[8:])
- continue
- # fix the version indicator and save
- open(name, "w").write("\xb3\xf2\r\n" + data[4:8] + xmarshal.dumps(c))
- if __name__ == "__main__":
- import os
- import sys
- if sys.argv != 2:
- print("Usage: %s python-file" % os.path.basename(sys.argv[0]))
- sys.exit(1)
- fix_dir(sys.argv[1])
- # for the sake of completeness, here are the code-fragments to automatically generate
- # the opcode substitution table
- if 0:
- import marshal
- def fill(c, d):
- if len(c.co_code) != len(d.co_code):
- print("len mismatch", c, d)
- return
- for i, j in zip(c.co_code, d.co_code):
- # if i in m and not table[i] == j:
- # print "mismatch %c (%x) => %c (%x)" % (ord(i),ord(i),ord(j),ord(j))
- v = table.setdefault(i, {})
- v[j] = v.get(j, 0) + 1
- pass
- return
- for root, dirs, files in os.walk("library"):
- for name in files:
- name = os.path.join(root, name)
- if not name.endswith("pyc"):
- continue
- f2 = os.path.join("/tmp/python-defaults-2.7.2/Python-2.5.4/Lib", name[8:])
- if not os.path.exists(f2):
- continue
- print("loading", name)
- try:
- c = xmarshal.loads(open(name).read()[8:])
- except:
- print("error", name)
- continue
- d = marshal.loads(open(f2).read()[8:])
- fill(c, d)
- codes_c = filter(lambda x: type(x) == type(c), c.co_consts)
- codes_d = filter(lambda x: type(x) == type(c), d.co_consts)
- for i, j in zip(codes_c, codes_d):
- fill(i, j)
- pass
- pass
- def print_table(m):
- k = m.keys()
- k.sort()
- table = {}
- for i in k:
- print("%c (%02x %s) =>" % (ord(i), ord(i), bin(ord(i))), end="")
- for j, count in m[i].iteritems():
- if j == i:
- continue
- table[ord(i)] = ord(j)
- print(
- "\t%c (%02x %s) [%d]" % (ord(j), ord(j), bin(ord(j)), count), end=""
- )
- # print("%c (%02x %s) => %c (%02x %s)\t%d\t%s" % (ord(i),ord(i),bin(ord(i)),ord(j),ord(j),bin(ord(j)),ord(j)-ord(i),bin(ord(i)^ord(j)|0x100).replace('0', ' ')))
- print()
- return table
- import re
- re.compile(r"offset loc_(\w+)").findall(
- "dd offset loc_8096DC4, offset loc_8096963, offset loc_8095462"
- )
- def load(name):
- a = re.compile(r"offset loc_(\w+)").findall(open(name).read())
- a = [int(i, 16) for i in a]
- c = a[:]
- c.sort()
- c = [(i, c.index(i)) for i in a]
- d = {}
- for i, (addr, pos) in enumerate(c):
- d[addr] = (i, pos)
- return c, d
|