Shocker 发表于 2021-8-9 16:03

x86反混淆IDA Python脚本 (一)

本帖最后由 Shocker 于 2021-8-10 16:35 编辑

## 前言
本文将给出一种基于capstone,ida的去混淆脚本
- 反混淆原理基于b站周壑大佬的视频
(https://www.bilibili.com/video/BV1F64y1Z7FK)

-部分代码参考了看雪大佬 会飞的鱼油 代码
[[原创]利用活跃变量分析来去掉vmp的大部分垃圾指令 ](https://bbs.pediy.com/thread-265950.htm)

## 原理
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赋值时,会发现有值,说明第一个写可以被删去.

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


## 附完整脚本
```
# 根据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 =
g_cs_ebx =
g_cs_ecx =
g_cs_edx =
g_cs_ebp =
g_cs_esp =
g_cs_esi =
g_cs_edi =

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!=None:
            patch_addr=regs
            for key in regs:#说明patch_addr的指令写已经被读,判断该指令有效
                  if regs == patch_addr:#将所有引用该地址的值清空
                        # print(regs)
                        regs=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!=None: #说明上次的写到这次的写操作之间没有读,因为如果有读操作,则regs为None
                patch_addr=regs
                for key in regs:#判断该指令是否对多个寄存器进行了写操作
                  if key != reg_name and regs==patch_addr:#说明patch_addr的指令对多个寄存器有写操作,不进行patch操作
                        patch_flag=False
                if patch_flag==True \
                and not (idc.print_insn_mnem(regs)=='call'\
                or idc.print_insn_mnem(regs)=='push'\
                or idc.print_insn_mnem(regs)=='pop'):
                  patch(regs) #排除所有的call,push,pop指令,认为其是有效指令
      regs=code.address
```

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

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 ...

坐等白嫖大佬的脚本[滑稽]{:301_986:}

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

学习学习!我自己写的代码,过后我自己都看不懂了,自动混淆!{:1_907:}
页: [1] 2 3
查看完整版本: x86反混淆IDA Python脚本 (一)