编写一个简单的 SoftICE 命令扩展
作者: 一块三毛钱
邮箱: zhongts@163.com
日期: 2005.5.2
本文编写了一个简单的 SoftICE 命令扩展(也就是插件),添加了两条扩展命令 zts_pestruct 和 zts_string。可以查看 PE 头结构和查找指定内存范围的所有字符串。
参考资料[1] 中给出了一种编写 SoftICE 命令扩展的办法,先编写一个 WinDBG 命令扩展,然后通过 SoftICE 自带的 kd2sys 工具把它转换成内核驱动程序,可以在 SoftICE 中使用。但是 [1] 文介绍的方法不能实现卸载的功能,所以我想直接写一个驱动程序来实现动态加载和卸载的功能。因为 kd2sys 的作用就是在注册表里面添加对应的键值,把从 kernel32, ntdll, msvcrt.dll 等导入的函数指向 ntice.sys。所以只需要自己写一个小程序添加注册表键值,那么可以直接写驱动程序而不需要先编写一个动态库文件然后转换成驱动程序,而且可以动态卸载。于是按照 [1] 文介绍的方法并参考 [2] [3] 编写了一个简单的驱动,发现可以启动驱动程序,不过在 SoftICE 中并没有我所实现的扩展命令,跟踪后发现 SoftICE 不会加载我的内核驱动,好像是因为我的驱动程序没有调试信息,于是又修改编译连接选项给驱动添加调试信息,这时候虽然可以看到扩展命令,也可以执行扩展命令,但是加载过程中又出现蓝屏错误。在下对驱动编程是一个菜菜鸟,对于跟踪调试也是一个菜菜鸟,实在找不出问题出在哪里。那位大侠如果能够解释这个问题,盼望能够和大家一起分享。
看来采用 [1] 文介绍的方法指望 SoftICE 主动加载我们编写的插件是不太可能,通过学习 [4] 提供的源代码我找到了另外的办法,也就是找到 SoftICE 加载插件的函数,我们直接调用它来加载我们的插件。下面开始分析代码:
DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
LOCAL _status : NTSTATUS
invoke DbgPrint, $CTA0("___________________________DriverEntry\n")
mov _status, STATUS_DEVICE_CONFIGURATION_ERROR
mov eax, pDriverObject
assume eax:ptr DRIVER_OBJECT
mov [eax].DriverUnload, offset _DriverUnload
assume eax:nothing
; int 3
invoke _si_Init
.if eax
lea eax, DriverEntry
and eax, 0fffff000h
.while TRUE
cmp word ptr [eax], 'ZM'
.break .if ZERO?
sub eax, 4096
.endw
invoke _si_LoadKDE, eax
mov _status, STATUS_SUCCESS
.endif
mov eax, _status
ret
DriverEntry endp
_DriverUnload proc pDriverObject:PDRIVER_OBJECT
invoke DbgPrint, $CTA0("___________________________bye bye ...\n")
; int 3
invoke _si_ClearBangFuncsArray
ret
_DriverUnload endp
DriverEntry 是驱动程序的入口,在该函数内首先指定卸载时的函数,然后调用 _si_Init 函数查找一些 ntice.sys 的内部函数和结构。_si_LoadKDE 就是 SoftICE 用来加载插件的函数,我们直接调用它来加载我们的插件,传入的参数是我们编写的插件模块的内存地址。_DriverUnload 是卸载驱动的函数,在该函数中我们把插件添加的命令移去,通过 _si_ClearBangFuncsArray 函数来实现。
_si_Init proc uses ebx
LOCAL _IceBase, _IceCodeBase, _IceCodeSize
mov eax, offset HeapReAlloc
mov eax, dword ptr [eax+2]
mov eax, [eax]
and eax, 0fffff000h
.while TRUE
cmp word ptr [eax], 'ZM'
.break .if ZERO?
sub eax, 4096
.endw
mov _IceBase, eax
invoke DbgPrint, $CTA0("NTice MZ-header found at %08X\n"), eax
mov eax, _IceBase
assume eax : ptr IMAGE_DOS_HEADER
mov ebx, [eax].e_lfanew
add eax, ebx
assume eax : ptr IMAGE_NT_HEADERS
mov ebx, [eax].OptionalHeader.BaseOfCode
add ebx, eax
mov _IceCodeBase, ebx
mov ebx, [eax].OptionalHeader.SizeOfCode
mov _IceCodeSize, ebx
invoke _InString, _IceCodeBase, _IceCodeSize, addr LoadKDE_Signs, 27
test eax, eax
jz exit_0
add eax, _IceCodeBase
add eax, 12
add eax, dword ptr [eax]
add eax, 4
mov _si_LoadKDE, eax
invoke DbgPrint, $CTA0("_si_LoadKDE found at %08X\n"), eax
invoke _InString, _IceCodeBase, _IceCodeSize, addr BangFuncsArray_Ds32_Signs, 42
test eax, eax
jz exit_0
add eax, _IceCodeBase
mov ebx, dword ptr [eax+38]
mov si_pKDEFuncNum, ebx
mov ebx, dword ptr [eax+7]
sub ebx, 4
mov si_pBangFuncsArray, ebx
invoke _InString, _IceCodeBase, _IceCodeSize, addr Expression2Integer_Signs, 43
test eax, eax
jz exit_0
add eax, _IceCodeBase
add eax, 13
add eax, dword ptr [eax]
add eax, 4
mov _si_Expression2Integer, eax
invoke DbgPrint, $CTA0("_si_Expression2Integer found at %08X\n"), eax
mov eax, 1
ret
exit_0:
sub eax, eax
ret
_si_Init endp
_si_Init 函数首先查找 ntice.sys 在内存中的地址,为了实现这个功能,我们从 ntice.sys 导入了 HeapReAlloc 函数
mov eax, offset HeapReAlloc
mov eax, dword ptr [eax+2]
mov eax, [eax]
这三条指令可以取得 ntice.sys 中 HeapReAlloc 函数的地址,有一点 PE 结构知识再反汇编一下就可以明白为什么。然后查找 Dos Header 从而找到 ntice.sys 在内存中的地址。接下来就是在 ntice.sys 的代码段中查找几个有用的内部函数和结构,比如 _si_LoadKDE 函数。
LoadKDE_Signs db 8Bh, 46h, 17h, ;mov eax, [esi+17h]
0A3h, 0, 0, 0, 0, ;mov dword_19AC00, eax
0FFh, 76h, 17h, ;push dword ptr [esi+17h]
0E8h, 0, 0, 0, 0, ;call _LoadKDE
83h, 4Eh, 23h, 2h, ;or dword ptr [esi+23h], 2
5Fh, ;pop edi
33h, 0C0h, ;xor eax,eax
5Eh, ;pop esi
0C2h, 8h, 0 ;ret 0008
invoke _InString, _IceCodeBase, _IceCodeSize, addr LoadKDE_Signs, 27
test eax, eax
jz exit_0
add eax, _IceCodeBase
add eax, 12
add eax, dword ptr [eax]
add eax, 4
mov _si_LoadKDE, eax
invoke DbgPrint, $CTA0("_si_LoadKDE found at %08X\n"), eax
调用 _InString 函数查找 LoadKDE_Signs 特征字符串,找到后就可以在偏移 10 的地方找到 call _LoadKDE 命令,那么偏移 12 的地方存储的就是 _LoadKDE 函数相当于 call _LoadKDE 指令的偏移值。把这样那样的偏移计算一下就可以找到 _si_LoadKDE 函数的地址。其他几个函数和结构也是一样。大家可以参考 [4] 添加更多的内部函数,也可以自己反汇编 ntice.sys 找到更多的内部函数。
_si_ClearBangFuncsArray proc uses ebx ecx edx esi edi
LOCAL _ZtsICECodeBase, _ZtsICECodeEnd
lea eax, _si_Init
and eax, 0fffff000h
.while TRUE
cmp word ptr [eax], 'ZM'
.break .if ZERO?
sub eax, 4096
.endw
assume eax : ptr IMAGE_DOS_HEADER
mov ebx, [eax].e_lfanew
add eax, ebx
assume eax : ptr IMAGE_NT_HEADERS
mov ebx, [eax].OptionalHeader.BaseOfCode
add ebx, eax
mov _ZtsICECodeBase, ebx
mov eax, [eax].OptionalHeader.SizeOfCode
add eax, ebx
mov _ZtsICECodeEnd, eax
sub eax, eax
mov ebx, si_pKDEFuncNum
mov ebx, dword ptr [ebx]
mov edx, si_pBangFuncsArray
.while eax<ebx
;循环取出每一个命令的地址,判断是不是 ZtsICE 添加的命令
mov ecx, [edx]
.if ecx>=_ZtsICECodeBase && ecx<=_ZtsICECodeEnd
;如果是,则把后面的所有命令往前移,覆盖刚才的命令
dec ebx
mov edi, edx
mov esi, edx
add esi, 268 ;268 是每一条命令占用的字节
mov ecx, ebx
sub ecx, eax
imul ecx, 268/4
rep movsd
.else
inc eax
add edx, 268
.endif
.endw
mov eax, si_pKDEFuncNum
mov dword ptr [eax], ebx
ret
_si_ClearBangFuncsArray endp
_si_ClearBangFuncsArray 函数用来清除插件添加的扩展命令。si_pKDEFuncNum 指向扩展命令(指得是所有的扩展命令,不一定是我们编写的插件添加的)的数目,si_pBangFuncsArray 指向存储扩展命令结构的地方。每一条扩展命令占用 268 字节(这是针对 DS3.2 版本而言,其他的版本可以参考[4])。循环判断每一条命令的地址是不是在我们编写的插件的代码范围内,从而判断是不是我们添加的命令,如果是就把后面的所有命令结构往前移,覆盖刚才的命令,相当于删除了刚才的命令。
上面就是整体的框架结构,具体的可以参考附件代码 ZtsICE。下面是我给添加的两条扩展命令 zts_pestruct 和 zts_string,命令如何实现的也可以参考代码,需要说明的是 GetExpression 和 _si_Expression2Integer 函数的用法。当执行扩展命令的时候经常需要传递命令行参数,对于一般参数可以直接引用 args 变量,对于一个数值参数可以调用 GetExpression 函数,多个数值参数可以调用 _si_Expression2Integer 函数。调用这两个函数而不是自己实现一个函数的好处是可以处理好像 ebx+1000 的表达式。
最后再说一下安装的问题,通过给注册表添加特定的键值可以实现安装,但是需要重新启动,原因是因为直接给注册表添加键值不能够马上更新 SCM 数据库中的内容,需要重新启动才行,kd2sys 就有这个问题。可以参考 [6] 了解为什么。解决办法就是通过 [5] 自带的 KmdManager 工具注册驱动,然后打开注册表添加 DependOnService 键值。也可以自己写一个小程序实现上面的过程,很简单,可以参考附件中的代码。
参考资料:
[1] MSDN系列(11)--给SoftICE写插件
http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2204
[2] Debugging Tools for Windows SDK
[3] DDK
[4] IceExt v0.66
http://stenri.pisem.net/
[5] KmdKit
http://www.freewebs.com/four-f/
[6] KmdTut 中文翻译
http://asm.yeah.net
附件:ZtsICE.rar
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课