Skip to content

Commit 444ea44

Browse files
committed
ECB variant
1 parent 02f6697 commit 444ea44

1 file changed

Lines changed: 44 additions & 21 deletions

File tree

src/rat_king_parser/config_parser/utils/decryptors/config_decryptor_rijndael.py

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,9 @@
3333
from hashlib import md5
3434
import logging
3535
from re import DOTALL, compile, search
36-
from contextlib import suppress
37-
38-
HAVE_CRYPTODOMEX = False
39-
with suppress(ImportError):
40-
from Cryptodome.Cipher import AES
41-
from Cryptodome.Util.Padding import pad, unpad
42-
43-
HAVE_CRYPTODOMEX = True
4436

37+
from Cryptodome.Cipher import AES
38+
from Cryptodome.Util.Padding import pad, unpad
4539

4640
from ...config_parser_exception import ConfigParserException
4741
from ..data_utils import bytes_to_int, decode_bytes
@@ -53,12 +47,24 @@
5347

5448
# Is old AES - specifically Rijndael in CBC mode with MD5 hashing for key derivation
5549
class ConfigDecryptorRijndael(ConfigDecryptor):
50+
_ALGO_MAP = {
51+
b"\x17": AES.MODE_CBC,
52+
b"\x1a": AES.MODE_CFB,
53+
b"\x18": AES.MODE_ECB,
54+
}
5655
# MD5 hash pattern used to detect AES key
5756
_PATTERN_MD5_HASH = compile(rb"\x7e(.{3}\x04)\x6f.{4}\x11\x06\x0c", DOTALL)
57+
_PATTERN_MD5_HASH2 = compile(rb"\x7e(.{3}\x04)\x28.{3}\x06\x6f.{4}\x13\x04", DOTALL)
5858
_KEY_AS_ARG = compile(rb"\x7e(.{3}\x04)\x28.{3}\x06\x7e.{3}\x04\x28.{3}\x06\x80.{3}\x04", DOTALL)
59+
5960
# key size 16 and 1 = CBC, 2 = ECB
6061
# https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.ciphermode?view=net-9.0
6162
# _PATTERN_AES_MODE = compile(rb"[\x06-\x09]\x6f.{4}\x21(.)\x00{8}\x59\x21(.)\x00{8}\x58\xd4\x8d.{4}\x13\x05")
63+
# check AES mode
64+
_AES_MODE = compile(rb"\x06\x09\x6f.{4}\x06(.)\x6f.{4}\x06\x6f.{4}\x13", DOTALL)
65+
_SIMPLE_MD5 = compile(rb"\x11\x04\x16\x09\x16\x1f\x10\x28.{4}\x11\x04\x16\x09\x1f\x0f\x1f\x10\x28", DOTALL)
66+
_PATTERNS_MD5 = (_PATTERN_MD5_HASH, _PATTERN_MD5_HASH2)
67+
_KEY_PATTERNS = (_KEY_AS_ARG,)
6268

6369
def __init__(self, payload: DotNetPEPayload) -> None:
6470
super().__init__(payload)
@@ -69,15 +75,17 @@ def __init__(self, payload: DotNetPEPayload) -> None:
6975
logger.debug("Incompatible Decryptor")
7076
raise IncompatibleDecryptorException(e)
7177

72-
# Given ciphertext, creates a Cipher object with the AES key and decrypts
78+
# Given ciphertext, creates a Cipher object with the AES/3DES key and decrypts
7379
# the ciphertext
7480
def _decrypt(self, ciphertext: bytes) -> bytes:
7581
unpadded_text = ""
7682
cipher = AES.new(self.key, mode=self.mode)
7783
block_size = AES.block_size
7884
try:
7985
padded_text = cipher.decrypt(ciphertext)
80-
padded_text = padded_text[16:] # Remove IV
86+
if self.mode == AES.MODE_CBC:
87+
# Remove the first 16 bytes of the decrypted text as they are the IV
88+
padded_text = padded_text[16:]
8189
except ValueError:
8290
padded_text = cipher.decrypt(pad(ciphertext, block_size))
8391
try:
@@ -100,13 +108,15 @@ def decrypt_encrypted_strings(self, encrypted_strings: dict[str, str]) -> dict[s
100108
key = encrypted_strings[raw_key_field]
101109
self.key = self._derive_key(key)
102110
else:
103-
key_hit = search(self._KEY_AS_ARG, self._payload.data)
104-
key_rva = bytes_to_int(key_hit.groups()[0])
105-
raw_key_field = self._payload.field_name_from_rva(key_rva)
106-
key = encrypted_strings[raw_key_field]
107-
self.key = self._derive_key(key)
111+
for key_pattern in self._KEY_PATTERNS:
112+
key_hit = search(key_pattern, self._payload.data)
113+
key_rva = bytes_to_int(key_hit.groups()[0])
114+
raw_key_field = self._payload.field_name_from_rva(key_rva)
115+
key = encrypted_strings[raw_key_field]
116+
self.key = self._derive_key(key)
117+
break
108118
except Exception as e:
109-
raise ConfigParserException(f"Failed to derive AES/3DES key: {e}")
119+
raise ConfigParserException(f"Failed to derive AES key: {e}")
110120

111121
decrypted_config_strings = {}
112122
for k, v in encrypted_strings.items():
@@ -120,7 +130,7 @@ def decrypt_encrypted_strings(self, encrypted_strings: dict[str, str]) -> dict[s
120130
b64_exception = False
121131
try:
122132
decoded_val = b64decode(v)
123-
except Exception:
133+
except Exception as e:
124134
b64_exception = True
125135
# If it was not base64-encoded, leave the value as it is
126136
if b64_exception:
@@ -152,17 +162,30 @@ def _derive_key(self, key_unhashed: str) -> bytes:
152162
md5_hash.update(key_unhashed.encode("utf-8"))
153163
key = md5_hash.digest()
154164

155-
logger.debug(f"Key derived: {key}")
165+
if search(self._SIMPLE_MD5, self._payload.data):
166+
# check if simple md5
167+
logger.debug("Simple MD5 detected")
168+
key = key[:15] + key[:16] + b"\x00"
169+
logger.debug("Key derived: %s, from key: %s", key.hex(), key_unhashed)
156170
return key
157171

158172
# Extracts the AES/3DES key RVA from the payload
159173
def _get_key_rva(self) -> int:
160-
logger.debug("Extracting AES/3Des key value...")
161-
key_hit = search(self._PATTERN_MD5_HASH, self._payload.data)
174+
logger.debug("Extracting AES key value...")
175+
key_hit = None
176+
for pattern in self._PATTERNS_MD5:
177+
key_hit = search(pattern, self._payload.data)
178+
if key_hit:
179+
break
162180
if not key_hit:
163181
raise ConfigParserException("Could not find AES key pattern")
164182

183+
# check if AES mode is different from CBC:
184+
_AES_MODE = search(self._AES_MODE, self._payload.data)
185+
if _AES_MODE:
186+
self.mode = self._ALGO_MAP[_AES_MODE.groups()[0]]
187+
logger.debug(f"AES mode: {self.mode}")
188+
165189
key_rva = bytes_to_int(key_hit.groups()[0])
166190
logger.debug(f"AES key RVA: {hex(key_rva)}")
167-
168191
return key_rva

0 commit comments

Comments
 (0)