花指令

花指令

https://www.anquanke.com/post/id/208682

花指令原理

ida采用的是线性扫描反汇编算法,也就是一步一步往下识别,一旦在中间插入奇怪的立即数,ida可能就会识别出错,进而不能正确反编译

花指令实现

首先,我们插入花指令不能阻碍我们程序的正常运行,这是最基本的

下面使用一段常规代码进行花指令的添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
#include<windows.h>
int main(int argc, char* argv[])
{
char s[] = "1234234";
int i = 0, k = 1;

for (; i < 10; ++i)
{
k += i;
}
printf("%d", k);
return 0;
}

没插花指令前的反编译

永真跳转

1
2
3
4
5
6
7
8
9
10
11
__asm {
push eax;
xor eax, eax;
test eax, eax;
jz LABEL1;
jnz LABLE2;
LABLE2:
__emit 0xe8;
LABEL1:
pop eax;
}

先来看看效果

可以看到ida不能正确反编译

原理

xor eax,eax能够保证eax的值为0,异或自己,相同为0

根据目标操作数修改符号标志位、奇偶标志位、零标志位,经过这一步后,ZF为1

test eax,eax是按位与操作,并且会根据值修改ZF标志位,并且只有当位都清 0 时,零标志位才置 1

ZF为1时,jz进行跳转,到LABEL1,因为里面没有其他值,所以代码正常执行,但是在 反编译的时候,因为是线性扫描,所以遇到了LABEL2里面的0xe8(call的机器码),就会将其后面的机器码当作要调用的地址,这样一来,jz就直接跳转到被错误识别的汇编代码中,所以会出现错误

去除

可以看到这里面的jz和jnz后面的地址存在+1,这就基本可以看出是花指令了,找到对应的地址,按u取消定义

可以看到这里也是两个函数,对下面的按C弄成代码

然后为了反编译,要把0xe8使用90填充,也就是nop,填充后按C即可

按P弄成函数后反编译,和上面的基本一致

插入立即数

1
2
3
4
5
6
7
8
__asm {
xor eax, eax;
jz LABEL1;
__emit 0x11;
__emit 0x22;
__emit 0x33;
LABEL1:
}

效果

识别出错,原理和上面一样,感觉这种稍微恶心一点,因为有一段可能是正常的,需要一个一个试

直接nop掉即可

破坏堆栈

我们可以插入对esp和eip的操作进而破坏堆栈

1
2
3
4
5
6
__asm {
xor eax, eax;
jz LABEL1;
add esp, 8;
LABEL1:
}

ida能够正常反编译,但是在函数末端会发现堆栈错误提示

call&ret构造花指令

call本质是先将其 下一条指令压入栈中,再jmp函数地址

ret则是pop eip

所以我们可以修改返回地址,然后在中前插入垃圾数

1
2
3
4
5
6
7
8
9
__asm {
call LABEL1;
__emit 0x83;

LABEL1:
add dword ptr ss : [esp] , 8;
ret;
__emit 0xf3;
}

效果

这里我们插入的不是机器码,所以没有识别成奇怪的代码,但是还是让ida出现了错误

去除

全部nop掉即可

原理

这里之所以程序能够进行正常的运行,是因为call会把其下一条指令的地址压入栈中,也就是41459F,(不知道为什么这里会多出一个db 36h -.-),接下来执行将当前esp地址的值+8,也就是栈顶存储的值+8,也就是返回地址加8,正好对应4145A7,下面的代码c之后是正常的代码,所以不会出错

在此基础上,我们可以构造各种花指令

下面连接有一些花指令

https://www.bilibili.com/read/cv13177757

call嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__asm {
call LABEL1;
_emit 0xE8;
LABEL2:
jmp LABEL3;
_emit 0;
_emit 0;
_emit 0xE8;
_emit 0xf6;
_emit 0xff;
_emit 0xff;
_emit 0xff;
LABEL1:
call LABEL2;
LABEL3:
add esp, 8;
}

效果

原理

这里之所以程序能够正常运行,是因为只有两个call对堆栈有影响,所以只需要在最后esp+8就可以回到原来的堆栈

这里先callA9,然后0xE8和后面的结合成了call指令,这里可以看到call到了全是int 3的地方

然后这个9f一直在call自己,所以可以判断是花指令

把这两个函数nop掉之后

所以这个调用也没用了

同理这个也可以nop掉,那么堆栈会不平衡,add esp,8也要nop

得到正常代码

jmp变形-SUSCTF2022-tttree

感觉很巧妙,拿出来看看

动调可以看得更清楚

前面的push没什么好说的,关键在call之后的

当前的栈顶,call会将其之后的地址压入栈顶

可以看到是B7D82FF9B0

下面两步就是将当前栈顶的元素先+0x191b给到rax

下一步是把rax里的值给到rsp+16

1646127416441

经过两步pop,可以发现来到了存储原本返回值+0x191b的栈的位置

retn先将栈顶元素pop到eip,再jmp,而eip正好记录我们下一步执行的指令

这样就是一个简单的jmp

解决方法就是先计算跳转的地址,然后改成jmp即可