首页 > 代码库 > 关于编译型语言函数的调用(三)
关于编译型语言函数的调用(三)
接上文: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的程序,你会发现,加载就出现异常了,只能使用静态反汇编
父老常讲,道高一尺,魔高一丈,
世界是美好的,也是丑陋的,既要勇于奉献,也要知道保护自己,才是真正的道理
好吧,关于函数的调用,我们就扯这么多,回想起<黑客帝国>,有时候都不知道这世界是不是真实,至少代码应该是真实的吧
好像,又是夜深人静的时候了...
关于编译型语言函数的调用(三)