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

  • 6c4cb9d518f725b5c92f68699992f5525592328a47517d5897d971aac0ab6539UnpacMe

References

Analysis

  • In main.init the config buffer is sent to the decrypt function
  • The config buffer is globally stored data in .rdata

Config

  • First 2 bytes are the config length (big endian)
  • 16 byte key
  • 16 byyyyttt IV
  • AES CRT
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)
key: 9fcaf5ebad151480a5487255a06bdea2
iv: 97227cbfa9d48e92837aeb6ae9b3d959
data: 2036a58ea90828139c4e8e03f462fafe50eece8980753fd5d225dad54cebf89b71fa7eb913c0d01a995bebb7e19c947a97cc7a9d110d5e432293496a63324346a3b3d06790a8e2ea7dab74ccfcac459c19e66783a41a93165d0b470f5886fa2acb46a07a1ac1e3cda62d0c5c94803bb2953becdf8d9e7f1c445f327c90eca798d1247f57fc6508d08af502d998d30bcb8c47ef8c178a629be7cb36a6e3c5ef6614f4c75a7de05b120f8791b51998232eb7b8f03fac6e1e0c8aa0
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/bin","uuid":"5ded6243dc8ff999034d5e7b5b39dfc6","key":"23e11df804048597e2a71b4723fc70943d17d6d23dda67727847c2678f488199"}'
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/bin","uuid":"5ded6243dc8ff999034d5e7b5b39dfc6","key":"23e11df804048597e2a71b4723fc70943d17d6d23dda67727847c2678f488199"}'
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
gotit
b'{"secure":false,'
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/bins","uuid":"2ce85e4f37bbbd220aae018c9364908e","key":"d14e3f9fb01f0a02edd8592a70695ed46ceedd47081fcbb4fe9983b690d87394"}'

Yara Rule

rule SparkRAT {
strings:
    $s1 = "main.decrypt" 
    $s2 = "Spark/client/config.GetBaseURL"
condition:
    filesize > 8MB and
    all of them

}

Testing

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")
/tmp/samples/9c672b05b987575f132c5f798a162abfa487ac60dcf19a19220b4451b10d79a2
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/bins","uuid":"2ce85e4f37bbbd220aae018c9364908e","key":"d14e3f9fb01f0a02edd8592a70695ed46ceedd47081fcbb4fe9983b690d87394"}'
/tmp/samples/c24630ac0393879f0a49b298e23fd8bdb5caf2fc7d544735d5dc1e3d8bd31100
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/ws/","uuid":"6cbb52af667c0f0fb172f4d9dddac476","key":"29ffea5af983e8821e658d2f7c5fead70672a0f3eb486f658d10935b280cedaf"}'
/tmp/samples/efd8049d67d1bcdb6c0aba4da7ca6d4feacc65eb874447cc2c69d12dd4a83675
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/search/sss","uuid":"41c311a6df0696cfc097e3f8ce9e7f03","key":"1e21f4461c638b1a49c0abbd191214ffdcd19ed9b217bb09e2d4731e0db84ef8"}'
/tmp/samples/60c163c0f00a2aeeae8648898d75314b1c5e749777f227ac6c3c84656a68516e
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/search/","uuid":"86400df343d83699fc79362b8ff81285","key":"ec7edf99b3a1c4b5369af6195d8968c523cf24a5cc195475af15e4b22b76bf48"}'
/tmp/samples/07667fb2483b4727216ad7f7ce6413f32bea9530ad405fc7a415812ef6f3449a
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/search/ws","uuid":"53bdc54a4f7daccd67e58b6d9e574be2","key":"7c0d186ff178f23de57e9ad1c18a9b5e18e6af7842075ed81d6c68c65bb00c67"}'
/tmp/samples/35da1fcf3156cf1d4c200ddebe7047a4830965522daaa4972be445ba36eddc62
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/s","uuid":"02067c0ccc5b959ef7ae5cd86e2f3847","key":"542138332bc51501683a7c36f5df6c11000d432b03bebbee08353e91ef53ba84"}'
/tmp/samples/9c672b05b987575f132c5f798a162abfa487ac60dcf19a19220b4451b10d79a2.id2
ERROR
/tmp/samples/33d0fad1937c17ef1a7ef241497224ab603af12bc6fd56b320c9d7ab80294204
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/ws/","uuid":"a8058814339facd02b223f1a2f269e7e","key":"63b38d39d9c88e104fd4c6cd6ca6567ceac1e0ca382027dd624152550805b9ef"}'
/tmp/samples/2a6693166d2db53f60f7b18d49329204dc685a0c12c5cb8be1b62ced46980041
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/wss/","uuid":"559c86d4718108f01df0aa3f5fab645f","key":"8e32846214b8fc5e7a85e4636fa9344e6cfbf4c1e9f7e94cbdb7711e8c766276"}'
/tmp/samples/78780bbc3be24085b453f77b1aee712a4b3fc41126a473a360768d3cc85a1f55
b'{"secure":false,"host":"www.rakishevkenes.com","port":443,"path":"/search/","uuid":"d7a83d889d92ce210d037a1e7088cc5f","key":"405d56dfc12771cfc47bd5c8073004d8f86abb72f080afe741adbccc964ad125"}'
/tmp/samples/d2c72e0d3a6a14fc83ae9f0ee58da6674ec7096fd7ddf61a7fabb6737c08fffb
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/search/ws/","uuid":"c0dcb22d353fc045a61cb1f7ad69ea95","key":"129d16865bb3a2f0edec87380d0da44937bbee6fd3953a12a6f4fed4f2b2f5d6"}'
/tmp/samples/739ae6a61c12e14cca5f0c9cad3264f9cb7db70577efc9be354719455426443f
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/search/","uuid":"07645affa036169b475b8a9673fc0b24","key":"2944b2545c11df9d31c3e822a25b87c33235291e88432cd703839065ee2efa2c"}'
/tmp/samples/9c672b05b987575f132c5f798a162abfa487ac60dcf19a19220b4451b10d79a2.nam
ERROR
/tmp/samples/d3e19d396ade2fca0f69b766d9683068486009103d1a0aa57b3b6228b30bded2
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/","uuid":"f9ed0a92b6f56b76bdc014e9fe35cc45","key":"5951357b6514c159c5874da3e42572508197d03d09c406969e91061053766d13"}'
/tmp/samples/8ae2e582e6886753843e2f31001fe373bacc1cc0231bec8a6ba64d39394fe48c
b'{"secure":false,"host":"www.rakishevkenes.com","port":443,"path":"/search","uuid":"df9a256cc58c870ab3e5fde8e7bb51d1","key":"e88e8eaf0bf021d4d0ad83902df32e5d9928cc982b9f319a063d0284f6ab743f"}'
/tmp/samples/6c4cb9d518f725b5c92f68699992f5525592328a47517d5897d971aac0ab6539
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/bin","uuid":"5ded6243dc8ff999034d5e7b5b39dfc6","key":"23e11df804048597e2a71b4723fc70943d17d6d23dda67727847c2678f488199"}'
/tmp/samples/d065d44d0412aef867f66626b5c4a3d7d0a3bdb59c61712b0c71efbf9865a7a6
b'{"secure":false,"host":"45.32.120.181","port":8000,"path":"/","uuid":"2eebe48d50ce1e9cc8903edcd2008cba","key":"64de765054d237a1aab8d042a608c446a41a112bb78c910eef6c957b8d67704a"}'
/tmp/samples/36b8623b6b1120313cd6a82751d5eb175d274d2952a8b3d3d6f69874fb4e88b9
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/search/","uuid":"60c6e56cde980a7f505f5d86369ee973","key":"1a4282db476e8d9f08fb7ed322af8d79c6700411a79d7f15d01208aba64ec3a2"}'
/tmp/samples/db1b6d7e289c8c15bf9531dcc7f965a9c5a133b4f0438a95cde2ab2482080fd0
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/ws/","uuid":"f0137073f6cb31bd2bbad8123d02e8e8","key":"67e316e4d43c7454dab6bf2e6c9cb75d72b8a01d795c3384789a155ae59a7c76"}'
/tmp/samples/0b687729eb0cf87dafd6a27b4338f1aac5750eeaad3779bcea32c74602b50bb3
b'{"secure":false,"host":"www.rakishevkenes.com","port":80,"path":"/search/","uuid":"10b931c2e8ac02aa0d764959ae125e59","key":"e4d59dda45051129e17a239596c607754287b618e82483c6d8be49354dfa45b4"}'
/tmp/samples/fbe68d517f32b3a3507c6dfe6af41334a4c023d42a5a89725ea6e3fe28df4392
b'{"secure":false,"host":"rakishevkenes.com","port":80,"path":"/","uuid":"8207670d5dbea70d8bfde24b23442a88","key":"c111b9d0d5fd663c4344bd16b01ffca8c66dc3de361621ce3e706a7a142db70a"}'
/tmp/samples/9c672b05b987575f132c5f798a162abfa487ac60dcf19a19220b4451b10d79a2.id1
ERROR
/tmp/samples/b724da89a80fc6aa20d1214c42f002ac850c5b11c37873a0a06f91e3168a8031
b'{"secure":false,"host":"www.rakishevkenes.com","port":443,"path":"/search/ws","uuid":"a38e6696e312347fb788ad90d827ed66","key":"758c812bd31eec5c920528bd4c8be23022de186c587dfb171f97e8ee44c8fd63"}'
/tmp/samples/9c672b05b987575f132c5f798a162abfa487ac60dcf19a19220b4451b10d79a2.til
ERROR
/tmp/samples/9c672b05b987575f132c5f798a162abfa487ac60dcf19a19220b4451b10d79a2.id0
ERROR