本帖最后由 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
键为: 文件路径
值为: 文件路径:*:enabledshell32.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
键为: 文件路径
值为: 文件路径:*:enabledshell32.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 ZwDeviceIoControlFile 0x20 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[m:m+4] = dword_to_list(n)
#print dword_to_list(n)
if (k - m) == 4 and ord(virut_old[m-1]) != 0xe8 and ord(virut_old[m-1]) != 0xe9:
if ord(virut_old[m-1]) == 0xeb:
virut_old[m-1] = chr(0xe9)
else:
tmp = virut_old[m-2]
virut_old[m-2] = chr(0xf)
if ord(tmp) != 0x0f:
tmp = ord(tmp) + 0x10
print "offset is :%x,added tmp is :%x" % (m-1,tmp)
virut_old[m-1] = 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)[ebp], eax ; 修改根据函数hash 异或的值
seg007:7FF920E3
seg007:7FF920E3 loc_7FF920E3:
seg007:7FF920E3 00 85 9F 12 F3 04 add byte ptr ss:(loc_7FF9029E+1 - 7B05F000h)[ebp], al ;
seg007:7FF920E9 28 85 A4 12 F3 04 sub byte ptr ss:(loc_7FF902A3+1 - 7B05F000h)[ebp], al ;这两语句是同步的
seg007:7FF920EF 30 A5 80 12 F3 04 xor byte ptr ss:(loc_7FF9027A+6 - 7B05F000h)[ebp], 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)[ebp]
seg007:7FF92103 0F B7 84 8D F1 1E F3 04 movzx eax, word ptr ss:(stru_7FF90EF1.offset - 7B05F000h)[ebp+ecx*4]
seg007:7FF9210B 0F B6 8C 8D F3 1E F3 04 movzx ecx, ss:(stru_7FF90EF1.num - 7B05F000h)[ebp+ecx*4]
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)[ebp] ; 交换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)[ebp]
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)[ebp] ; "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 [ecx+added_code_startva], ransom_word (随机word) added_code_startva添加的病毒代码起始虚拟地址
else
sbb word ptr [ecx+added_code_startva], 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所在块的起始地址
例如:
call dword ptr[api] 变成了 jmp virut
else
直接修改入口 使其指向push (added_virus_code_length - 2) & 0xfffffffe所在块的起始地址
else
if found_api_kernel32:
修改找到的kernel32的api调用,使其指向header中第一个块变形后对应的rva
例如:
call dword ptr[api] 变成了 jmp virut
else
直接修改入口 使其指向header中第一个块变形后对应的rva
更多内容看idb吧
附件密码virut
对于附件中idb:
我让virut注入到自身这样容易看,注入基址7FF90000
virut自己申请的空间基址00390000 用来解密body
感染的文件map的起始地址3a0000
|