Skip to content

Instantly share code, notes, and snippets.

@netadr
Created June 29, 2024 21:35
Show Gist options
  • Save netadr/2784b6c20f3e5e9250abf3108c9c38c4 to your computer and use it in GitHub Desktop.
Save netadr/2784b6c20f3e5e9250abf3108c9c38c4 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
"""
Extracts "burned-in" virtual filesystem objects (including plugins and
configuration data) from an EQGRP StraitBizarre sample:
https://www.virustotal.com/gui/file/f0285338e59322079bafe5780e1a26ef0d5d62cc0138b0725bd7a37084d03204
Author: netadr
Date: 2024-06-29
"""
import logging
import speakeasy
import struct
import zlib
from argparse import ArgumentParser
from io import BytesIO
from pathlib import Path
from pefile import PE, RESOURCE_TYPE
from speakeasy import Win32Emulator
DEOBFUSCATE1_ADDR = 0x7FFF2A3F0F6C
DEOBFUSCATE2_ADDR = 0x7FFF2A3F0EEC
KEY1_ADDR = 0x7FFF2A40B020
KEY2_ADDR = 0x7FFF2A40B030
HEADER_LEN = 0x10
def get_logger():
logger = logging.getLogger("sbz")
if not logger.handlers:
sh = logging.StreamHandler()
logger.addHandler(sh)
logger.setLevel(logging.INFO)
return logger
def deobfuscate_data(emu: Win32Emulator, data: bytes) -> bytes:
data_len = len(data)
data_ptr = emu.heap_alloc(data_len)
emu.mem_write(data_ptr, data)
out_ptr = emu.heap_alloc(data_len)
context_ptr = emu.heap_alloc(0x102)
emu.mem_write(context_ptr, b"\x00" * 0x102)
emu.call(DEOBFUSCATE1_ADDR, [context_ptr, KEY1_ADDR, KEY2_ADDR])
emu.call(DEOBFUSCATE2_ADDR, [context_ptr, data_len, data_ptr, out_ptr])
return emu.mem_read(out_ptr, data_len)
def dump_objects(goodies: bytes, dir: Path):
bundle = BytesIO(goodies)
count = struct.unpack(">I", bundle.read(0x4))[0]
while count != 0:
(id_1, id_2, id_3, size) = struct.unpack(">IIII", bundle.read(HEADER_LEN))
data = bundle.read(size)
with open(dir / f"{id_1:08x}_{id_2:08x}_{id_3:08x}.bin", "wb") as object:
object.write(data)
count -= 1
parser = ArgumentParser(prog="dump_resources")
parser.add_argument("implant", type=Path)
parser.add_argument("output_dir", type=Path)
args = parser.parse_args()
if not args.output_dir.exists():
args.output_dir.mkdir()
pe = PE(args.implant)
se = speakeasy.Speakeasy(logger=get_logger())
module = se.load_module(args.implant)
rt_data_idx = [entry.id for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries].index(
RESOURCE_TYPE["RT_RCDATA"]
)
rt_data_directory = pe.DIRECTORY_ENTRY_RESOURCE.entries[rt_data_idx]
for entry in rt_data_directory.directory.entries:
rva = entry.directory.entries[0].data.struct.OffsetToData
size = entry.directory.entries[0].data.struct.Size
data = pe.get_data(rva, size)
raw_data = deobfuscate_data(se.emu, data)
dump_objects(zlib.decompress(raw_data), args.output_dir)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment