Skip to content

Commit

Permalink
Add SparkRAT config parser
Browse files Browse the repository at this point in the history
  • Loading branch information
t-mtsmt authored Aug 3, 2024
1 parent f9f935f commit 8a11991
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 0 deletions.
49 changes: 49 additions & 0 deletions data/yara/CAPE/SparkRAT.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
rule SparkRAT
{
meta:
author = "t-mtsmt"
description = "SparkRAT Payload"
cape_type = "SparkRAT Payload"

strings:
$path_00 = "/client/common"
$path_01 = "/client/config"
$path_02 = "/client/core"
$path_03 = "/client/service/basic"
$path_04 = "/client/service/desktop"
$path_05 = "/client/service/file"
$path_06 = "/client/service/process"
$path_07 = "/client/service/terminal"
$path_08 = "/modules"
$path_09 = "/utils"
$cmd_00 = "PING"
$cmd_01 = "OFFLINE"
$cmd_02 = "LOCK"
$cmd_03 = "LOGOFF"
$cmd_04 = "HIBERNATE"
$cmd_05 = "SUSPEND"
$cmd_06 = "RESTART"
$cmd_07 = "SHUTDOWN"
$cmd_08 = "SCREENSHOT"
$cmd_09 = "TERMINAL_INIT"
$cmd_10 = "TERMINAL_INPUT"
$cmd_11 = "TERMINAL_RESIZE"
$cmd_12 = "TERMINAL_PING"
$cmd_13 = "TERMINAL_KILL"
$cmd_14 = "FILES_LIST"
$cmd_15 = "FILES_FETCH"
$cmd_16 = "FILES_REMOVE"
$cmd_17 = "FILES_UPLOAD"
$cmd_18 = "FILE_UPLOAD_TEXT"
$cmd_19 = "PROCESSES_LIST"
$cmd_20 = "PROCESS_KILL"
$cmd_21 = "DESKTOP_INIT"
$cmd_22 = "DESKTOP_PING"
$cmd_23 = "DESKTOP_KILL"
$cmd_24 = "DESKTOP_SHOT"
$cmd_25 = "COMMAND_EXEC"
condition:
3 of ($path_*) and 3 of ($cmd_*)
}
73 changes: 73 additions & 0 deletions modules/processing/parsers/CAPE/SparkRAT.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import json
import io
import logging
from contextlib import suppress

HAVE_PYCYPTODOMEX = False
with suppress(ImportError):
from Crypto.Cipher import AES
from Crypto.Util import Counter

HAVE_PYCYPTODOMEX = True

log = logging.getLogger(__name__)


DESCRIPTION = "SparkRAT configuration parser."
AUTHOR = "t-mtsmt"


def extract_data_before_string(data, search_string, offset):
search_bytes = search_string.encode('utf-8')

position = data.find(search_bytes)
if position == -1:
return b''

start_position = max(position - offset, 0)
return data[start_position:position]


def decrypt_config(enc_data, key, iv):
counter = Counter.new(128, initial_value=int.from_bytes(iv, 'big'))
cipher = AES.new(key, mode=AES.MODE_CTR, counter=counter)
dec_data = cipher.decrypt(enc_data)
config = dec_data.decode('utf-8')
return json.loads(config)


def extract_config(data):
if not HAVE_PYCYPTODOMEX:
log.error('Missed pycryptodomex. Run: poetry install')
return {}

search_string = 'DXGI_ERROR_DRIVER_INTERNAL'
config_buf_size = 0x180
config_buf = extract_data_before_string(data, search_string, offset=config_buf_size)

if len(config_buf) == 0:
log.error('Configuration is not found.')
return {}

if config_buf == b'\x19' * config_buf_size:
log.debug('Configuration does not exist because the template data in the ConfigBuffer was not replaced.')
return {}

try:
with io.BytesIO(config_buf) as f:
data_len = int.from_bytes(f.read(2), 'big')
key = f.read(16)
iv = f.read(16)
enc_data = f.read(data_len - 32)
return decrypt_config(enc_data, key, iv)
except Exception as e:
log.error('Configuration decryption failed: %s', e)
return {}


if __name__ == '__main__':
import sys
from pathlib import Path

data = Path(sys.argv[1]).read_bytes()
print(extract_config(data))

0 comments on commit 8a11991

Please sign in to comment.