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

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


运行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)
运行之后效果如下:

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

这个去混淆脚本依旧有个缺点,就是无法自动去除ida中未识别的指令,只能保存未数据(虽然无法全自动去除,但是也保证了不会过分去除导致正常代码无法运行)
针对这些数据块,只需要根据给出的地址,去手动让ida识别为指令,然后再运行一遍去混淆脚本即可,最终效果如下:

再让大模型润色一下,得到最终的完整逻辑
; ============================================================
; 主逻辑:
; 1) 建立栈帧
; 2) 反复清空局部缓冲区(疑似扰动/延时/混淆残留)
; 3) 通过 call/pop 取当前位置,计算内部基址
; 4) 调用辅助函数获得目标缓冲区
; 5) 从 base+0x32F0E8 拷贝 0x19202 字节到目标缓冲区
; 6) 用 base+0x223866 的 0x14 字节循环密钥对目标缓冲区异或
; 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;
}
混淆前效果:

直接跳转运行shellcode
混淆之后的效果:


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