刚在52破解看了一篇花指令的文章,感觉挺好的。
第一种花指令是jmp+垃圾数据
比如
jmp label
//这里存放垃圾指令或数据
label:
//正常指令
push ebp
mov ebp,esp
第二种是假分支跳转
xor eax, eax;
test eax, eax;
jnz LABEL1;
jz LABEL2;
xor eax,eax这让eax为0,逻辑运算指令会影响ZF标志位的,所以会置ZF为1,那么永远会跳转到LABEL2处,这样LABEL1处的代码全部是垃圾指令,不管看起来多么正常,统统都是垃圾指令。
第三种是call和retn配合,看起来好像调用了一个函数,但实际上啥事也没做就返回了。
我写了一段测试代码。
int main(int argc, char* argv[]) { printf("hello world"); int a; __asm { call LABEL9; _emit 0x55; _emit 0x8b; _emit 0xec; _emit 0xff; _emit 0x83; _emit 0xec; _emit 0xc; LABEL9: add dword ptr ss : [esp], 0x0D; ret; __emit 0xF3; } a = 3; return 0; }
_emit 0x55; _emit 0x8b; _emit 0xec; _emit 0xff; _emit 0x83; _emit 0xec; _emit 0xc;
这中间有_emit的都是垃圾数据,干扰分析者的。
我们真正有效的代码就是
int a;
a=3;
return 0;
没了。
add dword ptr ss : [esp], 0x0D;
这个改变函数返回地址的,调用完call Lable9之后,栈顶会存放函数的返回地址,也就是_emit 0x8b这个数据的地址。由于是垃圾数据,我们必须不能让它返回到这个地址,要改一下
改到下一句有效代码的地址处,也就是a=3;这句代码的地址,那么就需要让返回地址加一个值,加多少呢?这个我是反汇编之后用OLLYDBG调试之后算出来的。具体看一下下面的图
0040D708 . 68 6C2F4200 push tes22.00422F6C ; /Arg1 = 00422F6C ASCII "hello world" 0040D70D . E8 5EFFFFFF call tes22.0040D670 ; es22.0040D670 0040D712 . 83C4 04 add esp,0x4 0040D715 . E8 07000000 call tes22.0040D721 0040D71A . 55 push ebp 0040D71B . 8BEC mov ebp,esp 0040D71D FF db FF 0040D71E . 83EC 0C sub esp,0xC 0040D721 . 36:830424 0D add dword ptr ss:[esp],0xD 0040D726 . C3 retn 0040D727 ? f3:c745 fc 03000000 rep mov dword ptr ss:[ebp-0x4],0x3
调用完call 0040D721时,栈顶的返回地址是0040D71A,我们想让它返回到0040D727这个正常的地方,0040D727-0040D71A=0x0D,也就是说返回地址需要+0xD
这就是
add dword ptr ss:[esp],0xD 这句代码中为什么要加0xD的原因,加多少是和你添加的垃圾数据有关系的,你加的数据越多,这个值就越大。
有个问题,0xD这个值是我编译之后查看Ollydbg才算出来的,能不能在编写程序的时候就可以知道,这个地方不会呀。
这种干扰效果还是比较明显的。我们看一下在IDA中的效果。
0040D708 push offset aHelloWorld ; "hello world" .text:0040D70D call _printf .text:0040D712 add esp, 4 .text:0040D715 call near ptr loc_40D71D+4 .text:0040D71A push ebp .text:0040D71B mov ebp, esp .text:0040D71D .text:0040D71D loc_40D71D: ; CODE XREF: _main_0+25p .text:0040D71D inc dword ptr [ebx-7CC9F314h] .text:0040D71D _main_0 endp ; sp-analysis failed .text:0040D71D .text:0040D723 add al, 24h .text:0040D725 or eax, 45C7F3C3h .text:0040D72A cld .text:0040D72B add eax, [eax] .text:0040D72B ; --------------------------------------------------------------------------- .text:0040D72D db 2 dup(0), 33h .text:0040D730 ; --------------------------------------------------------------------------- .text:0040D730 rcr byte ptr [edi+5Eh], 5Bh .text:0040D734 add esp, 44h .text:0040D737 cmp ebp, esp .text:0040D739 call __chkesp .text:0040D73E mov esp, ebp .text:0040D740 pop ebp .text:0040D741 retn
是不是感觉特别乱呀,花指令就是这种效果,让你感觉很烦。
还有一种是也是看似分支跳转实际上是无条件跳转
#include "stdafx.h" #include "windows.h" int main(int argc, char* argv[]) { int a; printf("hello world"); LoadLibrary("fldsjfljsfljsflsjflsjfl"); __asm{ cmp eax, 0; jc LABEL6_1; jnc LABEL6_2; LABEL6_1: _emit 0xE8; LABEL6_2: } a = 41; return 0; }
用LoadLibrary加载一个文件,这个文件很明显不管是谁的电脑上都不可能存在的一个文件,那么返回值一定是0,也就是说cmp eax,0的结果一定是0,也就是一定不会产生进位后者借位
,也就是CF标志位一定不会置1,也就是LABEL6_1的流程永远不会到达,那么Lable6_1的代码就是垃圾代码。我曾经遇到过这种花指令。当时很头疼就放下了。今天自己调试了一遍,感觉我有可以了。
看一下在OD中的效果。
0040D708 . 68 6C2F4200 push tes22.00422F6C ; /Arg1 = 00422F6C ASCII "hello world" 0040D70D . E8 5EFFFFFF call tes22.0040D670 ; es22.0040D670 0040D712 . 83C4 04 add esp,0x4 0040D715 . 8BF4 mov esi,esp 0040D717 . 68 A82F4200 push tes22.00422FA8 ; /FileName = "fldsjfljsfljsflsjflsjfl" 0040D71C . FF15 6CA14200 call dword ptr ds:[<&KERNEL32.LoadLibraryA>] ; LoadLibraryA 0040D722 . 3BF4 cmp esi,esp 0040D724 . E8 3739FFFF call tes22.00401060 0040D729 . 83F8 00 cmp eax,0x0 0040D72C . 72 02 jb short tes22.0040D730 0040D72E . 73 01 jnb short tes22.0040D731 0040D730 > E8 C745FC29 call 2A3D1CFC 0040D735 ? 0000 add byte ptr ds:[eax],al 0040D737 ? 0033 add byte ptr ds:[ebx],dh 0040D739 ? c05f 5e 5b rcr byte ptr ds:[edi+0x5e],0x5b 0040D73D . 83C4 44 add esp,0x44 0040D740 . 3BEC cmp ebp,esp 0040D742 . E8 1939FFFF call tes22.00401060 0040D747 . 8BE5 mov esp,ebp
看下在IDA中的效果,和在OD中的效果差不多。