概念
插花指令是隐藏不想进行逆向工程的代码块(或其他功能)的一种方法,它在将垃圾代码插入实际代码中的同时,确保原始程序正确运行。 但是,程序没有很好地反编译,程序的内容难以理解,有混乱视听的效果。
简单来说,就是在代码中混入垃圾数据,阻碍静态分析。
花指令分类
花命令大致分为可执行花的命令和不可执行花的命令两种。
可执行的花指令
可执行的花指令是指该花指令代码的一部分在程序的正常运行中执行
但是,运行这些代码没有意义
执行前后寄存器的值不会发生任何变化(当然eip之类的除外)
反汇编程序也能成功识别部分代码
目的
首先,花指令的首要目的仍然是增加静态分析的难度,使代码的真正意图难以识别
然后,这个花命令破坏了反编译的分析,堆栈指针可以在反编译引擎中引发异常。 (我们当然知道堆栈指针实际上没有问题,但反编译引擎还有待完善。)
不可执行的花指令
不可执行花指令是指该花指令代码的一部分在程序的通常执行中不被执行
不可执行的花指令利用反汇编线性扫描算法的缺陷,在静态分析时可以看到错误码
最常见的花命令
如图所示,这是ctf主题中最常见和最简单的花指令之一,是典型的不可执行花指令。
路径方法
首先,将0x00401D92代码转换为数据
然后,将0x00401D94的数据转换为代码,对0x00401D94、0x00401D94的数据进行nop即可
我们知道E9是与jmp指令对应的机器码,反汇编器读取E9时,下一次作为跳转目标的偏移读取4字节的数据,所以会看到错误的汇编代码。
写法
如果在编写程序时嵌入_asm _emit 0E9,则反编译器将以下指令作为地址数据处理: 下一个指令的实际4字节与地址数据还是操作指令无关。
__asm {
_emit 075h #jmp $ 4
_emit 2h
_emit 0E9h
_emit 0EDh
}
嵌入上述4字节数据会导致程序反汇编反编译错误。 请注意,这里的75是jnz的机器码,因此执行到此为止时Zflag=0。
具体例子
无花指令源程序
我在这里写了tea加密算法。 用msvc编译,生成pdb文件,便于分析,并在ida中打开
#包含
#包含
voidencrypt(uint32_t*v,uint32_t* k ) }
uint32_t v0=v[0],v1=v[1],sum=0,I;/*设置up * /
uint32_t delta=0x9e3779b9;/* a key计划常数* /
uint32_t k0=k[0],k1=k[1],k2=k[2],k3=k[3];/* cache密钥* /
for(I=0; i 32; I ()/*basiccyclestart ) /
苏美三角洲;
v0=(V14 ) k0 ) V1sum ) ^ ) V15 ) k1 );
v1=(v04 ) k2 ) v0sum ) ^ ) v05 ) k3 );
(/)结束周期) /
v[0]=v0; v[1]=v1;
}
int main () )。
int a=1;
uint 32 _ tflag [ ]={ 1234,5678 };
uint 32 _ tkey [ ]={ 9,9,9 };
加密(flag,key );
printf(‘%d,%d ‘,flag[0],flag[1] );
返回0;
}
首先,可以看到main函数与源代码相差不大
可以看到encrypt函数也与源代码大致相同
添加花命令
接下来添加两个花命令
第一个花命令是main函数中,我们上面提供的最简单的花命令的编写方法
第二个花命令是在encrypt函数中可以执行花的命令。 这里具体分析该花命令及其相应的去除方法
#
include
#include
#define JUNKCODE __asm{
__asm jmp junk1
__asm __emit 0x12
__asm junk2:
__asm ret
__asm __emit 0x34
__asm junk1:
__asm call junk2
}
void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
JUNKCODE
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
} /* end cycle */
v[0] = v0; v[1] = v1;
}
int main() {
int a = 1;
uint32_t flag[] = { 1234,5678 };
uint32_t key[] = { 9,9,9,9 };
__asm {
_emit 075h
_emit 2h
_emit 0E9h
_emit 0EDh
}
encrypt(flag, key);
printf(“%d,%d”, flag[0], flag[1]);
return 0;
}
第一个花指令
首先ida打开查看main函数:
虽然还是反编译成功了,但是可以看到内容已经完全错误,我们再来看main函数的汇编代码,也就是我们加入的第一个花指令
可以看到这里出现的红色就是我们的第一处花指令,patch方法同上,我们主要看第二处花指令。
第二个花指令
f5反编译直接报错
可以发现花指令的混淆作用还是很明显的,那我们继续跟进到花指令的反汇编代码处
这里框出的指令就是我们加入的花指令,逻辑其实特别清晰,就是先跳转到junk1,再call junk2,call junk2的时候会把地址0x004118D3压栈,然后进入junk2中执行retn指令又会把地址0x004118D3 pop到eip中,然后接下来程序继续正常执行。
去除方法
这种连续的可执行花指令的去除方法特别简单,直接整块nop掉即可。
但是真正的复杂程序里这种花指令的数量很多,人工nop很耗时,同时极容易出错,所以我们真正应该掌握的是自动化的方法,编写脚本匹配花指令模板进行去除。
去除花指令
这里是我们要去除的花指令模板
#define JUNKCODE __asm{
__asm jmp junk1
__asm __emit 0x12
__asm junk2:
__asm ret
__asm __emit 0x34
__asm junk1:
__asm call junk2
}
这里是idapython编写的ida匹配模板去除花指令脚本
def nop(addr, endaddr):
while addr < endaddr:
PatchByte(addr, 0x90)
addr += 1
def undefine(addr, endaddr):
while addr < endaddr:
MakeUnkn(addr, 0)
addr += 1
def dejunkcode(addr, endaddr):
while addr < endaddr:
MakeCode(addr)
# 匹配模板
if GetMnem(addr) == ‘jmp’ and GetOperandValue(addr, 0) == addr + 5 and Byte(addr+2) == 0x12:
next = addr + 10
nop(addr, next)
addr = next
continue
addr += ItemSize(addr)
dejunkcode(0x00411820, 0x00411957)
undefine(0x00411820, 0x00411957)
MakeFunction(0x00411820, -1)
重要函数解析
MakeCode(ea) #分析代码区,相当于ida快捷键C
ItemSize(ea) #获取指令或数据长度
GetMnem(ea) #得到addr地址的操作码
GetOperandValue(ea,n) #返回指令的操作数的被解析过的值
PatchByte(ea, value) #修改程序字节
Byte(ea) #将地址解释为Byte
MakeUnkn(ea,0) #MakeCode的反过程,相当于ida快捷键U
MakeFunction(ea,end) #将有begin到end的指令转换成一个函数。如果end被指定为psdhb(-1),IDA会尝试通过定位函数的返回指令,来自动确定该函数的结束地址
idapython提供的函数挺多的,这里的函数如果有不太理解意思的,可以在ida的python命令行中自行尝试一下,对照着ida汇编窗口的变化和函数返回值很快就能掌握函数的用法
运行脚本
我们在ida中运行脚本,然后可以发现那段花指令已经成功nop掉了,按下f5反编译:
可以看到encrypt函数被成功的反编译了。
对于较复杂的程序而言,编写模板匹配脚本去除花指令是十分重要的,可以做到准确无误,同时节省了很多时间。
特殊花指令
还有一类较特殊的花指令,它不会影响反汇编和反编译,只是单纯的混淆视听
譬如我们程序需要将某个特定值(这里假设是0x12)压栈,正常操作应该是:
push 0x12
加入花指令后,这个操作可以变成这样:
push 0x26
xor dword ptr ss:[esp], 0x34
我们很容易可以看出来这两种写法是等效的,当我们要压栈的数据是一些很明显的特征值的时候,这种花指令可以很好的保护我们的特征值,防止算法特征被迅速识别
当然这里只是一个简单的例子,这种花指令复杂起来将会使得分析难度大大提升。
总结
花指令还有一些其他的运用方法,包括一些很特殊罕见的运用,本文介绍的是较为常见的花指令,也是我在学习过程中经常碰到的花指令。
逆向的路还好长…慢慢学慢慢学