yuansunxue 发表于 2012-5-25 00:11

一次virut分析之旅

本帖最后由 yuansunxue 于 2012-5-25 00:14 编辑

基本信息

报告名称:Virut分析报告                                                   
作者:yuansunxue                                                            
报告更新日期:2012 5 24                                       
样本发现日期:09年                                          
样本类型:      virus                                             
样本文件大小/被感染文件变化长度:22528 感染长度是变化的   
样本文件MD5 校验值:2da42b41b25566ba6186751bc216e498                           
样本文件SHA1 校验值:df2dc50d3d6d481cde483df26e61a2ed350759687eca0c5b4a9d53cc225e5722                           
壳信息:no                                                      
可能受到威胁的系统:32位
相关漏洞:no                                                   
已知检测名称:virut                                          


简介

这个不用介绍了


被感染系统及网络症状


文件系统变化
.exe .scr

注册表变化

添加
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List
键为: 文件路径
值为: 文件路径:*:enabled:@shell32.dll,-1

SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer 键为UpdateHost 值为端口和主机IP的二进制值 长度为6 加密的


网络症状

连接到IRC server ilo.brenz.pl ant.trenz.pl


详细分析/功能介绍

当病毒被运行后,会进行如下操作:



1.   注入恶意代码除了进程快照前4个的进程
       注入的恶意代码会:
       设置如下注册表键值 添加到防火墙白名单

      HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List
      键为: 文件路径
      值为: 文件路径:*:enabled:@shell32.dll,-1
       连接irc server:

    ilo.brenz.pl
    ant.trenz.pl
       接收来自server的命令:
      下载文件执行
      更新server地址

2.   hook ZwCreateFile ZwOpenFile 感染其操作的.exe .scr文件


3.   hook ZwDeviceIoControlFile 屏蔽含有如下关键字的upd包

$eset
#avg
microsoft
windowsupdate
wilderssecurity
threatexpert
castlecops
spamhaus
cpsecure
arcabit
emsisoft
sunbelt
securecomputing
rising
prevx
pctools
norman
k7computing
ikarus
hauri
hacksoft
gdata
fortinet
ewido
clamav
comodo
quickheal
avira
avast
esafe
ahnlab
centralcommand
drweb
grisoft
nod32
f-prot
jotti
kaspersky
f-secure
computerassociates
networkassociates
etrust
panda
sophos
trendmicro
mcafee
norton
symantec
defender
rootkit
malware
spyware
virus

4.    hook ZwCreateProcess ZwCreateProcessEx向其创建的进程注入恶意代码



技术热点及总结


上面的行为都是老生常谈 对于virut基本上都差不多


virut的精华在于其多态变形的过程

主要在这里说一下virut的感染以及变形过程

文中的一些结构参考了徐大力的文章 virut分析
在此表示感谢

一、前期判断

主要在 hook ZwCreateFile ZwOpenFile的过程中virut会获取其操作的文件进行感染
感染后缀为exe,scr的

文件开头是 WINC,WCUN,WC32,PSTO不感染

二、一些介绍

首先说下控制很多行为的dword

该样本的值为 0x1b0

命名为:control_flag

0x1决定感染方式 为1 patch api调用 为0 直接修改入口
    patch api从入口开始搜索 e8 然后去call的目的地址判断吧是否是 ff25

    或者直接搜索 ff15 而且api是由kernel32导出的 这是因为感染后的程序要用到这个来获取其kernel32的基址
0x100 决定变形从哪个块开始 为0x100 表示从最后一个快开始 否则随机选取

0x2 决定了是否需要在入口代码节剩余空间加入解密添加的病毒代码 这里添加了解密代码就要在后面对添加的代码进行加密

0x4 如果入口节有足够的空间插入代码 插入的代码会对添加的病毒代码进行解密 则这里控制了解密的方式 是加还是减

0x20 决定是否要hook ZwDeviceIoControlFile0x20 hook

0x40 决定了是否插入花指令 如果0x40不插

0x80 决定是否恢复ssdt 为0x80恢复

0x10 决定是否在移动硬盘写入autorun.inf



我理解virut变形主要的思想为:
将有用的指令放到一片片花指令块中,然后用jmp将这些指令块连接在一起 指令的执行顺序是不会变的



virut 代码大致可以分为2部分 header和body
它的变种不少 但基本上只是 header变化body功能大致不变
header主要负责前期一些工作,比如获取kernel32基址 获取相关api 检测调试器
创建event或者Semaphore 解密body等


感染的过程中 header 多态变形 body通过存放在header中的随机key加密的
运行的时候会被header解密出来 对应的加密idapytho我会放到附件里

在感染前 virut 会自己把 变形后的header还原

还原时 virut依靠两个结构BLOCK_Info和RELOC_Info来操作的

BLOCK_Info   
org_NumberOfOpcode dw ?                   ; 变形前的opcode数目
org_offsetStart   dw ?                  ; 本块指令 变形前 在virut中的偏移
aft_offsetStart   dw ?                  ; 本快指令变形后 在virut中的偏移
art_NumberOfOpcode dw ?                   ; 变形后 本块指令中opcode数目
                                        ; 最高位为1时代表 本块指令变形前是以跳转指令或者返回指令结尾
BLOCK_Info   ends

RELOC_Info    struc ; (sizeof=0x6)
o_offset      dw ?                  ; 跳转指令操作数 变形前在virut中的偏移
NexOffset       dw ?                  ; 跳转指令 下一条指令的偏移
TarOffset       dw ?                  ; 跳转指令目的地址的偏移
RELOC_Info    ends



同时对于这个样本来说对跳转还有特殊的操作:

比如:
对于jcc跳转的一个还原例子:

003A01D0^\75 F7         jnz   short 003A01C9

还原之后:
003A01D0^\0F85 F3FFFFFF   jnz   003A01C9
其他jcc的跳转也是一样的操作

对应的反汇编代码就不贴了 对应的idapython如下
#!/usr/bin/env python
#-*- coding:UTF:8-*-

def TO_BYTE(a):
    return a&0xff

def TO_WORD(a):
    return a&0xffff

def TO_DWORD(a):
    return a&0xffffffff

def test_bit(address, off):
    while off >= 0x20:
      off -= 0x20
      address += 4
    return (Dword(address) >> off) & 1

def dword_to_list(a):
    b = list()
    for i in range(4):
      b.append(chr(a&0xff))
      a >>= 8
    return b

def main(is_patch = 1, code_begin = 0x03902F5, changed_block = 0x403200,patch_addres = 0x390000):
    reloc_base = TO_DWORD(code_begin - 0x4F312F5)
    number_blocks = Byte(TO_DWORD(reloc_base + 0x4f31874))
    flag_codechange = Dword(TO_DWORD(reloc_base + 0x4f31de1))
    rva_body = Dword(TO_DWORD(reloc_base + 0x4f31ddd))
    #print "reloc_base is :%x\n,number_blocks is :%x \n,flay_codechange is :%x \n,rva_body is :%x \n" %(reloc_base,number_blocks,flag_codechange,rva_body)
    virut_old = list()

    count = 0
    number_blocks -= 1
    for i in range(number_blocks):
      start = Word(TO_DWORD(reloc_base + i * 8 + 0x4f31879))
      end = Word(TO_DWORD(reloc_base + i * 8 + 0x4f3187b))
      #print "start is :%x,end is %x" % (start,end)
      temp = changed_block + start
      if start > rva_body:
            start = TO_DWORD(start - 0x4898)
      end &= 0x7fff
      for j in range(end):
            if test_bit(TO_DWORD(reloc_base + 0x4f31c75), start) == 0:
                if flag_codechange != 0:
                  if (start >= flag_codechange)and (start - flag_codechange) < 4:
                        virut_old.append(chr(Byte(TO_DWORD(reloc_base + 0x4f31de5 + start - flag_codechange))))
                        temp += 1
                        start += 1
                        continue
                virut_old.append(chr(Byte(TO_DWORD(temp))))
                #print "offset : %x movbs content is :%x" % (temp,Byte(TO_DWORD(temp)))
            temp += 1
            start += 1
    #修复跳转地址
    repair = TO_DWORD(reloc_base + 0x4f31de9)
    for i in range(0x28):
      m = Word(repair)
      n = Word(repair + 4)
      k = Word(repair + 2)
      n = TO_DWORD(n-k)
      virut_old = dword_to_list(n)
      #print dword_to_list(n)
      if (k - m) == 4 and ord(virut_old) != 0xe8 and ord(virut_old) != 0xe9:
            if ord(virut_old) == 0xeb:
                virut_old = chr(0xe9)
            else:
                tmp = virut_old
                virut_old = chr(0xf)
                if ord(tmp) != 0x0f:
                  tmp = ord(tmp) + 0x10
                  print "offset is :%x,added tmp is :%x" % (m-1,tmp)
                  virut_old = chr(TO_BYTE(tmp))
      repair += 6
      
    if is_patch:
      for i in virut_old:
            PatchByte(patch_addres, ord(i))
            patch_addres +=1
    f = open("virut_header.bin", "wb")
    f.write("".join(virut_old))
    f.close()
    print "finished"
if __name__ == "__main__":
    print "start to restore"
    main()

virut多态的过程 就是header的多态 方式一是在远程线程里面 方式二是在hook ZwCreateFile ZwOpenFile

所以说方式一有可能会用到 这要看感染的时机 方式二一定会用到

方式一:改变header的指令执行顺序和随机header里一些特殊的常量 字符串

1、改变header指令的执行顺序,主要用到了如下结构的数组:
INS_order
{
offset    word 可以改变顺序的指令变形前 在header中的偏移
num    byte 有多少条指令可以改变顺序
}

virut遍历数组 通过随机数来决定是否改变指令顺序

2、:前面说过header会根据函数名hash获取函数地址 在感染的过程 virut会改变header里面的push的函数名hash的值和计算hash的方法,只是随机的dword异或原来的值

对该样本来说算法如下:
    for c in name:
      value = value * 0x0f
      chr_value = neg(ord(c),4)
      value = value + chr_value
    value &= 0xffffffff
    value = value ^ 0x495820ee
只是修改最后面异或的值

virut在header里面保存了一个push 函数名hash 指令offset的数组 通过这个数组来用随机的dword异或原来的值 因为计算hash的函数异或的地方用这个值异或 所以可以保证不会出错

3、随机SemaphoreName的名字 随机一些常数

对应的反汇编:
seg007:7FF920DB 0F 31                                       rdtsc
seg007:7FF920DD 89 85 54 11 F3 04                           mov   dword ptr ss:(loc_7FF90153+1 - 7B05F000h), eax ; 修改根据函数hash 异或的值
seg007:7FF920E3
seg007:7FF920E3                               loc_7FF920E3:
seg007:7FF920E3 00 85 9F 12 F3 04                           add   byte ptr ss:(loc_7FF9029E+1 - 7B05F000h), al          ;
seg007:7FF920E9 28 85 A4 12 F3 04                           sub   byte ptr ss:(loc_7FF902A3+1 - 7B05F000h), al   ;这两语句是同步的
seg007:7FF920EF 30 A5 80 12 F3 04                           xor   byte ptr ss:(loc_7FF9027A+6 - 7B05F000h), ah   ;修改时间检测的counter
seg007:7FF920F5 E8 CD FD FF FF                              call    Update_pushed_hash                                  ;更新push的函数hash
seg007:7FF920FA 33 C9                                       xor   ecx, ecx      ; counter = 0
seg007:7FF920FC
seg007:7FF920FC                               @lopp_change_order:                     ; CODE XREF: inject_thread+1E9j
seg007:7FF920FC 51                                          push    ecx
seg007:7FF920FD 8D B5 00 10 F3 04                           lea   esi, (bubianxing_virut_start - 7B05F000h)
seg007:7FF92103 0F B7 84 8D F1 1E F3 04                     movzx   eax, word ptr ss:(stru_7FF90EF1.offset - 7B05F000h)
seg007:7FF9210B 0F B6 8C 8D F3 1E F3 04                     movzx   ecx, ss:(stru_7FF90EF1.num - 7B05F000h)
seg007:7FF92113 03 F0                                       add   esi, eax
seg007:7FF92115
seg007:7FF92115                               loc_7FF92115:                           ; CODE XREF: inject_thread+1E1j
seg007:7FF92115 51                                          push    ecx
seg007:7FF92116 E8 0D 24 00 00                              call    func_getinslength
seg007:7FF9211B 6A 05                                       push    5
seg007:7FF9211D 58                                          pop   eax
seg007:7FF9211E E8 40 F5 FF FF                              call    random          ; 随机0~eax-1
seg007:7FF92123 0A D2                                       or      dl, dl            ; 通过随机数来决定是否改变指令执行顺序
seg007:7FF92125 74 04                                       jz      short @change_weizhi ; 交换esi 指向相邻两条指令的顺序
seg007:7FF92127 03 F1                                       add   esi, ecx
seg007:7FF92129 EB 1D                                       jmp   short loc_7FF92148
seg007:7FF9212B                               ; ---------------------------------------------------------------------------
seg007:7FF9212B
seg007:7FF9212B                               @change_weizhi:                         ; CODE XREF: inject_thread+1BDj
seg007:7FF9212B 8D BD 26 5C F3 04                           lea   edi, (tmp_array - 7B05F000h) ; 交换esi 指向相邻两条指令的顺序
seg007:7FF92131 51                                          push    ecx
seg007:7FF92132 56                                          push    esi
seg007:7FF92133 F3 A4                                       rep movsb               ; 拷贝first_ins到7ff94c26
seg007:7FF92135 E8 EE 23 00 00                              call    func_getinslength ; 因为在拷贝的过程中esi 值会增加 这里esi已经指向下一条指令
seg007:7FF9213A 5F                                          pop   edi
seg007:7FF9213B F3 A4                                       rep movsb               ; 用next_ins覆盖前面的指令
seg007:7FF9213D 59                                          pop   ecx
seg007:7FF9213E 57                                          push    edi
seg007:7FF9213F 8D B5 26 5C F3 04                           lea   esi, (tmp_array - 7B05F000h)
seg007:7FF92145 F3 A4                                       rep movsb               ; 将first_ins写到 next_ins的后面
seg007:7FF92147 5E                                          pop   esi
seg007:7FF92148
seg007:7FF92148                               loc_7FF92148:                           ; CODE XREF: inject_thread+1C1j
seg007:7FF92148 59                                          pop   ecx
seg007:7FF92149 E2 CA                                       loop    loc_7FF92115
seg007:7FF9214B 59                                          pop   ecx
seg007:7FF9214C FE C1                                       inc   cl
seg007:7FF9214E 80 F9 09                                    cmp   cl, 9
seg007:7FF92151 72 A9                                       jb      short @lopp_change_order
seg007:7FF92153 6A 64                                       push    64h ; 'd'
seg007:7FF92155 58                                          pop   eax
seg007:7FF92156 E8 08 F5 FF FF                              call    random          ; 随机0~eax-1
seg007:7FF9215B 85 D2                                       test    edx, edx
seg007:7FF9215D 75 0D                                       jnz   short loc_7FF9216C
seg007:7FF9215F 8D BD 04 12 F3 04                           lea   edi, (aJdcbc_1 - 7B05F000h) ; "JdcBc"
seg007:7FF92165 B1 05                                       mov   cl, 5
seg007:7FF92167 E8 0B F5 FF FF                              call    create_random_char ;随机产生SemaphoreName的名字

方式二:把 header 分成一个个小的块 此时每个块的指令都是有用的指令 然后将每个块中的指令淹没在花指令块中 此时块内的真实指令执行顺序是
不变的并把相关信息 用BLOCK_Info描述 接下来用跳转连接各块 块与块之间的顺序和之前比没有变化


最后描述下其感染过程:
if 满足条件(是pe文件 不是dll 没有overlay subsystem!= 10 没有被感染)
    对header进行分块
    if control_flag & 0x1
      从入口寻找ff15或者ff25的kernel32的api调用
    else
      nothing
    随机存放块的位置 对每个块插入花指令
    修复块内的跳转指令,包括0f 8x类跳转变成7x的跳转
    用jmp连接各块,保证块的执行顺序
    更新解密body的key 并对body进行加密
    if control_flag & 0x2 and 入口节有足够的空间
      在入口节空间插入如下代码
      push (added_virus_code_length - 2) & 0xfffffffe偶数对齐
      clc
      pop reg
      loop_decode:
      (根据control_flag只插入加或者减其中的一条)
      if control_flag & 0x4

            adc word ptr , ransom_word (随机word)added_code_startva添加的病毒代码起始虚拟地址
      else
            sbb word ptr , ransom_word (随机word)added_code_startva添加的病毒代码起始虚拟地址
      sub reg,2
      jge loop_decode
      

      然后把上面的指令分成5组
      push (added_virus_code_length - 2) & 0xfffffffe偶数对齐
      clc
      这两条指令为一组
      剩下一条一组
      首先会产生产生5个块 将上面的五组指令隐藏在5个花指令块中
      接下来会用jmp将这些块连接起来,最后一块跳向header中第一个块变形后的地址

      跟据control_flag 再一次对body进行加密

      if found_api_kernel32:
            修改找到的kernel32的api调用,使其push (added_virus_code_length - 2) & 0xfffffffe所在块的起始地址
            例如:
                calldword ptr变成了jmp virut


               
      else
            直接修改入口 使其指向push (added_virus_code_length - 2) & 0xfffffffe所在块的起始地址
      
    else
      if found_api_kernel32:
            修改找到的kernel32的api调用,使其指向header中第一个块变形后对应的rva
            例如:
                calldword ptr变成了jmp virut


               
      else
            直接修改入口 使其指向header中第一个块变形后对应的rva

更多内容看idb吧
附件密码virut
对于附件中idb:

我让virut注入到自身这样容易看,注入基址7FF90000

virut自己申请的空间基址00390000 用来解密body

感染的文件map的起始地址3a0000                       

Cser2 发表于 2012-5-25 00:13

分析病毒、、、膜拜啊

淡然出尘 发表于 2012-5-25 01:32

看起来不错啊。

littlefater 发表于 2012-5-26 09:10

各种膜拜袁哥{:301_1003:}

自由飞翔 发表于 2012-5-26 10:34

很全面,学习了。

yuansunxue 发表于 2012-5-28 11:53

littlefater 发表于 2012-5-26 09:10 static/image/common/back.gif
各种膜拜袁哥

:loveliness:原来你们认识

usertest 发表于 2012-6-8 13:05

看起来不错

roymond922 发表于 2012-6-11 17:17

学习了!感谢分享!

www52pojiecn 发表于 2014-1-12 16:20

感谢,有些兴奋,加速学习
页: [1]
查看完整版本: 一次virut分析之旅