打造属于自己的“查壳”工具
文/图 冀云
壳是一种保护软件的工具,壳有加密壳和压缩壳,有的壳两种功能都有。壳这东西,我也不怎么了解,说多了怕说错,影响我在大家面前的形象。废话不多说了,只说说今天我的目的。此次想跟大家探讨一些编写“查壳”工具的实现方法,我只把我的方法说给大家,有不足的地方欢迎指正和批评。
PEiD这款工具想必很多人都用过吧?尤其是我们《黑客防线》的朋友,对它就更是了如指掌了。PEiD为什么能查壳呢?这是因为PEiD有一个文本类型的数据库,我的PEiD数据库的名称是“userdb.txt”,复制下来一段给大家看看!
[ASPack v2.12]
signature = 60 E8 03 00 00 00 E9 EB 04 5D 45 55 C3 E8 01
ep_only = true
第一行是加壳软件的名称版本,第二行是壳的特征码,第三行我就不知道了,但我们不用管它,因为跟我们的文章没有关系嘛。比较关键的是加壳软件的名字和特征码,因为我们查壳是查什么?就是查这个加壳软件的名字嘛!怎么查?靠特征码查呗!事实上我发现,PEiD使用的特征码都是连续的,而且大多都是从程序入口处开始取的特征码。这样的方法也算正常,黑防的有些高手,用OD打开要调试的软件,一看前面几行就知道这个软件加的是什么壳(汗!黑防的高手就是厉害)。大家看看图1,就知道特征码确实是从文件的入口点取出的,并且是连续的。
图1
有了这个方法,我们就知道该怎么办了。怎么办?先找到文件的入口点呗!文件的入口点其实就在PE文件的某个结构里保存的,大家可以自己查阅MSDN看一下,这里就不多说关于PE的内容了。入口点一般在IMAGE_OPTIONAL_HEADER结构中的AddressOfEntryPoint成员中保存,只要把这个成员的值读出来,入口点就找到了。记得曾经有人说过“源代码面前没有秘密”,那么我们就直接看代码吧。
IMAGE_DOS_HEADER *pDosHeader=(IMAGE_DOS_HEADER *)lpBase;
pNtHeader=(IMAGE_NT_HEADERS *)((char *)lpBase+pDosHeader->e_lfanew);
dwOEP=pNtHeader->OptionalHeader.AddressOfEntryPoint;
其中的dwOEP中保存的就是入口点的值了。很简单吧?不过在这之前我们应该先判断一下打开的文件是不是PE文件,然后再读取入口点。不过我还是省略了一些打开文件、映射文件视图等的操作,不过也很简单,代码也就两三行。
hFile=CreateFile(FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
hMap=CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,0);
lpBase=MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);
这其中没有一句错误处理,我们就假设都能保证正常返回吧。前面的处理都一切顺利,但是怎样才能读出入口点处的N个特征码呢?又用什么读呢?ReadFile()?ReadProcessMemory()?说真的,这里当初确实把我给难住了,用ReadFile读的话,文件的句柄用哪个?怎么定位?想不出来。用ReadProcessMemory()读的话,读哪个进程的?也不知道。怎么办呢?结果我发现PEiD上还有一个“文件偏移”按钮。文件偏移是干什么的?用UltraEdit32打开在PEiD里打开的那个文件看看“00001001”这个偏移的内容,如图2所示,没想到在文件偏移处也能找到传说中的特征码。好,就从这里下手了。但是“文件偏移”又怎么找呢?我们继续往下看。
图2
其实我们找到的入口点是一个相对偏移地址(RVA),顾名思义,它是一个“相对”地址,也就是一个“偏移量”。那么RVA怎么才能转换成文件偏移呢?好像微软没有提供这方面的API函数吧?因此只能我们自己解决了。我的方法比较笨,分三步:首先循环扫描每个节表,得到每个节在内存中的RVA(在IMAGE_SECTION_HEADERD的VirtualAddress成员中保存),还有该节的大小(在IMAGE_SECTION_HEADERD的SizeOfRawData中保存);判断我们的入口点的RVA是否在某个节的范围内;最后,在某个节中用我们的RVA减去该节的起始RVA再加上该节在文件中所处的文件偏移即可得到。方法看起来复杂,其实实现起来并不怎么复杂,里面涉及到的还是PE文件头的那些个结构。我们依然先看代码实现。
for(i=0;i<pNtHeader->FileHeader.NumberOfSections;i++)
{
if((dwOEP>=(pSecHeader[i].VirtualAddress))&&(dwOEP<(pSecHeader[i].VirtualAddress+pSecHeader[i].SizeOfRawData)))
{
FileOffSet=dwOEP-pSecHeader[i].VirtualAddress+pSecHeader[i].PointerToRawData;
break;
}
}
代码中的FileOffSet就保存了我们的文件偏移地址。这样的话,我们就从得到的文件偏移地址处开始读取我们的特征码吧。我选用的读取特征码的程序,还是我使用的那个例子程序。读取特征码的方法就不多说了,更简单了,直接看代码。
hFile=CreateFile(FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,
SetFilePointer(hFile,FileOffSet,NULL,FILE_BEGIN);
ReadFile(hFile,buffer,15,(unsigned long *)a,NULL);
因为ASPack v2.12的特征码一共15位,所以ReadFile()函数读15位就可以了。读出特征码后,最后一步就是比较了。
for(int i=0;i<16;i++)
{
if(buffer[i]!=code[i])
{
lstrcpy(types,"No found");
break;
}
else if(i==15)
{
lstrcpy(types,"Aspack v2.12");
}
}
我给出的代码比较简陋,比较特征码的实现也不是很好,但用strcmp之类的函数比较的话恐怕实现不了,因为特征码有“00”这种字符。
好了,程序的整体就是这样了,测试结果如图3所示。是不是觉得我们的入口点、文件偏移跟PEiD的不一样呢?这是因为PEiD用的是 16进制,而我们的是10进制。值的大小其实没有什么不同,只是表示的方法不一样而已了。
图3
最后再补充一点,作为大家扩展性的知识来了解一下吧,毕竟我们这里也提到了特征码这个概念。我们还是先从“userdb.txt”中复制一段特征码出来看看。
[ASPack v1.07b]
signature = 90 75 ?? E9
ep_only = true
这个很显然是ASPack1.07的特征码了,但里面的“??”是什么意思呢?其实“??”是通配符。不管特征码是什么字符,只要跟“??”比较的话,都可以忽略不计。特征码中除了会出现“??”以外,还有“%”,是重复的意思,举个例子,比如“%3 45”的意思就是在接下来的3个位置中是“45”。这两个通配符可是杀毒软件中都用得到的哦
上一篇: 子网划分的两个例子