科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航



ZDNet>网络频道>ZD评测>杀毒软件技术之静态查毒引擎的实现

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

了躲避杀毒软件的查杀,电脑病毒开始进化。病毒为了躲避杀毒软件的查杀,逐渐演变为变形的形式,每感染一次,就对自身变一次形,通过对自身的变形来躲避查杀。

来源: 2007年08月30日

关键字:查毒 扫描 加壳 未知病毒 虚拟机 特征码 杀毒软件

 病毒与反病毒技术在很多朋友眼中总有一些神秘感。在接下来的几期文章中,我们会邀请Rising的技术专家向大家介绍关于杀毒软件所用到的方方面面的技术,以求使大家更好的明白杀毒软件到底是如何工作的,同时使大家了解到杀毒技术是如何随着病毒技术的日益变化逐步发展到今天的。在这个过程中,我们会写一系列代码来演示相关的技术,虽然简单,但那可是一个真实引擎的原型。

  病毒大致分为以下几类:传统病毒,宏病毒,恶意脚本,木马、黑客、蠕虫、破坏性程序。

  病毒与引擎的变迁

  简单特征码

  80 年代末期,基于个人电脑病毒的诞生,随即就有了清除病毒的工具──反病毒软件。这一时期,病毒所使用的技术还比较简单,从

而检测相对容易,最广泛使用的就是特征码匹配的方法。

  特征码是什么呢?比如说,“如果在第1034字节处是下面的内容:0xec , 0x99, 0x80,0x99,就表示是大麻病毒。”这就是特征码,一

串表明病毒自身特征的十六进制的字串。特征码一般都选得很长,有时可达数十字节,一般也会选取多个,以保证正确判断。杀毒软件通

过利用特征串,可以非常容易的查出病毒。

  广谱特征

  为了躲避杀毒软件的查杀,电脑病毒开始进化。病毒为了躲避杀毒软件的查杀,逐渐演变为变形的形式,每感染一次,就对自身变一

次形,通过对自身的变形来躲避查杀。这样一来,同一种病毒的变种病毒大量增加,甚至可以到达天文数字的量级。大量的变形病毒不同形态之间甚至可以做到没有超过三个连续字节是相同的。

  为了对付这种情况,首先特征码的获取不可能再是简单的取出一段代码来,而是分段的,中间可以包含任意的内容(也就是增加了一些不参加比较的“掩码字节”,在出现“掩码字节” 的地方,出现什么内容都不参加比较)。这就是曾经提出的广谱特征码的概念。这个技术

在一段时间内,对于处理某些变形的病毒提供了一种方法,但是也使误报率大大增加,所以采用广谱特征码的技术目前已不能有效的对新病毒进行查杀,并且还可能把正规程序当作病毒误报给用户。

  1、传统病毒:能够感染的程序。通过改变文件或者其他东西进行传播,通常有感染可执行文件的文件型病毒和感染引导扇区的引导型病毒;

  2、宏病毒(Macro):利用Word、Excel 等的宏脚本功能进行传播的病毒;

  3、恶意脚本(Script):做破坏的脚本程序。包括HTML 脚本、批处理脚本、VB 、JS 脚本等;

  4、木马(Trojan)程序:当病毒程序被激活或启动后用户无法终止其运行。广义上说,所有的网络服务程序都是木马,判定是否是木马病毒的标准不好确定,通常的标准是:在用户不知情的情况下安装,隐藏在后台,服务器端一般没有界面无法配置;

  5、黑客(Hack) 程序:利用网络来攻击其他计算机的网络工具,被运行或激活后就象其他正常程序一样有界面;黑客程序是用来攻击/破坏别人的计算机,对使用者本身的机器没有损害;

  6、蠕虫(Worm)程序:蠕虫病毒是一种可以利用操作系统的漏洞、电子邮件、P2P软件等自动传播自身的病毒;

  7、破坏性程序(Harm):病毒启动后,破坏用户计算机系统,如删除文件,格式化硬盘等。常见的是bat 文件,也有一些是可执行文件,有一部分和恶意网页结合使用。

  启发式扫描

  为了对付病毒的不断变化和对未知病毒的研究,启发式扫描方式出现了。启发式扫描是通过分析指令出现的顺序,或特定组合情况等常见病毒的标准特征来决定文件是否感染未知病毒。因为病毒要达到感染和破坏的目的,通常的行为都会有一定的特征,例如非常规读写文件,终结自身,非常规切入零环等等。所以可以根据扫描特定的行为或多种行为的组合来判断一个程序是否是病毒。

  行为判定

  针对变形病毒、未知病毒等复杂的病毒情况,极少数杀毒软件采用了虚拟机技术,达到了对未知病毒良好的查杀效果。它实际上是一种可控的,由软件模拟出来的程序虚拟运行环境,就像我们看的电影《黑客帝国》一样。在这一环境中虚拟执行的程序,就像生活在母体

(Matrix)中的人,不论好坏,其一切行为都是受到建筑师(architect)控制的。虽然病毒通过各种方式来躲避杀毒软件,但是当它运行在虚拟机中时,它并不知道自己的一切行为都在被虚拟机所监控,所以当它在虚拟机中脱去伪装进行传染时,就会被虚拟机所发现,如此一来,利

用虚拟机技术就可以发现大部分的变形病毒和大量的未知病毒。

  引擎技术对比

  各种引擎技术相比,虚拟机就像是一个侦探,可以根据对人的行为识别犯罪活动;启发式扫描就像是警察,看你身上携带了枪支而怀疑你;广谱特征是拿着照片追查已知的罪犯,但是会注意是否带了假发或者墨镜来逃避检查;而特征码识别就只是通过对人的外貌来判断。

简单总结一下各种引擎技术的优缺点:

  解压缩与去壳

  病毒隐藏自身的方法还有加壳和压缩两种方法。加壳是通过一系列的数学运算,将可执行程序或动态链接文件的编码进行改变,以达到缩小程序体积或加密程序编码的目的。通常常见的加壳工具有UPX、ASPack 等。病毒通过使用不同种类或者版本的加壳软件,对自身进行加壳,使得杀毒软件无法发现真正的病毒体,以逃避查杀。并且由于加壳的工具种类很多,同一个工具也存在不同的版本。

  为了检测已加壳的病毒,就必须要针对不同种类不同版本的壳编写脱壳程序才可以发现壳内隐藏的真正病毒体。所以,杀毒软件对病毒的查杀能力也在一定程度上取决于他自身的脱壳能力。

  压缩是普通用户日常经常使用的减小文件体积的方法,常见的工具软件有WinZip、WinRAR等。病毒有时候会隐藏在压缩包内部,如果一个杀毒软件没有解压缩的能力就不可能查杀压缩包内的病毒。同时,如果杀毒软件不具备相 应格式的压缩能力,在查杀病毒后就不能复原压缩包,导致压缩包破坏。可见,杀毒软件要想做到对病毒的全面捕获与查杀,脱壳解压能力也是至关重要的。

  利用特征码技术的静态杀毒引擎

  特征码的选取

  在进入程序的详细讲解之前,先来讲一下对于病毒程序的特征码通常是如何选取出来的,以及特征码的结构是什么样子的。通常选择特征码是按照以下思路。

  1、获取一个病毒程序的长度,根据长度可以将文件分为几份,份数根据样本长度而定,可以是3~5 份,也可以更多。分成几段获取特征码的方法可以很大程度上避免采用单一特征码误报病毒现象的发生,也可以避免特征码过于集中造成的误报。

  2、每份中选取通常为16 或32 个字节长的特征串。

  在选取时,应该采取如下的原则:

  1、如果选出来的信息是通用信息,即很多文件该位置特征码技术速度快,准确率高不能对付变形病毒或加密病毒静态广谱特征扫描技术

可以检测部分变形病毒误报率高启发式扫描技术(静态扫描+ 未知特征)误报率低,能检测变形病毒和病毒变种对未知病毒的检测能力较低行为判定技术基于强大而完整的虚拟机技术能够对未知病毒进行判别,对标准病毒准确率高实现难度大,速度慢都是一样的信息,那么舍弃,调整偏移量后重新选取。

  2、如果选取出来的信息是全零的字节。那么也要调整偏移后重新选取。

  关键数据结构

  这一部分我们来解决病毒库的问题,顾名思义,这是一个记录病毒二进制特征的数据库。在本文这个例子中,我们使用如下的方法来记录每一个病毒:

  1、我们把一个病毒分成不同的块,在每一块中取一个特征,用一个名为VSIGNATURE 的结构(下面会详述)来描述;

  2、用一个名为VRECORD 的结构来把一个病毒的所有VSIGNATURE 组织在一起,这样就构成了对一个病毒的完整描述;

  3、把这些完整的描述用链表组织起来,就是我们的病毒数据库了。

  下面就来看看上面提到的两个重要结构VSIGNATURE和VRECORD。一个VSIGNATURE 是一个特征,很多个特征组成了一条病毒记录,也就是一个VRECORD。

typedef struct tagVSIGNATURE

{

BAV_SIGN_TYPE eType;

DWORD dwOffset;

DWORD dwSize;

BYTE Signature[MAX_SIGNATURE_LEN];

}VSIGNATURE,*PVSIGNATURE;

typedef struct tagVRECORD

{

int nSize;

DWORD dwVirusID;

DWORD dwSignCount;

PVSIGNATURE pVSing[8];

DWORD dwTreatCount;

PVTREATMENT pVTreat[8];

}VRECORD,*PVRECORD;

  VSIGNATURE结构是用于存放单一特征码的,其中的eType 成员变量是一个枚举结构,用来定义特征码的类型,这里演示工程里目前我们只定义了一种简单文件特征。dwOffset 成员存储该特征码的偏移量。dwSize 成员存储特征码的长度。字节型的Signature数组成员存放特征

码串,最大长度由 MAX_SIGNATURE_LEN 宏控制,目前为32 字节。

  VRECORD 结构用于存放病毒库中每个病毒记录的内容。其中nSize用于控制结构的版本,目前我们不用过多关心。dwVirusID成员指定病毒的ID 编号。dwSignCount成员存放特征码(VSIGNATURE)的段数,也就是对于该病毒取了多少段特征码。pVSing 数组成员存放每段特征码

的内容,这里用数组是为了演示方便,以后我们会改为可变长度的数据结构。dwTreatCount成员存放处理该病毒的方法数量。pVTreat数组成员存放处理该病毒的每种方法的内容。这两个成员要到我们增加杀毒方法的时候才会用到。

  下面来看一个具体的例子,eicar测试病毒的第一条特征是这样的:

{

BS_PHY_FILE, 0, 32,

0x58, 0x35, 0x4F, 0x21, 0x50, 0x25, 0x40, 0x41,

0x50, 0x5B, 0x34, 0x5C, 0x50, 0x5A, 0x58, 0x35,

0x34, 0x28, 0x50, 0x5E, 0x29, 0x37, 0x43, 0x43,

0x29, 0x37, 0x7D, 0x24, 0x45, 0x49, 0x43, 0x41,

}

  它表明这是一个简单文件特征,特征起始地址0,特征长度32,后面32 个字节是具体特征值。

  库与引擎类

  清楚了病毒库的构成后,接下来我们看一下具体的扫描方法。关键要把引擎、库与被扫描对象之间的基本关系和分工搞清楚。

  引擎(CEngine)负责被扫描对象的遍历,对应到目前版本的代码上,引擎遍历目录,将找到的文件生成被扫描对象(CScanObj)交给当前病毒库对象的Search()方法。而病毒数据库对象(CVirusDB)则负责在自己管理的库中搜索。为了演示程序的简单,这一版的病毒库没有从文件中加载,而是直接在CVirusDB::Load()中编码进去的。从整体上来看,搜索过程经历了下面3 个阶段:

  1. CEngine遍历目录,生成被扫描对象并把该对象传递到病毒数据库中m_pcVDB->Search(&cScanObj)

  2. 病毒数据库搜索自身的记录CVirusDB::Search(),并调用被扫描对象CFileObject 的Compare()方法进行匹配;

  3. 被扫描对象返回匹配结果,病毒库对象(CVirusDB)根据结果来决定是否是病毒,并返回病毒ID。

  下面就来仔细看看以上提到的每一个关键部分:

  首先是引擎的目录遍历。CEngine 类中DFS()函数是负责文件系统深度优先搜索的函数,其中以下一段就是产生一个对象,然后传递给CVirusDB::Search()方法来查毒:

{

m_cScanResults.dwObjCount++;

CFileObject cScanObj;

cScanObj.m_eObjType = BO_PHY_FILE;

cScanObj.m_strObjName = lpszPathName;

if( !cScanObj.Open() )

{

// TODO: show error here.

return;

}

DWORD dwVID = m_pcVDB->Search(&cScanObj);

if( dwVID )

{

PSCAN_RECORD pScanRecord = new SCAN_RECORD;

if(pScanRecord)

{

CFileObject* pScanObj = new

CFileObject(cScanObj);

pScanRecord->dwVirusID = dwVID;

pScanRecord->eResult =

BR_WITH_VIRUS;

pScanRecord->pScanObject= pScanObj;

pScanRecord->pNext =

m_cScanResults.pScanRecords;

m_cScanResults.pScanRecords = pScanRecord;

m_cScanResults.dwRecCount++;

}

}

cScanObj.Close();

}

  其次是病毒数据库的搜索工作。CVirusDB类中的Search函数是用于在病毒库中匹配特征的成员函数,内容如下:

DWORD CVirusDB::Search(CScanObject* pScanObj)

{

list::iterator iter = m_listVRecords.begin();

while(iter!=m_listVRecords.end())

{

PVRECORD pVRec = *iter;

ASSERT(pVRec);

if(pVRec)

{

bool bVirus = true;

for(unsigned int i=0; idwSignCount;

i++)

{

bVirus &= pScanObj->Compare

(pVRec->pVSing[i]->dwOffset,

pVRec->pVSing[i]->dwSize,

pVRec->pVSing[i]->Signature);

if(!bVirus) break;

}

// match all signatures

if(bVirus)

{

return pVRec->dwVirusID;

}

}

else

{

// error

return 0xFFFFFFFF;

}

iter++;

}

// no match record in VirusDB

return 0;

}

  While 循环是遍历本病毒库中所有的记录。在For循环中,根据指向VRECORD 结构的指针pVRec中dwSignCount 的内容可知特征码的数量。然后循环用pScanObj->Compare()函数比较每段特征码与文件中指定偏移处的内容是否一致,如果全部一致,则说明该文件是病毒。

  最后,被扫描对象进行特征串的匹配其实就是memcmp,这一步没有什么特别的。

  本次主要为大家介绍了根据特征码查病毒的方法,源代码可以在网站http://bav.netsv.org 下载。在真实的环境中,查毒引擎的设计要比这个例子复杂上很多,一般都有多个库,还利用了诸如脱壳,解压,虚拟机等等技术,这些我们将在日后的文章中逐渐介绍。

推广二维码
邮件订阅

如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

重磅专题