BlackMatter Ransomware
BlackMatter Ransomware Config Extraction
def unhex(hex_string):
import binascii
if type(hex_string) == str:
return binascii.unhexlify(hex_string.encode('utf-8'))
else:
return binascii.unhexlify(hex_string)
def tohex(data):
import binascii
if type(data) == str:
return binascii.hexlify(data.encode('utf-8'))
else:
return binascii.hexlify(data)
def ror(value, count=1, base=8):
value = (value >> count | value << (base - count)) & (2**base - 1)
return value
out = 0
for i in 'ntdll.dll\x00':
out = (ord(i) + ror(out, count=13, base=32)) & 0xffffffff
print(hex(out))
APLib
Credit: Sandor Nemes (snemes)
import struct
from binascii import crc32
from io import BytesIO
__all__ = ['APLib', 'decompress']
__version__ = '0.6'
__author__ = 'Sandor Nemes'
class APLib(object):
__slots__ = 'source', 'destination', 'tag', 'bitcount', 'strict'
def __init__(self, source, strict=True):
self.source = BytesIO(source)
self.destination = bytearray()
self.tag = 0
self.bitcount = 0
self.strict = bool(strict)
def getbit(self):
# check if tag is empty
self.bitcount -= 1
if self.bitcount < 0:
# load next tag
self.tag = ord(self.source.read(1))
self.bitcount = 7
# shift bit out of tag
bit = self.tag >> 7 & 1
self.tag <<= 1
return bit
def getgamma(self):
result = 1
# input gamma2-encoded bits
while True:
result = (result << 1) + self.getbit()
if not self.getbit():
break
return result
def depack(self):
r0 = -1
lwm = 0
done = False
try:
# first byte verbatim
self.destination += self.source.read(1)
# main decompression loop
while not done:
if self.getbit():
if self.getbit():
if self.getbit():
offs = 0
for _ in range(4):
offs = (offs << 1) + self.getbit()
if offs:
self.destination.append(self.destination[-offs])
else:
self.destination.append(0)
lwm = 0
else:
offs = ord(self.source.read(1))
length = 2 + (offs & 1)
offs >>= 1
if offs:
for _ in range(length):
self.destination.append(self.destination[-offs])
else:
done = True
r0 = offs
lwm = 1
else:
offs = self.getgamma()
if lwm == 0 and offs == 2:
offs = r0
length = self.getgamma()
for _ in range(length):
self.destination.append(self.destination[-offs])
else:
if lwm == 0:
offs -= 3
else:
offs -= 2
offs <<= 8
offs += ord(self.source.read(1))
length = self.getgamma()
if offs >= 32000:
length += 1
if offs >= 1280:
length += 1
if offs < 128:
length += 2
for _ in range(length):
self.destination.append(self.destination[-offs])
r0 = offs
lwm = 1
else:
self.destination += self.source.read(1)
lwm = 0
except (TypeError, IndexError):
if self.strict:
raise RuntimeError('aPLib decompression error')
return bytes(self.destination)
def pack(self):
raise NotImplementedError
def aplib_decompress(data, strict=False):
packed_size = None
packed_crc = None
orig_size = None
orig_crc = None
if data.startswith(b'AP32') and len(data) >= 24:
# data has an aPLib header
header_size, packed_size, packed_crc, orig_size, orig_crc = struct.unpack_from('=IIIII', data, 4)
data = data[header_size : header_size + packed_size]
if strict:
if packed_size is not None and packed_size != len(data):
raise RuntimeError('Packed data size is incorrect')
if packed_crc is not None and packed_crc != crc32(data):
raise RuntimeError('Packed data checksum is incorrect')
result = APLib(data, strict=strict).depack()
if strict:
if orig_size is not None and orig_size != len(result):
raise RuntimeError('Unpacked data size is incorrect')
if orig_crc is not None and orig_crc != crc32(result):
raise RuntimeError('Unpacked data checksum is incorrect')
return result
import struct
import pefile
RANSOMWARE_FILE = r'/tmp/blackmatter.bin'
data = open(RANSOMWARE_FILE, 'rb').read()
pe = pefile.PE(data = data)
# Get resource data
r_data = None
for s in pe.sections:
if b'rsrc' in s.Name:
r_data = s.get_data()
# Parse data from resource
seed = struct.unpack('<I',r_data[:4])[0]
data_size = struct.unpack('<I',r_data[4:8])[0]
enc_data = r_data[8:]
print("Seed: %s" % hex(seed))
print("Size: %d" % data_size)
Decryption Routine
Reference: Tesorion Blackmatter blog
def decrypt(enc_data, data_size, seed):
fixed = seed
decrypted = bytearray()
for i in range(data_size-1):
if i & 3 == 0:
next_value = struct.unpack('<I',enc_data[i:i+4])[0]
seed = (0x8088405 * seed + 1) & 0xffffffff
rnd = ((seed * fixed) >> 32) & 0xffffffff
dw = next_value ^ rnd
decrypted.append((dw >> ((i & 3) * 8)) & 0xff)
return decrypted
def gen_key_stream(seed, key_length):
fixed = seed
keystream = b''
for i in range(0,key_length-1,4):
seed = (0x8088405 * seed + 1) & 0xffffffff
key_dw = ((seed * fixed) >> 32) & 0xffffffff
keystream += struct.pack('<I',key_dw)
return keystream
def decrypt(enc_data, data_size, seed):
out = []
keystream = gen_key_stream(seed, data_size)
for i in range(data_size):
out.append(enc_data[i] ^ keystream[i])
return bytes(out)
ap_data = decrypt(enc_data, data_size, seed)
ptxt_data = aplib_decompress(ap_data)
ptxt_data
import base64
ptr = 0
rsa_data = ptxt_data[ptr:128]
ptr += 128
affiliate_id_data = ptxt_data[ptr:ptr+32]
ptr+= 32
config_flags = ptxt_data[ptr:ptr+22]
ptr+= 8
config_values_offset = struct.unpack('<I',ptxt_data[ptr:ptr+4])[0]
config_values_buffer = ptxt_data[ptr+config_values_offset:]
config_values = []
for c in config_values_buffer.split(b'\x00'):
config_values.append(base64.b64decode(c))
def is_ascii(s):
return all(c < 128 for c in s)
print("RSA: %r\n" % rsa_data)
print("Affiliate ID: %r\n" % affiliate_id_data)
print("Flags: %s\n" % tohex(config_flags))
for c in config_values:
if not is_ascii(c):
c = new_data = decrypt(c,len(c), seed)
print("%s\n" % b' | '.join([s.replace(b'\x00',b'') for s in c.split(b'\x00\x00')]))