首页 > 代码库 > 关于编译型语言函数的调用(三)

关于编译型语言函数的调用(三)

接上文:http://blog.csdn.net/prsniper/article/details/40653235

类delete就不说了,有兴趣的朋友自己跟踪看看吧,提示一下:析构函数也有参数和返回值.


下面我们看下裸函数,裸函数从执行效率上是可以与汇编语言媲美的,然而它不太好逆过来说,我们就顺着说吧

前面说得构造函数的临时堆栈,恢复寄存器等等,有人给了个名称叫prolog和epilog

而裸函数并没有自动编译这些部分,其实说白了,裸函数相当于汇编语言中的一个标签,其调用受前面几种约定约束

此外空的裸函数是什么都不做的,非必要的不做,必要的也不做,就是什么都要自己实现,其实就是要自己汇编

当然了,裸函数不能是类的成员函数,举例中我们使用__cdecl约定,直接照搬fnDefaultCall的,不管执行效率

132:      ret = fnNakedCall(19, 20, 21, &var1);
00401428   lea         edx,[ebp-14h]
0040142B   push        edx
0040142C   push        15h
0040142E   push        14h
00401430   push        13h
00401432   call        @ILT+15(fnNakedCall) (00401014)
00401437   add         esp,10h
0040143A   mov         dword ptr [ebp-18h],eax
133:
可以看出调用约定符合__cdecl的约定,那么跟踪一下看看:

68:
69:   __declspec(naked) int __cdecl fnNakedCall(int arg1, short arg2, char arg3, void *arg4)
70:   {
004012D0   push        ebp
71:       // 1. 到这里所有寄存器的值与调用前一样
72:       // 2. 用变量名引用任何局部变量等同于引用主调函数变量或参数
73:       // 3. 必须负责寄存器的维护, 这里函数作为__cdecl
74:       __asm{
75:           push        ebp                 ; prolog begin
76:           mov         ebp, esp
004012D1   mov         ebp,esp
77:           sub         esp, 50h
004012D3   sub         esp,50h
78:           push        ebx
004012D6   push        ebx
79:           push        esi
004012D7   push        esi
80:           push        edi
004012D8   push        edi
81:           lea         edi, [ebp-50h]
004012D9   lea         edi,[ebp-50h]
82:           mov         ecx, 14h
004012DC   mov         ecx,14h
83:           mov         eax, 0CCCCCCCCh
004012E1   mov         eax,0CCCCCCCCh
84:           rep stos    dword ptr [edi]     ; prolog end
004012E6   rep stos    dword ptr [edi]
85:
86:           // var1 = arg1;
87:           mov         eax, dword ptr [ebp + 8]        ; [esp + 8]
004012E8   mov         eax,dword ptr [ebp+8]
88:           mov         dword ptr [ebp-4], eax          ; [esp - 4]
004012EB   mov         dword ptr [ebp-4],eax
89:           // var2 = arg2;
90:           mov         cx, word ptr [ebp + 0Ch]
004012EE   mov         cx,word ptr [ebp+0Ch]
91:           mov         word ptr [ebp - 8], cx
004012F2   mov         word ptr [ebp-8],cx
92:           // var3 = arg3;
93:           mov         dl, byte ptr [ebp + 10h]
004012F6   mov         dl,byte ptr [ebp+10h]
94:           mov         byte ptr [ebp - 0Ch], dl
004012F9   mov         byte ptr [ebp-0Ch],dl
95:           // p = (int *)arg4;
96:           mov         eax, dword ptr [ebp + 14h]
004012FC   mov         eax,dword ptr [ebp+14h]
97:           mov         dword ptr [ebp - 10h], eax
004012FF   mov         dword ptr [ebp-10h],eax
98:           // *p = -1;
99:           mov         ecx, dword ptr [ebp - 10h]
00401302   mov         ecx,dword ptr [ebp-10h]
100:          mov         dword ptr [ecx], 0FFFFFFFFh
00401305   mov         dword ptr [ecx],0FFFFFFFFh
101:          // return 22;
102:          mov         eax, 16h            ; 0x16 = 22
0040130B   mov         eax,16h
103:
104:          pop         edi                 ; epilog begin
00401310   pop         edi
105:          pop         esi
00401311   pop         esi
106:          pop         ebx
00401312   pop         ebx
107:          mov         esp, ebp
00401313   mov         esp,ebp
108:          pop         ebp                 ; epilog end
00401315   pop         ebp
109:          // return to caller function(do not use ret 10h)
110:          ret
00401316   ret
--- No source file  --------------------------------------------------------------
00401317   int         3
虽然复制过来有点错位,不过不难看出,我们的汇编代码被照搬了,用曾泰的话说:丝毫不差.

这就意味着,我们只要保证寄存器调用后符合调用约定,此外就可以为所欲为了

我不希望大家堕入哲学的深渊,不过我只能用这样的语句来描述:

什么都要自己做和什么都能自己做,往往是一回事,这就是辩证.


还记得前面说的fastcall中变量被存来存去吗,嘿嘿,用naked就可以避免,实现真正的fastcall

此外,一些常用的内联汇编的函数,也可以用这种方式输出,当然了,需要具备一定的汇编语言基础

再如,我们可以连堆栈都不构造,直接使用esp代替ebp,即省去prolog和epilog.


文章似乎到这里就结束了,那么知道这些有什么用呢?如果你有这样的想法,那就再好不过了,通常人知道了也就知道了

能有这样的想法,说明有学以致用的习惯或者说性格.

那么,我们就试着提出几个用处吧:


1. 提升调试程序的能力,更深入的找出BUG的根源,并得出更深一层的解决方法

比如莫名其妙的崩溃,缓冲区溢出,还有这种:


2.提升程序开发水平,比如常用的strcpy,memset等等函数,应该自己写,更好的发挥机器的性能,

法国一个科学家,用一台普通的计算机计算圆周率,其效率居然可以与一台超级计算机相媲美

单靠数学是无法完成这样的工作的.他必然对计算机的指令架构十分熟悉


3.对逆向和软件加密有一定程度的认知

比如单单知道eax作为返回值这一点,你就该认识到,仅用一个函数去做授权验证是很容易绕过的!

既然能想到绕过别人程序的办法,就应该想出防止别人简单绕过的方案

实际的软件加密和破解虽然并没有那么简单,不过起码有这样的一个认知,


说起破解,我到还想再废话一下,有些学习破解的朋友,反汇编出来,修改指令,就认为天下的软件就这么干就能破解了

对软件行业充满了失落感,那么我们来举个例子:

使用OD载入一个程序:

00531001 >  60              pushad
00531002    E8 03000000     call    0053100A
00531007  - E9 EB045D45     jmp     45B014F7
0053100C    55              push    ebp
0053100D    C3              retn

一开始就发现情形不太对,当然专职搞破解的朋友马上看得出来,这是加壳以后的程序,一开始便运行的是壳的程序,

解压或解密出实际的代码,然后跳到实际入口去执行,那么脱壳即可啦,那我们再继续看

046620D1    81EC 00040000   sub     esp, 400
046620D7    55              push    ebp
046620D8    8BEC            mov     ebp, esp
046620DA    50              push    eax
046620DB    EB 0E           jmp     short 046620EB
046620DD    838B 84240804 0>or      dword ptr [ebx+4082484], 0
046620E4    00EB            add     bl, ch
046620E6    01F0            add     eax, esi
046620E8    EB 04           jmp     short 046620EE
046620EA    54              push    esp
046620EB  ^ EB F1           jmp     short 046620DE
046620ED    6BEB 0C         imul    ebp, ebx, 0C
046620F0    B7 6B           mov     bh, 6B
046620F2    8D40 FB         lea     eax, dword ptr [eax-5]
046620F5    EB 01           jmp     short 046620F8
046620F7    09EB            or      ebx, ebp
046620F9    06              push    es
046620FA    9B              wait
046620FB    0D EBF4E7E8     or      eax, E8E7F4EB
04662100    EB 0D           jmp     short 0466210F
04662102    9C              pushfd
04662103    FE89 45ECEB03   dec     byte ptr [ecx+3EBEC45]
04662109    BB 4B62EB06     mov     ebx, 6EB624B
0466210E  - 66:EB F3        jmp     short 00002104

在046620EB处 jmp     short 046620DE,然而反汇编出来并没有开始为0x046620DE的指令

只有046620DD 是 or dword ptr[ebx+....], 0,这是怎么回事呢

我们把046620DD的字节改为int 3即0xCC再看一下

直接输入16进制CC一个字节

再看反汇编窗口

居然变成了

046620DE    8B8424 08040000 mov     eax, dword ptr [esp+408]

恰好在jmp的目的地址,这不是欺负老实人吗?


不错,这就是花指令,而且是花指令的一种,让你反汇编了以后也是乱的

当然,花指令直接汇编级别的加密,如果你逆向360的程序,你会发现,加载就出现异常了,只能使用静态反汇编

父老常讲,道高一尺,魔高一丈,

世界是美好的,也是丑陋的,既要勇于奉献,也要知道保护自己,才是真正的道理


好吧,关于函数的调用,我们就扯这么多,回想起<黑客帝国>,有时候都不知道这世界是不是真实,至少代码应该是真实的吧

好像,又是夜深人静的时候了...

关于编译型语言函数的调用(三)