64位Windows环境的安全软件调试过程浅析
2018-09-12◆劳伟
◆劳 伟
64位Windows环境的安全软件调试过程浅析
◆劳 伟
(中国农业银行软件开发中心 北京 100000)
64位Windows下的安全软件驱动程序普遍通过ObRegisterCallbacks内核函数注册进程、线程操作回调函数实现自我保护,其调试方法与普通软件存在较大差异。本文描述了调试安全软件时解除进程和线程保护机制的操作过程,分析、证实了微软未公开的进、线程操作CallbackList链表结构。
64位;Windows;安全软件;ObRegisterCallbacks;CallbackList;调试
0 引言
安全软件作为软件的一种,运行出现问题时同样需要对其进行调试以定位原因。64位Windows系统引入了PatchGuard机制对内核的关键位置进行了监控,阻断未经授权的内核改动,因此安全软件普遍采用了微软官方推荐的通过ObRegisterCallbacks向内核注册进程、线程操作回调函数的自我保护机制,其调试过程相应地出现了很多变化。
1 进线程自我保护
进线程自我保护是指采用相关的技术保护自身的进程和线程不被跟踪调试或强行结束。
64位Windows环境下实现进线程自我保护的主要方法如下:
(1)采用Ring3 API Inline Hook[1]。该方法修改API函数的前几个字节,执行跳转指令至保护函数,通过保护函数对潜在的威胁进行阻断。该方法安全稳定,但容易被绕过。
(2)破坏PatchGuard保护层,对内核中的API函数进行Inline Hook[2]。该方法易引发争议,商业类安全软件基本不会采用该方法。
(3)通过ObRegisterCallbacks向内核注册进、线程句柄操作回调函数[3]。该方法为微软官方所推荐,也是目前安全软件在64位Windows环境下的常规做法。该方法通过内核回调安全软件注册的函数对进、线程操作进行监控,对包括跟踪、调试在内的潜在威胁操作进行阻断。本文描述的调试过程针对的就是采用此类保护机制的安全软件。
2 双机调试
调试通过ObRegisterCallbacks注册进、线程保护机制的安全软件,首先需要解除安全软件注册的回调函数,通常采取网络或串口双机调试的方式进行操作。
为简化描述,本文称被调试的64位Windows系统为目标系统,执行WinDbg输入调试指令的Windows系统为Host系统。
2.1 网络方式
(1)选择该方式的目标系统需同时满足Win8或以上版本、网卡符合微软所列型号的要求[4]。在目标系统以管理员身份执行下列命令配置内核网络调试模式。
bcdedit /debug on
bcdedit /dbgsettings net hostip:A.B.C.D port:N
A.B.C.D为Host系统的IP,N为WinDbg接收调试连接的监听端口。上述操作成功时,目标系统生成一个会话Key并回显至控制台,保存下来以便在Host系统配置WinDbg时使用。
目标系统有多个网卡时,需执行下列命令以指定向Host系统发送调试信息、接收调试指令的网卡。
bcdedit /set "{dbgsettings}" busparams BusNo.DevNo.FuncNo
打开设备管理器网卡属性的常规标签即可查看网卡PCI总线号(BusNo)、设备号(DevNo)、功能号(FuncNo)等参数。
完成上述操作后,关闭目标系统。
(2)在Host系统以管理员身份运行WinDbg, 点选Kernel Debug内核调试菜单项,打开NET网络标签页,输入监听端口号及目标系统生成的会话Key。
点确定按钮后,开始等待目标系统建立网络连接,如图1所示。
图1 等待目标系统建立连接示意
2.2 串口方式
目标系统部署在虚拟机环境时,推荐采用此方式在宿主机系统上对目标系统进行调试。
(1)以COM1串口连接为例,在目标系统以管理员身份执行下列命令。
bcdedit /debug on
bcdedit /dbgsettings serial debugport:1 baudrate:115200
(2)在宿主机系统上设置虚拟机管道串口,如图2所示。
图2 虚拟机管道串口设置示意
(3)在宿主机系统上以管理员身份运行WinDbg, 点选Kernel Debug菜单项,打开COM标签页,BandRate波特率输入115200,Port输入\.pipedebug,勾选Pipe和Reconnect选项。
点击确定按钮,即开始等待目标系统建立串口连接。
注意,WinDbg不保存内核调试参数,如需多次调试,推荐创建带运行参数的快捷方式以保存相关的设置。以串口调试为例,快捷方式中目标栏的运行参数可设置为:
-k com:pipe,port=\.pipedebug,resets=0,reconnect
3 解除安全软件的进程和线程保护机制
启动目标系统,确认安全软件完成启动后,在WinDbg指令窗口上按Ctrl+Break(或Ctrl+Fn+B)中断目标系统进入调试状态。
3.1 解除进程保护
(1)首先确认进程操作回调函数链表头的位置,即Process对象类型中CallbackList成员的内存地址。
执行下列指令查看Process对象类型的结构。
dt nt!_OBJECT_TYPE poi(nt!PsProcessType)
图3 Win7 x64的Process对象类型的结构示意
poi()指令获取指针所指对象的地址。PsProcessType是指向Process对象类型的指针。如图3左下浅灰标记所示,Win7 x64系统的进程操作回调函数链表头偏移位置是0xc0,因该位置在不同系统之间存在差异,本文后续表述用PlistHeadOffset对其进行指代。
(2)CallbackList是由表头节点和各表项节点构成的双向环形链表。迄今为止微软未公开表项节点的结构,已知的推论均源自相关人员对内核代码的静态分析[5]。
typedef struct _CALLBACK_ENTRY_ITEM {
LIST_ENTRY EntryItemList; // 16B
OB_OPERATION Operations; // 4B
ULONG Active; // 4B
CALLBACK_ENTRY* CallbackEntry; // 8B
POBJECT_TYPE ObjectType; // 8B
POB_PRE_OPERATION_CALLBACK PreOperation; // 8B
POB_POST_OPERATION_CALLBACK PostOperation; // 8B
__int64 unk; // 8B
} CALLBACK_ENTRY_ITEM,
*PCALLBACK_ENTRY_ITEM;
表项节点的成员推论如下:EntryItemList为指向下一节点的FLINK指针和指向上一节点的BLINK指针;Operations为操作类型的集合;Active为节点活动标记;CallbackEntry为指向回调入口的指针;ObjectType为指向触发回调的对象类型的指针;PreOperation和PostOperation为操作前、后回调的函数指针。
(3)执行下条指令显示进程操作回调函数链表的内容。
!list -x "dq @$extret L8"
poi(nt!PsProcessType)+PlistHeadOffset
图4 进程操作回调函数链表内容示意
如图4所示,fffffa80`01847d00是进程操作回调函数链表头的内存地址,减去偏移量0xc0等于fffffa80`01847c40,即目标系统内核中的Process对象类型的内存地址。
推论指出各表项节点的Operations、ObjectType、PreOperation的偏移位置分别为0x10、0x20、0x28。
表头节点为LIST_ENTRY结构,包括FLINK和BLINK两个分别指向第一个和最后一个表项节点的指针,无表项节点时两指针均指向表头,故图4右上划线标记处并非PreOperation,灰灰底标记处是否属于安全软件注册的PreOperation,还需进一步核实。
安全软件通过驱动程序注册进程操作回调函数时,传给内核的参数需满足下列要求[6]:
Operations:打开句柄、复制句柄操作均触发回调机制时,传入数值3。
ObjectType:传入PsProcessType,即操作Process对象类型时触发回调机制。
PreOperation:传入安全软件驻留内存的操作前回调函数的入口地址。
图4各表项节点的Operations(中间栏每块第二行划线标记低位4字节)均为3;ObjectType均指向Process对象类型(地址fffffa80`01847c40);PreOperation指向地址(灰底标记)均位于安全软件驱动程序可驻留的内存地址范围内。综上所述,图4各表项节点均具有进程操作回调节点的特征。
(4)执行下条指令显示进程操作前回调函数所属的模块信息,以确认安全软件的进程操作回调节点。
!list -x ".if (poi(@$extret+0x28) < 0) {lmv a
poi(@$extret+0x28) }" poi(nt!PsProcessType)+ PlistHeadOffset
图5 进程操作前回调函数模块信息示意
根据模块信息中的文件名称、时间戳、校验值等数据可以从网上搜索、判定其所属的机构。经核实,图5所示的模块均为安全软件的驱动模块,分属两家软件商。
(5)解除目标系统安全软件的进程保护,需将相关节点的进程操作前回调的函数指针清0。下列指令清除图4中的前两处函数指针(灰底标记位置)。
eq 0xfffff8a0`00512d98 0; 0xfffff8a0`0040f9c8 0
3.2 解除线程保护
解除了进程保护机制之后,还需进一步解除安全软件的线程保护机制。
(1)确认线程操作回调函数链表头的位置,即Thread对象类型中CallbackList成员的内存地址。
首先执行下列指令查看Thread对象类型的结构。
dt nt!_OBJECT_TYPE poi(nt!PsThreadType)
图6 Win7 x64的Thread对象类型的结构示意
PsThreadType是指向内核Thread对象类型的指针。
Win7 x64系统的线程操作回调函数链表头的偏移位置是0xc0,如图6中灰底标记所示,因该位置在不同系统之间存在差异,本文后续表述用TlistHeadOffset对其进行指代。
根据结构推论,线程操作回调函数链表由表头节点和各表项节点组成,各表项节点均为CALLBACK_ENTRY_ITEM结构。
(2)执行下条指令显示线程操作回调函数链表的内容。
!list -x "dq @$extret L8"
poi(nt!PsThreadType)+ TlistHeadOffset
图7 线程操作回调函数链表内容示意
目标系统的线程操作回调函数链表头的内存地址如图7所示为fffffa80`01847bb0,减去偏移量0xc0等于fffffa80`01847af0即目标系统Thread对象类型的内存地址。
推论指出各表项节点的Operations、ObjectType、PreOperation的偏移位置分别为0x10、0x20、0x28。
表头节点为LIST_ENTRY结构,只包括两个指针,故图7蓝色标记处并非PreOperation。
安全软件通过驱动程序注册线程操作回调函数时,传给内核的参数需满足下列要求[6]:
Operations:打开句柄、复制句柄操作均触发回调机制时,传入数值3。
ObjectType:传入PsThreadType,即操作Thread对象类型时触发回调机制。
PreOperation:传入安全软件驻留内存的操作前回调函数的入口地址。
图7各表项节点的Operations均为3;ObjectType均指向了Thread对象类型(地址fffffa80`01847af0);PreOperation指向地址(灰底标记)均位于安全软件驱动程序可驻留的内存地址范围内。综上所述,图7各表项节点均具有线程操作回调节点的特征。
(3)执行下条指令显示线程操作前回调函数所属的模块信息,以确认安全软件的线程操作回调节点。
!list -x ".if (poi(@$extret+0x28) < 0) { lmv a
poi(@$extret+0x28)}"poi(nt!PsThreadType)+TlistHeadOffset
(4)将相关节点的线程操作前的回调函数指针清0,以解除目标系统安全软件的线程保护。下列指令清除图7中的前两处函数指针(灰底标记位置)。
eq 0xfffff8a0`00512dd8 0; eq 0xfffff8a0`0040fa08 0
3.3 恢复目标系统运行
解除安全软件的进、线程保护机制后,将目标系统恢复至运行状态。
4 安全软件进程调试
在目标系统中启动相关的调试软件,如图8所示,调试软件成功附加安全软件的相关进程,证实了结构推论的有效性。
图8 调试软件附加安全软件进程的示意
安全软件和其他软件的进程调试过程没有本质的差异,故本文不再对其展开描述。需要注意的是结束调试前,调试软件需脱离被调试的进程,以避免安全软件出现异常。
5 目标系统状态恢复
调试完毕后,在被调试的目标系统上以管理员身份执行bcdedit /debug off命令,关闭内核调试模式,重启系统将目标系统恢复至进线程保护机制完全生效的状态。
6 结束语
64位Windows环境下调试安全软件时需要通过双机调试的方式解除内核中安全软件注册的进、线程操作回调函数。本文通过具体的调试过程,分析、证实了微软未公开的CallbackList链表结构,为开展安全软件调试工作提供了依据和参考。
[1]Hack214.另类绕过Ring3下inline hook[J].黑客防线,2008.
[2]Kelvyn Taylor.PatchGuard触安全厂商软肋[N].每周电脑报,2007.
[3]tianhz.教你在64位Win7系统下使用ObRegisterCallbacks内核函数来实现进程保护[EB/OL].https://bbs.pediy.com/thread-189926.htm.
[4]Microsoft.Setting Up Kernel-Mode Debugging over a NetworkCable Manually[EB/OL].https://docs.microsoft. com/ en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection.
[5]douggem.ObRegisterCallbacksandcountermeasures. [EB/OL].https://douggemhax.wordpress.com/2015/05/27/obregistercallbacks-and-countermeasures.
[6]Microsoft._OB_OPERATION_REGISTRATION structure[EB/OL].https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_ob_operation_registration.