该病毒是一个可以结束大多数杀毒软件的恶意程序,病毒通过微软一个漏洞来加载驱动程序,该漏洞是内核的函数发生栈溢出导致,使得R3程序可以在0环执行任意代码,驱动加载后结束反病毒软件,并通过向Atapi发送IRP感染userinit.exe实现自启动,下载并执行其它恶意程序。
EXE部分
病毒主体执行后会在系统目录Program FilesMSDN目录下生成文件000000000,qwegier.exe,LHL13.sys,PciDisk.sys,在C盘根目录下生成sysLoad文件,解密自身带的一部分数据并拷贝到申请的一块内存中,然后在这里生成一个新的线程,停止PciDevice的服务,加载PciDisk.sys驱动,并打开PciDisk创建的符号链接.PciFtDisk,并通过DeviceIoControl发送两个Io控制码0x2200180和0x22001C。
漏洞概述
WINDOWS系统的内核函数RtlQueryRegistryValues存在栈溢出漏洞,病毒利用自己构造的一串数据写入注册表SYSTEM\CurrentControlSet\Control\TimeZoneInformation的ActiveTimeBias键值下,当设置系统时间时,内核会调用RtlQueryRegistryValues去读这个值,导致发生栈溢出,若读取的数据放在栈里面,会覆盖调用RtlQueryRegistryValues的函数的返回地址,触发漏洞。正常情况下,ActiveTimeBias是一个DWORD类型的键值,病毒执行时,会改成REG_BINARY类型,并写入自己构造的数据。
经验证,WinXP,VISTA以及Win7上都存在该漏洞。
构造SHELLCODE的主要线程
病毒运行时,动态解密一块数据,拷贝到申请的内存中,然后在这里创建线程,文件threaddata为DUMP出来的代码,线程入口位于A61DD0,主要任务是完成SHELLCODE调用的内核函数的动态定位,修改注册表,准备漏洞发的必要条件,启动CMD进程,把SHELLCODE拷贝到CMD进程空间中去触发漏洞,主要流程是:
- 在系统盘Program Filesmsdn目录下生成LHL3.sys文件
- 取得系统信息,得到ntoskrnl模块的加载地址,从磁盘上调用LoadLibraryEx从磁盘上把内核模块加载到用户空间中,取得SHELLCODE调用中用到的内核函数地址,取得相对你猜并加上内核加载的虚拟地址,这样得到当前内核模式中函数地址并放入SHELLCODE相应位置,生成病毒的SHELLCODE
- 以“C:\windows\system32\cmd.exe /c time %.2d:%.2d:%.2d”命令行启动CMD进程,其中的时间被格式化成当前时间,在CMD进程中申请一块0x640大小的内存并把SHELLCODE拷贝进去,删除原来的ActiveTimeBias,重新写一个REG_BINARY类型,长度为14H的串,格式如下(以双字形式):
+0:00000000
+4:00000000
+8:00000000
+C:在CMD进程中申请的地址,也就是SHELLCODE所在的地址(XP系统)
+10:在CMD进程中申请的地址,也就是SHELLCODE所在的地址(VISTA,Win7系统)
到这里,触发漏洞的条件已经具备了,把挂起的CMD主线程恢复运行,然后等待CMD进程结束后删除ActiveTimeBias键值。
CMD进程运行之后,会设置时间,漏洞触发,其函数调用关系如下图:
Shellcode
漏洞在CMD进程空间中被触发, ShellCode也在CMD进程空间得到执行,文件Shellcode中是DUMP出来的代码,入口点是150000,主要工作是解析PE文件,把驱动加载到内核地址空间中,流程如下 :
- 打开%Program Files%\MSDN\LHL13.
sys ,得到大小,在分页池中申请相应大小的内存,然后把文件读进来 - 以PE头中SizeOfImage大小申请非分布内存,重新按内存中对齐方式定位每一个节,拷贝到非分页内存中,修复导入地址表,指向内核函数地址,修复重定位表,完成驱动加载
- 自己调用驱动入口DriverEntry,返回后释放之前申请的分页内存
- 从漏洞触发时的一个上层函数搜索到那个没有正常返回的函数的返回地址,设置EAX=STATUS_SUCCESS,然后转到那个地址去执行。
漏洞触发及正常返回
在XP系统上,漏洞触发时,内核中函数调用的流程是:
NtSetSystemTime -> ExpSetSystemTime -> ExpRefreshTimeZoneInformation -> RtlSetActiveTimeBias -> RtlQueryRegistryValues -> RtlpCallQueryRegistryRoutine -> RtlpQueryRegistryDirect
在RtlSetActiveTimeBias中会调用RtlQueryRegistryValues去读注册表键值ActiveTimeBias,RtlSetActiveTimeBias函数的栈分布情况及调用RtlQueryRegistryValues的代码如下:
其中Context位于[ebp-8],其地址被放在一个结构中传给RtlQueryRegistryValues,这个地址经过RtlpCallQueryRegistryRoutine最终被传给RtlpQueryRegistryDirect,RtlpQueryRegistryDirect函数是真正把栈中RtlSetActiveTimeBias的返回地址给覆盖的函数。
栈溢出
进入RtlSetActiveTimeBias时的栈分布如下:
最终有漏洞的函数把从注册表中读出的0x14个字节写入从Context开始的位置,XP系统上偏移+0C的位置正好覆盖到RtlSetActiveTimeBias的返回地址(Vista系统上Context在[ebp-0c]的位置,所以写到注册表中的Shellcode的地址为偏移+10)。
返回地址被覆盖时的栈如图:
此时RtlSetActiveTimeBias的返回地址已经被覆盖,值为在CMD进程中的SHELLCODE的地址150000。
执行SHELLCODE后安全返回
先列出调用关系:
NtSetSystemTime -> ExpSetSystemTime -> ExpRefreshTimeZoneInformation -> RtlSetActiveTimeBias -> RtlQueryRegistryValues -> RtlpCallQueryRegistryRoutine -> RtlpQueryRegistryDirect
RtlSetActiveTimeBias返回时,就回到病毒的SHELLCODE中,此时的ESP指向上图中最上边的那个位置,EBP = 0,RtlSetActiveTimeBias的返回地址由于已经被覆盖,所以只能通过ESP得到RtlSetActiveTimeBias的上层函数ExpRefreshTimeZoneInformation的返回地址,这个地址在函数ExpSetSystemTime中,取出这个地址,它的前四字节就是调用的相对偏移量,加上本身的地址,又得到函数ExpRefreshTimeZoneInformation的地址,在从这个地址搜索得到RtlSetActiveTimeBias原来的返回时的地址,设置返回值EAX为成功,然后转到这里去执行,如图:
第8行中的add ebp, 0FCh,FC是ExpRefreshTimeZoneInformation局部变量的大小加上在栈中保存的三个寄存器所占的空间,现在EBP指向的是ExpRefreshTimeZoneInformation函数的栈帧。
取出[ebp+4]是ExpRefreshTimeZoneInformation的返回地址,在函数ExpSetSystemTime中,函数find_return_addr如下图:
从ExpSetSystemTime函数中调用ExpRefreshTimeZoneInformation函数的地方,加上相对调用的偏移,也就是图中add ecx, [eax-4],取得函数ExpRefreshTimeZoneInformation的地址,然后在里面暴搜RtlSetActiveTimeBias正常情况下的返回地址。
然后由上页图中的push eax; ret转过去,此时这次的调用返回正常的执行流程。
PciDisk.sys
PciDisk驱动主要通过向atapi.sys驱动程序发送IRP来感染userinit.exe文件。
驱动入口处创建一个设备DevicePciFtDisk和一个符号链接DosDevicesPciFtDisk。
从磁盘打开atapi.sys文件,并从INIT节搜索到IRP_MJ_DEVICE_CONTROL和IRP_MJ_SCSI处理例程的地址。
从系统目录打开文件userinit.exe并读入数据,由FileHandle得到FileObject,进一步通过FileObject与与之关联的Vpb得到文件系统所创建的卷设备对象,就是userinit.exe文件所在的卷设备对象,然后构造一个主功能号为IRP_MJ_FILE_SYSTEM_CONTROL的IRP,IoControlCode为FSCTL_GET_RETRIEVAL_POINTERS,发往文件系统的卷设备对象,如下图:
等IRP完成后,得到文件userinit.exe的VCN和LCN对应关系表。
然后取得atapi驱动类型为FILE_DISK类型的设备,得到DR0设备对象。
当驱动收到应用层发过来的DeviceIoControl请求时,会把传入的数据通过写入磁盘,完成对文件的Hook操作,这里主要是自己构造SRB请求包,向Atapi发送IRP_MJ_SCSI类型的IRP实现的。
如图:
别处,代码中还有对atapi.sys分发例程的挂钩恢复操作以及反调试代码,若发现在存在ntice.sys和syser.sys驱动,会向当前线程ETHREAD写入一块垃圾数据。
LHL3.sys
该驱动主要实现SSDT的恢复,杀掉大部分的安全软件。
驱动入口取得内核模块的基址和大小,并暴搜找到函数PspTerminateThreadByPointer的地址。
若搜不到,则从寻出表找到PsTerminateSystemThread函数的地址,并从该地址向后搜索取到PspTerminateThreadByPointer的地址,然后恢复该函数的前8个字节,然后通过ZwQuerySystemInformation遍历系统中的进程表,并杀掉病毒中定义的进程,包括:
这里杀进程的主要流程是:通过枚举得到的进程ID,得到进程对象EPROCESS,对进程对象中的链表头ThreadListHead遍历找到的每一个线程调用PspTerminateThreadByPointer。
然后分两种情况对SSDT进行恢复:
若系统中不存在江民软件,取出系统中SSDT表的位置,换算并取到在磁盘文件中的偏移,从磁盘文件中读取原始的SSDT数据后进行重定位并恢复。
若系统中存在江民软件,先读取原始SSDT数据,同上步,然后申请两块内存,先把当前正在使用的SSDT表拷贝过去,把原始SSDT也拷贝到紧后面,参数个数表同理,然后给SSDT结构赋值指向新的函数地址表和个数表,并把函数个数乘以2放入Count。
这样构造的SSDT,原来的函数地址(Hook的或是没Hook的)没有改变,而后半部分新加入的都是被恢复的函数地址。
然后病毒通过wrmsr改变MSR的IA32_SYSENTER_EIP,指向自己的代码,在这段代码中,把系统服务调用号加中原来的SSDT函数数量放入EAX后就调用原来的系统服务入口。这样,系统服务函数的地址就被重定向到了SSDT的后半部分。
最后,病毒开一个系统线程,循环调用杀进程的函数来结束杀软。