-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathdecrypt_gcm.py
More file actions
129 lines (95 loc) · 4.48 KB
/
decrypt_gcm.py
File metadata and controls
129 lines (95 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/usr/bin/env python3
"""
This script has two purposes:
1. Extract and decrypt Pyarmor bytes string from protected file.
2. Utilize info generated by analyze_crypted_code.py to generate completely decrypted file.
"""
import ast
import json
import sys
from Crypto.Cipher import AES
from typing import Optional
# Insert result of ida_getkey script here
KEY = bytes.fromhex('5c961198c441be7baafa61ded3fb39ea')
def get_third_argument(filepath: str) -> Optional[bytes]:
"""Parses an obfuscated script and attempts to extract the encoded module passed to __pyarmor__()."""
with open(filepath, 'r') as file:
content = file.read()
tree = ast.parse(content)
# Function visitor to find the first function call
class FunctionCallVisitor(ast.NodeVisitor):
def __init__(self):
self.first_function_call = None
def visit_Call(self, node):
if isinstance(node.func, ast.Name): # Only consider named function calls
func_name = node.func.id
self.first_function_call = (func_name, node.args)
return # Stop after the first function call
self.generic_visit(node)
visitor = FunctionCallVisitor()
visitor.visit(tree)
if visitor.first_function_call:
func_name, args = visitor.first_function_call
if len(args) >= 3:
if not isinstance(args[2], ast.Constant) or not isinstance(args[2].value, bytes):
raise Exception("3rd argument is not bytes constant")
return args[2].value
return None
def get_bytes_from_pyc(filepath: str) -> bytes:
with open(filename, "rb") as fp:
module = fp.read()
# This is somewhat dirty, might not work for all versions.
bytes_pos = module.find(b"__pyarmor__\x73")
if bytes_pos == -1:
raise Exception("Unable to locate pyarmor data in compiled module")
bytes_pos += len(b"__pyarmor__\x73")
armor_len = int.from_bytes(module[bytes_pos:bytes_pos+4], 'little')
if armor_len < 0x200 or armor_len > 10 * 1024 * 1024:
raise Exception(f"String length implausible: {armor_len}")
return module[bytes_pos+4:bytes_pos+4+armor_len]
def decrypt_gcm_without_tag(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
"""Decrypts AES in GCM mode while ignoring the authentication tag."""
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
return cipher.decrypt(ciphertext)
filename = sys.argv[1]
if filename.endswith(".py") or filename.endswith(".pyc"):
if filename.endswith(".py"):
armor_bytes = get_third_argument(filename)
if armor_bytes is None:
raise Exception("Unable to find third __pyarmor__ argument")
else:
armor_bytes = get_bytes_from_pyc(filename)
if armor_bytes[20] == 9:
# BCC mode has two consecutive blobs. The first contains an ELF.
nonce = armor_bytes[36:40] + armor_bytes[44:52]
bcc_start = int.from_bytes(armor_bytes[28:32], 'little')
bcc_end = int.from_bytes(armor_bytes[56:60], 'little')
ciphertext = armor_bytes[bcc_start:bcc_end]
plaintext = decrypt_gcm_without_tag(KEY, nonce, ciphertext)
with open(filename + ".dec.elf", "wb") as fpw:
fpw.write(plaintext[16:])
print(f"[!] Detected BCC mode! ELF saved as {filename}.dec.elf.")
# Second part should be a "normal" type 8 blob.
armor_bytes = armor_bytes[bcc_end:]
nonce = armor_bytes[36:40] + armor_bytes[44:52]
ciphertext = armor_bytes[int.from_bytes(armor_bytes[28:32], 'little'):]
plaintext = decrypt_gcm_without_tag(KEY, nonce, ciphertext)
with open(filename + ".dec", "wb") as fpw:
fpw.write(plaintext)
print(f"{filename}.dec saved. Now run analyze_crypted_code using the special Python interpreter.")
elif filename.endswith(".py.dec") or filename.endswith(".pyc.dec"):
with open(filename, "rb") as fp:
module = bytearray(fp.read())
crypted_regions = json.load(open(filename + ".json"))
for region in crypted_regions:
skip = int.from_bytes(module[0:4], 'little') + int.from_bytes(module[4:8], 'little')
start = region["ciphertext_offset"] + skip
size = region["ciphertext_size"]
ciphertext = module[start:start+size]
nonce = bytes.fromhex(region["nonce"])
plaintext = decrypt_gcm_without_tag(KEY, nonce, ciphertext)
module[start:start+size] = plaintext
with open(filename + "2", "wb") as fpw:
fpw.write(module)
else:
print("Don't know what to do with this file type.")