1107 字
6 分钟
花指令

花指令原理#

反编译器的线性反编译(理解花指令的重点)(https://bbs.kanxue.com/thread-279604.htm)#

反编译器的工作原理是,从exe的入口AddressOfEntryPoint处开始,依序扫描字节码,并转换为汇编,比如第一个16进制字节码是0xE8,一般0xE8代表汇编里的CALL指令,且后面跟着的4个字节数据跟地址有关,那么反编译器就读取这一共5个字节,反编译为`CALL 0x地址` 。
对应的,有些字节码只需要一个字节就可以反编译为一条指令,例如0x55对应的是`push ebp`,这条语句每个函数开始都会有。同样,有些字节码又需要两个、三个、四个字节来反编译为一条指令。
也就是说,如果中间只要一个地方反编译出错,例如两条汇编指令中间突然多了一个字节0xE8,那反编译器就会将其跟着的4个字节处理为CALL指令地址相关数据给反编译成一条`CALL 0x地址`指令。但实际上0xE8后面的四个字节是单独的字节码指令。这大概就是**线性反编译**。
线性扫描和递归下降
线性扫描:
线性扫描的特点:从入口开始,一次解析每一条指令,遇到分支指令不会递归进入分支。
递归下降:
当使用线性扫描时,比如遇到call或者jmp的时候,不会跳转到对应地址进行反汇编,而是反汇编call指令的下一条指令,这就会导致出现很多问题。
递归下降分析当遇到分支指令时,会递归进入分支进行反汇编。

基础花指令类型#

1.jmp(并不能骗过ida)#

__asm{
jmp LABEL1;
_emit 0xe8; // 无用操作数
LABEL1:
}

2.jnz,jz一类的跳转(可以看作是互补的跳转)#

__asm{
jz/jo/jc label1;
jnz/jno/jnc label1;
_emit 0x55; // 无用操作数
_emit 0xe8;
label1:
}

3.永真永假跳转#

__asm{
clc; // 清除进位标志 CF,设置 CF 为 0
jnc label1; // 如果 CF 为 1,跳转到 label1
_emit junkcode; // 垃圾数据,不会执行
label1:
}
/*
__asm{
xor eax, eax;
jz label1;
_emit 0xe8;
label1:
}
*/

4.call、ret指令#

__asm {
call LABEL9; // 调用 LABEL9,实际上是将 LABEL9 的地址压入堆栈,并跳转到 LABEL9 执行
_emit 0x83; // 插入字节 0x83,作为垃圾数据,不会被执行到
LABEL9:
add dword ptr ss : [esp], 8; // 修改堆栈顶的返回地址,加上 8(根据操作数的个数来定)
ret; // 从 LABEL9 返回,返回地址被修改过
__emit 0xF3; // 插入字节 0xF3,作为垃圾数据,不会被执行到
}

5.扩充#

call指令会压入地址后并实现jmp跳转,ret指令会弹出栈顶地址并实现jmp到该地址,ret指令是用来维护堆栈平衡的。由于call指令会压入一个地址,可以用add esp, 4来实现堆栈平衡(也可以用pop来实现堆栈平衡),这样就相当是一个jmp指令,这种实现可以骗过ida。

__asm{
call label1;
...
label1:
add esp, 4;
...
} // jmp

还可以通过call指令还可以用来查看eip

__asm{
_emit 0xE8;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x00;
}

test.c#

#include <stdio.h>
int main()
{
__asm {
call LABEL9;
_emit 0x83;
LABEL9:
add dword ptr ss : [esp] , 8;
ret;
_emit 0x55;
}
printf("hello world1\n");
__asm {
jo LABEL10;
jno LABEL10;
_emit 0xE8;
LABEL10:
}
printf("hello world2\n");
__asm {
xor eax, eax;
jz LABEL11;
_emit 0xE8;
LABEL11:
}
/*
__asm {
clc;
jnc label11;
_emit 0xF3;
label11 :
}
*/
printf("hello world3\n");
__asm {
_emit 0xE8;
_emit 0x01;
_emit 0x00;
_emit 0x00;
_emit 0x00;
_emit 0x55;
pop eax;
}
printf("hello world4\n");
}

将基本花指令组合形成比较的复杂花指令#

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void encr(char* input)
{
__asm {
jo label1;
jno label1;
_emit 0xE8;
}
/********junk_code********/
/*************************/
__asm {
label2:
call label3;
_emit 0x55;
_emit 0xf3;
}
/********junk_code********/
for (int i = 0; i < strlen(input); i++) {
input[i] += 0x33;
input[i] ^= 0x33;
}
/*************************/
__asm {
label4:
}
for (int i = 0; i < strlen(input); i++) {
input[i] -= 0x10;
}
__asm {
xor eax, eax;
jz label5;
}
/********junk_code********/
/*************************/
__asm {
label1:
xor eax, eax;
jz label2;
_emit 0x55;
}
/********junk_code********/
for (int i = 0; i < strlen(input); i++) {
input[i] += 0x22;
input[i] ^= 0x22;
}
/*************************/
__asm {
label3:
add esp, 4;
xor eax, eax;
jz label4;
}
/********junk_code********/
/*************************/
__asm {
label5:
}
/********junk_code********/
/*************************/
return;
}
int main(int argc, const char** argv)
{
char cipher[33] = { 86, 92, 81, 87, 107, 33, 33, 34, 35, 29, 34, 35, 36, 37, 29, 33, 34, 35, 36, 37, 34, 35, 37, 34, 35, 34, 37, 34, 35, 33, 34, 109, 0};
encr(argv[1]);
if (strcmp(cipher, argv[1]) == 0) {
printf("right");
}
else {
printf("wrong");
}
}
花指令
https://mizuki.mysqil.com/posts/花指令/
作者
sh4d0w
发布于
2025-11-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时