吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7562|回复: 21
收起左侧

[原创] x86反混淆IDA Python脚本 (一)

  [复制链接]
Shocker 发表于 2021-8-9 16:03
本帖最后由 Shocker 于 2021-8-10 16:35 编辑

前言

本文将给出一种基于capstone,ida的去混淆脚本

原理

  1. 对寄存器的两次写操作之间若没有读操作,则第一次写操作无效

    mov eax,esi  读esi,写eax
    mov eax,edi  读edi,写eax   

    由于第二次写eax操作时没有进行读操作,则第一句指令可以nop掉

    mov eax,esi  读esi,写eax
    mov ecx,eax  读eax,写ecx
    mov eax,edi  读edi,写eax   

    因为两次写eax之间存在对eax的读操作,则第一句指令有效.

  2. 所有对内存的写指令均有效,例如

    push eax
    pop eax 
    //因为push指令有效,则认为pop有效
  3. 寄存器的高位的低位寄存器默认为整个寄存器,例如

    mov al,ch

    认为对eax的写操作,ecx的读操作

  4. 不对eflags寄存器进行细分,例如

    cmp eax,0x11111111
    test eax,0x0

    认为是对eflags的整体操作,则第一句指令可以删去

实现过程

用一个字典记录所有对寄存器的写操作,初始值为None

regs={'eax':None,
'ecx':None,
'edx':None,
'ebx':None,
'esp':None,
'ebp':None,
'esi':None,
'edi':None,
'eflags':None,
}

若遇到写指令,则将该key的值设置为指令地址,例如

0x401000 mov eax,esi 

regs={'eax':0x401000
.......................

若遇到读指令,则将该值清空

0x401002 mov ecx,eax
regs={'eax':None
.......................

若连续两次读指令

0x401000 mov eax,esi 
0x401002 mov eax,edi
regs={'eax':0x401000
.......................

将第二句指令给regs[eax]赋值时,会发现有值,说明第一个写可以被删去.

去混淆结果

对一个vmp加壳后的程序进行反混淆分析,图中标黄指令为可删去的指令
res.jpg

附完整脚本

# 根据capstone的ida反混淆脚本
# 依据两次写,没有读寄存器的原则对指令进行反混淆
# 例如      mov eax,esi
#           mov eax,ebp
# 等价于    mov eax,ebp

import capstone
from idaapi import *
import keypatch

patcher=keypatch.Keypatch_Asm()

md=capstone.Cs(capstone.CS_ARCH_X86,capstone.CS_MODE_32)
md.detail=True

g_cs_eax = [capstone.x86_const.X86_REG_AH, capstone.x86_const.X86_REG_AL, capstone.x86_const.X86_REG_AX]
g_cs_ebx = [capstone.x86_const.X86_REG_BH, capstone.x86_const.X86_REG_BL, capstone.x86_const.X86_REG_BX]
g_cs_ecx = [capstone.x86_const.X86_REG_CH, capstone.x86_const.X86_REG_CL, capstone.x86_const.X86_REG_CX]
g_cs_edx = [capstone.x86_const.X86_REG_DH, capstone.x86_const.X86_REG_DL, capstone.x86_const.X86_REG_DX]
g_cs_ebp = [capstone.x86_const.X86_REG_BP, capstone.x86_const.X86_REG_BPL]
g_cs_esp = [capstone.x86_const.X86_REG_SP, capstone.x86_const.X86_REG_SPL]
g_cs_esi = [capstone.x86_const.X86_REG_SI, capstone.x86_const.X86_REG_SIL]
g_cs_edi = [capstone.x86_const.X86_REG_DI, capstone.x86_const.X86_REG_DIL]

def cs_extendRegTo32bit(cs_reg):

    if(cs_reg in g_cs_eax):
        return capstone.x86_const.X86_REG_EAX

    elif(cs_reg in g_cs_ebx):
        return capstone.x86_const.X86_REG_EBX

    elif(cs_reg in g_cs_ecx):
        return capstone.x86_const.X86_REG_ECX 

    elif(cs_reg in g_cs_edx):
        return capstone.x86_const.X86_REG_EDX 

    elif(cs_reg in g_cs_ebp):
        return capstone.x86_const.X86_REG_EBP 

    elif(cs_reg in g_cs_esp):
        return capstone.x86_const.X86_REG_ESP 

    elif(cs_reg in g_cs_esi):
        return capstone.x86_const.X86_REG_ESI 

    elif(cs_reg in g_cs_edi):
        return capstone.x86_const.X86_REG_EDI   

    else:
        return cs_reg

regs={'eax':None,
'ecx':None,
'edx':None,
'ebx':None,
'esp':None,
'ebp':None,
'esi':None,
'edi':None,
'eflags':None,
}

def patch(addr):
    # print(regs)
    print('0x%x'%addr)
    set_color(addr, CIC_ITEM, 0x00ffff)
    # patcher.patch_code(addr,'nop',patcher.syntax,True,False)

start_addr=0x8660BF #起始地址
end_addr=0x0866155 #结束地址

codes=get_bytes(start_addr,idc.next_head(end_addr)-start_addr)
for code in md.disasm(codes,start_addr):
    readList,writeList = code.regs_access() # readList对寄存器的读,writeList对寄存器的写
    # print(code)
    for r in readList: #对寄存器的读
        # print(code.reg_name(cs_extendRegTo32bit(r)))
        reg_name=code.reg_name(cs_extendRegTo32bit(r))
        if reg_name not in regs:   #对寄存器没有读操作
            continue
        elif regs[reg_name]!=None:
            patch_addr=regs[reg_name]
            for key in regs:#说明patch_addr的指令写已经被读,判断该指令有效
                    if regs[key] == patch_addr:#将所有引用该地址的值清空
                        # print(regs)
                        regs[key]=None

    for w in writeList: #对寄存器的写
        patch_flag=True #这里是决定是否patch的标志
        reg_name=code.reg_name(cs_extendRegTo32bit(w))
        # continue
        if reg_name not in regs:   #对寄存器没有写操作,这里有可能对内存进行写,所以不处理
            continue
        elif regs[reg_name]!=None: #说明上次的写到这次的写操作之间没有读,因为如果有读操作,则regs[reg_name]为None
                patch_addr=regs[reg_name] 
                for key in regs:#判断该指令是否对多个寄存器进行了写操作
                    if key != reg_name and regs[key]==patch_addr:#说明patch_addr的指令对多个寄存器有写操作,不进行patch操作
                        patch_flag=False
                if patch_flag==True \
                and not (idc.print_insn_mnem(regs[reg_name])=='call'\
                or idc.print_insn_mnem(regs[reg_name])=='push'\
                or idc.print_insn_mnem(regs[reg_name])=='pop'):
                    patch(regs[reg_name]) #排除所有的call,push,pop指令,认为其是有效指令
        regs[reg_name]=code.address

代码示例见
https://github.com/PShocker/x86_de_confuse

免费评分

参与人数 5吾爱币 +4 热心值 +5 收起 理由
itachi137 + 1 + 1 热心回复!
huiker231 + 1 用心讨论,共获提升!
成熟的美羊羊 + 1 + 1 用心讨论,共获提升!
nevinhappy + 1 + 1 谢谢@Thanks!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Tamluo 发表于 2021-8-10 23:43
ah读和写不会影响al的读和写.??
 楼主| Shocker 发表于 2021-8-9 21:46
成熟的美羊羊 发表于 2021-8-9 21:03
如果遇到这种将指令进行拆分的情况
mov ah,1
mov al,11

脚本改改,还能用,具体就是
记录al,ah的读和写,且al,ah的读和写等同于eax的读和写,
eax的读和写等同于ah,al寄存器的读写,
而ah读和写不会影响al的读和写.
ScarLxrd 发表于 2021-8-9 17:07
成熟的美羊羊 发表于 2021-8-9 21:03
本帖最后由 成熟的美羊羊 于 2021-8-9 21:48 编辑

如果遇到这种将指令进行拆分赋值的情况
mov ah,1
mov al,11
又是一个麻烦事【滑稽】
成熟的美羊羊 发表于 2021-8-9 21:52
Shocker 发表于 2021-8-9 21:46
脚本改改,还能用,具体就是
记录al,ah的读和写,且al,ah的读和写等同于eax的读和写,
eax的读和写等同于ah ...

坐等白嫖大佬的脚本[滑稽]
GuiXiaoQi 发表于 2021-8-10 08:49
厉害,66666
头像被屏蔽
asdswd 发表于 2021-8-10 11:21
支持一下厉害,66666
bingsky 发表于 2021-8-10 12:03
厉害,写汇编的吗
space218 发表于 2021-8-10 15:48
学习学习!我自己写的代码,过后我自己都看不懂了,自动混淆!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-3-29 10:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表