Skip to content

Commit 6383643

Browse files
committed
Refactor and fix config parsing and utility functions
Improved type checks in rat_config_parser.py by using isinstance, fixed a bug in config length validation, and enhanced key reporting logic. Corrected parameter names and error messages in data_utils.py utility functions. Fixed logging and key extraction logic in config_decryptor_rijndael.py. Improved error handling and logging in config_decryptor_aes_with_iv_pbkdf.py and config_decryptor_random_hardcoded.py. Optimized CustomAttribute lookup in dotnetpe_payload.py for better performance and accuracy.
1 parent 572e2f5 commit 6383643

File tree

7 files changed

+56
-40
lines changed

7 files changed

+56
-40
lines changed

src/rat_king_parser/config_parser/rat_config_parser.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,21 @@ def __init__(
8181
if data is None and not isfile(file_path):
8282
raise ConfigParserException("File not found")
8383
# Filled in _decrypt_and_decode_config()
84-
self._incompatible_decryptors: list[int] = []
85-
try:
86-
self._dnpp = DotNetPEPayload(file_path, yara_rule, data)
87-
except Exception as e:
88-
raise e
84+
self._incompatible_decryptors: list[Any] = []
85+
self._dnpp = DotNetPEPayload(file_path, yara_rule, data)
8986
self.report["sha256"] = self._dnpp.sha256
9087
self.report["yara_possible_family"] = self._dnpp.yara_match
9188

9289
# Assigned in _decrypt_and_decode_config()
9390
self._decryptor: ConfigDecryptor = None
9491
self.report["config"] = self._get_config()
95-
self.report["key"] = (
96-
self._decryptor.key.hex()
97-
if self._decryptor is not None and self._decryptor.key is not None
98-
else "None"
99-
)
92+
key_hex = "None"
93+
if self._decryptor is not None and self._decryptor.key is not None:
94+
if isinstance(self._decryptor.key, bytes):
95+
key_hex = self._decryptor.key.hex()
96+
else:
97+
key_hex = str(self._decryptor.key)
98+
self.report["key"] = key_hex
10099
self.report["salt"] = (
101100
self._decryptor.salt.hex()
102101
if self._decryptor is not None and self._decryptor.salt is not None
@@ -122,7 +121,7 @@ def _decrypt_and_decode_config(
122121
config_fields_map[k] = field_name
123122
item_data[field_name] = v
124123
if len(item_data) > 0:
125-
if type(item) is config_item.EncryptedStringConfigItem:
124+
if isinstance(item, config_item.EncryptedStringConfigItem):
126125
# Translate config value RVAs to string values
127126
for k in item_data:
128127
item_data[k] = self._dnpp.user_string_from_rva(item_data[k])
@@ -159,7 +158,7 @@ def _decrypt_and_decode_config(
159158
if self._decryptor is None:
160159
raise ConfigParserException("All decryptors failed")
161160

162-
elif type(item) is config_item.ByteArrayConfigItem:
161+
elif isinstance(item, config_item.ByteArrayConfigItem):
163162
for k in item_data:
164163
arr_size, arr_rva = item_data[k]
165164
item_data[k] = self._dnpp.byte_array_from_size_and_rva(
@@ -168,7 +167,7 @@ def _decrypt_and_decode_config(
168167

169168
decoded_config.update(item_data)
170169
# UrlHost is a marker of a special case until this can be standardized
171-
if len(decoded_config) < min_config_len and "UrlHost" not in item_data:
170+
if len(decoded_config) < min_config_len and "UrlHost" not in decoded_config:
172171
raise ConfigParserException(
173172
f"Minimum threshold of config items not met: {len(decoded_config)}/{min_config_len}"
174173
)

src/rat_king_parser/config_parser/utils/data_utils.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@
3131

3232

3333
# Converts a bytes object to an int object using the specified byte order
34-
def bytes_to_int(bytes: bytes, order: str = "little") -> int:
34+
def bytes_to_int(data: bytes, order: str = "little") -> int:
3535
try:
36-
return int.from_bytes(bytes, byteorder=order)
36+
return int.from_bytes(data, byteorder=order)
3737
except Exception:
38-
raise ConfigParserException(f"Error parsing int from value: {bytes}")
38+
raise ConfigParserException(f"Error parsing int from value: {data}")
3939

4040

4141
# Decodes a bytes object to a Unicode string, using UTF-16LE for byte values
@@ -57,8 +57,8 @@ def decode_bytes(byte_str: bytes | str) -> str:
5757

5858

5959
# Converts an int to a bytes object, with the specified length and order
60-
def int_to_bytes(int: int, length: int = 4, order: str = "little") -> bytes:
60+
def int_to_bytes(value: int, length: int = 4, order: str = "little") -> bytes:
6161
try:
62-
return int.to_bytes(length, order)
62+
return value.to_bytes(length, order)
6363
except Exception:
64-
raise ConfigParserException(f"Error parsing bytes from value: {int}")
64+
raise ConfigParserException(f"Error parsing bytes from value: {value}")

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,13 @@ def decrypt_encrypted_strings(
173173
return decrypted_config_strings
174174

175175
# Extracts AES key candidates from the payload
176+
<<<<<<< Updated upstream
176177
def _get_aes_key_candidates(self, encrypted_strings: dict[str, str]) -> list[bytes]:
178+
=======
179+
def _get_aes_key_candidates(
180+
self, encrypted_strings: dict[str, str]
181+
) -> list[bytes]:
182+
>>>>>>> Stashed changes
177183
logger.debug("Extracting AES key candidates...")
178184
keys = []
179185

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ def _decrypt(self, iv: bytes, ciphertext: bytes) -> bytes:
6767
)
6868

6969
cipher = AES.new(self.key, mode=CBC, iv=iv)
70-
unpadded_text = ""
7170

7271
try:
7372
unpadded_text = cipher.decrypt(ciphertext)
@@ -113,7 +112,7 @@ def decrypt_encrypted_strings(
113112
result = decode_bytes(self._decrypt(self.iv, decoded_val))
114113
except ConfigParserException as e:
115114
last_exc = e
116-
print("error", e)
115+
logger.debug(f"Error decrypting {k}: {e}")
117116

118117
if result is None:
119118
logger.debug(

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ def _get_hardcoded_hosts(self) -> list[str]:
7979
hardcoded_hosts = []
8080
for rva in hardcoded_host_rvas:
8181
try:
82-
harcoded_host = self._payload.user_string_from_rva(bytes_to_int(rva))
83-
if harcoded_host != ".":
84-
hardcoded_hosts.append(harcoded_host)
82+
hardcoded_host = self._payload.user_string_from_rva(bytes_to_int(rva))
83+
if hardcoded_host != ".":
84+
hardcoded_hosts.append(hardcoded_host)
8585
except Exception as e:
8686
logger.error(f"Error translating hardcoded host at {hex(rva)}: {e}")
8787
continue

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3030
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3131
# SOFTWARE.
32-
import logging
32+
from logging import getLogger
3333
from base64 import b64decode
3434
from hashlib import md5
3535
from re import DOTALL, compile, search
@@ -42,7 +42,7 @@
4242
from ..dotnetpe_payload import DotNetPEPayload
4343
from .config_decryptor import ConfigDecryptor, IncompatibleDecryptorException
4444

45-
logger = logging.getLogger(__name__)
45+
logger = getLogger(__name__)
4646

4747

4848
# Is old AES - specifically Rijndael in CBC mode with MD5 hashing for key derivation
@@ -110,11 +110,14 @@ def decrypt_encrypted_strings(self, encrypted_strings: dict[str, str]) -> dict[s
110110
else:
111111
for key_pattern in self._KEY_PATTERNS:
112112
key_hit = search(key_pattern, self._payload.data)
113+
if not key_hit:
114+
continue
113115
key_rva = bytes_to_int(key_hit.groups()[0])
114116
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
117+
if raw_key_field in encrypted_strings:
118+
key = encrypted_strings[raw_key_field]
119+
self.key = self._derive_key(key)
120+
break
118121
except Exception as e:
119122
raise ConfigParserException(f"Failed to derive AES key: {e}")
120123

src/rat_king_parser/config_parser/utils/dotnetpe_payload.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,13 @@ def custom_attribute_from_type(self, typespacename: str, typename: str) -> dict:
234234
"""
235235
config = {}
236236
try:
237+
ca_map = {}
238+
for ca in self.dotnetpe.net.mdtables.CustomAttribute.rows:
239+
idx = ca.Parent.row_index
240+
if idx not in ca_map:
241+
ca_map[idx] = []
242+
ca_map[idx].append(ca)
243+
237244
for td in self.dotnetpe.net.mdtables.TypeDef.rows:
238245
if (
239246
td.TypeNamespace.value != typespacename
@@ -244,19 +251,21 @@ def custom_attribute_from_type(self, typespacename: str, typename: str) -> dict:
244251
self.dotnetpe.net.mdtables.Property.rows
245252
):
246253
if pd.Name.value.startswith(
247-
"Boolean_",
248-
"BorderStyle_",
249-
"Color_",
250-
"Byte",
251-
"Int32_",
252-
"SizeF_",
253-
"String_",
254+
(
255+
"Boolean_",
256+
"BorderStyle_",
257+
"Color_",
258+
"Byte",
259+
"Int32_",
260+
"SizeF_",
261+
"String_",
262+
)
254263
):
255264
continue
256-
for ca in self.dotnetpe.net.mdtables.CustomAttribute.rows:
257-
if (
258-
ca.Parent.row_index == pd_row_index + 1
259-
): # CustomAttribute Parent index is 1-based
265+
# CustomAttribute Parent index is 1-based
266+
target_index = pd_row_index + 1
267+
if target_index in ca_map:
268+
for ca in ca_map[target_index]:
260269
# Extract the value from the CustomAttribute blob
261270
blob_data = ca.Value.value
262271
if blob_data and blob_data != b"\x01\x00\x00\x00":

0 commit comments

Comments
 (0)