转载

分析重装系统也无法清除的鬼影病毒

声明:本文由【MS509 Team】成员expsky原创,仅用于技术交流分享,禁止将相关技术应用到不当途径。

整理电脑的时候找到自已以前分析的一个鬼影病毒的资料,当时兼容市面上主要的windows系统(XP, win7,包含x86和x64系统)样本来自国外,有不少亮点,当时花了不少时间把所有原理分析出来并重新用汇编和C++实现了出来。以前的一些资料简单整理了下,分享出来,也给自己以前的工作留个记录。

该木马样本通过感染MBR达到早于系统得到执行。整个样本以非文件形式存在,直接写人磁盘扇区。包括感染的MBR以及保存在磁盘末尾非文件系统的payload。

木马执行流程从MBR开始,通过hook和注册回调函数的机制,将所有的木马程序在windows启动过程中逐级得到执行, windows启动完毕后最终将下载者dll注入到指定的系统进程中,下载者代码循环执行而告终。

木马亮点有:

绕过windows的PatchGuard保护

兼容XP, win7(x86,x64)(bootkit型底层木马兼容不同系统是挺有挑战的)

自保护功能:感染木马后通过WinHex等工具查看到的MBR是正常的,而且无法修改被感染的MBR(不是简单的修改失败,而是会呈现MBR被修改成功的假象)

从内核态向用户态的指定系统进程注入下载者DLL(根据系统是32位还是64位,会注入相应的32位和64位版本的DLL)

启动过程中关闭指定进程

木马工作原理

1)木马 植入程序运行后感染MBR 和磁盘末尾未分区部分(无文件,直接写入磁盘扇区)

感染的数据主要包括6个部分:

感染的MBR;

启动部分代码;

x86驱动代码;x64驱动代码;

x86下载者DLL;x64下载者DLL

(前两部分未压缩,其他4部分进行了aPLib压缩)

2)重启电脑后感染的MBR接管执行:

a) 加载末尾20个扇区的前18个扇区(未压缩数据)到常规内存并执行

b) 加载第19个扇区(原始MBR)到0x7C00

c) 加载第20个扇区(配置信息:驱动、下载者代码的大小、payload所在扇区位置数据)到常规内存

d) 调用Int15扫描常规内存,保存ARD结构体用于后续常规内存的分页扫描(查找ntoskrnl.exe映像)

e) hook IVT表中int13向量(用于监控后续磁盘读操作)

f) 跳转到0x7C00控制权转交原始MBR,系统继续启动

ARD: AddressRange Descriptor Structure +0:BaseAddrLow:基地址的低32位 +4:BaseAddrHigh:基地址的高32位 +8:LengthLow:长度(字节)的低32位 +12:LengthHigh:长度(字节)的高32位 +16:Type:这个地址范围的地址类型(1:AddressRangeMemory;2:?AddressRangeReserved;Other:Undefined)

3)Int13 钩子监视系统启动过程的读磁盘操作,当读取到kdcom.dll 时接管执行(绕过PatchGuard的时机)

a)int13钩子根据读取文件的前0×200字节的校验值和PE特征码识别kdcom.dll,根据kdcom.dll PE的mechine字段判断系统为32位还是64位(32位和64位部分hook函数不同,整体流程一致)

b)根据前面保存的ARD结构体扫描常规内存,找到ntoskrnl.exe基地址(根据文件hash值),再根据ntoskrnl PE导出表hook函数IoCreateDriver(根据函数名hash值,64位系统hook MmMapIoSapce)

c)控制权继续转交系统

4)IoCreateDriver钩子函数接管执行

a)先恢复IoCreateDriver钩子

b)同前机制,根据ntoskrnl.exe基地址和PE导出表找到函数PsSetLoadImageNotifyRoutine

c)调用PsSetLoadImageNotifyRoutine,注册回调函数

d)控制权继续转交系统

5)系统加载模块时,上步PsSetLoadImageNotifyRoutine注册的回调函数接管执行

a)如前机制,得到NtReadFile,NtClose,NtOpenFile,ExAllocatePool函数指针

b)申请8K内存,再打开Device/Harddisk0/Patition0,将驱动部分压缩数据读入其中

c)申请0×3600字节内存,解压驱动数据到其中

d)释放8K压缩数据内存,转入驱动代码部分执行

6)驱动部分压缩数据解压后的执行

a)根据实际加载的内存地址,对驱动部分的常量进行重定位

b)InstallProcessNotifyCallback注册回调函数,回调函数监控当创建的进程如包含在屏蔽进程列表中时,直接return

c)PsSetLoadImageNotifyRoutine注册回调函数,当创建进程包含在注入进程列表中时注入下载者DLL

d)Hook NtReadFile, NtWriteFile, 当尝试读写感染的MBR或磁盘末尾未分区处,都返回正常值,隐藏被感染迹象。同样写入数据到此部分也会做相应保护,不会真正写入,但会呈现已被写入的假象

内核向用户态进程注入代码原理

通过KeInitializeApc、KeInsertQueueApc(未文档化函数)插入内核APC回调函数、ExQueueWorkItem插入WorkItem回调函数,附加用户进程,将注入代码复制到用户进程内存空间,最后通过一个用户APC指向注入代码,并异步得到执行。

详细流程如下:

1)PsSetLoadImageNotifyRoutine注册回调函数

2)任意进程加载模块时,上步注册的回调函数得到执行

调用FsRtlIsNameInExpression,判断加载的是kernel32.dll,是则继续执行,否则返回。(The FsRtlIsNameInExpressionroutine determines whether a Unicode string matches the specified pattern.)

分析重装系统也无法清除的鬼影病毒

FsRtlAllocatePool申请0×34字节内核非分页内存,用于后续APC对象及回调参数。

将kernel32.dll基址存入0×34字节的最后一个DWORD中(用于APC回调函数入参)

调用KeGetCurrentThread得到当前线程对象(用于APC初始化的入参)

调用KeInitializeApc、KeInsertQueueApc(未文档化函数)插入内核APC回调函数(执行后续流程)

分析重装系统也无法清除的鬼影病毒

3)上步插入的内核APC函数异步执行

调用ExFreePool释放APC结构体内存

FsRtlAllocatePool申请0×40字节非分页内核内存(用于后续EVENT对象、WORKITEM对象及其参数)

调用KeGetCurrentThread、PsGetCurrentProcess、PsGetCurrentThreadProcessId得到进程对象,线程对象,PID(存入上面申请的内存中,作为参数传给ExQueueWorkItem插入的回调函数)

调用KeInitializeEvent,用于同步,等待WorkItem执行完毕

调用ExQueueWorkItem插入后续流程的回调函数

调用KeWaitForMutexObject等待上面的WorkItem回调函数执行完毕

调用ExFreePool释放上面申请的0×40字节内核内存

分析重装系统也无法清除的鬼影病毒

4)上步ExQueueWorkItem插入的回调函数执行

调用PsLookupProcessByProcessId得到EPROCESS

调用PsGetProcessImageFileName通过EPROCESS得到进程文件名

计算进程文件名的hash值与想要注入代码的进程列表做对比(包含在内继续执行,否则返回)

分析重装系统也无法清除的鬼影病毒

调用KeStackAttachProcess将当前线程附加到目标进程用户态地址空间

配置入参数ClientId, ObjectAttributes后,调用ZwOpenProcess打开目标进程

分析重装系统也无法清除的鬼影病毒

调用ZwAllocateVirtualMemory申请目标进程用户态内存

mov指令将内核中的注入代码拷贝到上步申请的用户态进程空间

分析重装系统也无法清除的鬼影病毒

调用FsRtlAllocatePool申请0×30字节内核非分页内存用于APC对象

调用KeInitializeApc,KeInsertQueueApc插入用户APC(注入代码在用户态进程执行)

分析重装系统也无法清除的鬼影病毒

调用ZwClose,KeUnstackDetachProcess,ObDereferenceObject释放资源

末尾调用KeSetEvent设置事件,用于同步,通知WorkItem执行完毕

分析重装系统也无法清除的鬼影病毒

5)上步插入的用户APC异步得到执行(注入的代码在目标进程执行)

至此,完成了内核态向目标用户进程注入代码并得到执行

自保护原理

木马自保护功能:当木马正常工作后,用WinHex等磁盘查看工具是看不到MBR及磁盘末尾被感染的迹象的,并且用工具对MBR或磁盘末尾进行修改后,呈现被改动的假象,但实际并未修改成功。木马代码依然存在在磁盘相应位置。

通过hook NtReadFile和NtWriteFile两个函数实现的自保护功能

当写数据到MBR或磁盘末尾处时,写入的数据存入内存而不真正写入磁盘,当读取磁盘MBR或末尾时,不真实读取,而用之前暂存的数据代替,呈现数据被写入的假象,实现自我保护

内存0×20000(128k)

0×0 磁盘末尾最后一个扇区数据(配置信息)
0×200 原始MBR
0×400 保存被修改过的MBR(假MBR)
0×600 感染后的MBR
0×800 磁盘末尾0xFC00字节(126个扇区,63K)
0×10400 0×20000 保存被修改后的磁盘末尾0xFC00字节数据(126个扇区,63K),假的磁盘末尾数据

(1)NtWriteFile钩子函数

a)确认写入的数据是否在MBR处或磁盘末尾0xFC00处

b)当向MBR写入数据时:将写入的数据保存到上表内存中0×400处(假MBR),以及更新0×200(原始MBR)和0×600(染后后MBR)的末尾0x4C的数据信息(只更新MBR中分区表等数据信息,而不改变MBR中的代码);修改磁盘MBR处后0x4C的分区表等数据,修改磁盘倒数第2个扇区(备份的原始MBR)的后0x4C数据部分

c)当向磁盘末尾0xFC00写入数据时:计算写入数据与末尾0xFC00的交叉情况,保存写入的数据到0×10400处(假的磁盘末尾数据)

(2)NtReadFile钩子函数

a)确认读取的数据是否在MBR处或磁盘末尾0xFC00处

b)当读取MBR时,用上表中0×400处的假MBR代替

c)当读取磁盘末尾时,用上表中0×10400处假数据代

结语

这是几年前逆向分析的基于MBR的bootkit型样本(来自国外),运行相当稳定(也尝试过改造来从BIOS启动,但主板芯片的差异,以及植入BIOS的程序兼容性很难保证,最终稳定性不好)该样本当时未在国内传播,逆向老外的样本过程中发现各种细节处理的非常到位,师夷长技以自强 。^_^

*本文原创作者: expsky ,本文属FreeBuf原创奖励计划,未经许可禁止转载

原文  http://www.freebuf.com/articles/system/109515.html
正文到此结束
Loading...