Icesword 驱动部分分析

icesword 是通过 PspCidTable 这个表来遍厉进程的, PspCidTable 是一个没有被 ntoskrnl . exe 导出的。

这就涉及到如何定位PspCidTable 的问题。icesword 是通过搜索特征串的方式定位 PspCidTalbe . PspCidTable 是一个 HANDLE_TALBE 结构 .
PsLookupProcessByProcessId 函数中会引用 PspCidTalbe 变量。icesword 从psLookupProcessByProcessId 函数的前几十个字节内搜索 PspCidTalbe 变量。那有人就可能会想,那我把 PsLookupProcessByProcessId 这个函数给 patch 了 , 他不就找不到
PspCidTalbe 变量了吗? 对你说的没错,是可以这样。当然我们能想到这点。icesword 的作者也能想到这点。作者为了防止你这么做也采取了相应的对策。他采取的对策就是运行前效验恢复的方法。它在执行关键的系统函数时会比较函数的头几十个字节是否被修改。
如果被修改了它就会把被修改的给恢复成系统原来的内容,那我们可能就会提出一个疑问,如果我在它启动之前就 patch 了他要效验
的函数,它怎么能知道系统原来的内容呢?这个问题提的好。

 

现在就让我们来看看 icesword 的作者是怎么做到这一点的。我们就拿PsLookupProcessByProcessId 函数来说吧。PsLookupProcessByProcessId 函数是 ntoskrnl . exe 文件导出的。

作者不是用我们通常
的方法来定位 PsLookupProcessByProcessId 函数的,也就是说 ispubdrv . sys 并没有导入这个函数。同样也没有通过MmGetSystemRoutineAddress 函数来得PsLookupProcessByProcessId的地址。那他是怎么获的 PsLookupProcessByProcessId 的地址的呢?
那可能有的人就会想到他是通过自己打开ntoskrnl . exe 文件然后分析导出函数做的,对icesword 的作者就是这么做的。当然他这里还是
有技巧在的。

作者操作文件也没有用我们写驱动程序时常用的操作文件的方式来访问文件。我们平时在驱动程序里面打开和读写文件时大多是使用 ZwCreateFile , ZwOpenFile , ZwReadFile , ZwWriteFile , NtCreateFile 等等函数 . 这样的话作者就可以避免一些文件过滤程序。打开文件用 IoCreateFile 函数。在读文件的时候没有用正常的文件相关的API 函数,而是用的 IofCallDriver 来做的。

 

因为
本人对驱动不熟悉,也不清楚他 IoCallDriver 做什么用,只知道调用完了这个 IoCallDriver 函数后,数据就被读出来了。 这就防止了通常的文件读写过滤程序。自己分析 pe 文件,找出他要定位的函数的导出地址。然后他会把函数的前 几十个字节读出来,当然这里又涉及到代码重定位的问题。(熟悉 pe 的人可能都会理解重定位的问题的问题,这里我就不讲了。如果有不理解的可以自己参考 PE 文件格式的相关
文档。)作者自己把读出来的代码片段自己做重定为。这样他就得到了函数开头部分的原始代码
。作者通过这种方法,就获得了原始的效验数据
。这样他运行系统函数的时候就保证函数没有被 patch 过。当然了如果你不怕麻烦的话可以把自己的 patch 放到更深的调用路径上


这样即使是用 windbg , softice , syser 调试器下断点调试,也是断不住的。当然了你也不能用调试器调试,因为 icesword . exe 会在一个timer 中不停的重新设置 int 1 , int 3 的中断处理函数。设置成 windows ntoskrnl . exe 中的缺省处理函数。即使你用硬件断点寄存器也是不管用的。那有的人就会说既然设置成 windows ntoskrnl . exe 中的缺省处理函数就可以使用 windbg 双机调试 . icesword 也做了处理 ,
icesword 会通过 KdDebuggerEnabled 变量判断是否允许内核调试。如果允许调试的话 . icesword 会调用 KdDisableDebugger 函数禁止内核调试。

icesword.exe 在执行的时候会放出一个驱动程序 ispubdrv.sys,icesword.exe 装载这个驱动,这个驱动安装后就不会卸载。直到系统重新启动。这可能是因为驱动中调用了PsSetCreateThreadNotifyRoutine 函数.下面是这个函数在 ddk 中的介绍。使用此种方法不然卸载

///
PsSetCreateThreadNotifyRoutine registers a driver-supplied callback that is subsequently notified when a new thread is created and when such a thread is deleted.

NTSTATUS
PsSetCreateThreadNotifyRoutine(
IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine
);
Any driver that successfully registers such a callback must remain loaded until the system itself is shut down.
//

虽然ddk 中说成功的调用PsSetCreateThreadNotifyRoutine 函数就要保留驱动直到系统重新启动,但是还是有办法做到可以卸载的。

Icesword 是如何列出隐藏进程?

Icesword 是通过 PspCidTable 这个表来遍厉进程的,PspCidTable 是一个没有被 ntoskrnl.exe 导出的。这就涉及到如何定位PspCidTable 的问题。icesword 是通过搜索特征串的方式定位 PspCidTalbe. PspCidTable 是一个 HANDLE_TALBE 结构. PsLookupProcessByProcessId 函数中会引用 PspCidTalbe 变量。Icesword 从 PsLookupProcessByProcessId 函数的前几十个字节内搜索 PspCidTalbe 变量。那有人就可能会想,那我把 PsLookupProcessByProcessId 这个函数给 patch 了,他不就找不到 PspCidTalbe 变量了吗? 没错,是可以这样。当然我们能想到这点。Icesword 的作者也能想到这点。作者为了防止你这么做,也采取了相应的对策。他采取的对策就是运行前效验恢复的方法。它在执行关键的系统函数时会比较函数的头几十个字节是否被修改。如果被修改了它就会把被修改的给恢复成系统原来的内容,那我们可能就会提出一个疑问,如果我在它启动之前就 patch 了他要效验的函数,它怎么能知道系统原来的内容呢?这个问题提的好。现在就让我们来看看 icesword 的作者是怎么做到这一点的。我们就拿
PsLookupProcessByProcessId 函数来说吧。 PsLookupProcessByProcessId 函数是 ntoskrnl.exe 文件导出的。作者不是用我们通常的方法来定位 PsLookupProcessByProcessId 函数的,也就是说 ispubdrv.sys 并没有导入这个函数。同样也没有通过MmGetSystemRoutineAddress 函数来得PsLookupProcessByProcessId的地址。那他是怎么获的 PsLookupProcessByProcessId 的地址的呢?

那可能有的人就会想到他是通过自己打开ntoskrnl.exe 文件然后分析导出函数做的,对icesword 的作者就是这么做的。当然他这里还是有技巧在的。作者*作文件也没有用我们写驱动程序时常用的*作文件的方式来访问文件。我们平时在驱动程序里面打开和读写文件时大多是使用ZwCreateFile, ZwOpenFile, ZwReadFile, ZwWriteFile, NtCreateFile 等函数,这样的话作者就可以避免一些文件过滤程序。作者打开文件用 IoCreateFile 函数。在读文件的时候作者没有用正常的 文件相关的 API 函数,而是用的 IofCallDriver 来做的。因为本人对驱动不熟悉,也不清楚他 IoCallDriver 做什么用,只知道调用完了这个 IoCallDriver 函数后,数据就被读出来了。 这就防止了通常的文件读写过滤程序。自己分析 pe 文件,找出他要定位的函数的导出地址。然后他会把函数的前 几十个字节读出来,当然这里又涉及到代码重定位的问题。(熟悉 pe 的人可能都会理解重定位的问题的问题,这里我就不讲了。如果有不理解的可以自己参考 PE 文件格式的相关文档。)作者自己把读出来的代码片段自己做重定为。这样他就得到了函数开头部分的原始代码。作者通过这种方法,就获得了原始的效验数据。这样他运行系统函数的时候就保证函数没有被 patch 过。当然了如果你不怕麻烦的话可以把自己的 patch 放到更深的调用路径上。这样即使是用 windbg, softice, syser 调试器下断点调试,也是断不住的。当然了你也不能用调试器调试,因为Icesword.exe 会在一个 timer 中不停的重新设置 int 1,int 3 的中断处理函数。设置成 windows ntoskrnl.exe 中的缺省处理函数。即使你用硬件断点寄存器也是不管用的。那有的人就会说既然设置成 windows ntoskrnl.exe 中的缺省处理函数就可以使用 windbg 双机调试.icesword 也做了处理, icesword 会通过 KdDebuggerEnabled 变量判断是否允许内核调试。如果允许调试的话. icesword 会调用 KdDisableDebugger 函数禁止内核调试。

这里顺便在说两个分析 icesword 中遇到的反调试小陷阱这里把代码片段列出来,希望作者原谅

.text:000xxxF0 mov [ebp+IoControlCode], eax
.text:000xxxF3 mov eax, [esp+5Ch-6Ch] ; 反调试代码
.text:000xxxF7 push eax
.text:000xxxF8 mov eax, [esp+60h-6Ch]
.text:000xxxFC pop ebx
.text:000xxxFD cmp eax, ebx
.text:000xxxFF jz short loc_1240B ; 如果没有被调试则会跳转
.text:000xxx01 mov eax, 200EDBh
.text:000xxx06 not eax
.text:000xxx08 push eax
.text:000xxx09 pop edi
.text:000xxx0A stosd

.text:000xxxF3 mov eax, [esp+5Ch+6Ch]

当单步执行到这条指令或者在这条指令上设置断点的时候,因为当调试器在这条指令上弹出的时候会用到被调试程序的堆栈来保存 EFLAGS,CS,EIP, (如果 int 1,或 int 3 处理函数用任务门就可以解决这个问题。)例如 当代码执行到这条指令时 ESP = 805E4320h 执行完这条指令是 eax 的值为 [ESP+5Ch-6Ch]=[ESP-10h]=[805E4320h-10h]=[805E4310h] 的值。当单步执行到 .text:000xxxF8 mov eax, [esp+60h-6Ch] 指令的时候 ESP=805E432Ch 以为其中入栈了一个 eax 所以 ESP=805E432Ch, 执行完 .text:000xxxF8 mov eax, [esp+60h-6Ch] 条指令的时候 eax = [ESP+60h-6Ch]=[ESP- Ch]=[805E432Ch-Ch]=[805E4310h] 如果不调试的情况下 读的是同一个地址的值,所以两个值比较应该是相同的 也就是 .text: 000xxxFD cmp eax, ebx 这条指令的比较结果应该是相同的。这个指令 .text:000xxxFF jz short loc_1240B 执行后直接跳转到。如果是被调试器调试的情况下 .text:000xxxFF jz short loc_1240B 不会跳转。 如果不跳转时下面的代码 会覆盖掉系统的当前 ETHREAD 指针。接下来在调用很多系统函数都会导致系统崩溃,并且是崩溃到系统模块里面,这样给你定位错误带来误导。

.text:000xxx68 push 1 ; Alignment
.text:000xxx6A push 40h ; Length
.text:000xxx6C push CurrentEProcessObject ; Address
.text:000xxx72 call ds:ProbeForRead

这里是故意做个异常来实现跳转。如果你在 .text:000xxx72 call ds:ProbeForRead 指令上单步执行的时候调试器会跑飞了,也就是说从调试器退出了,没有继续跟踪下去。

接下来说我们的 PspCidTable 我们找到了 PspCidTable 变量后, PspCidTable [这个 HANDLE_TABLE 的句柄表中,保存着所有进程和线程对象的指针。PID(进程ID)和 ThreadID(线程ID)就是在这个句柄表中的索引。这个 HANDLE_TABLE 不属于任何进程,也没有链在 HANDLE_TABLE 链上。全局变量 PspCidTable 中是指向这个 HANDLE_TABLE 的指针。这个 HANDLE_TABLE 还有一点和别的 HANDLE_TABLE 都不同,就是它的 HANDLE_TABLE_ENTRY 中的第一个32bit 放着的是对象体指针(当然需要转换)而不是对象头指针(对象指针就是对象体指针)。] (特别注明 在[]的话不是俺写的是在网上抄来的这里特别感谢 “JIURL玩玩Win2k进程线程篇 HANDLE_TABLE” 文章的作者:JIURL ) 我们之要想到办法遍历这个 PspCidTable 句柄表就可以遍历到系统的所有进程。

icesword 为了遍历这个表他使用了系统为公开的 ntoskrnl.exe 的导出函数ExEnumHandleTable 。

icesword 定位到 ntoskrnl.exe 导出的 ExEnumHandleTable函数
这个函数是未公开的函数。这个函数的函数原形可能是 VOID STDCALL ExEnumHandleTable (PULONG HandleTable, PVOID Callback, PVOID Param, PHANDLE Handle OPTIONAL); 其中的参数 PULONG HandleTable 就可以用 PspCidTable 做参数. PVOID Callback 的类型为 bool (*EXENUMHANDLETABLECALLBACK)

(HANDLE_TALBE_ENTRY*,DWORD PID,PVOID Param) 函数指针。PVOID Param 参数就是传送给回调函数的参数。PHANDLE Handle OPTIONAL 这个参数俺还没搞懂什么意思。在说俺也用不到他,所以也不管他了随他去吧。当调用 ExEnumHandleTable 函数的时候 函数在每次枚举到表中的一个句柄时都会调用一次回调函数。当调用的 Callback 回调函数返回值为 0 时继续枚举句柄表,如果返回 1 时则停止枚举。

吴琰翔2006-10-30 12:36:10

re:下面是刚才看到的一个分析文章...

下面是刚才看到的一个分析文章

IceSword有反调试功能,搞的我想研究一下它的原理都举步维艰。不过同样是pjf的作品FileMgr却没有设防,正好拿来开刀。
经过IrpMon、IrpTrace、WinObj、NTObjects、WinDbg等工具的左右夹击,终于发现FileMgr通过直接IoCallDriver(/Device/Ftdisk设备对象, 自定义的Irp);来绕过大部分的文件过滤驱动。然后再研究IceSword,果然是一样的原理。它和我准备用来实现knlls的原理一样,这种"英雄所见略同"既是偶然也是必然。虽说IceSword绕过系统API自己实现了大量的*作,但文件系统实在复杂,在Ftdisk之下就要考虑NTFS和FAT32之类的文件系统格式了,绕过系统支持不同的文件系统是不现实的,所以在磁盘类驱动层必然要把读写文件的控制权交给系统。
不过,FileMgr和IceSword还是棋差一招,调用IoCallDriver会经过Io管理器,由Io管理器根据DRIVER_OBJECT的MajorFunction[]来调用相应的Dispatch例程,只要修改MajorFunction[]就能hook发往Ftdisk的Irp了。我本以为IceSword会直接找到并调用Ftdisk各个Dispatch例程,但在IceSword启动的前或后加载我的钩子都能起效,可见IceSword还是用到了"不安全"的MajorFunction[]。
知道原理是一回事,"虎口拔牙"又是另一回事。

文件系统相关IRP太复杂了,实时性要求又高,在没完全搞清楚之前还是不要动手,先写出knlls再说。

今天又研究了一下,发现IceSword调用Ftdisk的Dispatch只是表面现象,Ftdisk上的钩子没有勾到什么有价值的数据。改成挂钩/FileSystem/NTFS,这才获得了所有访问文件系统的*作。如果是FAT32则需要挂钩/FileSystem/FastFat
我还没搞清除Ftdisk和ntfs/FastFat之间究竟是怎么个关系,Ftdisk应该是在具体的文件系统驱动之上提供统一的"接口"才对,但实际上它们既不以NextDevice联系,也不以AttachedTo/AttachedDevice联系,Hook到的数据完全搞不清楚其含义,于是我郁闷了。难道是通过DeviceNode?
刚刚开始探索文件系统结构,就已经找不着北了。看来写knlls任重道远啊。
ftdisk是位于ntfs这类filesystem以下..disk这类storage driver以上的一个value added的驱动程序
他在partmgr的帮助下完成一下value added的功能
比如disk mirror等等
至于ftdisk跟disk的联系跟filesystem的联系都不是使用attach的方式完成的...

跟filesystem的联系是通过vpb来完成的
跟disk这是通过io control传递device object的指针完成的

发送到ftdisk的fdo的irp大部分是io control
而发送到ftdisk的pdo的irp则几乎都是read write

系统引导以后..disk.sys被加载..成功以后windows向其发送一个query bus relationship的irp...这个irp首先到达的驱动不是disk.sys而是作为disk的class的upperfilter加载的驱动...partmgr.sys

partmgr.sys在这个irp上设置一个完成routine然后传递下去...disk会为其每一个分区创建一个pdo..

irp完成的时候..partmgr.sys的完成routine调用..对irp作进一步的处理

他解析返回的device relation结构
对于里面返回的每一个device object..如果是以前没有的现在新出现了..则构造一个volume arrival的iocontrol发送到ftdisk.


如果是以前有的而现在却没有了则发送一个volume removal到ftdisk
完成这些*作了以后ftdisk设置device relation的count成员为0...这样windows并不认为disk返回了很多pdo..以至于windows并不发送诸如start stop这类pnp的irp到disk为每个分区创建的pdo..当然这个工作是有人来完成的..partmgr会发送这些irp到disk去
要验证这个说法很简单..构造一个query bus relation发送到disk.sys就能看到pdo.而发送到partmgr.sys就什么都看不到
windows也不承认disk创建的设备是pdo.因为windows会为每个pdo创建device node.而这些设备并没有对应的device node

ftdisk接受到用disk本身的pdo跟disk创建的分区的pdo作为input产生的volume arrival的时候就创建一个pdo.并且保持这两个指针.这个新创建得pdo是一个FILE_DEVICE_DISK类型的pdo.windows在IoCreateDevice的检查这种类型.为其创建一个vpb结构.并关联上去.同时ftdisk为这个新创建的pdo注册一个特别的device interface...系统里面的mountmgr.sys在这个device interface上注册了一个notification..mountmgr.sys得到运行.为这个pdo分配一个盘符.并创建符号连接/dosdevices/c:这样的到ftdisk创建的pdo上...

在第一次访问这个新创建的pdo的时候.比如某程序要读取c:/boot.txt...windows解析这个名字发现c:是一个符号链接.重定向到ftdisk的pdo上.再检查这个pdo.发现附加得有vpb.再检查vpb发现没有filesystem mount在上面.于是通知各个系统里面注册了得filesystem开始尝试mount这个新的volume.mount成功以后修改vpb的一个指针指向filesystem在mount过程中为这个即将要使用的新分区所创建的一个device上面.这样就通过vpb把filesystem跟ftdisk连接到了一起.

大致的情况就是如此.其中disk.sys是有源代码的.fastfat也是有源代码的.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值