SparkRAT
Open Source GO Rat
Overview
Spark is an open source RAT developed in GO that has been observed by SentinelOne in targeted attacks 'orchestrated by a Chinese-speaking threat actor'.
Sample
-
6c4cb9d518f725b5c92f68699992f5525592328a47517d5897d971aac0ab6539
UnpacMe
References
from Crypto.Cipher import AES
import struct
from Cryptodome.Util import Counter
data = bytes.fromhex('00 DA 9F CA F5 EB AD 15 14 80 A5 48 72 55 A0 6B DE A2 97 22 7C BF A9 D4 8E 92 83 7A EB 6A E9 B3 D9 59 20 36 A5 8E A9 08 28 13 9C 4E 8E 03 F4 62 FA FE 50 EE CE 89 80 75 3F D5 D2 25 DA D5 4C EB F8 9B 71 FA 7E B9 13 C0 D0 1A 99 5B EB B7 E1 9C 94 7A 97 CC 7A 9D 11 0D 5E 43 22 93 49 6A 63 32 43 46 A3 B3 D0 67 90 A8 E2 EA 7D AB 74 CC FC AC 45 9C 19 E6 67 83 A4 1A 93 16 5D 0B 47 0F 58 86 FA 2A CB 46 A0 7A 1A C1 E3 CD A6 2D 0C 5C 94 80 3B B2 95 3B EC DF 8D 9E 7F 1C 44 5F 32 7C 90 EC A7 98 D1 24 7F 57 FC 65 08 D0 8A F5 02 D9 98 D3 0B CB 8C 47 EF 8C 17 8A 62 9B E7 CB 36 A6 E3 C5 EF 66 14 F4 C7 5A 7D E0 5B 12 0F 87 91 B5 19 98 23 2E B7 B8 F0 3F AC 6E 1E 0C 8A A0 A7 CC 16 99 30 68 97 FA 2A FA 6C 20 7A 90 36 6A 25 F4 03 61 26 D5 9F 74 CD 16 9C 68 EE 4A DC 9D F8 B6 81 4F AD 32 E9 79 C0 89 57 AD F7 7A 43 AE A0 63 4E CD 6A CD A5 88 53 4F 2C D9 0C BC 8E 7B 3C 53 55 A6 A7 EA 47 92 EA 29 C6 11 B6 3B EB 88 09 96 52 64 C4 9D 7E C7 70 9E 1C 1C D2 FF 24 5A EE A6 00 17 30 53 52 67 B1 6E 0A 48 E1 DC 35 F6 C6 F1 67 79 C2 1C 0F D1 75 98 44 E7 D1 C3 18 EC B2 65 44 06 10 31 7E 17 92 E9 24 27 E0 1A 97 A3 2B 35 45 4A 28 0F 92 11 96 E9 AC 75 C0 AF 4E CF D2 6E AF 34')
ptr = 0
size = struct.unpack('>H', data[ptr:ptr+2])[0]
ptr += 2
config_data = data[ptr:ptr+size]
ptr = 0
key = config_data[ptr:ptr+16]
ptr += 16
iv = config_data[ptr:ptr+16]
ptr += 16
enc_data = config_data[ptr:]
print(f"key: {key.hex()}")
print(f"iv: {iv.hex()}")
print(f"data: {enc_data.hex()}")
# WARNING: Pycrytodome is insane
# when the IV size == Key size you need to do this fancy init of the IV
# the counter replaces the IV value
# Ref. https://github.com/binref/refinery/blob/master/refinery/units/crypto/cipher/__init__.py#L320
# Also suggested use initial value and emtpy nonce
# ctr = AES.new(KEY, AES.MODE_CTR, initial_value=NONCE_IV, nonce=b'')
counter = Counter.new(len(key) * 8, initial_value=int.from_bytes(iv, 'big'))
cipher = AES.new(key, AES.MODE_CTR, counter=counter)
out = cipher.decrypt(enc_data)
egg = b'{"'
if egg == out[:2]:
print(out)
def decrypt(key, iv, data):
counter = Counter.new(len(key) * 8, initial_value=int.from_bytes(iv, 'big'))
cipher = AES.new(key, AES.MODE_CTR, counter=counter)
out = cipher.decrypt(data)
return out
decrypt(key, iv, enc_data)
import pefile
import re
import struct
file_data = open('/tmp/samples/9c672b05b987575f132c5f798a162abfa487ac60dcf19a19220b4451b10d79a2', 'rb').read()
pe = pefile.PE(data=file_data)
data = None
for s in pe.sections:
if s.Name[:6] == b'.rdata':
data = s.get_data()
assert data is not None
egg = rb'\x00[\x64-\xff]'
for m in re.finditer(egg, data, re.DOTALL):
end = m.end()
size = struct.unpack('>H', m.group())[0]
#print(f"Testing offset: {hex(end)}")
candidate = data[end:end+size]
if bytes.fromhex('CC 02 C0 BA') == candidate[:4]:
print("gotit")
if b'\x00\x00\x00' in candidate or b'\xFF\xFF\xFF' in candidate:
continue
tmp_data = data[end:end+16+16+16]
tmp_out = decrypt(tmp_data[:16], tmp_data[16:32], tmp_data[32:])
print(tmp_out)
if tmp_out[:2] == b'{"':
out = decrypt(candidate[:16], candidate[16:32], candidate[32:])
print(out)
break
def get_config(file_path):
config = None
file_data = open(file_path, 'rb').read()
pe = pefile.PE(data=file_data)
data = None
for s in pe.sections:
if s.Name[:6] == b'.rdata':
data = s.get_data()
assert data is not None
egg = rb'\x00[\x64-\xff]'
for m in re.finditer(egg, data, re.DOTALL):
end = m.end()
size = struct.unpack('>H', m.group())[0]
#print(f"Testing offset: {hex(end)}")
candidate = data[end:end+size]
if b'\x00\x00\x00' in candidate or b'\xFF\xFF\xFF' in candidate:
continue
tmp_data = data[end:end+16+16+16]
tmp_out = decrypt(tmp_data[:16], tmp_data[16:32], tmp_data[32:])
if tmp_out[:2] == b'{"':
out = decrypt(candidate[:16], candidate[16:32], candidate[32:])
config = out
break
return config
# import required module
import os
# assign directory
directory = '/tmp/samples'
# iterate over files in
# that directory
for filename in os.listdir(directory):
f = os.path.join(directory, filename)
# checking if it is a file
if os.path.isfile(f):
print(f)
try:
config = get_config(f)
print(config)
except:
print("ERROR")