根据PE 结构取特征码 在
上一期文章中我们说过,特征码的提取是将文件划分为不同的部分,然后从每部分中提取一定长度的内容作为特征码。这样提取特征码的方法存在着一个问题就是很多病毒的特征码有类似的部分,例如我们讨论的PE结构,很多PE文件开头部分有很大一部分是相同的,所以按照等分划分文件的方法来提取特征码是不理想的。这时我们考虑到了利用PE结构,从每个节中提取一定的内容作为特征码,或者以各种关键点为参照,在附近找特征码。这样一来,可以大大避免了上面所提到的等分划分文件提取特征码方法的弊端,增强了不同病毒间特征码的差异性。例如本次对于CIH 病毒的检测,就检查了PE Header 附近和入口点附近的特征。
杀毒引擎的架构 上期文章中,由于篇幅所限,并没有向大家介绍整个杀毒引擎的架构,只是详细讲解了关于查毒的部分代码片段。本期前面我们已经为大家讲解了PE 文件的格式等内容,所以为了能使大家对实际的杀毒引擎的工作有感性的认识,本次对于上次的代码进行了修改,加入了CIH 病毒的查毒功能,同时也增加了相关的操作内存扫描对象的类以及PE文件结构分析的类。可以说这次的程序已经很像一个真正的杀毒软件中所用的引擎了。下面就给大家详细的说明一下整个杀毒引擎的架构。
| 类名 |
实现功能 |
| CEngine |
引擎的主要类,实现杀毒主流程的控制。文件遍历,创建被查杀对象CScanObject,调用病毒库对象CVirusDB来检测,并将结果记录下来。 |
| CFileObject |
派生于CScanObject 对象,实现CScanObject 中定义的虚函数,用于物理文件的相关操作。 |
| CMemFileObject |
派生于CScanObject 对象,实现CScanObject 中定义的虚函数,用于内存文件对象的相关操作。 |
| CParsePE |
根据传入的CMemFileObject 对象参数,实现对PE文件的确定以及PE 格式的分析, |
| CScanObject |
扫描对象基类,定义扫描病毒时需要用到的对各种病毒对象操作方法,但仅仅虚函数,真正的功能需要在派生于它的对象中进行实现。 |
| CVirusDB |
实现病毒库的加载和卸载操作,主要暴露了Search()方法,用于将扫描对象在本病毒库中检测。调用扫描对象里的Compare()方法进行特征码匹配法。 |
| CVirusInfo |
根据病毒ID获取病毒相关信息,由于用于演示,故只能返回病毒的名字。 |
根据面向对象的设计方法,我们把整个杀毒引擎划分为不同的类。每个类对应的功能如下表所示那么,为什么需要定义这些类呢。这就要从我们拿到一个病毒时如何分析着手。首先查毒前提是我们要有一个已知病毒的特征库,其中存放了我们所有可以查得到的病毒的特征码。这样我们在遇见病毒时,才可以根据病毒库中的特征码分析这个文件(当然病毒有可能在内存中或者其他地方,这里只是举例)是否是病毒。所以我们定义了CVirusDB 类。
随后我们就要对这个病毒进行分析,这个病毒有可能是磁盘上的某个文件,也有可能是邮箱中的一封信,也有可能是在内存中,对于不同的位置,就需要有不同的操作方法。这就是CScanObject 及其各个派生类(CFileObject,CMemFileObject)的由来,以后我们可能还会需要实现更多地派生类用于处理不同的对象,到时候只需要实现相应的方法,而不需要影响整个引擎的框架。
在对病毒进行详细的分析时,就需要对格式进行分析。例如PE 格式分析,我们就要利用前面讲过的PE文件相关结构对其进行拆解。定义CParsePE的用途就是分析PE格式文件。我们以后还可以定义CParseOLE 等针对不同格式的分析器。在确定了一个病毒后,为了将病毒信息告知用户,又定义了CVirusInfo 对象去进行与病毒信息相关的操作。
最后,对以上这些类进行统一的调度,决定何时应该创建或销毁某些对象等操作都交于CEngine 处理。并且CEngine类还实现了遍历被扫描对象以及与主程序传送扫描结果的功能。以便主程序可以最终生成病毒扫描结果反馈给用户。所以,整个项目的架构我们就使按照对这种流程的分析进行构建的。
代码的变更 通过前面总体的对各个类的功能以及病毒分析流程的概述,相信大家已经对我们的查毒引擎的架构有了一个整体的理解。但可能对于新增加的类以及PE分析部分的详细代码还不是很熟悉。同时由于本次对上次的代码进行了一定程度的修改,以适应功能的增加,所以某些重要部分也需要向大家解释一下。
首先,先要说一下这次新增的一些重要结构体,其中最主要的就是FSPE
typedef struct tagFSPE { // control the struct version INT nSize; INT m_nSectionCount; INT m_nImportCount; bool m_bMZFile; bool m_bPEFile; PIMAGE_DOS_HEADER m_pImageDosHeader; PIMAGE_FILE_HEADER m_pFileHeader; PIMAGE_OPTIONAL_HEADER32 m_pOptionalHeader; PIMAGE_SECTION_HEADER m_aSectionHeaders[MAX_SECTIONS]; PIMAGE_NT_HEADERS m_pNtHeaders; PIMAGE_IMPORT_DESCRIPTOR m_aImportDescriptors[MAX_IMPORTS]; PIMAGE_EXPORT_DIRECTORY m_pExportDirectory; PIMAGE_RESOURCE_DIRECTORY m_pResourceDirectory; // common use LPBYTE m_pEntryPoint; }FSPE, *PFSPE; |
这个结构用于存储PE结构的主要信息,供之后的分析使用。
其次就是我们新增的CParsePE类,这个类目前仅有两个方法,分别是:
bool CParsePE::BasicParse(IN CMemFileObject* pScanObj, OUT FSPE* pFSPE); DWORD CParsePE::AddrM2F(IN FSPE* pFSPE, IN LPVOID lpMemAddr); |
BasicParse主要实现将一个内存中执行对象的PE结构进行分析存放于传入F S P E 结构体指针所指的变量中。而AddrM2F 主要实现的是虚拟地址到文件地址的转换,这个函数目前实现的还不太完善,有一些假设在极端的情况下是不正确的,我们会在以后改进。我们还从CScanObject 类派生了内存影射文件对象CMemFileObject,这个类实现了每一个CScanObject 中定义的虚函数,操作内存影射文件,同
时新增了自身的LPBYTE CMemFileObject::GetBuffer()方法,以便返回内存指针。
这次有一个重要的改进,我们扩展了特征码的类型BAV_SIGN_TYPE。上次只有BS_PHY_FILE,也就是纯文件偏移,这次增加了BS_STRUCT_OFFSET,就是以某个结构的参考点偏移,实现了两个方法BS_SUB_NT_HEADERS和BS_SUB_ENTRY_POINT。BS_SUB_NT_HEADERS 是以PE
Header为参考点,BS_SUB_ENTRY_POINT是以入口点为参考点。
在VirusDB 类的Search()方法中可以看到新增了的对于BS_STRUCT_OFFSET类型病毒特征的检查。这样就可以实现我们前面说过的对CIH 病毒的检查了。增加的代码如下:
case BS_STRUCT_OFFSET: { // determine which parser we need if(pVSing->dwSubType>=BS_SUB_PE_BEGIN && pVSing->dwSubType<=BS_SUB_PE_END) { // PE parser need BO_MEM_FILE object if(pScanObj->GetObjType()!=BO_MEM_FILE) { ASSERT(false); bVirus = false; } FSPE stFSPE; CParsePE cParser; if( cParser.BasicParse((CMemFileObject*)pScanObj, &stFSPE) ) { switch(pVSing->dwSubType) { case BS_SUB_NT_HEADERS: bVirus&=pScanObj-> Compare((LPBYTE)stFSPE.m_pNtHeaders+ pVSing->nOffset,pVSing->nSize,pVSing->Signature, pVSing->eLogicOp); break; case BS_SUB_ENTRY_POINT: bVirus&=pScanObj->Compare((LPBYTE)stFSPE.m_pEntryPoint+pVSing->nOffset, pVSing->nSize, pVSing->Signature, pVSing->eLogicOp); break; default: bVirus = false; ASSERT(false); } } else { bVirus = false; } } else { ASSERT(false); } } |
CScanObject 及其继承类的Compare()方法进行了增加,增加了逻辑操作参数(BAV_SIGN_LOGIC_OPERATION)。也就是不仅仅可以判断相等(BL_EQUAL),还可以判断不等(BL_NOT_EQUAL),以后还可以扩展更多地逻辑比较参数。其他一些细小的变动,在此就不占用篇幅赘述了,读者可以参考代码进行分析。
CIH 的识别 这次增加了一个真正的病毒的识别,也给大家一个信心,这个演示的引擎离真实的情况也并不遥远。CIH的原理在网上有很多介绍,源代码也可以下载,我在这里就不介绍了。CIH 的识别我们取了3 个特征。首先是在PE Header前一个字节如果非0,就有可能感染了,CIH 自己
也利用了这个来判断。但是这个特征是不一定可靠的,没有感染CIH病毒的程序这个地方也可能因为各种原因变成非0,所以我们还加了两条代码特征。CIH会改变代码入口,指向自己,根据这个,我们取了入口点偏移特征,将sidt动作和后面挂文件系统钩子两个动作作为了特征,这样就比较可靠了。当然,这3个特征都集中在病毒头部,如果要更可靠,避免家族内的误报,还可以增加一些病毒体后面的代码。