0x01 花指令简介
花指令是企图隐藏掉不想被逆向工程的代码块 (或其它功能) 的一种方法, 在真实代码中插入一些垃圾代码的同时还保证原有程序的正确执行, 而程序无法很好地反编译, 难以理解程序内容, 达到混淆视听的效果。花指令会使得 IDA 在分析过程中出现问题,可能产生堆栈不平衡。
0x02 堆栈不平衡
IDA 中产生堆栈不平衡的原因可能是由于不同的函数调用约定,也可能是在实现代码保护插入了代码混淆(特指使用 push/push+ret 实现函数调用)。
关于函数调用约定:
函数的调用约定,顾名思义就是对函数调用的一个约束和规定(规范),描述了函数参数是怎么传递和由谁清除堆栈的。它决定以下内容:(1)函数参数的压栈顺序,(2)由调用者还是被调用者把参数弹出栈,(3)以及产生函数修饰名的方法。
主要需要了解三种调用约定:__cdecl、__stdcall、__fastcall。
1、__cdecl
cdecl(C declaration,即C声明)是源起C语言的一种调用约定,也是C语言的事实上的标准。在x86架构上,其内容包括:
- 函数实参在线程栈上按照从右至左的顺序依次压栈。
- 函数结果保存在寄存器 EAX / AX / AL 中
- 浮点型结果存放在寄存器 ST0 中
- 编译后的函数名前缀以一个下划线字符
- 调用者负责从线程栈中弹出实参(即清栈)
- 8 比特或者 16 比特长的整形实参提升为32比特长。
- 受到函数调用影响的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
- 不受函数调用影响的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
- RET指令从函数被调用者返回到调用者(实质上是读取寄存器EBP所指的线程栈之处保存的函数返回地址并加载到IP寄存器)
2、__stdcall
stdcall是由微软创建的调用约定,是Windows API的标准调用约定。非微软的编译器并不总是支持该调用协议
- 函数实参在线程栈上按照从右至左的顺序依次压栈。
- 由被调用者负责清栈
- 编译后的函数后缀以符号“@”
- 寄存器EAX, ECX和EDX被指定在函数中使用,返回值放置在EAX中
- 其他方面与 __cdecl 一样
3、__fastcall。
此约定还未被标准化,不同编译器的实现也不一致。
Microsoft/GCC fastcall
Microsoft 或 GCC 的 fastcall 约定(也即 msfastcall )把第一个(从左至右)不超过32比特的参数通过寄存器ECX/CX/CL传递,第二个不超过32比特的参数通过寄存器EDX/DX/DL,其他参数按照自右到左顺序压栈传递。Borland fastcall
从左至右,传入三个参数至EAX, EDX和ECX中。剩下的参数推入栈,也是从左至右。 在32位编译器Embarcadero Delphi中,这是缺省调用约定,在编译器中以register形式为人知。 在i386上的某些版本Linux也使用了此约定。