大多数PE文件结构都使用偏移地址。因此,只要偏移地址与实际数据匹配,PE文件格式就可以嵌套。也就是说,PE文件是可以变形的,只要保证PE文件格式的偏移地址和结构,基本没有问题。对于PE可执行文件,为了保护可执行文件或压缩可执行文件,通常会对文件进行加壳。接触过软件破解的人应该都知道shell的概念。让我们写一个shell检查工具。首先,用ASPack给之前写好的程序加壳。打开ASPack加壳工具,如图1所示。图1ASPack加壳工具界面对测试软件进行一次加壳,加壳前用PEiD校验,如图2。图2.PEiD校验shellFromFigure2,可以看出该程序是一个VisualC++5.0Debug版本的程序。其实程序是用VisualC++6.0写的,这里是PEiD识别错误。不过只要使用VisualC++6.0编译并选择Release版本,就可以正确识别PEiD。使用ASPack对程序进行打包,然后使用PEiD查看打包情况,如图3所示。图3使用PEiD查看打包后的文件从图3可以看出,PEiD识别出文件已经打包,并是用ASPack打包的。PEiD如何识别程序加壳,加了什么样的壳?PEiD目录下有一个名为“userdb.txt”的签名文件。打开这个文件,看大致内容就知道里面保存了shell的特征码。程序员的任务就是自己实现一个这个shell的识别工具。外壳的识别是通过特征码来进行的,特征码的提取通常是所选文件的入口。shell会修改程序的入口点,所以对于shell的特征码,选择入口点比较合适。这里的工具主要是用来学习和演示的,所以写的查壳工具应该可以识别两种,第一种可以识别用VisualC++6.0编译的文件,第二种可以识别ASPack打包的程序。当然,ASPack打包工具有很多版本,只要能识别上面演示的ASPack即可。如何提取特征码?程序以二进制形式存在,无论是在磁盘上还是在内存中。签名是从程序的入口处提取出来的,所以可以用C32Asm打开这些十六进制形式的文件,在入口处提取签名,或者用OD加载程序到内存中提取签名。这里选择使用OD提取签名。使用OD加载解压后的程序,如图4所示。图4OD加载为打包后的文件入口可以看到,这是解压后程序入口处的代码。在图4中,“HEX数据”一栏就是代码对应的十六进制代码,这里需要做的就是将这些十六进制代码提取出来。提取结果如下:"\x55\x8B\xEC\x6A\xFF\x68\x00\x65\x41\x00"\"\x68\xE8\x2D\x40\x00\x64\xA1\x00\x00\x00"\"\x00\x50\x64\x89\x25\x00\x00\x00\x00\x83"\"\xC4\x94"按照这一步也提取了ASPack的特征码,提取结果如下:"\x60\xE8\x03\x00\x00\x00\xE9\xEB\x04\x5D"\"\x45\x55\xC3\xE8\x01\x00\x00\x00\xEB\x5D"\"\xBB\xED\xFF\xFF\xFF\x03\xDD\x81\xEB\x00""\xC0\x01"有了这些特征码,就可以开始编程了。我们先定义一个存储签名的数据结构,结构如下:#defineNAMELEN20#defineSIGNLEN32typedefstruct_SIGN{charszName[NAMELEN];BYTEbSign[SIGNLEN+1];}SIGN,*PSIGN;使用该数据结构定义2个存储签名的全局变量,如下:SIGNSign[2]={{//VC6"VC6","\x55\x8B\xEC\x6A\xFF\x68\x00\x65\x41\x00"\"\x68\xE8\x2D\x40\x00\x64\xA1\x00\x00\x00"\"\x00\x50\x64\x89\x25\x00\x00\x00\x00\x83"\"\xC4\x94"},{/ASPACK"ASPACK","\x60\xE8\x03\x00\x00\x00\xE9\xEB\x04\x5D"\"\x45\x55\xC3\xE8\x01\x00\x00\x00\xEB\x5D"\"\xBB\xED\xFF\xFF\xFF\x03\xDD\x81\xEB\x00""\xC0\x01"}};程序界面是在PE查看器的基础上完成的,如图5所示。图5查壳程序结果提取特征码后,只留下特征码进行查壳工作匹配。这个很简单,只要把文件的入口编码和特征编码匹配起来,匹配相同就会给出相应的信息。查壳代码如下:VOIDCPeParseDlg::GetPeInfo(){PBYTEpSign=NULL;//定位文件入口位置pSign=(PBYTE)((DWORD)m_lpBase+m_pNtHdr->OptionalHeader.AddressOfEntryPoint);//比较入口特征代码if(memcmp(Sign[0].bSign,pSign,SIGNLEN)==0){SetDlgItemText(IDC_EDIT_PEINFO,Sign[0].szName);}elseif(memcmp(Sign[1].bSign,pSign,SIGNLEN)==0){SetDlgItemText(IDC_EDIT_PEINFO,Sign[1].szName);}else{SetDlgItemText(IDC_EDIT_PEINFO,"unknown");}}这样就完成了查壳程序的功能。程序中提取的特征码长度为32字节。由于这只是一个简单的例子,当你提取特征代码时,为了提高准确率,你需要做一些更多的测试。
