基础花指令&去花指令

本文主要作为个人学习记录,主要参考了Captain_RB大佬博客的文案,侵删。

花指令又称脏字节,英文为”junkcode”,顾名思义,即在程序中加入的一些垃圾指令,其目的是在不妨碍原有程序执行的前提下,阻碍程序反编译,增加静态分析难度,隐匿不想被逆向分析的代码块,混淆代码,绕过特征识别。
:本文使用的反编译器是ida7.7,编译环境:gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)

以下将按照不同分类介绍一些常见的花指令:

0x01不可执行花指令

它是利用反汇编器静态分析算法的缺陷使得代码在反编译解析时出错。其原理使反汇编分析执行流命中执行会出错的垃圾数据,就会造成解析错误,而实际执行过程中垃圾数据并不会执行。

1.硬编码多字节指令

最常见的

1
2
3
4
jz Label        // 采用条件互补的方法跳转绕过垃圾数据
jnz Label
junkcode
Label:

其中 junkcode 可以是任何的多字节指令的硬编码,我一般的操作是写一个nop然后用hex编辑器将90改为E8。为什么要改为E8:

img

可以看到X86_64架构下,call指令为E8 + 一个偏移地址,如果把这个junkcode设为E8,反汇编时会自动把后面的机器码识别为偏移地址,出现“吞指令”的现象。

实践一下

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void func(){
puts("im here!");
}


int main(){
func();
return 0;
}

先写一个简单的C程序,然后再func的puts前,插入内联汇编

1
2
3
4
5
6
7
8
9
void func(){
__asm__ __volatile__(
"jz label;"
"jnz label;"
"nop;"
"label:;"
);
puts("im here!");
}

要记得在gcc编译时加入-masm=”intel”否则linux下的内联汇编默认使用AT&T语法。

此时的反编译和反汇编:

img

img

将label上的nop改为E8,输出到ELF后重新用ida打开:

img

可以看到main边上已经没有func了

img

有时候也会解析成这样:

img

第二个是我想要的效果,不知道为什么第一个不行,可能是因为后面的指令无法被解析吧。

总之都达到了混淆反汇编的效果。

去花:看到这种jz jnz,直接把从第一个跳转jz,一直到跳转目标地址之间直接全nop掉就行了。

2.破坏堆栈平衡

如果反编译器检测到指令破坏了堆栈平衡,即函数返回时与调用时堆栈状态发生了变化,就会报错。可以利用这一点构造破坏堆栈平衡的花指令,如下代码:

1
2
3
4
test eax,0         // 构造必然条件实现跳转,绕过破坏堆栈平衡的指令
jz label
add esp,0x1
label:

反汇编效果:

img

原理主要是由于ida解析时碰到条件判断语句会同时解析两条支路,导致出现两条路的栈不平衡的现象。

不过这个花指令似乎并没有混淆到高版本的ida:

img

虽然报错了,但是依然能够正常反编译。

可能是由于

  1. 代码不够复杂
  2. 需要更多的junkcode

去花:通过逻辑分析,把整块不执行的代码和跳转代码nop掉即可。

0x02 可执行花指令

花指令在程序执行过程中会被执行,但执行这些代码没有任何意义,执行前后不改变任何寄存器的值,也不改变程序执行逻辑和结果,目的是加大静态分析的难度,或是混淆特征码,绕过特征检测。

1.call + 修改返回地址

1
2
3
4
call label
label:
add [esp],5
ret

在call中修改返回地址,使call执行完后跳过label中的指令,达到执行但是相当于没执行的效果。

并且混淆了sp指针,让ida报栈不平衡的同时,识别到ret指令,会直接将函数一分为二。

1
2
3
4
5
6
7
8
9
10
void func(){
puts("hello!");
__asm__ __volatile__(
"call label;"
"label:;"
"add [rsp],5;"
"ret;"
);
puts("im here!");
}

我尝试直接使用这个汇编,但是出现了报错,原因暂时不明。

1
2
Assembler messages:
错误: ambiguous operand size for `add'

所以我自己魔改了一下:

1
2
3
4
5
6
7
8
9
10
11
12
void func(){
puts("hello!");
__asm__ __volatile__(
"call label;"
"label:;"
"pop rax;"
"add rax,7;"
"push rax;"
"ret;"
);
puts("im here!");
}

缺点是长了2个字节,所以add部分改成了7,原理是一样的,只不过原来是直接在esp上add,现在改为将esp pop出来加了在push回去。

反编译效果:

img

img

img

反汇编效果:

img

真-硬生生拆成两个函数。

可以用于隐藏函数尾部部分代码时使用。

去花:从call $+5到第一个retn这段全部nop掉即可

2.混淆特征码

这种类别的花指令组合形式很多,用于混淆木马、病毒的特征码,躲避AV查杀,不一定会造成反汇编失败,但是会对反汇编分析造成干扰。

1
2
3
4
mov op1,op2     ---->    push op2 / pop op1
jmp label ----> push label / ret
call label ----> push label_next_instruction / push label / ret
push op ----> sub esp,4 / mov [esp],op

主要是通过更长的代码去替换更短的代码,从而达到混淆特征码的效果。

尝试了一下第2条:

1
2
3
4
5
6
7
8
9
void func(){
puts("hello!");
__asm__ __volatile__(
"push label;"
"ret;"
"label:;"
);
puts("im here!");
}

编译时需要加入 -no-pie,因为在内联汇编中添加了.text段的地址(push label)。

反编译效果:

img

反汇编效果比想象的要好:

img

直接把后面那段整没了。