【簡要介紹】
~~~~~~~~~~~
好了,開始把你的大腦中的16位MS-DOS編碼概念,迷人的16位偏移地址,中斷,駐留記憶體的方法...都清除掉。所有這些我們已經使用了很多年的東西,現在已經再也不用了。是的,它們確實現在用不到了。在這篇教程裡面,當我說Win32,我的意思是Windows 95(normal,OSR1,OSR2),Windows 98,Windows NT或Windows 3.x+Win32s。最最明顯的變化,至少在我看來是由中斷變成了API,在這之前是由16位暫存器和偏移地址變到了32位的。Windows給我們開了使用其它語言代替ASM(和C)的方便之門,但是我仍然對ASM情有獨鍾:利用它能更好的理解一些東西和更容易的最佳化(hi Super!)。正如我在上面所說的,你必須使用一種新東西叫做API。你必須知道這些引數必須在堆疊中,而且呼叫這些API是使用的CALL。
注:在上面我把上面所有提到的平臺叫做Win32,我把Win95(它的所有版本)和Win98叫做Win9x,把Windows 2000叫做Win2k。請注意這一點。
%由16位到32位程式設計的改變%
~~~~~~~~~~~~~~~~~~~~~~~~
我們現在將會使用雙字(DWORD)而不是單字(WORD)了,而這個改變將會給我們一個全新的世界。在已知的CS,DS,ES和SS:FS,GS之外,我們又多了兩個段。而且我們有32位暫存器如EAX,EBX,ECX,EDX,ESI,EDI,EBP和ESP。讓我們來看看對這些暫存器怎麼操作:假如我們要使用EAX的less significant word(簡稱LSW),我們該怎麼做呢?這個部分可以使用AX來訪問,即處理LSW。假如EAX=0000000,我們想要在它的LSW放置1234h。我們必須簡單地使用一個"mov ax,1234h"就可以了。但是如果我們想要訪問EAX的MSW(Most Significant Word),該怎麼做呢?為了達到這個目的我們不能使用一個暫存器了:我們必須使用ROL。問題並不是在這裡,它是把MSW值移到了LSW。
當我們得到一個新語言的時候,我們總是要試的一個經典的例子:"Hello world!" :)
%Win32中的Hello World%
~~~~~~~~~~~~~~~~~~~~~~
它很簡單,我們必須使用"MessageBoxA"這個API,所以我們用大家已經知道的"extrn"命令來定義它,把引數壓棧然後呼叫這個API。注意這個字串必須為ASCIIZ(ASCII,0),記住引數必須以相反的順序壓棧。
;-------從這裡開始剪下----------------------------------------------------
.386 ; Processor (386+)
.model flat ; Uses 32 bit registers
extrn ExitProcess:proc ; The API it uses
extrn MessageBoxA:proc
;-
;利用"extrn"我們把在程式中要用到的所有API列出來。ExitProcess是我們用來把
;控制權交給作業系統的API,而MessageBoxA用來顯示一個經典的Windows訊息框。
;-
.data
szMessage db "Hello World!",0 ; Message for MsgBox
szTitle db "Win32 rocks!",0 ; Title of that MsgBox
;------------------------------------------------------------------------------------------------------------------------------
;這裡我們不能把真正病毒的資料放這裡了,因為這是一個例子,我們不能
;使用它,而且又因為如果我們不在這裡放置一些資料,TASM將會拒絕彙編。
;無論如何...在第一次產生你的病毒主體的時候用它放置資料。
;-
.code ; Here we go!
HelloWorld:
push 00000000h ; Sytle of MessageBox
push offset szTitle ; Title of MessageBox
push offset szMessage ; The message itself
push 00000000h ; Handle of owner
call MessageBoxA ; The API call itself
;------------------------------------------- ; int MessageBox(
; HWND hWnd, // handle of owner window
; LPCTSTR lpText, // address of text in message box
; LPCTSTR lpCaption, // address of title of message box
; UINT uType // style of message box
; );
;
;在呼叫這個API之前,我們把引數壓棧,如果你還記得,堆疊使用那個迷人的
;東西叫做LIFO(後進先出Last In First Out),所以我們要按相反的順序來
;壓引數。讓我們看看這個函式的每個引數的簡要描述:
;
; hWnd:標誌將要被建立的訊息框的宿主視窗(owner window)。
; 如果這個引數是NULL,這個訊息框沒有宿主視窗。
; lpText:指向以空字元結尾的包含將要顯示訊息的字串的指標。
; lpCaption:指向一個以空字元結尾的字串的指標,這個字串是這個
; 對話方塊的標題。如果這個引數是一個NULL,預設的標題Error被使用。
; uType:以一些位標誌來確定對話方塊的樣式和行為。這個引數可為一些標誌的組合。
;-
push 00000000h
call ExitProcess
;--------------------------------------------------------------------------------------------------------------------
; VOID ExitProcess(
; UINT uExitCode // exit code for all threads
; );
; 這個函式在Win32環境下相當於著名的Int 20h,和Int 21h的00,4C功能等等。
; 它是關閉當前程式的簡單方式,即結束程式執行。下面給出唯一的一個引數:
;
; uExitCode:標誌程式退出的程式碼,並作為所有執行緒終止時的程式碼。使用
; GetExitCodeProcess函式來重新整理這個程式的退出值。使用GetExitCodeThread
; 函式來重新整理一個執行緒的退出值。
;-
end HelloWorld
;-----到這裡為止剪下-------------------------------------------------------
正如你看到的,編寫程式碼很簡單。可能沒有16位環境下那麼簡單,但是如果你考慮到32位所帶給我們的優點確實很簡單了。現在,既然你已經知道怎麼來編寫"Hello World",你就有能力來感染整個世界了;)
%Rings%
~~~~~~~
我知道你對下面的東西很害怕,但是,正如我所演示的,它看起來沒有那麼難。讓我們記住你必須清楚的東西:處理器有4個特權級別:Ring-0,Ring-1,Ring-2和Ring-3,越往後就有越多的限制,而病毒要是用第一個特權級別,幾乎編碼時沒有任何限制。只要記住在迷人的DOS下面,我們總是處於Ring-0...現在想到在Win32平臺下你還可以做相同的事情...好了,停止幻想了,讓我們開始工作。
Ring-3還表示"使用者"級,在這個級別下,我們有很多的限制,那確實不能我們的需要。Microsoft程式設計師在他們發行Win95的時候犯了個錯誤,聲稱它是"無法感染"的,正如在這個作業系統賣出去之前所表明的,利用可怕的Bizatch(後來改名為Boza,但那是另外一段歷史了)。他們認為這些API不能被一個病毒訪問和使用,但是他們沒有想到病毒編寫者們的超級智慧,所以...我們可以在使用者級下編寫病毒,毫無疑問,你只要看看大量近期釋出的新的Win32執行期病毒,它們都是Ring-3級下的...它們不差,不要誤解了我,Ring-3病毒是現在有可能感染所有Win32環境下檔案的病毒。它們是未來...主要是因為即將釋出的Windows NT 5.0(或者Windows 2000)。我們不得不尋找能使我們的病毒(由Bizatch生成的病毒傳播很差,因為它對API地址"harcoded",並且它們可能會因Windows版本的改變而改變)存活的API,而且我們可以用其它不同的方法來實現,正如我後面解釋到的。
Ring-0是另外一段歷史了,和Ring-3有著很大的區別。在這個級別下我們擁有核心編碼的級別,"核心(kernel)級別"。是不是很迷人啊?我們可以訪問埠,放置我們還沒有夢想過的程式碼...和原先的彙編最接近的東西。我們不使用一些已知的花招是不能直接訪問一些東西的,如IDT修改,SoPinKy/29A在29A#3裡發表的"Call Gate"技術,或者VMM插入,在Padania或者Fuck Harry病毒裡見到的技術。當我們直接利用VxD的服務時,我們不需要API,而且它們的地址在所有Win9x系統中是被假設為相同的,所以我們"hardcode"它們。我將在fully dedicated to Ring-0這一章裡面深入討論。
%重要的東西%
~~~~~~~~~~~~
我想無論如何我應該在這篇教程的開頭放上這些,然而我知道知道總比不知道好啊:)好了,讓我們來討論Win32作業系統內部的東西。
首先,你必須清楚一些概念。讓我們從selector開始。什麼是一個selector呢?相當簡單,它是一個非常大的段,而且它組成了Win32的記憶體,也叫做平坦記憶體。我們可以用4G記憶體(4,294,967,295位元組),僅僅透過使用32位地址。那所有這些記憶體是怎麼組織的呢?看看下面的示意圖:
__________________________
| |<-------OFFSET=00000000h <-> 3FFFFFFFh
| 應用程式程式碼和資料 |
|__________________________|
| |<-------OFFSET=40000000h <-> 7FFFFFFFh
| 共享記憶體 |
|__________________________|
| |<-------OFFSET=80000000h <-> BFFFFFFFh
| 核心 |
|__________________________|
| |<-------OFFSET=C0000000h <-> FFFFFFFFh
| 裝置驅動 |
|__________________________|
結果:我們擁有4G可用記憶體。是不是很迷人啊?
注意一件事情:WinNT的後兩段是分開的。現在我將給出你必須知道的一些定義,其它的一些本文之外的一些概念,我假設你已經知道了。
VA:
VA表示Virtual Address,即某些程式的地址,但是在記憶體中(記住在Windows中在記憶體中和在磁碟上是不一樣的)。
RVA:
RVA表示Relative Virtual Address。清楚這個概念很重要,RVA是檔案在記憶體對映(由你或由系統)時的偏移地址。
RAW Data:
RAW Data是我們用來表示資料物理的儲存的,也就是說,在磁碟(磁碟上的資料!=記憶體中的資料)上的儲存。
Virtual Data:
Virtual Data是指那些已經被系統載入記憶體的資料。
File Mapping:
一種技術,在所有的Win32環境下都有,由一種快速的(並使用更少記憶體)檔案操作方法和比DOS更容易理解的方法組成。所有我們在記憶體中改變的東西,在磁碟上也會改變。檔案對映還是所有Win32環境(甚至NT)下記憶體之間交換資訊的唯一方法。
%怎麼來編譯東西%
~~~~~~~~~~~~~~~~
該死,我幾乎忘記了這個:)編譯一個Win32 ASM程式的通常引數是,至少在這篇教程的所有例子中,按如下(當ASM檔案的名字為'program',但是沒有任何副檔名):
tasm32 /m3 /ml program,,;
tlink32 /Tpe /aa program,program,,import32.lib
pewrsec program.exe
我希望足夠清晰了。你還可以使用makefiles,或者建立一個bat檔案來使它自動完成(就象我做的!)。