首页
社区
课程
招聘
编写一个简单的 SoftICE 命令扩展
发表于: 2005-5-2 21:00 7072

编写一个简单的 SoftICE 命令扩展

2005-5-2 21:00
7072

编写一个简单的 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


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 342
活跃值: (323)
能力值: ( LV9,RANK:450 )
在线值:
发帖
回帖
粉丝
2
绝对支持.
2005-5-2 21:43
0
雪    币: 210
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
UP~~~~~~~~~~~
2005-5-2 22:46
0
雪    币: 97697
活跃值: (200829)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
4
好文章。。。
2005-5-2 23:28
0
雪    币: 603
活跃值: (617)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
5
强人~
2005-5-2 23:57
0
雪    币: 288
活跃值: (415)
能力值: ( LV9,RANK:290 )
在线值:
发帖
回帖
粉丝
6
支持~
虽然现在多数是用OD
2005-5-3 00:07
0
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
7
支持 !
驱动我不懂,还在学习
2005-5-3 08:49
0
雪    币: 389
活跃值: (912)
能力值: ( LV9,RANK:770 )
在线值:
发帖
回帖
粉丝
8
专业的,职业化的。
2005-5-3 14:37
0
游客
登录 | 注册 方可回帖
返回
//