Overview

Sample

Sha256: 33D6C2BF629E34D4F11F3C680A3EF60501769DBDAC658E3A4A119D5AC81BFF79 MalwareBazaar

References

Config Extractor

import pefile
import struct 
import malduck


FILE_PATH = '/tmp/33d6c2bf629e34d4f11f3c680a3ef60501769dbdac658e3a4a119d5ac81bff79.bin'

file_data = open(FILE_PATH, 'rb').read()


def is_ascii(s):
    return all(c < 128 or c == 0 for c in s)


def get_config_entry(data, ptr):
    # Read the marker WORD
    marker = None
    marker_data = data[ptr:ptr + 2]
    if is_ascii(marker_data):
        marker = marker_data.decode('utf-8')
    ptr += 2
    # Skip the remaining header
    ptr += 6
    # Read hash value
    hash_value = struct.unpack("<I",data[ptr:ptr+4])[0]
    ptr += 4
    # Read config RVA
    rva = struct.unpack("<I",data[ptr:ptr+4])[0]
    ptr += 4
    # Read the config size
    size = struct.unpack("<I",data[ptr:ptr+4])[0]
    ptr +=4
    return {"marker":marker,"hash_value":hash_value,"rva":rva,"size":size}, ptr


def get_config_entries(data, ptr):
    config_entries = []
    config_entry,ptr = get_config_entry(data, ptr)
    config_entries.append(config_entry)
    while data[ptr] != 0:
        config_entry,ptr = get_config_entry(data, ptr)
        config_entries.append(config_entry)
    return config_entries


def get_section_table_end(pe):
    end_of_last_section = None
    if pe.FILE_HEADER.Machine == 0x014c:
        #32bit
        image_section_header_size = 0x28
        #image_section_header = image_dos_header.e_lfanew + sizeof(c_uint) + sizeof(IMAGE_FILE_HEADER) + x * sizeof(IMAGE_SECTION_HEADER) + image_nt_headers.FileHeader.SizeOfOptionalHeader
        end_of_last_section = pe.NT_HEADERS.FILE_HEADER.SizeOfOptionalHeader  + pe.DOS_HEADER.e_lfanew  + 4 + image_section_header_size * pe.FILE_HEADER.NumberOfSections 
    elif pe.FILE_HEADER.Machine == 0x8664:
        #64bit
        image_section_header_size = 0x28
        end_of_last_section = pe.NT_HEADERS.FILE_HEADER.SizeOfOptionalHeader  + pe.DOS_HEADER.e_lfanew  + 24 + image_section_header_size * pe.FILE_HEADER.NumberOfSections 
    return end_of_last_section


def config_table_scan(data, scan_start, scan_end):
    ptr = scan_start
    while ptr < scan_end:
        if data[ptr] != 0:
            break 
        ptr += 1
    if ptr == scan_end:
        ptr = None
    return ptr


def get_config_data(data, ptr, size):
    return data[ptr:ptr+size]


def config_wordlist_handler(config_data):
    ptxt_data = malduck.aplib.decompress(config_data)
    hash_value = struct.unpack('<I',ptxt_data[:4])[0]
    wordlist = ptxt_data[4:].decode('utf-8')
    return {"type_hash":hash_value, "wordlist":wordlist.strip('\r\n').split('\r\n')}
    

def config_crc_client_ini_handler(config_data):
    ptxt_data = malduck.aplib.decompress(config_data)
    entry_data = []
    for entry in ptxt_data.split(b"\x00"):
        if len(entry) > 1:
            if is_ascii(entry):
                entry_data.append(entry.decode('utf-8'))
    c2_data = max(entry_data, key=len)
    return c2_data.split(' ')


pe = pefile.PE(data=file_data)

ptr_section_table_end = get_section_table_end(pe)

assert ptr_section_table_end is not None

ptr_config_table = config_table_scan(file_data, ptr_section_table_end + 1, pe.sections[0].PointerToRawData)

assert ptr_config_table is not None

# Determine the ISFB version based on the marker
marker_versions = {"FJ":"old_isfb",
                  "J1":"old_isfb",
                  "J2":"dreambot",
                  "J3":"isfb_v3_japan",
                  "JJ":"isfb_v2.14+",
                  "WD":"RM3"
                 }

marker = file_data[ptr_config_table:ptr_config_table+2].decode('utf-8')
print(f"Bot version: {marker_versions.get(marker,'Unknown')}")

# Currently we can't handle the new RM3 config entry structure
assert marker != "WD"

config_entries = get_config_entries(file_data, ptr_config_table)

assert len(config_entries) != 0


# Loop through configs and handle each type
for config_entry in config_entries:
    config_type_hash = config_entry.get("hash_value")
    config_rva = config_entry.get("rva")
    config_offset = pe.get_offset_from_rva(config_rva)
    assert config_offset < len(file_data)
    config_size = config_entry.get("size")
    assert config_size < 0x1000
    
    # Get config data
    config_data = get_config_data(file_data, config_offset, config_size)
    
    # Get config type handler
    if config_type_hash == 0xe1285e64:
        # CRC_PUBLIC_KEY
        print(f"CRC_PUBLIC_KEY at offset {hex(config_offset)}")
    elif config_type_hash in [0x8fb1dde1, 0xd722afcb]:
        # CRC_CLIENT_INI
        print(f"CRC_CLIENT_INI at offset {hex(config_offset)}")
        c2_list = config_crc_client_ini_handler(config_data)
        print("C2 List")
        for c2 in c2_list:
            print(f"\t{c2}")
    elif config_type_hash == 0x7a042a8a:
        # CRC_INSTALL_INI
        print(f"CRC_INSTALL_INI at offset {hex(config_offset)}")
    elif config_type_hash == 0x90f8aab4:
        # CRC_CLIENT64
        print(f"CRC_CLIENT64 at offset {hex(config_offset)}")
    elif config_type_hash in [0xda57d71a, 0x68ebb983]:
        # CRC_WORDLIST
        print(f"CRC_WORDLIST at offset {hex(config_offset)}")
        wordlist_data = config_wordlist_handler(config_data)
        print(wordlist_data)
    else:
        print(f"Unhandled config type {hex(config_type_hash)}")

    
Bot version: isfb_v2.14+
CRC_PUBLIC_KEY at offset 0x38800
CRC_CLIENT_INI at offset 0x38a00
C2 List
	trackingg-protectioon.cdn1.mozilla.net
	45.8.158.104
	trackingg-protectioon.cdn1.mozilla.net
	188.127.224.114
	weiqeqwns.com
	wdeiqeqwns.com
	weiqeqwens.com
	weiqewqwns.com
	iujdhsndjfks.com
CRC_WORDLIST at offset 0x38c00
{'type_hash': 3014533032, 'wordlist': ['list', 'stop', 'computer', 'desktop', 'system', 'service', 'start', 'game', 'stop', 'operation', 'black', 'line', 'white', 'mode', 'link', 'urls', 'text', 'name', 'document', 'type', 'folder', 'mouse', 'file', 'paper', 'mark', 'check', 'mask', 'level', 'memory', 'chip', 'time', 'reply', 'date', 'mirrow', 'settings', 'collect', 'options', 'value', 'manager', 'page', 'control', 'thread', 'operator', 'byte', 'char', 'return', 'device', 'driver', 'tool', 'sheet', 'util', 'book', 'class', 'window', 'handler', 'pack', 'virtual', 'test', 'active', 'collision', 'process', 'make', 'local', 'core']}
hex(struct.unpack('<I',b'\x89j\xc0\xd8')[0])
'0xd8c06a89'

TODO: Parse each of the config values individually