综合许多篇帖子而成,大多参考以末尾参考文章形式给出,适用于初学者,感谢大家提出意见。可结合末尾源码参考学习。感觉有用的话感谢大佬们点个赞。
简介
作用
- 欺骗反汇编器,让反汇编器无法正确反汇编出汇编代码,具体来说是破坏了反编译的分析,使得栈指针在反编译引擎中出现异常
- 从而加大静态分析的难度,使得逆向分析人员难以识别代码的真正意图
原理
由于反编译器的工作原理一般是线性扫描算法或递归下降反汇编算法。
- 线性扫描反汇编算法从程序的入口点开始反汇编,然后对整个代码进行扫描,反汇编扫描其过程中所遇到的每条指令。那么线性扫描算法的缺点也就显而易见了,由于其不定常的指令格式,在反汇编扫描过程中无法区分数据与代码,从而导致将代码段中嵌入的数据误解释为指令的操作码,以致最后得到错误的反汇编结果。
- 递归下降算法,递归下降算法通过程序的控制流来确定反汇编的下一条指令,遇到非控制转移指令时顺序进行反汇编,而遇到控制转移指令时则从转移地址处开始进行反汇编。该算法的缺点在于难于准确确定间接转移的目的地址。
PATCH方法
先在IDA中开启字节码的显示,我这里设置显示的字节码是8。
以下图中的花指令为例,我们鼠标光标点到0x004560FF,然后按快捷键D
,在下方为指令的地址0x00456100地址处,按快捷键C
将其转换为指令。然后将0x004560FF处的0xE8
Patch为0x90
,也就是打补丁为nop指令。
然后保持副本即可。
实现案例
简单花指令-多层JMP嵌套
如下是单层的JMP形式:
jmp LABEL1
db junk_code;
LABEL1:
甚至如下的多层嵌套:
//简单花指令-多层JMP嵌套
void example1()
{
__asm {
jmp LABEL1;
_emit 68h;
LABEL1:
jmp LABEL2;
_emit 0CDh;
_emit 20h;
LABEL2:
jmp LABEL3;
_emit 0E8h;
LABEL3:
}
a = 99;
}
如下图,因为IDA使用的是递归下降算法进行反汇编,所以这种花指令可以被IDA轻松识别。
互补条件代替JMP跳转
类似如下形式,无论如何都会跳转到LABEL1处:
jz LABEL1
jnz LABEL1
db junk_code
LABEL1:
在如下代码中,先对eax进行xor之后,再进行test比较,zf标志位肯定为1,就肯定执行jz LABEL2;
,也就是说中间0xC7永远不会执行。要记得:先压栈保存eax的值,最后再把eax的值pop出来。
void example2_1()
{
__asm {
push eax;
xor eax, eax;
test eax, eax;
jnz LABEL1;
jz LABEL2;
LABEL1:
_emit 0xC7;
LABEL2:
pop eax;
}
a = 21;
}
如下图,我们可以看到,IDA虽然识别栈帧错误,但是正确的程序流还是比较清晰的。
再如下,我们将中间填充代码改为0x21
,混淆效果明显了一些,结果如下。
那么我们再来一个加强版,代码如下:
void example2_3()
{
__asm {
xor eax, eax;
test eax, eax;
je LABEL1;
jne LABEL2;
LABEL2 :
_emit 0x5e;
and eax, ebx;
_emit 0x50;
xor eax, ebx;
_emit 0x74;
add eax, edx;
LABEL1:
}
a = 23;
}
call&ret构造花指令
如下案例,代码中的esp存储的就是函数返回地址,对[esp]+8,就是函数的返回地址+8,正好盖过代码中的函数指令和垃圾数据。
void example3()
{
__asm {
call LABEL9;
_emit 0x83;
LABEL9:
add dword ptr ss : [esp], 8;
ret;
__emit 0xF3;
}
a = 3;
}
如下图所示,是上述花指令代码的效果,这里最好自己动态跟以下。
call 指令的直观理解:push 函数返回地址; jmp 立即数
ret 指令的直观理解:pop eip; add esp,4
利用函数返回确定值
有些函数返回值是确定的,比如我们自己写的函数,返回值可以是任意非零整数,就可以自己构造永恒跳转。
还有些API函数也是如此,比如在Win下HMODULE LoadLibraryA(LPCSTR lpLibFileName);
函数,如果
我们故意传入一个不存在的模块名称,那么他就会返回一个确定的值NULL
,此时就可以通过这个函数来构造永恒跳转。如下例子:
void example4_1()
{
LoadLibrary(L"./hhhh");//函数返回值存储于eax中
__asm{
cmp eax, 0;
jc LABEL6_1;
jnc LABEL6_2;
LABEL6_1:
_emit 0xE8;
LABEL6_2:
}
a = 41;
}
混淆效果如下:
call和ret的组合
如下代码:
void __declspec(naked)__cdecl example5(int* a)//裸函数,开辟和释放堆栈由我们自己写。
{//55 8b ec 83
__asm
{
push ebp
mov ebp, esp
sub esp, 0x40
push ebx
push esi
push edi
mov eax, 0xCCCCCCCC
mov ecx, 0x10
lea edi, dword ptr ds : [ebp - 0x40]
rep stos dword ptr es : [edi]
}
*a = 5;
__asm
{
call LABEL9;
_emit 0xE8;
_emit 0x01;
_emit 0x00;
_emit 0x00;
_emit 0x00;
LABEL9:
push eax;
push ebx;
lea eax, dword ptr ds : [ebp - 0x0]; //将ebp的地址存放于eax
add dword ptr ss : [eax - 0x50], 26; //该地址存放的值正好是函数返回值,
//不过该地址并不固定,根据调试所得。加26正好可以跳到下面的mov指令,该值也是调试计算所得
pop eax;
pop ebx;
pop eax;
jmp eax;
__emit 0xE8;
_emit 0x03;
_emit 0x00;
_emit 0x00;
_emit 0x00;
mov eax, dword ptr ss : [esp - 8]; //将原本的eax值返回eax寄存器
}
__asm
{
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
}
}
也就是说思路有很多种,按照自己喜欢的方式组合,只要不影响其他正常代码的运行就可以,如下也是比较好的两种思路
call嵌套的其他思路1
call LABEL1
db 0E8h
LABEL2:
jmp LABEL3
db 0
db 0
db 0E8h
db 0F6h
db 0FFh
db OFFh
db OFFh
LABEL1:
:call LABEL2
LABEL3:
add esp,8
call嵌套的其他思路2
push eax
call LABEL1
db 29h
db 5Ah
LABEL1:
POP eax
imul eax,3
call LABEL2
db 29h
db5Ah
LABEL2:
add esp,4
pop eax
花指令原理另类利用
当我们理解了花指令的原理后,我们可以在将花指令中的垃圾数据替换为一些特定的特征码,可以对应的$“定位功能”$,尤其在SMC自解码这个反调试技术中可以运用。例如:
asm
{
Jz Label
Jnz Label
_emit 'h'
_emit 'E'
_emit 'l'
_emit 'L'
_emit 'e'
_emit 'w'
_emit 'o'
_emit 'R'
_emit 'l'
_emit 'D'
Label:
}
将这串特征码hElLowoRlD嵌入到代码中,那我们只需要在当前进程中搜索hElLowoRlD字符串,就可以定位到当前代码位置,然后对下面的代码进行SMC自解密。
小结
构造永恒跳转,添加垃圾数据
综合题目案例
IDC脚本去花
我们以题目[MSLRH].exe
为例子,分析一段花指令,使用IDA中的快捷键D
和C
来手动过花指令。手动分析清楚其流程后,我们可以写一个脚本,来批量匹配花指令的模式NOP
掉影响静态分析的代码。
写好的IDC脚本如下:
//文件名:test.idc
#include <idc.idc>
static main()
{
auto x,FBin,ProcRange;
FBin = "E8 0A 00 00 00 E8 EB 0C 00 00 E8 F6 FF FF FF";
//目标 = "E8 0A tel:00 00 00 90 EB 0C tel:90 90 90 90 90 90 90";
//花指令1的特征码
for (x = FindBinary(MinEA(),0x03,FBin);x != BADADDR;x = FindBinary(x,0x03,FBin))
{
x=x+5; //返回的x是第一个E8的地址,
//加上5是第二个E8的地址
PatchByte (x,0x90);//nop掉
x = x + 3; //00
PatchByte (x,0x90);
x++; //00 E8
PatchWord (x,0x9090);
x =x + 2 ; //F6 FF FF FF
PatchDword(x,0x90909090);
}
}
使用IDAPython去花
import idautils
import idc
def my_nop(addr, endaddr):
while addr < endaddr:
patch_byte(addr, 0x90)
addr += 1
pattern = "E8 0A 00 00 00 E8 EB 0C 00 00 E8 F6 FF FF FF"
cur_addr = 0x456000
end_addr = 0x467894
while cur_addr<end_addr:
cur_addr = idc.find_binary(cur_addr,SEARCH_DOWN,pattern)
print("patch address: " + str(cur_addr)) # 打印提示信息
if cur_addr == idc.BADADDR:
break
else:
my_nop(cur_addr+5,cur_addr+6)
my_nop(cur_addr+8,cur_addr+14)
cur_addr = idc.next_head(cur_addr)
附带源码:
JunkCode.7z
(5.11 KB, 下载次数: 93)
参考