花指令实质就是一串垃圾指令,主要用于干扰静态分析的难度防止反编译器进行反编译,并不影响程序的正常逻辑,在CTF中花指令的作用主要用于防止静态分析程序,花指令也可以被用在病毒或木马上,通过加入花指令来改变程序的特征码从而躲避杀软的扫描达到免杀的目的,花指令一般被分为两类,可执行花指令与不可执行花指令,不管执不执行都不会影响程序正常运行。
就是将一些数据插入正常指令中,并不会影响程序运行也不会被执行,只会导致反编译错误,这类花指令一般不属于CPU可以识别的操作码,所以就需要在上面用跳转跳过这些花指令才能保证程序的正常运行。
示例:
int main()
{
_asm {
xor eax, eax;
jz s;// 这里一定会跳转
_emit 0x11;
_emit 0x22;
_emit 0x33;// 0x33是xor指令的操作码,会导致后面正常的Push指令被错误解析
s:
}
printf("Hello n");
}
这类花指令比不可执行花指令稍微难一点,这是正常的汇编指令,它们运行完后不会改变原来程序的堆栈,寄存器,但能起到干扰静态分析的作用,一般分为两种,一种是改变堆栈操作,另一种是利用call指令或Jmp指令增加执行流程复杂度。
示例:
int main()
{
_asm {
push eax;
add esp, 4;//这里代替了pop从而保证栈推平衡
}
printf("Hello n");
}
想要深刻理解花指令就要明白反编译的工作原理,它是从exe的入口AddressOfEntryPoint处开始,依序扫描字节码,并转换为汇编,比如第一个16进制字节码是0xE8,一般0xE8代表汇编里的CALL指令,且后面跟着的4个字节数据跟地址有关,那么反编译器就读取这一共5个字节,反编译为<font style="color:rgb(166, 44, 100);">CALL 0x地址</font> ,对应的,有些字节码只需要一个字节就可以反编译为一条指令,例如0x55对应的是<font style="color:rgb(166, 44, 100);">push ebp</font>,这条语句每个函数开始都会有。同样,有些字节码又需要两个、三个、四个字节来反编译为一条指令,也就是说,如果中间只要一个地方反编译出错,例如两条汇编指令中间突然多了一个字节0xE8,那反编译器就会将其跟着的4个字节处理为CALL指令地址相关数据给反编译成一条<font style="color:rgb(166, 44, 100);">CALL 0x地址</font>指令。但实际上0xE8后面的四个字节是单独的字节码指令,反编译器有两种算法来进行反编译:线性扫描,递归下降。
线性扫描从程序的起始地址开始,逐条指令地进行解析,直到程序结束,遇到分支指令不会递归进入分支,比如遇到call或者jmp的时候,不会跳转到对应地址进行反汇编,而是反汇编call指令的下一条指令,这就会导致出现很多问题。
递归下降分析当遇到分支指令时,会递归进入分支进行反汇编,从文法的起始符号开始,逐步解析到终结符号。
用连续两条相反的条件跳转,或是通过stc/clc汇编指令来设置位,使条件跳转变为永真或者永假跳转
示例:
![花指令——新春快乐版 花指令]()
这中间插了一个脏指令,我们可以对着loc_41188按U取消定义来看插入了什么数据
![花指令——新春快乐版 花指令]()
插入了0xE8,这是call的机器码表示(0xE9是jmp的机器码),这个数据就是脏指令NOP掉就行,然后main按p就行了
![花指令——新春快乐版 花指令]()
用pop的方式来清除call的压栈,使栈平衡,从而用call实现jmp,IDA会认为call的目标地址为函数起始地址,导致函数创建错误
示例:
![花指令——新春快乐版 花指令]()
这里虽然不是call但是jnz与jz实现了jmp的作用,也是与call效果一样的
![花指令——新春快乐版 花指令]()
用add esp的方式来清除call的压栈,使栈平衡,从而用call实现jmp,这种直接把这一块全部NOP掉就行
call + add [esp], n + retn
![花指令——新春快乐版 花指令]()
用add [esp], n和retn的方式来改变返回地址,也是一样把这一块全部NOP掉就行
![花指令——新春快乐版 花指令]()
![花指令——新春快乐版 花指令]()
![花指令——新春快乐版 花指令]()
上面两种花指令本质都一样都是通过设置标志寄存器的值使得满足后面的条件跳转
上面的这些花指令都是比较常见的,其中的原理都差不多比较简单
我们在遇见花指令的时候很容易碰见堆栈不平衡,导致不能F5查看伪代码,此时就要修复栈指针
push ebp ----把基址指针寄存器压入堆栈
pop ebp ----把基址指针寄存器弹出堆栈
push eax ----把数据寄存器压入堆栈
pop eax ----把数据寄存器弹出堆栈
nop -----不执行
add esp,1-----指针寄存器加1
sub esp,-1-----指针寄存器加1
add esp,-1--------指针寄存器减1
sub esp,1-----指针寄存器减1
inc ecx -----计数器加1
dec ecx -----计数器减1
sub esp,1 ----指针寄存器-1
sub esp,-1----指针寄存器加1
jmp 入口地址----跳到程序入口地址
push 入口地址---把入口地址压入堆栈
retn ------ 反回到入口地址,效果与jmp 入口地址一样
mov eax,入口地址 ------把入口地址转送到数据寄存器中.
jmp eax ----- 跳到程序入口地址
jb 入口地址
jnb 入口地址 ------效果和jmp 入口地址一样,直接跳到程序入口地址
xor eax,eax 寄存器EAX清0
CALL 空白命令的地址 无效call
示例:
当一个程序段结束时栈指针应该指向栈底也就是esp与ebp相等,此时不应再进行操作
![花指令——新春快乐版 花指令]()
![花指令——新春快乐版 花指令]()
![花指令——新春快乐版 花指令]()
看一个正常的程序段结束
![花指令——新春快乐版 花指令]()
![花指令——新春快乐版 花指令]()
![花指令——新春快乐版 花指令]()
程序段结束后,不应发生mov esp,ebp的操作,因为在pop出栈后,esp和ebp的值相等,这一步是多余的,因为栈指针已经正确地回到了调用函数前的位置,这会引起栈指针不平衡
这就需要修改栈指针
注意:每条语句前的栈指针是这条语句未执行的栈指针
找到函数段的开始地址
![花指令——新春快乐版 花指令]()
计算结束地址的栈指针应为多少:
0x21E-0x4 = 0x21A
修改最后两句应为的栈指针:
Alt+k:
![花指令——新春快乐版 花指令]()
栈指针平衡
通过前面知道,经过pop栈针已经平衡,所以这两句汇编代码是没有必要的
![花指令——新春快乐版 花指令]()
删除这两条指令的目的是在已经发生了出栈操作并且栈指针ESP与基址指针EBP相等的情况下,不再手动调整栈指针,这是因为栈指针已经回到了调用函数之前的位置,不需要再额外的指令来处理栈平衡
原文始发于微信公众号(鼎新安全):花指令——新春快乐版
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
点赞
http://cn-sec.com/archives/3687770.html
复制链接
复制链接
-
左青龙
- 微信扫一扫
-
-
右白虎
- 微信扫一扫
-
评论