淺析防毒軟體開發原理(之查毒引擎)

成霸天發表於2013-10-10
        1994年2月18日,我國正式頒佈實施了《中華人民共和國計算機資訊系統安全保護條例》,在《條例》第二十八條中明確指出:“計算機病毒,是指編制或者在計算機程式中插入的破壞計算機功能或者毀壞資料,影響計算機使用,並能自我複製的一組計算機指令或者程式程式碼。”這是我國對於計算機病毒的正式定義,但是在實際中,所有會對使用者的電腦保安產生威脅的,都被劃入了廣義的病毒範疇。


        病毒大致分為以下幾類:傳統病毒,巨集病毒,惡意指令碼,木馬、黑客、蠕蟲、破壞性程式。

        1. 傳統病毒:能夠感染的程式。通過改變檔案或者其他東西進行傳播,通常有感染可執行檔案的檔案型病毒和感染引導扇區的引導型病毒;

        2. 巨集病毒(Macro):利用Word、Excel等的巨集指令碼功能進行傳播的病毒;

        3. 惡意指令碼(Script):做破壞的指令碼程式。包括HTML指令碼、批處理指令碼、VB、JS指令碼等;

        4. 木馬(Trojan)程式:當病毒程式被啟用或啟動後使用者無法終止其執行。廣義上說,所有的網路服務程式都是木馬,判定是否是木馬病毒的標準不好確定,通常的標準是:在使用者不知情的情況下安裝,隱藏在後臺,伺服器端一般沒有介面無法配置;

        5. 黑客(Hack) 程式:利用網路來攻擊其他計算機的網路工具,被執行或啟用後就象其他正常程式一樣有介面;黑客程式是用來攻擊/破壞別人的計算機,對使用者本身的機器沒有損害;

        6. 蠕蟲(Worm)程式:蠕蟲病毒是一種可以利用作業系統的漏洞、電子郵件、P2P軟體等自動傳播自身的病毒;

        7. 破壞性程式(Harm):病毒啟動後,破壞使用者計算機系統,如刪除檔案,格式化硬碟等。常見的是bat檔案,也有一些是可執行檔案,有一部分和惡意網頁結合使用。

        病毒與引擎的變遷

        簡單特徵碼

        80年代末期,基於個人電腦病毒的誕生,隨即就有了清除病毒的工具──反病毒軟體。這一時期,病毒所使用的技術還比較簡單,從而檢測相對容易,最廣泛使用的就是特徵碼匹配的方法。

        特徵碼是什麼呢?比如說,“如果在第1034位元組處是下面的內容:0xec , 0x99, 0x80,0x99,就表示是大麻病毒。”這就是特徵碼,一串表明病毒自身特徵的十六進位制的字串。特徵碼一般都選得很長,有時可達數十位元組,一般也會選取多個,以保證正確判斷。防毒軟體通過利用特徵串,可以非常容易的查出病毒。

        廣譜特徵

        為了躲避防毒軟體的查殺,電腦病毒開始進化。病毒為了躲避防毒軟體的查殺,逐漸演變為變形的形式,每感染一次,就對自身變一次形,通過對自身的變形來躲避查殺。這樣一來,同一種病毒的變種病毒大量增加,甚至可以到達天文數字的量級。大量的變形病毒不同形態之間甚至可以做到沒有超過三個連續位元組是相同的。

        為了對付這種情況,首先特徵碼的獲取不可能再是簡單的取出一段程式碼來,而是分段的,中間可以包含任意的內容(也就是增加了一些不參加比較的“掩碼位元組”,在出現“掩碼位元組”的地方,出現什麼內容都不參加比較)。這就是曾經提出的廣譜特徵碼的概念。這個技術在一段時間內,對於處理某些變形的病毒提供了一種方法,但是也使誤報率大大增加,所以採用廣譜特徵碼的技術目前已不能有效的對新病毒進行查殺,並且還可能把正規程式當作病毒誤報給使用者。

        啟發式掃描

        為了對付病毒的不斷變化和對未知病毒的研究,啟發式掃描方式出現了。啟發式掃描是通過分析指令出現的順序,或特定組合情況等常見病毒的標準特徵來決定檔案是否感染未知病毒。因為病毒要達到感染和破壞的目的,通常的行為都會有一定的特徵,例如非常規讀寫檔案,終結自身,非常規切入零環等等。所以可以根據掃描特定的行為或多種行為的組合來判斷一個程式是否是病毒。

        這種啟發式掃描比起靜態的特徵碼掃描要先進的多,可以達到一定的未知病毒處理能力,但還是會有不準確的時候。特別是因為無法確定一定是病毒,而不可能做未知病毒防毒。

        行為判定

        針對變形病毒、未知病毒等複雜的病毒情況,極少數防毒軟體採用了虛擬機器技術,達到了對未知病毒良好的查殺效果。它實際上是一種可控的,由軟體模擬出來的程式虛擬執行環境,就像我們看的電影《黑客帝國》一樣。在這一環境中虛擬執行的程式,就像生活在母體(Matrix)中的人,不論好壞,其一切行為都是受到建築師(architect)控制的。雖然病毒通過各種方式來躲避防毒軟體,但是當它執行在虛擬機器中時,它並不知道自己的一切行為都在被虛擬機器所監控,所以當它在虛擬機器中脫去偽裝進行傳染時,就會被虛擬機器所發現,如此一來,利用虛擬機器技術就可以發現大部分的變形病毒和大量的未知病毒。

        引擎技術對比

        各種引擎技術相比,虛擬機器就像是一個偵探,可以根據對人的行為識別犯罪活動;啟發式掃描就像是警察,看你身上攜帶了槍支而懷疑你;廣譜特徵是拿著照片追查已知的罪犯,但是會注意是否帶了假髮或者墨鏡來逃避檢查;而特徵碼識別就只是通過對人的外貌來判斷。

        簡單總結一下各種引擎技術的優缺點: 特徵碼技術 靜態廣譜特徵掃描技術 啟發式掃描技術(靜態掃描+未知特徵) 行為判定技術 速度快,準確率高 可以檢測部分變形病毒 誤報率低,能檢測變形病毒和病毒變種 基於強大而完整的虛擬機器技術能夠對未知病毒進行判別,對標準病毒準確率高 不能對付變形病毒或加密病毒 誤報率高 對未知病毒的檢測能力較低 實現難度大,速度慢 
        解壓縮與去殼

        病毒隱藏自身的方法還有加殼和壓縮兩種方法。加殼是通過一系列的數學運算,將可執行程式或動態連結檔案的編碼進行改變,以達到縮小程式體積或加密程式編碼的目的。通常常見的加殼工具有UPX、ASPack等。病毒通過使用不同種類或者版本的加殼軟體,對自身進行加殼,使得防毒軟體無法發現真正的病毒體,以逃避查殺。並且由於加殼的工具種類很多,同一個工具也存在不同的版本。為了檢測已加殼的病毒,就必須要針對不同種類不同版本的殼編寫脫殼程式才可以發現殼內隱藏的真正病毒體。所以,防毒軟體對病毒的查殺能力也在一定程度上取決於他自身的脫殼能力。

        壓縮是普通使用者日常經常使用的減小檔案體積的方法,常見的工具軟體有WinZip、WinRAR等。病毒有時候會隱藏在壓縮包內部,如果一個防毒軟體沒有解壓縮的能力就不可能查殺壓縮包內的病毒。同時,如果防毒軟體不具備相應格式的壓縮能力,在查殺病毒後就不能復原壓縮包,導致壓縮包破壞。可見,防毒軟體要想做到對病毒的全面捕獲與查殺,脫殼解壓能力也是至關重要的。

        利用特徵碼技術的靜態防毒引擎

        特徵碼的選取

        在進入程式的詳細講解之前,先來講一下對於病毒程式的特徵碼通常是如何選取出來的,以及特徵碼的結構是什麼樣子的。

        通常選擇特徵碼是按照以下思路:

        1. 獲取一個病毒程式的長度,根據長度可以將檔案分為幾份,份數根據樣本長度而定,可以是3~5份,也可以更多。分成幾段獲取特徵碼的方法可以很大程度上避免採用單一特徵碼誤報病毒現象的發生,也可以避免特徵碼過於集中造成的誤報。

        2. 每份中選取通常為16或32個位元組長的特徵串。

        在選取時,應該採取如下的原則:

        1. 如果選出來的資訊是通用資訊,即很多檔案該位置都是一樣的資訊,那麼捨棄,調整偏移量後重新選取。

        2. 如果選取出來的資訊是全零的位元組。那麼也要調整偏移後重新選取。

        當然調整的偏移量多少可以人為事先規定,也可以自動隨機調節。最後,將選取出來的幾段特徵碼及它們的偏移量存入病毒庫,標示出病毒的名稱即可。為了方便選取特徵碼,通常根據以上的思路編寫出特徵碼提取程式,自動提取特徵碼並作為病毒記錄存入病毒庫。

        關鍵資料結構

        下面我們來介紹一下整個引擎中關於特徵碼掃描部分的程式碼,整個引擎框架的結構由於篇幅有限,放在下期文章中進行介紹。

        首先介紹程式中的兩個重要結構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陣列成員存放處理該病毒的每種方法的內容。這兩個成員要到我們增加防毒方法的時候才會用到。

        為了演示程式的簡單,這一版的病毒庫沒有從檔案載入,而是直接在CVirusDB::Load()中編碼進去的。比如第一個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個位元組是具體特徵值。

        與引擎類

        清楚了這兩個結構,接下來我們看一下具體的掃描程式碼。特徵串的匹配其實就是memcmp,沒有什麼特別的,關鍵講解一下引擎、庫與被掃描物件之間的基本關係和分工。

        引擎(CEngine)負責被掃描物件的遍歷,病毒資料庫物件(CVirusDB)負責在自己管理的庫中搜尋。對應到目前版本的程式碼上,引擎遍歷目錄,將找到的檔案生成被掃描物件(CScanObj)交給當前病毒庫物件的Search()方法。

        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()函式比較每段特徵碼與檔案中指定偏移處的內容是否一致,如果全部一致,則說明該檔案是病毒。

        本次主要為大家介紹了根據特徵碼查病毒的方法。在真實的環境中,查毒引擎的設計要比這個例子複雜上很多,利用了諸如脫殼,解壓,虛擬機器等等技術,這些我們將在日後的文章中逐漸介紹。

相關文章