Overview

Sample: 22d7d67c3af10b1a37f277ebabe2d1eb4fd25afbd6437d4377400e148bcc08d6

References:

Helper Functions

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)

API Hashing

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))
0x411677b7

Config Decryption

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

Extract Config

The Blackmatter config is stored in the PE resource section .rsrc.

The first DWORD of the resource is the seed for an lcg that closely matches the zipcrypto lcg with constant 0x8088405.

The second DWORD is the size of the encrypted config and is followed by the encrypted config data.

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)
Seed: 0xffcaa1ea
Size: 3487

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
b'\x87\x19\xa80\xf4\xba\x94\x94\x92\x91X+fT\xf9l\x96\xd9\xa0\xf4A\x9fR\xf3g\xcf.\x19\xb9\xc9Z\x9bp\x91\xcb\xef\xaf\xbeZ\xe3\x9d\xae(X\x94Y\n\x8d\xb8\xb7d\xe5r\xfa\xb5#FF\xf8e\x9a\xda/\xbd\x8c7\xbf\xdd\xd6\x07\x97\xa5\xad\x9d\xad-\xed7\x96\x9d\x17\x9e\xa4\xadL\x19\x80\xd0\xe7\x0b\x05bA\xd3%\xe1\x8b\xeb\\\xc4\x92_\xa5j\xbf\x81\x0f\x91ny2\xd0\x16\xa8n:\xd9wI\xe7_\x901\x11K\x06\x0bVQ$x\xc0\x8d\xad\xa2\xaf\x19\xe4\x98\x08\xfb\xda[\x0b\xa6\xf30\xb0\x9c\xd4{O\xb9!Ox6\xaaF\xad\x00\x01\x01\x01\x01\x01\x01\x01$\x00\x00\x00\xa1\x00\x00\x00\xe2\x00\x00\x00\x00\x00\x00\x00\xf3\x01\x00\x00\xc8\x04\x00\x009\x05\x00\x002\x06\x00\x00\x1f\x07\x00\x00ro4BrnX5Zms1fmgmp9Hypi0hCgPduMrclWUIq05OADb1eHAmezreXJI46rfXbELjszc67ztiIrrUJUtMlON1LsA7puHNgfKMOAvLUpTmZlNYac7GNXnwBwAAAAB=\x00UqLSghWqzIY3WZfbVqvI/NH3zsibCQc59aY6wgDsa4SWrgzwNariy+RXqoUAAAAA\x00k8UWrwAbmN9xl+JkwBxI3YAbWNsAHijNQBgQycAYkOlAGKDHgBywywAdgOfAHIDdgBww48AcGN3AGHjVQBnI2cAYwOHxlLpKMZbiSgAZEOMAHMDxABuA04CrnsnAG2jnABtgy9u3a0rAGyDdwByI4cAYmNUAHQjTQBpwx0AbIMmAG0jFAA6wZ8DYXuFAHZDJABnA1UAemOnAG2jJ/sv8oQAamOkAGTDbepsuzQAcCN03eMpMABxg6QAcYMkAGbDFwBhg1cAcqN0AAAAA\x00ZQBuAGMAcwB2AGMAAAB0AGgAZQBiAGEAdAAAAG0AeQBkAGUAcwBrAHQAbwBwAHEAbwBzAAAAeABmAHMAcwB2AGMAYwBvAG4AAABmAGkAcgBlAGYAbwB4AAAAaQBuAGYAbwBwAGEAdABoAAAAdwBpAG4AdwBvAHIAZAAAAHMAdABlAGEAbQAAAHMAeQBuAGMAdABpAG0AZQAAAG4AbwB0AGUAcABhAGQAAABvAGMAbwBtAG0AAABvAG4AZQBuAG8AdABlAAAAbQBzAHAAdQBiAAAAdABoAHUAbgBkAGUAcgBiAGkAcgBkAAAAYQBnAG4AdABzAHYAYwAAAHMAcQBsAAAAZQB4AGMAZQBsAAAAcABvAHcAZQByAHAAbgB0AAAAbwB1AHQAbABvAG8AawAAAHcAbwByAGQAcABhAGQAAABkAGIAZQBuAGcANQAwAAAAaQBzAHEAbABwAGwAdQBzAHMAdgBjAAAAcwBxAGIAYwBvAHIAZQBzAGUAcgB2AGkAYwBlAAAAbwByAGEAYwBsAGUAAABvAGMAYQB1AHQAbwB1AHAAZABzAAAAZABiAHMAbgBtAHAAAABtAHMAYQBjAGMAZQBzAHMAAAB0AGIAaQByAGQAYwBvAG4AZgBpAGcAAABvAGMAcwBzAGQAAABtAHkAZABlAHMAawB0AG8AcABzAGUAcgB2AGkAYwBlAAAAdgBpAHMAaQBvAAAAAAA=\x00bQBlAHAAbwBjAHMAAABtAGUAbQB0AGEAcwAAAHYAZQBlAGEAbQAAAHMAdgBjACQAAABiAGEAYwBrAHUAcAAAAHMAcQBsAAAAdgBzAHMAAAAAAM==\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\x00'
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')]))
    
RSA: b'\x87\x19\xa80\xf4\xba\x94\x94\x92\x91X+fT\xf9l\x96\xd9\xa0\xf4A\x9fR\xf3g\xcf.\x19\xb9\xc9Z\x9bp\x91\xcb\xef\xaf\xbeZ\xe3\x9d\xae(X\x94Y\n\x8d\xb8\xb7d\xe5r\xfa\xb5#FF\xf8e\x9a\xda/\xbd\x8c7\xbf\xdd\xd6\x07\x97\xa5\xad\x9d\xad-\xed7\x96\x9d\x17\x9e\xa4\xadL\x19\x80\xd0\xe7\x0b\x05bA\xd3%\xe1\x8b\xeb\\\xc4\x92_\xa5j\xbf\x81\x0f\x91ny2\xd0\x16\xa8n:\xd9wI\xe7_\x901\x11K\x06\x0bV'

Affiliate ID: b'Q$x\xc0\x8d\xad\xa2\xaf\x19\xe4\x98\x08\xfb\xda[\x0b\xa6\xf30\xb0\x9c\xd4{O\xb9!Ox6\xaaF\xad'

Flags: b'000101010101010124000000a1000000e20000000000'

b'\xc4\xe2\x95wo\xed9>\x10\xdb\x16\xd9.\xa5\x01\xcc\xaeP\xc4t\xdc\xb0\xbc\xf5\xe2\x860\xde\x9e\x1b\x81Q\x91\x0e\xc9\x7f\xe4Y\xe6\x8aX*~\x0e\x84W\xb0\xed\x1aA\xe9f\x10\x0fq\xbd\x1eg\x02z\xa7\xac[\xa9z\x9el\x1b\xe1\x1f\xb8\xb1,\xbepPX:\xcc\xb1L\xc0\xc0\x13\x943\x99\x05\x97\x14CT'

b'8\xceF[\x0f\xbe\x93\xd3\x12\xfc\xe9$\xdf\xdf;\x96R\x86\xbf\x9a\x01q\x10\x82E\x02\xb7\xd0\xb9\xea\xe3\xf2\xd8\xb5\xa9\xaa\xc9\xda\x1d.E><S;\xf2\x0e'

b"\xf9\xa9\x82v\x1a\x0f\xc7\x8aT2\x9c\x9bIh\xbb\xb7\x03j\x96\xac\x01\x16^\xe47\xfb(\xbc\x10M\x11\x8e$n\x19\x9e\x1f\x7f\x88\x1d\xca\x0f\x14^\x93'r\xd3)j\xe3j\xebqK\xda\nZ1\xe3sV\xe6^z\xbd\n\x1b\xdd\n\xf0w%#YH\xcc\xc5\xba\x01\x14\xb5\xce$\xa1Q\xe9\xd1\x17\xbf\xdd\x9d\xe2\x11\xc0\x9aL!~\xf0a\x83\xa9:m\x04\xd3O\n\xd6_t\xd04\xe0\xe4\x83\xd6\xc2\xb9\tB&\x18^\xa3Y\xd0\xf3\xe6{S\xa3\x9d\xcby\xef\xc8z\xf7/\xd4./\x07N\xa3\x86%\xba\xe5X\xb8W\xa4Ry\xe3\rZ\x1c\xf4\xbd\xd1\xc4{o\xe9\xea1\xc9\xb2P\x030\x92\xab9\xe6js;\xb2\xce\x87\xcf\xdfw\xba\xf6^\x88\xdc\xb7\xf7\xa8N\xa23\x83v\xe9\x8e\xb2"

b'encsvc | thebat | mydesktopqos | xfssvccon | firefox | infopath | winword | steam | synctime | notepad | ocomm | onenote | mspub | thunderbird | agntsvc | sql | excel | powerpnt | outlook | wordpad | dbeng50 | isqlplussvc | sqbcoreservice | oracle | ocautoupds | dbsnmp | msaccess | tbirdconfig | ocssd | mydesktopservice | visio |  | '

b'mepocs | memtas | veeam | svc$ | backup | sql | vss |  | '

b'https://paymenthacks.com | http://paymenthacks.com | https://mojobiden.com | http://mojobiden.com |  | '

b'aheisler@hhcp.com:120Heisler | dsmith@hhcp.com:Tesla2019 | administrator@hhcp.com:iteam8** |  | '

b"      ~+                                       \r\n               *       +\r\n         '     BLACK        |\r\n     ()    .-.,='``'=.    - o -         \r\n           '=/_       \\     |           \r\n        *   |  '=._    |                \r\n             \\     `=./`,        '    \r\n          .   '=.__.=' `='      *\r\n +             Matter        +\r\n      O      *        '       .\r\n\r\n>>> What happens?\r\n   Your network is encrypted, and currently not operational. We have downloaded 1TB from your fileserver.\r\n   We need only money, after payment we will give you a decryptor for the entire network and you will restore all the data.\r\n\r\n>>> What guarantees? \r\n   We are not a politically motivated group and we do not need anything other than your money. \r\n   If you pay, we will provide you the programs for decryption and we will delete your data. \r\n   If we do not give you decrypters or we do not delete your data, no one will pay us in the future, this does not comply with our goals. \r\n   We always keep our promises.\r\n\r\n>> Data leak includes\r\n1. Full emloyeers personal data\r\n2. Network information\r\n3. Schemes of buildings, active project information, architect details and contracts, \r\n4. Finance info\r\n\r\n\r\n>>> How to contact with us? \r\n   1. Download and install TOR Browser (https://www.torproject.org/).\r\n   2. Open http://supp24yy6a66hwszu2piygicgwzdtbwftb76htfj7vnip3getgqnzxid.onion/7NT6LXKC1XQHW5039BLOV.\r\n  \r\n>>> Warning! Recovery recommendations.  \r\n   We strongly recommend you to do not MODIFY or REPAIR your files, that will damage them."

b''