第五节课花指令等混淆技术

Sher10ck 发布于 2026-04-24 231 次阅读


引言

本课时属于我们传统逆向中较难的一部分了,或许前面讲过的都是从反汇编层展开,这节课我们的学习将会从汇编层面(花指令考题方向),以及我们程序的运行逻辑(smc考题方向),以及一些更难一点的混淆(VM方向等等),本节课只能带大家去做一个初步的学习,混淆的方法千奇百怪,重要的是理解其中的门道。

一、花指令

先给几篇师傅们写的比较好的文章,方便大家参考学习:

逆向分析基础 --- 花指令实现及清除_jmp花指令逆向-CSDN博客

反汇编与花指令解析:从原理到实战-CSDN博客.

[原创][花指令]由易到难全面解析CTF中的花指令-逆向工程-看雪安全社区|专业技术交流与安全研究论坛

1.花指令介绍

花指令是企图隐藏掉不想被逆向直接拿到反汇编的代码块的一种方法, 在真实代码中插入一些垃圾代码来影响程序的反汇编,同时还保证原有程序的正确执行。花指令不是让程序变得如花美丽,而是来欺骗我们的IDA。这边我们需要明白反汇编的两种算法和两个关键寄存器

常用的两类反汇编算法:
1.线性扫描算法:逐行反汇编(无法将数据和内容进行区分)
2.递归行进算法:按照代码可能的执行顺序进行反汇编程序。

IDA中默认采用递归行进算法,虽然有一部分花指令会被避免,但是还有许多花指令仍然会出现在程序中

下面引用这位博主的博客内容来进行讲解反汇编与花指令解析:从原理到实战-CSDN博客.

第一类花指令:

指令形式:

xor eax, eax//eax寄存器置零
test eax, eax//ZF=1,是确定标志位【test不会将结果放在寄存器上,它只影响ZF的状态,如果EAX == 0,那么ZF = 1】
je LABEL1//ZF=1时触发跳转到正常指令处LABEL1 
jne LABEL2//ZF=0时触发跳转到干扰项处LABEL2,与上面的指令形成互补跳转
LABEL2 :
/*干扰项所在*/
LABEL1:
/*正常代码*/

经验:现在花指令大多数是确定标志位跳转,然后加上2段函数,正确函数会被干扰函数所影响导致反汇编出现问题,一版情况下,直接nop掉花指令即可,也可动调跳过花指令部分或者附加调试,然后进行静态分析。

常见有,jz,jnz,jmp。。。这些会导致后面的机器码被识别为地址,导致反汇编错误

花指令较多且相同时可以写python脚本去除,这边不做补充,自行学习总结

第二类花指令:

__asm {
push eax;
xor eax, eax;
test eax, eax;
jnzLABEL1;
jz LABEL2;
LABEL1:
_emit 0xE8;//与call助记符的机器码相同
LABEL2:
mov byte ptr[a], 0;
call LABEL3;
_emit 0xFF;//与adc助记符的字节码相同
LABEL3:
add dword ptr ss : [esp], 8;  //[esp] = [esp] + 8
ret;
__emit 0x11;
mov byte ptr[a], 2;
pop eax;
}

这里0xFF 在 x86 里可能是 inc/dec/call/jmp/push 等指令族的前缀字节,具体含义要看后面的 ModR/M 字节,所以单独一个 FF 会让 IDA 误把后面的字节一起当成一条指令,起到另一种混淆作用

分析这类汇编,需要先了解一下一些汇编转移指令call和ret的原理

Call指令:

(1)call + label:此处将call+ 标号的下一条语句的IP放入栈中,本例题中的0xFF

(2)ret指令:将call指令入站的地址数据全部出栈。

总结:由于call指令的特性,call后的数据会被识别为一个地址,导致原本的call label3被识别为地址,导致后续识别全部错误,但是程序本身运行不会收到影响,在call label3时先将程序的返回地址+8(add dword ptr ss : [esp], 8)再ret返回,使程序又正常的继续运行了。

去花指令:

(1)可以将垃圾指令全部nop,从push eax开始,到pop eax 结束

(2)也可只把call指令nop,让程序能够正常识别就行

(3)idc去花

第三类花指令:

Add类花指令:在干扰项中加入0x21,使正常代码被识别为两个数据求和了。

__asm {
push ebx;
xor ebx, ebx;
test ebx, ebx;
jnz LABEL5;
jzLABEL6;
LABEL5:
_emit 0x21;//与and助记符的机器码相同
LABEL6:
pop ebx;
}

去花:

(1)全部nop掉从push到pop。

(2)只去掉add

(3)Idc去花

第四类花指令:**

Mov指令,会将后面的代码识别为赋值指令

void example4()
{
__asm {
push ebx;
xor ebx, ebx;
test ebx, ebx;
jnz LABEL7;
jzLABEL8;
LABEL7:
_emit 0xC7;
LABEL8:
pop ebx;
}
a = 4;
}
去花指令:同第三类,不细说了

第五类花指令:

利用0x83指令这个前缀使的后面的指令被识别为add/sub/cmp等运算指令,导致程序中的跳转地址指令发生异常,无法使程序反汇编出来。

__asm {
call LABEL9;
_emit 0x83;
LABEL9:
add dword ptr ss : [esp], 8;
ret;
__emit 0xF3;
}
去花同上

第六类花指令

虽然也是用0xE8这个call指令来作为花指令的干扰项,但是前面的跳转设计发生改变,通过使用LoadLibrary("./hhhh")这条指令,由于函数返回值(<= 32 位)必须放在 EAX 寄存器里返回使函数返回值存储于eax中,然后导致eax为固定值而发生指定跳转,这个花指令属于利用API返回确定值,配合互补跳转语句和添加操作码作为干扰项实现反汇编的效果。代码如下

LoadLibrary("./hhhh");//函数返回值存储于eax中

_asm {
cmp eax, 0;
jc LABEL6_1;//cf=1触发跳转
jnc LABEL6_2;//cf=0触发跳转
LABEL6_1:
_emit 0xE8;//与call汇编指令对应
LABEL6_2:
}

如下为粘贴解释内容:调用函数,返回值是放在eax中的,这个花指令调用了LoadLibrary函数,加载了一个实际不存在的名为hhhh的库,查询API档案如下,可以看出,这个函数必定返回0,且存入eax,也就是说cmp指令使进位标志寄存器cf=0,产生了确定值,即确定跳转。

总结一下花指令:先构造固定跳转,让函数100%能走到正常函数中,再在0%不走的函数中加入混淆,让正常函数内容因为junk指令而被混淆,达到无法反汇编的目的

二、SMC

SMC(Self-Modifying Code)自解密技术简介

SMC(自修改代码)是一种在程序运行过程中动态修改自身代码的技术,常用于软件保护与恶意代码混淆。在自解密场景中,程序初始状态下的关键代码通常以加密形式存在,只有在运行时通过解密逻辑还原为真实指令,从而实现“静态不可见、动态可执行”的效果。

其基本流程为:程序入口首先执行一段解密例程(Decoder Stub),该例程读取加密代码段,对其进行异或(XOR)、加法、轮换或自定义算法解密;解密完成后,通过跳转(jmp/call)进入已还原的真实代码执行。执行结束后,有些样本还会再次加密自身,实现“执行即销毁”的效果。

SMC技术的核心优势在于对抗静态分析工具,如 IDA Pro 或 Ghidra,使反汇编结果呈现为乱码或无效指令。同时结合花指令、控制流扭曲等手段,可以显著提高逆向难度。

在实际分析中,常见方法包括:内存断点跟踪解密过程、Dump解密后代码、或在关键跳转处截获真实执行流。SMC虽强,但其运行时必然暴露明文逻辑,这也是逆向分析的突破口。

三、VM

VM结构组成

程序运行时候,通过解释操作码(opcode)通常以一个数组构成,数组中存着许多数字,通过不同的数字选择不同的函数(handle)进行操作

vm init

结构体构建,里面存着vm的整个构建过程,包括用到的寄存器,以及对应opcode调用相关handle通过寄存器执行到相关操作

vmstart

逐步执行opcode中的内容

总结:

创建结构体以设定寄存器与需要执行的函数以及opcode

执行结构体,通过调用opcode调用相关值和函数进行加密操作(此操作要仔细分析执行器是怎么运行的)

官方术语

opcode:虚拟指令分发器

通常是以下面的方式出现

while(i<len(opcode)):
    if(opcode[i]==0x00):
        i+=1
    elif(opcode[i]==0x01):
        i+=2
    elif(opcode[i]==0x02):
        i+=3

之后opcode就是有一堆操作数和操作码组成了,然后调用执行器就对opcode进行出栈并运行操作

例题:[GWCTF 2019]babyvm

分析可得,4个寄存器+一个栈底指针,其中第二个为指令长度,第五个为读取操作码

剩下的每一个数对应一个操作,这个过程就是虚拟机创建,定义哪些地址用于存储数据那些用于操作,

紧接着start函数中

直到测量长度时候结束,否者一直执行start_0

start0即为执行器,操作数扫描,去扫该执行第几个操作,扫到之后执行对应位置的操作,因为定义mov等操作的时候已经对pc进行自增了,也就是会只要运行一次函数就会把全套操作执行完。

此作者没有提供个人介绍。
最后更新于 2026-04-24