Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1dc7812
rijndael
doomedraven Apr 14, 2025
ae9d588
Update src/rat_king_parser/config_parser/utils/decryptors/config_decr…
doomedraven Apr 14, 2025
9b300bc
rijndael
doomedraven Apr 14, 2025
02f6697
rijndael
doomedraven Apr 14, 2025
ea12ecd
fix FP extractions
doomedraven Apr 15, 2025
67cfd61
simplify logic
doomedraven Apr 15, 2025
15cea49
Update config_decryptor_aes_with_iv_pbkdf.py
doomedraven Apr 16, 2025
762e1e1
fix
doomedraven Apr 22, 2025
444ea44
ECB variant
doomedraven Apr 22, 2025
a6edc8d
ECB variant
doomedraven Apr 22, 2025
2edf9d9
Merge pull request #31 from doomedraven/fixes
jeFF0Falltrades Jun 10, 2025
c393115
Merge pull request #30 from doomedraven/rijndael
jeFF0Falltrades Jun 10, 2025
c5e9e48
Implements --preserve-keys/preservation or replacement of obfuscated …
jeFF0Falltrades Jun 10, 2025
62ca8ef
Pre-commit on all files
jeFF0Falltrades Jun 10, 2025
23e979d
Merge pull request #33 from jeFF0Falltrades/32-produce-human-readable…
jeFF0Falltrades Jun 10, 2025
4292366
Update README.md
jeFF0Falltrades Jun 10, 2025
e595a4e
Account for cases where a single port was extracted from the configur…
cccs-rs Jul 18, 2025
4180a46
Ensure assessment of potential list of ports doesn't result in an Att…
cccs-rs Jul 18, 2025
99ffe57
Merge pull request #34 from cccs-rs/master
jeFF0Falltrades Jul 27, 2025
572e2f5
Bump to 4.2.1
jeFF0Falltrades Jul 27, 2025
bfd9344
Add 'HostsFE' to normalized keys in config normalization
doomedraven Jan 7, 2026
86d6312
Update value handling in rat_config_parser
doomedraven Jan 7, 2026
f14ac9b
Refactor AES mode usage and clean up code
doomedraven Jan 7, 2026
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
480 changes: 473 additions & 7 deletions README.md

Large diffs are not rendered by default.

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.1.6"
__version__ = "4.2.1"
46 changes: 45 additions & 1 deletion src/rat_king_parser/config_parser/rat_config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
ConfigDecryptor,
IncompatibleDecryptorException,
)
from .utils.decryptors.config_decryptor_plaintext import KNOWN_CONFIG_FIELD_NAMES
from .utils.dotnetpe_payload import DotNetPEPayload

logger = getLogger(__name__)
Expand All @@ -64,6 +65,7 @@ def __init__(
yara_rule: Rules = None,
data: bytes = None,
remap_config: bool = False,
preserve_obfuscated_keys: bool = False,
) -> None:
self.report = {
"file_path": file_path,
Expand All @@ -74,6 +76,7 @@ def __init__(
"config": {},
}
self.remap_config = remap_config
self.preserve_obfuscated_keys = preserve_obfuscated_keys
try:
if data is None and not isfile(file_path):
raise ConfigParserException("File not found")
Expand Down Expand Up @@ -175,10 +178,32 @@ def _decrypt_and_decode_config(
for k in sorted(config_fields_map.keys()):
key_name = config_fields_map[k]
value = decoded_config[key_name]

# Run your normalization (e.g. converting HostsFE -> Hosts)
key_normalized, value = check_key_n_value(key_name, value)

if key_normalized != key_name:
normalized_fields.append(key_name)
sorted_decoded_config[key_normalized] = value

# --- LOGIC TO APPEND INSTEAD OF OVERWRITE ---
if key_normalized in sorted_decoded_config:
existing_val = sorted_decoded_config[key_normalized]

# Case 1: Values are Strings (e.g. "1.2.3.4:80")
if isinstance(existing_val, str) and value:
# Append with a comma separator
sorted_decoded_config[key_normalized] = f"{existing_val},{value}"

# Case 2: Values are Lists (e.g. ["1.2.3.4:80"])
elif isinstance(existing_val, list):
# If the new value is also a list, extend; otherwise append
if isinstance(value, list):
sorted_decoded_config[key_normalized] = existing_val + value
else:
sorted_decoded_config[key_normalized].append(value)
else:
# Key does not exist yet, create it
sorted_decoded_config[key_normalized] = value
# Ensure config items added by decryptors dynamically are preserved
sorted_decoded_config.update(
{
Expand All @@ -205,6 +230,17 @@ def _get_config(self) -> dict[str, Any]:
except Exception as e:
raise ConfigParserException(f"Could not identify config: {e}")
logger.debug(f"Config found at RVA {hex(config_start)}...")
if not self.preserve_obfuscated_keys and self._is_likely_obfuscated_config(
decrypted_config
):
logger.debug(
"Preserve keys set to False and possible obfuscation detected; Translating keys..."
)
decrypted_config = {
f"obfuscated_key_{idx}": v
for idx, (_, v) in enumerate(decrypted_config.items(), 1)
}

return decrypted_config

# Attempts to retrieve the config via brute-force, looking through every
Expand Down Expand Up @@ -278,3 +314,11 @@ def _get_config_verify_hash_method(self) -> Tuple[int, dict[str, Any]]:
if min_config_len < self._MIN_CONFIG_LEN_FLOOR:
raise e
min_config_len -= 1

def _is_likely_obfuscated_config(self, config: dict[str, Any]) -> bool:
for key in config.keys():
if not key.isascii():
return True
if key in KNOWN_CONFIG_FIELD_NAMES:
return False
return True
2 changes: 1 addition & 1 deletion src/rat_king_parser/config_parser/utils/config_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def _derive_item_value(self, folder_id: bytes) -> str:
class EncryptedStringConfigItem(ConfigItem):
def __init__(self) -> None:
super().__init__(
"encrypted string", rb"\x72(.{3}\x70)(?:\x28.{4})?\x80(.{3}\x04)"
"encrypted string", rb"(?<!\x72.{3}\x70)\x72(.{3}\x70)(?:\x28.{4})?\x80(.{3}\x04)"
)

# Returns the encrypted string's RVA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from typing import Any

normalized_keys = {
"Hosts": ("HOSTS", "Hosts", "ServerIp", "hardcodedhosts", "PasteUrl"),
"Hosts": ("HOSTS", "Hosts", "HostsFE", "ServerIp", "hardcodedhosts", "PasteUrl"),
"Ports": ("Port", "Ports", "ServerPort"),
"Mutex": ("MTX", "MUTEX", "Mutex", "mutex_string"),
"Version": ("VERSION", "Version"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
from .config_decryptor_ecb import ConfigDecryptorECB
from .config_decryptor_plaintext import ConfigDecryptorPlaintext
from .config_decryptor_random_hardcoded import ConfigDecryptorRandomHardcoded
from .config_decryptor_rijndael import ConfigDecryptorRijndael

__all__ = [
ConfigDecryptor,
IncompatibleDecryptorException,
ConfigDecryptorAESWithIV,
ConfigDecryptorAESWithIV_pbkdf,
ConfigDecryptorRijndael,
ConfigDecryptorECB,
ConfigDecryptorDecryptXOR,
ConfigDecryptorRandomHardcoded,
Expand All @@ -46,6 +48,7 @@
SUPPORTED_DECRYPTORS = [
ConfigDecryptorAESWithIV,
ConfigDecryptorAESWithIV_pbkdf,
ConfigDecryptorRijndael,
ConfigDecryptorECB,
ConfigDecryptorDecryptXOR,
ConfigDecryptorRandomHardcoded,
Expand Down
Loading