第5个样本
软件环境:动态调试时使用VMWare虚拟机,虚拟机系统版本XP SP3,使用反汇编工具:OllyICE,IDA 5.0,WINDBG6.11
5.exe是一个被感染型病毒感染的样本。5.exe入口点的病毒代码在用户Temp文件夹释放了Track.exe并运行,然后就跳到了被感染前文件的原入口点执行了。
Track.exe即为病毒主文件,UPX加壳,直接用OD手脱修复后即可分析。这是一个有感染型病毒特征的,利用添加由svchost.exe加载的服务项来实现其相应dll启动并实现下载和感染功能的病毒。
Track.exe主要行为(先讲第一次运行时,再讲多实例运行时):
1. 当文件自身处于可移动介质中时,会调用explorer.exe打开所在的盘符根目录。尝试访问\\.\pipe\96DBA249-E88E-4c47-98DC-E18E6E3E3E5A并与之关换数据,注意是访问而不是创建,由于第一次运行时该命名管道尚不存在,所以没有实际动作,其作用要在后面才体现出来。
2. 第一次运行时,会取消对BITS服务的ServiceDll即%systemroot%\system32\qmgr.dll的系统文件保护之后,将自身资源中FILE类型的102号资源覆盖qmgr.dll,并启动BITS服务,并在临时目录释放FoxLoad.bat并执行,在Track.exe退出后该批处理便将原始的Track.exe复制到%systemroot%\system32\dllcache\lsasvc.dll,并删除原始文件。
3. 如果不是第一次运行,这里动作会有很大的不同。由于这是一个感染型病毒,第一次运行感染系统后,有可能被感染的文件再度被运行(以及下面将提到的dll文件执行时可能将自己复制多份再在360进程中运行等原因),系统中很可能出现两个以上的该病毒实例在运行。这个时候,后面将说到前一个实例的dll在执行过程中将会创建并写入\\.\pipe\96DBA249-E88E-4c47-98DC-E18E6E3E3E5A,并把其自身相对文件名(即未带文件夹路径,但有后缀名)写入其中,这样后一个实例的exe文件在启动后将通过这个命名管道读取到前一个实例的dll文件名,使得后一个实例将选择与前一个实例写入不一样的文件并启动不一样的服务,从而使得多个实例同时在系统中长驻。为了做到这一点,首先程序exe和dll中内置一个可选的系统的ServiceDll名称列表,,一共有18个ServiceDll名称,以及对应的服务项名称列表。ServiceDll名称依次为(为篇幅考虑,仍然用逗号分隔)
qmgr.dll,shsvcs.dll,mspmsnsv.dll,xmlprov.dll,es.dll,ntmssvc.dll,upnphost.dll,ssdpsrv.dll,netman.dll,mswsock.dll,tapisrv.dll,browser.dll,shsvcs.dll,cryptsvc.dll,pchsvc.dll,regsvc.dll,schedsvc.dll,appmgmts.dll
对应的服务项名称依次为(仍然用逗号分隔)
BITS,FastUserSwitchingCompatibility,WmdmPmSN,xmlprov,EventSystem,Ntmssvc,upnphost,SSDPSRV,Netman,Nla,Tapisrv,Browser,Themes,CryptSvc,helpsvc,RemoteRegistry,Schedule,AppMgmt
这样,exe启动时首先选择以上18项服务中已存在的可用的,又与前一个实例不同的服务名和ServiceDll名,来作为覆盖系统文件和启动服务的目标。
如果以上服务皆不满足要求,exe还会读取注册表
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost]
的netsvcs键的值,这是一个多字符串数组,它表示了所有依靠svchost.exe -k netsvcs启动的服务名,病毒将会在其中选择一个服务名,作为其释放的dll文件的文件名依靠启动的服务,然后尝试创建服务,并将其ServiceDll设置为相应的dll。这种情况下的服务启动类型都是自动,特点是服务名、显示名和ServiceDll文件名一致。
qmgr.dll(不是第一次运行会叫其他名字)的行为:
DllMain函数行为:
1. 判断自身所在进程是否是360tray.exe,如是则尝试向设备\\.\360SpShadow0发送IOCTL为0x222048的DeviceIoControl请求,从而试图使360检测失效。
2. 创建一个新线程,这里为方便表示为Thread2,然后就返回了。
Thread2运行后行为:
1. 查找当前进程中有无avp.exe或bdagent.exe,如果有,则申请一块0x4000000字节大的内存,前面全用nop指令填充,最后五字节搞一个跳转指令,然后代码故意跳进这块内存中执行,到最后再跳回原代码中。这一步对执行没有什么影响,如果是针对avp.exe和bdagent.exe的,可能是故意考验这两款软件的防堆栈数据执行的功能,尚不知真碰到会有何后果。
2. 查找当前进程中有无360tray.exe,如有,则将自身文件复制到同一目录下(这里是system32)的1l1.dll,并使用CREATE_SUSPENDED参数启动一个新的360tray.exe进程,将其入口点代码改成调用LoadLibraryA加载1l1.dll,这个进程被唤醒之后就将加载1l1.dll,这时1l1.dll的DllMain函数将使用前面提到的方式访问\\.\360SpShadow0试图达到使360的检测失效。
3. 将内存中的一段字符串解密出来,是下载病毒的远程服务器域名列表。
4. 创建一个新线程,这里为方便表示为Thread3,Thread2循环每500ms检测一次Thread3线程结束的标志,这里为方便把它称为bThread3Finish标志,直到Thread3将bThread3Finish置位,Thread2才继续。Thread3线程行为见下文。
5. 再创建三个线程,分别为Thread4,Thread5,Thread6,行为见下文。
6. 等待一段时间(具体没有看是多久)后,才开始写hosts代表,只是把hosts代表覆盖为只剩一行,即
127.0.0.1 localhost
7. 获得%systemroot%\system32\dllcache\lsasvc.dll(即病毒原主文件的副本)的文件大小(访问不成功,将循环100次重试),将其映像到内存,然后再启动两个线程,设为Thread7和Thread8。
8. 等待Thread6Func完成,之后再创建新线程,设为Thread9。
9. 最后搞个死循环长驻内容但不再干活。
各个Thread的行为都比较多,这里只拿出个别详细说明过程,比如Thread3线程是KillAV部分,所以重点说明,其他的只简要说明干了什么。必须说明的是,这个病毒有BUG,其中有一些功能所需要的字符串居然是乱码,我原来以为是我漏掉了什么解密函数,但是后来运行的结果确实功能缺失,所以我只能尽力依照原来的代码流程说明它想要干什么。
Thread3线程行为:
1. 找出自身所在的服务项,根据前面的服务项生成方法,先从内置的18个中找到与自己文件名匹配的那一个的对应服务项名,如果18个中找不到,则显然是依靠系统注册表里的列表生成的服务,则服务项名必与文件名(无后缀)是相同的,将自身服务项启动类型设置为自动。
2. 删除安全模式注册表项
[\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot\Minimal]
[\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SafeBoot\Network]
3. 尝试访问设备\\.\LiTdi,如不成功则说明驱动未加载,向用户Temp文件夹中释放LiTdi.sys,并创建和启动驱动服务,服务名和显示名均为LiTdi。驱动服务启动后马上将LiTdi.sys文件和相应注册表服务项删除。然后再将尝试访问设备\\.\LiTdi,这次如果还不成功,就将bThread3Finish置位之后直接返回了。
4. 使用NtQuerySystemInformation获取系统内核的文件名和加载基址,然后调用LoadLibraryExA将内核文件加载后,找到MmGetSystemRoutineAddress,经重定位得到在内核的真实地址,然后使用DeviceIoControl与设备交互,IOCTL为0x222193,将这个真实地址传给驱动,以使驱动能够获得一些操作所需的重要的内核函数的地址,同时驱动将内核SSDT那张表的表头地址(调试符号是KiServiceTable)返回给用户层。这样的目的是使用户层能够通过重定位从内核文件中找到原始的表,并得到所有原始SSDT的内容,再传递给驱动还原之。然而由于用户层程序中的一个标志位没有设置(即为0),程序并没有进行还原SSDT的操作,而是直接跳到后面把之前LoadLibraryEx加载的内核文件映像又FreeLibrary卸掉了。
5. DeviceIoControl再次与驱动交互,IOCTL为0x2220C4,传入参数是自身的服务项注册表路径,驱动程序使用Hook其内核注册表HHIVE结构的GetCellRoutine函数(系统原本的GetCellRoutine函数是HvpGetCellMapped)的方法,达到隐藏自身服务项的目的。
6. 通过IOCTL为0x22225C的DeviceIoControl,让驱动创建注册表IFEO项目。但是这里存在BUG,传入的字符串是一长串类似加密后的乱码,但是根本没有解密函数,运行的结果证实了没有IFEO项。
7. 通过IOCTL为0x222114的DeviceIoControl,让驱动Tcpip设备的IRP_MJ_INTERNAL_DEVICE_CONTROL处理例程进行HOOK,似乎是过滤杀毒软件网站,但是这里又是BUG,传入的依然是乱码。
8. 找寻相应杀毒软件的ProcessId,通过IOCTL为0x222264的DeviceIoControl,让驱动结束其进程。这里同样有BUG,因为杀毒软件文件名列表和上面IFEO项用的是一样的,都是乱码。
9. 最后将bThread3Finish置位返回。
Thread4线程行为:
1. 创建命名管道\\.\pipe\96DBA249-E88E-4c47-98DC-E18E6E3E3E5A并把自身dll文件名写入其中,然后又不停循环。直到有更新的实例出现,该线程才执行下面的扫尾操作。
2. 这里开始是扫尾操作,向驱动设备发IOCTL为0x222164的DeviceIoControl请求使其还原对注册表服务项HIVE的GetCellRoutine和Tcpip.sys的IRP处理例程的HOOK
3. 修改自己原来占据的服务启动类型为手动
4. 解除对lsasvc.dll的文件映像并将该文件删除
5. 结束自身进程的其他线程后自行退出。
Thread5线程行为:试图启动一个IE进程访问某个网址,30秒后结束该IE进程。我之所以说某个,是因为这里又是一串乱码……
Thread6线程行为:
1. 当explorer.exe进程存在时,试图获得其Token
2. 通过尝试获取www.baidu.com网页的IP等信息来检查连网状态,直到成功,这时利用一个网站列表:
bbnn7.114central.com
bbnn7a.114central.com
bbnn7ba.114central.com
bbnn7bc.114central.com
bbnn7bd.114central.com
bbnn7jc.114central.com
bbnn7jey.114central.com
bbnn7jy.114central.com
bbnn7jyhr.114central.com
bbnn7jyr.114central.com
选择其中可以连接上而且其服务器IP地址等信息与www.490a-B8B5-9B8C1E870B0C.com这个域名不同的(我原来以为是要相同的,但看了好久,似乎是要不同的)一个域名。
3. 根据程序中保存的一个文件夹路径,以及一个文件名列表,与第2步取得的域名合在一起,将多个文件下载到用户Temp文件夹中,在确认下载下来的文件是PE文件后,试图以explorer.exe进程的用户权限运行它们。很遗憾的,这里相应的文件名列表和远程文件夹路径也是乱码。
Thread7线程行为:感染文件,感染除A、B及光盘盘符以及的所有逻辑盘符,排除以下目录
WINDOWS
WinNT
Documents and Settings
System Volume Information
RECYCLER
Common Files
ComPlus Applications
InstallShield Installation Information
Internet Explorer
Messager
microsoft frontpage
Movie Maker
MSN Gaming Zone
NetMeeting
Outlook Express
Windows Media Player
Windows NT
WindowsUpdate
WinRAR
对于exe文件,先检验其数字签名证书,通过验证的则不感染。不通过验证的exe,确定是正常的PE文件后,在其尾部加一个大小为0x7000字节的区段,将病毒代码和lsasvc.dll的映像内容写在其中,并修改程序入口点指向病毒代码开头。
后缀为htm、html、asp、aspx,文件名为index或Default的网页文件,之前未被同一个样本感染过的,则在后面加上一段文本。可惜这里这段文本又是乱码。
对大小为10KB到10MB的rar文件,先调用WINRAR.exe程序将其解压到用户Temp目录的同名文件夹中,然后对其中的文件进行感染,最后再调用WINRAR.exe把它打包回来。
Thread8线程行为:在当前系统的移动介质盘符根目录创建autorun.inf和recycle.{645FF040-5081-101B-9F08-00AA002F954E}\Ghost.exe,后者为lsasvc.dll的副本,autorun.inf的内容为
[autorun]
OPEN=recycle.{645FF040-5081-101B-9F08-00AA002F954E}\Ghost.exe
shell\open=打开(&O)
shell\open\Command=recycle.{645FF040-5081-101B-9F08-00AA002F954E}\Ghost.exe Bak
shell\open\Default=1
shell\explore=资源管理器(&X)
shell\explore\Command=recycle.{645FF040-5081-101B-9F08-00AA002F954E}\Ghost.exe Bak
autorun.inf和recycle.{645FF040-5081-101B-9F08-00AA002F954E}文件夹和Ghost.exe都设置系统、隐藏、只读属性。
Thread9线程行为:获取系统主机的外网IP地址,如果成功则有尝试扫描本机所在的网段进行通信的动作,这里我因为是内网,没有什么实际动作。
最后说一下驱动LiTdi.sys,功能前面基本有提过:
创建驱动设备\Device\LiTdi,符号连接\??\LiTdi,响应用户态程序的DeviceIoControl请求,可用的IOCTL和功能如下:
IOCTL=0x2220C4时,通过用户态程序传入注册表项,驱动程序使用Hook其内核注册表HHIVE结构的GetCellRoutine函数(系统原本的GetCellRoutine函数是HvpGetCellMapped)的方法,达到隐藏注册表项的目的。
IOCTL=0x222114时,通过对Tcpip设备的IRP_MJ_INTERNAL_DEVICE_CONTROL处理例程进行HOOK,达到过滤某些网站的目的。
IOCTL=0x222164时,还原前面两个IOCTL时所做的HOOK。
IOCTL=0x222193时,返回内核SSDT表表头KiServiceTable的位置。
IOCTL=0x22221F时,接收用户态传来的信息,还原SSDT
IOCTL=0x222264时,接收用户态传来的目标进程ID信息,进行强行结束进程。
IOCTL=0x22225C时,接收用户态传来的注册表项信息,写入注册表,这里用来写IFEO项。
其中对注册表的HOOK,用WINDBG观察,驱动加载于0xF9F16000基址,则驱动此调用后0xF9F16F5C处保存服务项的_HHIVE地址,0xF9F16F64处保存其中GetCellRoutine的位置,0xF9F16F60处保存原始函数即HvpGetCellMapped的地址,可以看到驱动调用后GetCellRoutine的位置已经被LiTdi.sys的函数给代替了。
lkd> dd f9f16f5c
f9f16f5c e101ba58 806364ac e101ba5c d50a373c
f9f16f6c 00000000 00000000 00000000 00000000
f9f16f7c 00000000 805d2974 00000000 00000000
f9f16f8c 00000000 00000000 00000000 00000000
f9f16f9c 00000000 00000fc8 00000000 00000000
f9f16fac 000011f0 00000d20 00000000 00000000
f9f16fbc 00000000 00000000 00000000 f9f17028
f9f16fcc f9f17032 f9f1704a f9f1705e f9f17076
lkd> dt e101ba58 _HHIVE
nt!_HHIVE
+0x000 Signature : 0xbee0bee0
+0x004 GetCellRoutine : 0xf9f167dc _CELL_DATA* *** ERROR: Module load completed but symbols could not be loaded for \??\C:\DOCUME~1\YICONG~1\LOCALS~1\Temp\LiTdi.sys
LiTdi+7dc
+0x008 ReleaseCellRoutine : 0x80636438 void nt!HvpReleaseCellMapped+0
+0x00c Allocate : 0x8063afa0 void* nt!CmpAllocate+0
+0x010 Free : 0x8063afec void nt!CmpFree+0
+0x014 FileSetSize : 0x8063aa08 unsigned char nt!CmpFileSetSize+0
+0x018 FileWrite : 0x8063b550 unsigned char nt!CmpFileWrite+0
+0x01c FileRead : 0x8063b428 unsigned char nt!CmpFileRead+0
+0x020 FileFlush : 0x8063b2e6 unsigned char nt!CmpFileFlush+0
+0x024 BaseBlock : 0xe101d000 _HBASE_BLOCK
+0x028 DirtyVector : _RTL_BITMAP
+0x030 DirtyCount : 0
+0x034 DirtyAlloc : 0x268
+0x038 RealWrites : 0x1 ''
+0x03c Cluster : 1
+0x040 Flat : 0 ''
+0x041 ReadOnly : 0 ''
+0x042 Log : 0x1 ''
+0x044 HiveFlags : 0
+0x048 LogSize : 0x400
+0x04c RefreshCount : 0
+0x050 StorageTypeCount : 2
+0x054 Version : 5
+0x058 Storage : [2] _DUAL
lkd> ln 806364ac
(806364ac) nt!HvpGetCellMapped | (8063664c) nt!HvpGetCellPaged
Exact matches:
nt!HvpGetCellMapped = <no type information>
替代函数实现隐藏_CM_KEY_NODE结构的功能,先调用原始HvpGetCellMapped,当发现获取到的KeyNode是需要隐藏的KeyNode时,将返回其ParentNode的最后一个SubKeyNode,如果需要隐藏的就是最后一个,则直接返回获取失败。据说这个招是MJ0011公开提出(见http://www.debugman.com/read.php?tid=497),后来就有代码放出来了。
以上就是五号样本分析的内容,由于该样本的配置数据自身有问题,很多行为在实际测试中都不能完整体现,只能根据其代码来判断其本来应该达到的效果。
全部5个样本的分析到此结束。