Created
June 29, 2024 21:35
-
-
Save netadr/2784b6c20f3e5e9250abf3108c9c38c4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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