decrypt25.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. #!/usr/bin/env python
  2. # Copyright Hagen Fritsch, 2012, License: GPL-2.0
  3. # Adaption and generalization for xdis use by Rocky Bernstein
  4. # Copyright 2019-2020, 2024
  5. # Dropbox Python bytecode decryption tool for Dropbox versions of 1.1x
  6. # (and possibly earlier) which uses Python bytecode 2.5.
  7. import struct
  8. import types
  9. from types import CodeType
  10. from typing import Dict
  11. import xdis.marsh as xmarshal
  12. # FIXME to use codeType2Portable
  13. # from xdis.codetype import codeType2Portable
  14. from xdis.codetype.code20 import Code2Compat
  15. from xdis.version_info import PYTHON3
  16. def rng(a: int, b: int) -> int:
  17. b = ((b << 13) ^ b) & 0xFFFFFFFF
  18. c = b ^ (b >> 17)
  19. c = c ^ (c << 5)
  20. return (a * 69069 + c + 0x6611CB3B) & 0xFFFFFFFF
  21. # This is replaced by Mersenne in newer versions.
  22. def get_keys(a, b) -> tuple[int, int, int, int]:
  23. ka = rng(a, b)
  24. kb = rng(ka, a)
  25. kc = rng(kb, ka)
  26. kd = rng(kc, kb)
  27. ke = rng(kd, kc)
  28. return (kb, kc, kd, ke)
  29. def MX(z, y, sum: int, key, p: int, e: int):
  30. return ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ (
  31. (sum ^ y) + (key[(p & 3) ^ e] ^ z)
  32. )
  33. def tea_decipher(v, key: tuple[int, int, int, int]):
  34. """
  35. Tiny Decryption Algorithm description (TEA)
  36. See https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
  37. """
  38. DELTA = 0x9E3779B9
  39. n = len(v)
  40. rounds = 6 + 52 // n
  41. sum = rounds * DELTA
  42. y = v[0]
  43. while sum != 0:
  44. e = (sum >> 2) & 3
  45. for p in range(n - 1, -1, -1):
  46. z = v[(n + p - 1) % n]
  47. v[p] = (v[p] - MX(z, y, sum, key, p, e)) & 0xFFFFFFFF
  48. y = v[p]
  49. sum -= DELTA
  50. return v
  51. def load_code(self) -> Code2Compat | CodeType:
  52. """
  53. Returns a Python code object like xdis.unmarshal.load_code(),
  54. but in we decrypt the data in self.bufstr.
  55. That is:
  56. * calculate the TEA key,
  57. * decrypt self.bufstr
  58. * create and return a Python code-object
  59. """
  60. a = self.load_int()
  61. b = self.load_int()
  62. key = get_keys(a, b)
  63. padsize = (b + 15) & ~0xF
  64. intsize = padsize / 4
  65. data = self.bufstr[self.bufpos : self.bufpos + padsize]
  66. # print("%d: %d (%d=%d)" % (self.bufpos, b, padsize, len(data)))
  67. data = list(struct.unpack("<%dL" % intsize, data))
  68. tea_decipher(data, key)
  69. self.bufpos += padsize
  70. obj = xmarshal._FastUnmarshaller(struct.pack("<%dL" % intsize, *data))
  71. code = obj.load_code()
  72. co_code = patch(code.co_code)
  73. if PYTHON3:
  74. return Code2Compat(
  75. code.co_argcount,
  76. code.co_nlocals,
  77. code.co_stacksize,
  78. code.co_flags,
  79. co_code,
  80. code.co_consts,
  81. code.co_names,
  82. code.co_varnames,
  83. code.co_filename,
  84. code.co_name,
  85. code.co_firstlineno,
  86. code.co_lnotab,
  87. code.co_freevars,
  88. code.co_cellvars,
  89. )
  90. else:
  91. return types.CodeType(
  92. code.co_argcount,
  93. code.co_nlocals,
  94. code.co_stacksize,
  95. code.co_flags,
  96. co_code,
  97. code.co_consts,
  98. code.co_names,
  99. code.co_varnames,
  100. code.co_filename,
  101. code.co_name,
  102. code.co_firstlineno,
  103. code.co_lnotab,
  104. code.co_freevars,
  105. code.co_cellvars,
  106. )
  107. try:
  108. a = bytearray()
  109. except:
  110. class bytearray(object):
  111. def __init__(self, s) -> None:
  112. self.l = map(ord, s)
  113. def __setitem__(self, idx, val) -> None:
  114. self.l[idx] = val
  115. def __getitem__(self, idx):
  116. return self.l[idx]
  117. def __str__(self) -> str:
  118. return "".join(map(chr, self.l))
  119. def __len__(self) -> int:
  120. return len(self.l)
  121. # Automatically generated opcode substitution table for v1
  122. # A different dropbox table for v.2 table is at
  123. # https://github.com/kholia/dedrop/blob/master/src/dedrop-v2/dedrop-v2.py
  124. # They are similar but not the same.
  125. table = {
  126. 0: 0,
  127. 1: 87,
  128. 2: 66,
  129. 4: 25,
  130. 6: 55,
  131. 7: 62,
  132. 9: 71,
  133. 10: 79,
  134. 12: 21,
  135. 13: 4,
  136. 14: 72,
  137. 15: 1,
  138. 16: 30,
  139. 17: 31,
  140. 18: 32,
  141. 19: 33,
  142. 22: 63,
  143. 26: 86,
  144. 29: 56,
  145. 31: 60,
  146. 33: 73,
  147. 34: 15,
  148. 35: 74,
  149. 36: 20,
  150. 38: 12,
  151. 39: 68,
  152. 40: 80,
  153. 41: 22,
  154. 42: 89,
  155. 43: 26,
  156. 50: 64,
  157. 51: 82,
  158. 52: 23,
  159. 54: 11,
  160. 55: 24,
  161. 56: 84,
  162. 59: 2,
  163. 60: 3,
  164. 61: 40,
  165. 62: 41,
  166. 63: 42,
  167. 64: 43,
  168. 65: 85,
  169. 66: 83,
  170. 67: 88,
  171. 68: 18,
  172. 69: 61,
  173. 70: 116,
  174. 71: 126,
  175. 72: 100,
  176. 73: 110,
  177. 74: 120,
  178. 75: 122,
  179. 76: 132,
  180. 77: 133,
  181. 78: 104,
  182. 79: 101,
  183. 80: 102,
  184. 81: 93,
  185. 82: 125,
  186. 83: 111,
  187. 84: 95,
  188. 85: 134,
  189. 86: 105,
  190. 88: 107,
  191. 89: 108,
  192. 90: 112,
  193. 91: 130,
  194. 92: 124,
  195. 93: 92,
  196. 94: 91,
  197. 95: 90,
  198. 97: 135,
  199. 99: 136,
  200. 100: 137,
  201. 101: 106,
  202. 102: 131,
  203. 103: 113,
  204. 104: 99,
  205. 105: 97,
  206. 106: 121,
  207. 107: 103,
  208. 111: 140,
  209. 112: 141,
  210. 113: 142,
  211. }
  212. # manual mapping of the rest
  213. table[37] = 81
  214. table[28] = 19
  215. table[87] = 96
  216. table[21] = 65
  217. table[96] = 119
  218. table[8] = 57
  219. table[32] = 28
  220. table[44] = 50
  221. table[45] = 51
  222. table[46] = 52
  223. table[47] = 53
  224. table[23] = 78
  225. table[24] = 77
  226. table[3] = 59
  227. table[11] = 75
  228. table[58] = 76
  229. misses: Dict[int, int] = {}
  230. def patch(code: bytes):
  231. code = bytearray(code)
  232. i = 0
  233. n = len(code)
  234. while i < n:
  235. op = code[i]
  236. if not op in table:
  237. print("missing opcode %d. code: " % op, repr(str(code)))
  238. misses[op] = misses.get(op, 0) + 1
  239. code[i] = table.get(op, op)
  240. i += 1
  241. if table.get(op, op) >= 90: # opcode.HAVE_ARGUMENT:
  242. i += 2
  243. return bytes(code) if PYTHON3 else str(code)
  244. try:
  245. from __pypy__ import builtinify
  246. except ImportError:
  247. builtinify = lambda f: f
  248. @builtinify
  249. def loads(s):
  250. """
  251. xdis.marshal.load() but with its dispatch load_code() function replaced
  252. with our decoding version.
  253. """
  254. um = xmarshal._FastUnmarshaller(s)
  255. um.dispatch[xmarshal.TYPE_CODE] = load_code
  256. return um.load()
  257. def fix_dropbox_pyc(fp):
  258. source_size = struct.unpack("I", fp.read(4))[0] # size mod 2**32
  259. ts = fp.read(4)
  260. timestamp = struct.unpack("I", ts)[0]
  261. b = fp.read()
  262. co = loads(b)
  263. return (2, 5, "0dropbox"), timestamp, 62131, co, False, source_size, None
  264. def fix_dir(path) -> None:
  265. import os
  266. for root, _, files in os.walk(path):
  267. for name in files:
  268. if not name.endswith("pyc"):
  269. continue
  270. name = os.path.join(root, name)
  271. print("fixing", name)
  272. data = open(name).read()
  273. try:
  274. c = xmarshal.loads(data[8:])
  275. except Exception as e:
  276. print("error", e, repr(e))
  277. # print repr(data[8:])
  278. continue
  279. # fix the version indicator and save
  280. open(name, "w").write("\xb3\xf2\r\n" + data[4:8] + xmarshal.dumps(c))
  281. if __name__ == "__main__":
  282. import os
  283. import sys
  284. if sys.argv != 2:
  285. print("Usage: %s python-file" % os.path.basename(sys.argv[0]))
  286. sys.exit(1)
  287. fix_dir(sys.argv[1])
  288. # for the sake of completeness, here are the code-fragments to automatically generate
  289. # the opcode substitution table
  290. if 0:
  291. import marshal
  292. def fill(c, d):
  293. if len(c.co_code) != len(d.co_code):
  294. print("len mismatch", c, d)
  295. return
  296. for i, j in zip(c.co_code, d.co_code):
  297. # if i in m and not table[i] == j:
  298. # print "mismatch %c (%x) => %c (%x)" % (ord(i),ord(i),ord(j),ord(j))
  299. v = table.setdefault(i, {})
  300. v[j] = v.get(j, 0) + 1
  301. pass
  302. return
  303. for root, dirs, files in os.walk("library"):
  304. for name in files:
  305. name = os.path.join(root, name)
  306. if not name.endswith("pyc"):
  307. continue
  308. f2 = os.path.join("/tmp/python-defaults-2.7.2/Python-2.5.4/Lib", name[8:])
  309. if not os.path.exists(f2):
  310. continue
  311. print("loading", name)
  312. try:
  313. c = xmarshal.loads(open(name).read()[8:])
  314. except:
  315. print("error", name)
  316. continue
  317. d = marshal.loads(open(f2).read()[8:])
  318. fill(c, d)
  319. codes_c = filter(lambda x: type(x) == type(c), c.co_consts)
  320. codes_d = filter(lambda x: type(x) == type(c), d.co_consts)
  321. for i, j in zip(codes_c, codes_d):
  322. fill(i, j)
  323. pass
  324. pass
  325. def print_table(m):
  326. k = m.keys()
  327. k.sort()
  328. table = {}
  329. for i in k:
  330. print("%c (%02x %s) =>" % (ord(i), ord(i), bin(ord(i))), end="")
  331. for j, count in m[i].iteritems():
  332. if j == i:
  333. continue
  334. table[ord(i)] = ord(j)
  335. print(
  336. "\t%c (%02x %s) [%d]" % (ord(j), ord(j), bin(ord(j)), count), end=""
  337. )
  338. # 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', ' ')))
  339. print()
  340. return table
  341. import re
  342. re.compile(r"offset loc_(\w+)").findall(
  343. "dd offset loc_8096DC4, offset loc_8096963, offset loc_8095462"
  344. )
  345. def load(name):
  346. a = re.compile(r"offset loc_(\w+)").findall(open(name).read())
  347. a = [int(i, 16) for i in a]
  348. c = a[:]
  349. c.sort()
  350. c = [(i, c.index(i)) for i in a]
  351. d = {}
  352. for i, (addr, pos) in enumerate(c):
  353. d[addr] = (i, pos)
  354. return c, d