Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/rat_king_parser/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__version__ = "4.2.2"
__version__ = "4.2.3"
18 changes: 10 additions & 8 deletions src/rat_king_parser/config_parser/rat_config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ def __init__(
# Assigned in _decrypt_and_decode_config()
self._decryptor: ConfigDecryptor = None
self.report["config"] = self._get_config()
self.report["key"] = (
self._decryptor.key.hex()
if self._decryptor is not None and self._decryptor.key is not None
else "None"
)
key_hex = "None"
if self._decryptor is not None and self._decryptor.key is not None:
if isinstance(self._decryptor.key, bytes):
key_hex = self._decryptor.key.hex()
else:
key_hex = str(self._decryptor.key)
self.report["key"] = key_hex
self.report["salt"] = (
self._decryptor.salt.hex()
if self._decryptor is not None and self._decryptor.salt is not None
Expand All @@ -122,7 +124,7 @@ def _decrypt_and_decode_config(
config_fields_map[k] = field_name
item_data[field_name] = v
if len(item_data) > 0:
if type(item) is config_item.EncryptedStringConfigItem:
if isinstance(item, config_item.EncryptedStringConfigItem):
# Translate config value RVAs to string values
for k in item_data:
item_data[k] = self._dnpp.user_string_from_rva(item_data[k])
Expand Down Expand Up @@ -159,7 +161,7 @@ def _decrypt_and_decode_config(
if self._decryptor is None:
raise ConfigParserException("All decryptors failed")

elif type(item) is config_item.ByteArrayConfigItem:
elif isinstance(item, config_item.ByteArrayConfigItem):
for k in item_data:
arr_size, arr_rva = item_data[k]
item_data[k] = self._dnpp.byte_array_from_size_and_rva(
Expand All @@ -168,7 +170,7 @@ def _decrypt_and_decode_config(

decoded_config.update(item_data)
# UrlHost is a marker of a special case until this can be standardized
if len(decoded_config) < min_config_len and "UrlHost" not in item_data:
if len(decoded_config) < min_config_len and "UrlHost" not in decoded_config:
raise ConfigParserException(
f"Minimum threshold of config items not met: {len(decoded_config)}/{min_config_len}"
)
Expand Down
12 changes: 6 additions & 6 deletions src/rat_king_parser/config_parser/utils/data_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@


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


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


# Converts an int to a bytes object, with the specified length and order
def int_to_bytes(int: int, length: int = 4, order: str = "little") -> bytes:
def int_to_bytes(value: int, length: int = 4, order: str = "little") -> bytes:
try:
return int.to_bytes(length, order)
return value.to_bytes(length, order)
except Exception:
raise ConfigParserException(f"Error parsing bytes from value: {int}")
raise ConfigParserException(f"Error parsing bytes from value: {value}")
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ def _get_hardcoded_hosts(self) -> list[str]:
hardcoded_hosts = []
for rva in hardcoded_host_rvas:
try:
harcoded_host = self._payload.user_string_from_rva(bytes_to_int(rva))
if harcoded_host != ".":
hardcoded_hosts.append(harcoded_host)
extracted_hosts = self._payload.user_string_from_rva(bytes_to_int(rva))
if extracted_hosts != ".":
hardcoded_hosts.append(extracted_hosts)
except Exception as e:
logger.error(f"Error translating hardcoded host at {hex(rva)}: {e}")
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,22 @@ def decrypt_encrypted_strings(self, encrypted_strings: dict[str, str]) -> dict[s
logger.debug("Decrypting encrypted strings...")
if not self.key:
try:
#
raw_key_field = self._payload.field_name_from_rva(self._key_rva)
if raw_key_field in encrypted_strings:
key = encrypted_strings[raw_key_field]
self.key = self._derive_key(key)
else:
for key_pattern in self._KEY_PATTERNS:
key_hit = search(key_pattern, self._payload.data)
if not key_hit:
continue
key_rva = bytes_to_int(key_hit.groups()[0])
raw_key_field = self._payload.field_name_from_rva(key_rva)
key = encrypted_strings[raw_key_field]
self.key = self._derive_key(key)
break
if raw_key_field in encrypted_strings:
key = encrypted_strings[raw_key_field]
self.key = self._derive_key(key)
break

except Exception as e:
raise ConfigParserException(f"Failed to derive AES key: {e}")

Expand Down
15 changes: 11 additions & 4 deletions src/rat_king_parser/config_parser/utils/dotnetpe_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,13 @@ def custom_attribute_from_type(self, typespacename: str, typename: str) -> dict:
"""
config = {}
try:
ca_map = {}
for ca in self.dotnetpe.net.mdtables.CustomAttribute.rows:
idx = ca.Parent.row_index
if idx not in ca_map:
ca_map[idx] = []
ca_map[idx].append(ca)

for td in self.dotnetpe.net.mdtables.TypeDef.rows:
if (
td.TypeNamespace.value != typespacename
Expand All @@ -253,10 +260,10 @@ def custom_attribute_from_type(self, typespacename: str, typename: str) -> dict:
"String_",
):
continue
for ca in self.dotnetpe.net.mdtables.CustomAttribute.rows:
if (
ca.Parent.row_index == pd_row_index + 1
): # CustomAttribute Parent index is 1-based
# CustomAttribute Parent index is 1-based
target_index = pd_row_index + 1
if target_index in ca_map:
for ca in ca_map[target_index]:
# Extract the value from the CustomAttribute blob
blob_data = ca.Value.value
if blob_data and blob_data != b"\x01\x00\x00\x00":
Expand Down