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) ah读和写不会影响al的读和写.?? 成熟的美羊羊 发表于 2021-8-9 21:03
如果遇到这种将指令进行拆分的情况
mov ah,1
mov al,11
脚本改改,还能用,具体就是
记录al,ah的读和写,且al,ah的读和写等同于eax的读和写,
eax的读和写等同于ah,al寄存器的读写,
而ah读和写不会影响al的读和写.
支持一下 本帖最后由 成熟的美羊羊 于 2021-8-9 21:48 编辑
如果遇到这种将指令进行拆分赋值的情况
mov ah,1
mov al,11
又是一个麻烦事【滑稽】
Shocker 发表于 2021-8-9 21:46
脚本改改,还能用,具体就是
记录al,ah的读和写,且al,ah的读和写等同于eax的读和写,
eax的读和写等同于ah ...
坐等白嫖大佬的脚本[滑稽]{:301_986:} 厉害,66666 支持一下厉害,66666 厉害,写汇编的吗 学习学习!我自己写的代码,过后我自己都看不懂了,自动混淆!{:1_907:}