Emotet x64 Stack Strings Config Emulation
Taking a look at the new Emotet stack strings config
Overview
The week of May 9th, 2022 Emotet released an update to their x64 malware that used "stack strings" and an obfuscator to protect the strings, keys, and c2s. This was a change from the enrypted strings and c2 tables that were usually stored at the beginning of the .text
and .data
sections. Our new approach for config extraction will be to identify the functions used to supply the strings and c2s and emulate them.
Samples
-
packed:
92033dc85730f7dc5dbd85369ea1db8806ce7581c1e9b4764a82abfc54e3146e
-
unpacked:
c688e079a16b3345c83a285ac2ae8dd48680298085421c225680f26ceae73eb7
Tools
- Dumpulator minidump emulation github
from dumpulator import Dumpulator
dp = Dumpulator("/tmp/emo2.dmp", quiet=True)
fn_addr_list = [0x07FFA3BA235D0, 0x7FFA3BA213C4,0x7FFA3BA21AAC,0x7FFA3BA2400C,0x7FFA3BA282D8,0x7FFA3BA2A36C,0x7FFA3BA2D370,0x7FFA3BA2DD3C,0x7FFA3BA2E468,0x7FFA3BA30C28,0x7FFA3BA31960,0x7FFA3BA33F28,0x7FFA3BA35980,0x7FFA3BA35B04,0x7FFA3BA3AFB0,0x7FFA3BA3F9A8,0x7FFA3BA3FEE8,0x7FFA3BA4012C,0x7FFA3BA41124,0x7FFA3BA412A4,0x7FFA3BA415A0,0x7FFA3BA42224,0x7FFA3BA43224,0x7FFA3BA44AEC,0x7FFA3BA465F0,0x7FFA3BA46744,0x7FFA3BA47140,0x7FFA3BA472A8,0x7FFA3BA490F8,0x7FFA3BA49850,0x7FFA3BA49A58,0x7FFA3BA49D04,0x7FFA3BA49FB4,0x7FFA3BA4BCB4,0x7FFA3BA4C168]
for fn_addr in fn_addr_list:
out = dp.call(fn_addr, [])
ptxt_str = dp.read_str(out, encoding='utf-16')
print(f"{hex(fn_addr)}: {ptxt_str}")
dp.read_str(out, encoding='utf-16')
import struct
key_decrypt_functions = [0x7FFA3BA33B90, 0x7FFA3BA22048]
for key_decrypt_function in key_decrypt_functions:
tmp_arg = dp.allocate(8)
out = dp.call(key_decrypt_function, [tmp_arg,tmp_arg, tmp_arg, tmp_arg])
key_header = bytes(dp.read(out, 8))
key_len = struct.unpack('<I',key_header[4:8])[0]
full_key_len = 8 + 2 * key_len
key = bytes(dp.read(out, full_key_len))
print(key)
c2_fns = [0x07FFA3BA2E70C, 0x7FFA3BA30D88,0x7FFA3BA4B054,0x7FFA3BA21528,0x7FFA3BA4A4CC,0x7FFA3BA4C2B8,0x7FFA3BA4BF80,0x7FFA3BA4AA74,0x7FFA3BA2DAB0,0x7FFA3BA43584,0x7FFA3BA34644,0x7FFA3BA2FD58,0x7FFA3BA35690,0x7FFA3BA3975C,0x7FFA3BA23BD0,0x7FFA3BA3519C,0x7FFA3BA2B610,0x7FFA3BA4C8B0,0x7FFA3BA3C9F8,0x7FFA3BA36A10,0x7FFA3BA4339C,0x7FFA3BA21F58,0x7FFA3BA4557C,0x7FFA3BA28BC8,0x7FFA3BA3C5B4,0x7FFA3BA45498,0x7FFA3BA21000,0x7FFA3BA24E50,0x7FFA3BA2FBC4,0x7FFA3BA33278,0x7FFA3BA468C0,0x7FFA3BA464FC,0x7FFA3BA28EE0,0x7FFA3BA274A0,0x7FFA3BA3092C,0x7FFA3BA24D58,0x7FFA3BA3E274,0x7FFA3BA2BCF8,0x7FFA3BA4CAF8,0x7FFA3BA4A340,0x7FFA3BA29820,0x7FFA3BA4A0F8,0x7FFA3BA494D8,0x7FFA3BA35C7C,0x7FFA3BA3D5C8,0x7FFA3BA21D48,0x7FFA3BA4103C,0x7FFA3BA28DCC,0x7FFA3BA22F64,0x7FFA3BA301BC,0x7FFA3BA2F454,0x7FFA3BA2B9E4,0x7FFA3BA24C38,0x7FFA3BA3CF80,0x7FFA3BA3E360,0x7FFA3BA45264,0x7FFA3BA49C14,0x7FFA3BA469D0,0x7FFA3BA281E4,0x7FFA3BA2DC28,0x7FFA3BA26F38,0x7FFA3BA45678,0x7FFA3BA24868,0x7FFA3BA35598]
def get_c2_from_fn(c2_fn):
c2_ip = dp.allocate(4)
c2_port = dp.allocate(4)
ret = dp.call(c2_fn, [c2_ip, c2_port])
c2_port_bytes = dp.read(c2_port, 4)
c2_port = struct.unpack('<H',c2_port_bytes[2:4])[0]
c2_ip_bytes = dp.read(c2_ip, 4)
c2_ip = f"{c2_ip_bytes[0]}.{c2_ip_bytes[1]}.{c2_ip_bytes[2]}.{c2_ip_bytes[3]}"
return f"{c2_ip}:{c2_port}"
for c2_fn in c2_fns:
c2 = get_c2_from_fn(c2_fn)
print(f"{c2}")
import re
import struct
import pefile
dump_image_base = 0x7FFA3BA20000
FILE_PATH = '/tmp/emo_unpacked_1020000.bin'
file_data = open(FILE_PATH, 'rb').read()
pe = pefile.PE(data=file_data)
egg = rb'\x48\x8D\x05(....)\x48\x89\x81..\x00\x00'
for m in re.finditer(egg, file_data):
fn_rel_offset = struct.unpack('<i', m.group(1))[0]
inst_offset = m.start()
fn_rva = pe.get_rva_from_offset(inst_offset) + 7 + fn_rel_offset
fn_addr = dump_image_base + fn_rva
c2 = get_c2_from_fn(fn_addr)
print(f"{hex(fn_addr)}: {c2}")