函数指针,可以为一个函数重命名,代替其进行函数调用,而我们也用函数指针做过简单的C语言面向对象编程:函数指针与C的面向对象 。书上说(很多人这样说):函数指针是为了便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。这从正向的角度来说依旧是总结性的,而不是本质性的,函数指针并非是针对于面向对象的,从本质来说它更是用来做数据与代码转换的,怎么说?
如果我们设计了个功能函数比较重要,也不想其他人轻易通过反汇编获取自己的函数设计思想,或者是为了给整个软件重要部分“加密保护”。有一种方式就是将对应函数的代码应藏到数据段中去,而调用函数时使用函数指针将该段数据解析成指令直接调用。我们以一个简单的例子来做分析:
#include "stdafx.h"
int add(int x, int y)
{
return (x+y);
}
int main(int argc, char* argv[])
{
printf("%d\n",add(10,20));
return 0;
}
假设这里的add()函数是一个需要进行保护/保密的函数,我们普通调用是通过函数名加参数直接调用,但是经过隐藏代码到数据段的方式就需要使用函数指针来调用了,如何操作?我们先来看该函数的反汇编代码和硬编码:
;add:
55 push ebp
8B EC mov ebp,esp
83 EC 40 sub esp,40h
53 push ebx
56 push esi
57 push edi
8D 7D C0 lea edi,[ebp-40h]
B9 10 00 00 00 mov ecx,10h
B8 CC CC CC CC mov eax,0CCCCCCCCh
F3 AB rep stos dword ptr [edi]
8B 45 08 mov eax,dword ptr [ebp+8]
03 45 0C add eax,dword ptr [ebp+0Ch]
5F pop edi
5E pop esi
5B pop ebx
8B E5 mov esp,ebp
5D pop ebp
C3 ret
由于汇编指令与硬编码对应,所以我们将硬编码提取出来存放到一个数组中去,然后在调用时用函数指针读取该数组,就相当于读取一条条指令(而这里是直接读取机器指令),其功能是完全相同的。如下所示,add数组看起来只是普通的数据:
#include "stdafx.h"
char add[] = {
0X55,
0X8B, 0XEC,
0X83, 0XEC, 0X40,
0X53,
0X56,
0X57,
0X8D, 0X7D, 0XC0,
0XB9, 0X10, 0X00, 0X00, 0X00,
0XB8, 0XCC, 0XCC, 0XCC, 0XCC,
0XF3, 0XAB,
0X8B, 0X45, 0X08,
0X03, 0X45, 0X0C,
0X5F,
0X5E,
0X5B,
0X8B, 0XE5,
0X5D,
0XC3,
};
int main(int argc, char* argv[])
{
int (*pAdd)(int,int);
pAdd = (int (__cdecl *)(int,int))&add;//将数据以指令的方式解析
//pAdd = (int (*)(int,int))&add;
//__cdecl属于默认,可以省略
printf("%d\n",pAdd(10,21));
return 0;
}
而在这种经过代码隐藏的程序中,其反汇编中就不能直接找到add()函数了,而必须要经过全局数组的地址来找硬编码,再经过硬编码转换至汇编进行分析,在数据量非常庞大的情况下这种方式就可以很有效地进行简单的代码隐藏(因为从本质来讲数据和代码并无两样)。
;main:
;...
;从数据区读取指令数组
;将数据区对应指令地址存放到函数栈中[ebp-4],然后修改eip为[ebp-4]进行调用
00401038 C7 45 FC 18 73 42 00 mov dword ptr [ebp-4],offset add (00427318)
0040103F 8B F4 mov esi,esp
00401041 6A 15 push 15h
00401043 6A 0A push 0Ah
00401045 FF 55 FC call dword ptr [ebp-4]
00401048 83 C4 08 add esp,8
0040104B 3B F4 cmp esi,esp
0040104D E8 DE 00 00 00 call __chkesp (00401130)
00401052 50 push eax
00401053 68 1C 20 42 00 push offset string "%d\n" (0042201c)
00401058 E8 53 00 00 00 call printf (004010b0)
0040105D 83 C4 08 add esp,8
;...
00401072 C3 ret
;add()对应在数据段的指令
00427318 55 8B EC 83 EC 40 U嬱冹@
0042731E 53 56 57 8D 7D C0 SVW峿.
00427324 B9 10 00 00 00 B8 ......
0042732A CC CC CC CC F3 AB 烫烫螳
00427330 8B 45 08 03 45 0C 婨..E.
00427336 5F 5E 5B 8B E5 5D _^[嬪]
0042733C C3 00 00 00 00 00 ......
测试结果: