核心部分的函数存在大量的混淆

SIMD混淆

ida无法正常分析,也无法正常反编译,但是通过调试就会发现

SIMD混淆

SIMD混淆

运行SIMD这些指令时,只有RIP寄存器会变(这个是正常行为),也就是说这些SIMD混淆指令是不会影响正常通用寄存器的运算的,所以可以全部patch,通过进一步调试分析,最终的去混淆ida python如下:

import os
import ida_ua
import ida_bytes
import ida_auto
import ida_kernwin
import ida_idaapi
import idc
import idautils

START = 0x00007FF96194D875
END   = 0x00007FF961954D21  

PRESERVE_EXTERNAL_XREF_TARGETS = False

FORCE_DEFINE_HIDDEN_CODE = True

PRINT_ALL_VALID_ADDRS = True

PRESERVE_ANY_GPR_TOUCHING_SIMD = True

PROTECTED_GPR_TOKENS = {
    "rax", "eax", "ax", "al", "ah",
    "rbx", "ebx", "bx", "bl", "bh",
    "rcx", "ecx", "cx", "cl", "ch",
    "rdx", "edx", "dx", "dl", "dh",
    "rsi", "esi", "si", "sil",
    "rdi", "edi", "di", "dil",
    "rbp", "ebp", "bp", "bpl",
    "rsp", "esp", "sp", "spl",
    "r8", "r8d", "r8w", "r8b",
    "r9", "r9d", "r9w", "r9b",
    "r10", "r10d", "r10w", "r10b",
    "r11", "r11d", "r11w", "r11b",
    "r12", "r12d", "r12w", "r12b",
    "r13", "r13d", "r13w", "r13b",
    "r14", "r14d", "r14w", "r14b",
    "r15", "r15d", "r15w", "r15b",
}

OUTPUT_TXT_NAME = "valid_instructions_7FF96FAAD874_7FF96FAB4D21.txt"

# 典型 SIMD / 向量指令助记符(可扩展)
SIMD_MNEMS = {
    "movaps", "movups", "movdqa", "movdqu", "movapd", "movupd",
    "addps", "subps", "mulps", "divps", "xorps", "andps", "orps",
    "addpd", "subpd", "mulpd", "divpd", "xorpd", "andpd", "orpd",
    "pshufd", "shufps", "unpcklps", "unpckhps",
    "pcmpeqb", "pcmpgtb", "packuswb", "punpcklbw", "punpckhbw",
    "paddb", "paddw", "paddd", "psubb", "psubw", "psubd",
    "pmullw", "pmulld", "pxor", "pand", "por",
    "psubusw", "pshufb", "punpcklqdq",
    "movd", "movq", "pinsrd", "pextrd", "cvtsi2ss", "cvttss2si",
    "vzeroupper", "vmovdqa", "vmovdqu", "vmovaps", "vmovups",
    "vaddps", "vsubps", "vmulps", "vdivps", "vxorps",
    "vaddpd", "vsubpd", "vmulpd", "vdivpd", "vxorpd",
    "vpaddd", "vpsubd", "vpxor", "vpand", "vpor",
    "vpshufb", "vperm2i128", "vpunpcklqdq"
}

REG_TOKENS = ("xmm", "ymm", "zmm", "mm")
SUSPICIOUS_DB_PREFIXES = {0x66, 0xC4, 0xC5, 0x62}


def is_code(ea):
    return ida_bytes.is_code(ida_bytes.get_flags(ea))


def decode_size(ea):
    insn = ida_ua.insn_t()
    return ida_ua.decode_insn(insn, ea)


def try_make_code(ea):
    size = decode_size(ea)
    if size > 0 and is_code(ea):
        return size

    ida_bytes.del_items(ea, ida_bytes.DELIT_SIMPLE, 1)
    created = idc.create_insn(ea)
    if created:
        size = decode_size(ea)
        if size > 0:
            return size

    return decode_size(ea)


def has_external_code_xref_to(ea, start, end):
    for xr in idautils.XrefsTo(ea, 0):
        frm = xr.frm
        if frm < start or frm >= end:
            if ida_bytes.is_code(ida_bytes.get_flags(frm)):
                return True
    return False


def get_disasm(ea):
    return (idc.generate_disasm_line(ea, 0) or "").strip()


def is_suspicious_db(ea):
    disasm = get_disasm(ea).lower()
    if not disasm.startswith("db"):
        return False
    b = ida_bytes.get_byte(ea)
    return b in SUSPICIOUS_DB_PREFIXES


def is_simd_like_insn(ea):
    mnem = (idc.print_insn_mnem(ea) or "").lower()
    if not mnem:
        return False

    if mnem in SIMD_MNEMS:
        return True

    if mnem.startswith("v") and len(mnem) >= 3:
        for i in range(8):
            op = idc.print_operand(ea, i)
            if not op:
                break
            l = op.lower()
            if any(tok in l for tok in REG_TOKENS):
                return True

    for i in range(8):
        op = idc.print_operand(ea, i)
        if not op:
            break
        l = op.lower()
        if any(tok in l for tok in REG_TOKENS):
            return True

    return False


def touches_protected_gpr(ea):
    """若指令操作数涉及受保护通用寄存器,则返回 True。"""
    for i in range(8):
        op = idc.print_operand(ea, i)
        if not op:
            break
        l = op.lower()
        for reg in PROTECTED_GPR_TOKENS:
            if (
                l == reg
                or l.startswith(reg + ",")
                or l.endswith(", " + reg)
                or l.startswith("[" + reg)
                or ("[" + reg + "+") in l
                or ("[" + reg + "-") in l
                or (", " + reg + "]") in l
                or (", " + reg + "+") in l
                or (", " + reg + "-") in l
                or (" " + reg) in l
            ):
                return True
    return False


def should_preserve_simd_insn(ea, start, end):
    if PRESERVE_ANY_GPR_TOUCHING_SIMD and touches_protected_gpr(ea):
        return True

    if PRESERVE_EXTERNAL_XREF_TARGETS and has_external_code_xref_to(ea, start, end):
        return True

    return False


def is_nop_insn(ea):
    return (idc.print_insn_mnem(ea) or "").lower() == "nop"


def nop_range(ea, size):
    ida_bytes.patch_bytes(ea, b"\x90" * size)
    ida_bytes.del_items(ea, ida_bytes.DELIT_SIMPLE, size)
    cur = ea
    end = ea + size
    while cur < end:
        idc.create_insn(cur)
        cur += 1


def save_valid_instructions(valid_insns, output_path, start, end):
    with open(output_path, "w", encoding="utf-8") as f:
        f.write(f"Range: {start:#x} - {end:#x}\n")
        f.write(f"Valid non-NOP instruction count: {len(valid_insns)}\n")
        f.write("=" * 100 + "\n")
        for ea, disasm in valid_insns:
            f.write(f"{ea:#018x}  {disasm}\n")


def scan_and_patch(start, end):
    ea = start
    patched = 0
    skipped_xref = 0
    undecoded = 0
    forced_code = 0
    suspicious_db = 0

    while ea < end:
        size = decode_size(ea)

        if size <= 0 and FORCE_DEFINE_HIDDEN_CODE:
            size = try_make_code(ea)
            if size > 0:
                forced_code += 1

        if size <= 0:
            undecoded += 1
            ea += 1
            continue

        if is_suspicious_db(ea):
            suspicious_db += 1
            if FORCE_DEFINE_HIDDEN_CODE:
                retry_size = try_make_code(ea)
                if retry_size > 0:
                    size = retry_size

        will_patch = False
        if is_simd_like_insn(ea):
            if should_preserve_simd_insn(ea, start, end):
                if PRESERVE_EXTERNAL_XREF_TARGETS and has_external_code_xref_to(ea, start, end):
                    skipped_xref += 1
                will_patch = False
            else:
                will_patch = True

        if will_patch:
            nop_range(ea, size)
            patched += 1

        ea += max(size, 1)

    ida_auto.auto_mark_range(start, end, ida_auto.AU_CODE)
    ida_auto.auto_wait()
    return patched, skipped_xref, undecoded, forced_code, suspicious_db


def collect_valid_non_nop(start, end):
    ea = start
    valid = []
    still_simd = []
    raw_db = []

    while ea < end:
        size = decode_size(ea)
        if size <= 0:
            ea += 1
            continue

        disasm = get_disasm(ea)
        if disasm.lower().startswith("db"):
            raw_db.append((ea, disasm))
        elif not is_nop_insn(ea):
            if is_simd_like_insn(ea):
                still_simd.append((ea, disasm))
            valid.append((ea, disasm))

        ea += max(size, 1)

    return valid, still_simd, raw_db


def run(start, end):
    total_patched = 0
    total_skipped_xref = 0
    total_undecoded = 0
    total_forced_code = 0
    total_suspicious_db = 0

    for _ in range(2):
        patched, skipped_xref, undecoded, forced_code, suspicious_db = scan_and_patch(start, end)
        total_patched += patched
        total_skipped_xref += skipped_xref
        total_undecoded += undecoded
        total_forced_code += forced_code
        total_suspicious_db += suspicious_db

    valid_non_nop_insns, still_simd, raw_db = collect_valid_non_nop(start, end)

    if PRINT_ALL_VALID_ADDRS:
        print("\n[valid non-NOP instruction addresses]")
        for va, _ in valid_non_nop_insns:
            print(f"{va:#018x}")

    idb_path = idc.get_idb_path()
    out_dir = os.path.dirname(idb_path) if idb_path else os.getcwd()
    out_path = os.path.join(out_dir, OUTPUT_TXT_NAME)
    save_valid_instructions(valid_non_nop_insns, out_path, start, end)

    msg = (
        f"[done] range: {start:#x} - {end:#x}\n"
        f"patched SIMD-like insns: {total_patched}\n"
        f"skipped (external xref targets): {total_skipped_xref}\n"
        f"forced hidden code definitions: {total_forced_code}\n"
        f"suspicious db prefixes seen: {total_suspicious_db}\n"
        f"undecoded bytes visited: {total_undecoded}\n"
        f"valid non-NOP instructions kept: {len(valid_non_nop_insns)}\n"
        f"remaining SIMD-like instructions: {len(still_simd)}\n"
        f"remaining raw db entries: {len(raw_db)}\n"
        f"saved to: {out_path}\n"
    )

    print(msg)

    if still_simd:
        print("\n[warning] remaining SIMD-like instructions:")
        for ea, disasm in still_simd[:100]:
            print(f"{ea:#018x}  {disasm}")

    if raw_db:
        print("\n[warning] remaining raw db entries:")
        for ea, disasm in raw_db[:100]:
            print(f"{ea:#018x}  {disasm}")

    ida_kernwin.info(msg)


if __name__ == "__main__":
    run(START, END)

运行之后效果如下:

SIMD混淆

同时会将这片地址范围内有效的指令及其地址输出到valid_instructions_7FF96FAAD874_7FF96FAB4D21.txt

SIMD混淆

这个去混淆脚本依旧有个缺点,就是无法自动去除ida中未识别的指令,只能保存未数据(虽然无法全自动去除,但是也保证了不会过分去除导致正常代码无法运行)

针对这些数据块,只需要根据给出的地址,去手动让ida识别为指令,然后再运行一遍去混淆脚本即可,最终效果如下:

SIMD混淆

再让大模型润色一下,得到最终的完整逻辑

; ============================================================
    ; 主逻辑:
; 1) 建立栈帧
; 2) 反复清空局部缓冲区(疑似扰动/延时/混淆残留)
; 3) 通过 call/pop 取当前位置,计算内部基址
; 4) 调用辅助函数获得目标缓冲区
; 5) 从 base+0x32F0E8 拷贝 0x19202 字节到目标缓冲区
; 6) 用 base+0x2238660x14 字节循环密钥对目标缓冲区异或
; 7) 调用解码后的代码
; ============================================================

    sub_main_logic proc near
    push    rbp
    push    rsi
    push    rdi
    mov     rbp, rsp
    sub     rsp, 0A0h

    ; r8  = 局部缓冲区起始地址
    ; rcx = 内层计数
    ; rdx = 外层计数
    ; 这段逻辑表现为:反复将一个 0x73 字节的小缓冲区清零
    lea     r8, [rsp+0A0h+var_80]
    xor     rdx, rdx

loc_zero_outer:
xor     rcx, rcx

loc_zero_inner:
mov     [r8+rcx], cl         ; 因为 cl 此时为 0,所以相当于写 0
    inc     rcx
    cmp     rcx, 72h
    jbe     short loc_zero_inner

    inc     rdx
    cmp     rdx, 3D092Bh
    jbe     short loc_zero_outer

    ; -----------------------------
        ; 取当前位置(RIP 技巧)并回推出内部基址
    ; -----------------------------
        call    $+5
        pop     rax
    sub     rax, 6F8BEh          ; rax = base

    ; 保存 base 到 xmm13,后续多次取回使用
    movq    xmm13, rax

    ; -----------------------------
        ; 调用辅助函数,返回目标缓冲区地址
    ; -----------------------------
        call    near ptr loc_7FF9619554EF+5
        mov     [rbp-8], rax         ; [rbp-8] = dst

    ; -----------------------------
        ; 构造 memcpy 参数
    ; dst = rax
    ; src = base + 0x32F0E8
        ; len = 0x19202
            ; -----------------------------
                movq    rsi, xmm13           ; rsi = base
    add     rsi, 32F0E8h         ; rsi = src
    mov     rcx, 19202h          ; rcx = size
    mov     rdi, rax             ; rdi = dst

    cld
    rep movsb                    ; memcpy(dst, src, 0x19202)

        ; -----------------------------
            ; 恢复目标地址,准备异或解码
    ; key = base + 0x223866
        ; key_len = 0x14
            ; -----------------------------
                mov     rdi, [rbp-8]         ; rdi = dst
    mov     r11, 14h             ; key length = 0x14
        xor     rax, rax
    xor     rcx, rcx             ; rcx = i

    movq    r9, xmm13            ; r9 = base
    add     r9, 223866h          ; r9 = key

loc_xor_loop:
xor     rdx, rdx             ; rdx 清零,供 div 取余使用
    mov     rax, rcx             ; rax = i
    div     r11                  ; rdx = i % 0x14

        mov     r10b, [r9+rdx]       ; key_byte = key[i % 0x14]
    xor     [rdi+rcx], r10b      ; dst[i] ^= key_byte

    inc     rcx
    cmp     rcx, 19201h
    jbe     loc_xor_loop

    ; -----------------------------
        ; 调用解码后的代码
    ; -----------------------------
        call    rdi

    add     rsp, 0A0h
    pop     rdi
    pop     rsi
    pop     rbp
    xor     rax, rax
    retn

sub_main_logic endp

//大致c逻辑为:
int sub_main_logic(void)
    {
        unsigned char tmp[0x80];
        unsigned char *base;
        unsigned char *dst;
        unsigned char *src;
        unsigned char *key;

        // 疑似扰动/混淆残留
        for (size_t outer = 0; outer <= 0x3D092B; outer++) {
            for (size_t inner = 0; inner <= 0x72; inner++) {
                tmp[inner] = 0;
            }
        }

        base = get_rip() - 0x6F8BE;
        dst  = sub_7FF9619554EF_plus_5();

    src = base + 0x32F0E8;
    memcpy(dst, src, 0x19202);

    key = base + 0x223866;
    for (size_t i = 0; i <= 0x19201; i++) {
        dst[i] ^= key[i % 0x14];
    }

    ((void(*)())dst)();
    return 0;
}

混淆前效果:

SIMD混淆

直接跳转运行shellcode

混淆之后的效果:

SIMD混淆

SIMD混淆

解密之后会继续运行shellcode逻辑,本质对shellcode不产生实质性影响