[调试逆向] [原创]360通杀5代机器狗工具驱动部分分析

发布者:gjden
发布于:2010-11-16 21:26
360通杀5代机器狗工具驱动部分分析

这个工具应该出好久了,由于入门比较晚,机器狗横行时,我还在学校里肯C++.这也是上周六,周日弄的,
这个工具也是无意间看到的,就把它下下来,想看看到底怎么做的,也当是练习练习,感觉逆向中学到了不少东西.
完全是静态分析,没有一点调试,如有错误,请指出!

OD加载运行工具,提取驱动文件,文件名是随机的,我这次提取出了文件名为01d73f48.sys,这个驱动做了很多HOOK,
而且HOOK类型也很丰富,有SSDT HOOK,IRP HOOK,EAT HOOK,INLINE HOOK等,MJ的对象劫持技术也用上了,看着貌似很强大.
这里做一个功能简单说明,由于程序比较大,很多地方为了偷懒,就直接F5了,呵呵.代码比较多,我就不贴代码了,具体看IDB.
本人水平有限,很多地方可能不准确,甚至是错误的,望大家指出.

1.驱动首先创建设备名为"\Device\DRIECTX0"设备,符号连接名为"\DosDevices\DRIECTX1",然后注册如下几个MajorFunction
IRP_MJ_CREATE,
IRP_MJ_CLOSE,
IRP_MJ_DEVICE_CONTROL
派遣例程均有sub_10520处理;
在派遣例程中,实际只处理了IRP_MJ_DEVICE_CONTROL,对IRP_MJ_CREATE和IRP_MJ_CLOSE是直接完成返回的.
此函数调用另外一个函数来完成IOCONTROL CODE的处理,有两个需要响应的控制码.
0xC07FE000
0xC07FE004
在0xC07FE000中根据用户传入的事件句柄,获取事件句柄的对象,这个对象是在内核中是全局的.
而在0xC07FE004是用来获取驱动处理信息的控制码,这里是获取驱动得到线程ID和进程ID(在后面有说明),
还有一个标志驱动处理类型的标志(这个数据跟用户态可能有很到关系,由于没有去逆向用户态程序,
也不能确定其具体功能,暂且这样认为),具体看idb.

2.接着便调用sub_12602()函数,这个函数便是此驱动的核心,此函数做了如下工作.
HookIoFreeIrp
HookIofCallDriver
获取文件\SystemRoot\system32\drivers\atapi.sys相关信息
获取文件\SystemRoot\system32\drivers\classpnp.sys相关信息
HookDiskDriver
HookClasspnpClassInit()
HookTopDriver \Device\Harddisk0\\DR0
HookTopDriver \Device\HarddiskVolume1
如果上两个设备顶层驱动HOOK成功,则继续做如下事情,否则恢复前面做的HOOK:
SaveDeviceObjectForDiskVolume
HookSCSI0
HookIoCreateFile
EAThookIofCallDriver
ObjectHijackAtapi
HookSSDTForSetInformationProcess
接下来,一个一个看

i.
对于HookIoFreeIRP这个函数,首先使用了指令cpuid来确定CPU的类型和支持.然后搜索函数IoFreeIRP的跳转
指令特征0x25FF,根据不同类型的CPU来确定如何修改IoFreeIRP()函数的入口处,入口可能会有两种,一种是
在入口处直接就是一个跳转,另一种是在执行几条指令后跳转,我虚拟机里的就是这种情况,
nt!IoFreeIrp:
804ef3f4 8bff            mov     edi,edi
804ef3f6 55              push    ebp
804ef3f7 8bec            mov     ebp,esp
804ef3f9 5d              pop     ebp
804ef3fa ff250cd25480    jmp     dword ptr [nt!KeTickCount+0x146c (8054d20c)]
804ef400 cc              int     3
跳转到8054d20c所指向的地址0x804ef406处
0x804ef406
真正的IoFreeIRP入口
804ef406 8bff            mov     edi,edi
804ef408 55              push    ebp
804ef409 8bec            mov     ebp,esp
804ef40b 53              push    ebx
804ef40c 56              push    esi
804ef40d 8b7508          mov     esi,dword ptr [ebp+8]
804ef410 66833e06        cmp     word ptr [esi],6
804ef414 57              push    edi
,至于如何修改入口这里分很多情况,根据CPU类型不同做的操作不同,但是思路都差不多,这里我就以具体一种情况分析,
这种情况是入口满足上面的入口方式,CPU为奔腾以上并且支持SSE2和CPU扩展,修改入口处为eb 01 f9 90 8b ff 90 e9
也就是如下代码:
EB 01         JMP SHORT next
F9            STC
next:
90            NOP
8BFF          MOV EDI,EDI
90            NOP
E9 xxxxxxxx   JMP MyIoFreeIrp
再在MyIoFreeIrp删除Irp Buffer中的Irp,然后调用原IoFreeIrp,这里的BUFFER形如:
BUFFER
{
int IrpCount;
pIRP[MAX_Len];
}
buffer是动态分配的,长度为0x40004,在idb我改名为Globe_IrpBuffer,IrpCount站4个字节,IRP部分可放0x1000个Irp指针.
这个buffer在disk,ftdisk驱动的设备栈顶层驱动中,被hook的Write,InteralDeviceControl例程中和被hook的
IofCallDriver被填充,但是IRP不能重复.并且在disk驱动中被hook的Write,RoutineInternelDevice例程中和被hook的IoFreeIrp中删除.

ii.IofCallDriver HOOK
也分两种情况处理,如果开始处有跳转,则将真的入口处到函数结束复制到一个分配的缓冲区中,如果开始出没有跳转,这
如果支持PAE,跳转前有一个NOP.

nop
jmp MyIoCallDriver        
:
:
RealIoCallDriver:
push MyIoCallDriver
ret

不支持PAE

jmp MyIoCallDriver      
:
:
RealIoCallDriver:
push MyIoCallDriver
ret

然后在MyIoCallDriver中检查HOOK是否被恢复,被回复则重置.最后调用正真的RealIofCallDriver,这里的RealRealIofCallDriver
指向的是那个分配的缓冲区.这里有两出执行点,一个jmp过去的,一个事push,ret过去的.
我虚拟机上的原始入口处:
kd> u IofCallDriver
nt!IofCallDriver:
804ef120 ff2500d25480    jmp     dword ptr [nt!KeTickCount+0x1460 (8054d200)]
804ef126 cc              int     3
804ef127 cc              int     3

804ef0e8
nt!IoBuildPartialMdl+0xbc:
804ef0e8 fe4a23          dec     byte ptr [edx+23h]
804ef0eb 8a4223          mov     al,byte ptr [edx+23h]

iii.
获取文件\SystemRoot\system32\drivers\atapi.sys相关信息
获取文件\SystemRoot\system32\drivers\classpnp.sys相关信息
这里有一个自定义的结构体,包含三个成员来标志一个文件,含有文件index(这个不知道跟句柄有什么关系),文件信息类的信息.
获取这这个文件的信息以便在在HOOk的IoCreateFile判定当前打开的是否是这个两个文件,是则设置文件句柄为NULL,返回.

iv
HOOK "Driver\Disk" 磁盘驱动,HOOK了Read,Write,DeviceControl,InternelDeviceRoutine.
在HOOK read中如果在当前栈上上层驱动存在classpnp.sys,atapi.sys则直接完成,其他放过.

v
HOOK classpnp,在这里虽然说是HOOK classpnp,但是我们都知道classpnp是没有设备的,其实就是一个函数提供库而已.
我们实际HOOK的DISK驱动的派遣例程.在此驱动的ClassInitialize()中搜寻disk驱动的例程,如果搜到DeviceControl,InternalDeviceControl,Write例程, 
则HOOK,新的例程跟disk驱动的是一样的.

vi
Hook设备
\Device\Harddisk0\DR0
\Device\HarddiskVolume
的设备栈的顶层驱动的WriteRoutine,InteralDeviceControl,也就是DISK,FTDISK的过滤驱动如果机器狗过滤了这两个设备栈,
那就是HOOK机器狗的驱动例程.并且保存:在New_WriteInteralDeviceControlRoutineTop中如果当前设备的驱动为DISK驱动
设备栈的顶层驱动或者是FTDISK驱动设备栈的顶层驱动(既是在HOOk时保存的DiskTopDriverObject,DiskVolumeTopDriverObject),
则放过其中Write,InteralDeviceControl请求,否则设置
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = 0xC0000013u;
IofCompleteRequest(Irp, 0);
return 0xC0000013u;

vii
如果VI步的两次HOOK都成功的话,则继续,否则恢复以上进行的HOOK.

1.保存卷驱动ftdisk的其他磁盘卷设备(不包含Device\HarddiskVolume1)到一个全局数组中,以供函数
CurrentDeviceIsInVolumeDevice(IRP)判定当前栈空间中是否存在此设备.

2.HOOK scsi的DeviceControl,InteralDeviceControl
如果存在设备\GLOBAL??\Scsi0: 或者\??\Scsi0:则进行HOOK DeviceControl,InteralDeviceControl.
在HOOK函数中根据IRP->IoStackLocatoion,找到完成例程,判定完成例程是是否在Classpnp.sys驱动模块中,
如果不在则完成IRP返回,同时保存当前进程ID,线程ID,并设置一个全局事件.

3,HookIoCreateFile
从开始搜索函数IopCreateFile,搜索特征为ff7508e8.
8056cb69 56              push    esi
8056cb6a 56              push    esi
8056cb6b ff753c          push    dword ptr [ebp+3Ch]
8056cb6e ff7538          push    dword ptr [ebp+38h]
8056cb71 ff7534          push    dword ptr [ebp+34h]
8056cb74 ff7530          push    dword ptr [ebp+30h]
8056cb77 ff752c          push    dword ptr [ebp+2Ch]
8056cb7a ff7528          push    dword ptr [ebp+28h]
8056cb7d ff7524          push    dword ptr [ebp+24h]
8056cb80 ff7520          push    dword ptr [ebp+20h]
8056cb83 ff751c          push    dword ptr [ebp+1Ch]
8056cb86 ff7518          push    dword ptr [ebp+18h]
8056cb89 ff7514          push    dword ptr [ebp+14h]
8056cb8c ff7510          push    dword ptr [ebp+10h]
8056cb8f ff750c          push    dword ptr [ebp+0Ch]
8056cb92 ff7508          push    dword ptr [ebp+8]
8056cb95 e882f2ffff      call    nt!IopCreateFile (8056be1c)
8056cb9a 3bc6            cmp     eax,esi
然后将其修改为我们自己的函数地址NewIopCreateFile
在函数中如果打开的文件是classpnp.sys,atapi.sys,则设置返回句柄为NULL.

4.EAThookIofCallDriver
这里是从内核ntoskrnl.exe的Export table中获取IoCallDriver,IofCallDriver的RVA,并把已经安排好的一个21字节的
BufferBuffer_21_IofCallDriver相对于ntoskrnl.exe基址的RVA替换掉前面获取的RVA,而在buffer里执行16条nop指令后跳转到
MyIofCallDriver,MyIoCallDriver中

EAT
|
|
|----RVA of function
|--->Buffer_21_IofCallDriver---+
|----RVA of function           |
|                              |
|                              |
              +----------------+
              |
              V
Buffer_21__IofCallDriver:
90909090
90909090
90909090
90909090
JMP MyIofCallDriver

EAT
| .
| .
|----RVA of function
|--->Buffer_21_IofCallDriver---+
|----RVA of function           |
| .                             |
| .                             |
              +----------------+
              |
              V
Buffer_21__IofCallDriver:
90909090
90909090
90909090
90909090
JMP MyIofCallDriver

其中MyIoCallDriver直接调用MyIofCallDriver.
MyIofCallDriver同时也就是前面HOOK IoCallDriver的新函数.在这里主要作用在前面提到的IRP BUFFER中保存当前IRP.

5.ObjectHijackAtapi
这里使用了MJ在tophet中提到的对象劫持技术,对象劫持技术也不多说了,去看看tophet文档,对象劫持技术源码貌似网上有.
不过这里仅仅用于获取发出IRP请求的线程的进程ID和线程ID以供返回给用户态请求.

6.HookSSDTForSetInformationProcess
 如果信息类为16(ProcessUserModeIOPL),则返回初始化失败0xC0000022,以防止用户模式的程序通过ProcessUserModeIOPL
给当前进程赋予IO 操作权限直接向磁盘控制器IO端口发送读写指令.
上传的附件:

声明:该文观点仅代表作者本人,转载请注明来自看雪