看流星社区

 找回密码
 注册账号
查看: 2065|回复: 0

某驱动的内核调试检测学习内核调试引擎加载机制

[复制链接]

该用户从未签到

发表于 2017-6-1 17:26:19 | 显示全部楼层 |阅读模式
标 题: 【原创】某驱动的内核调试检测学习内核调试引擎加载机制
作 者: 毁灭
时 间: 2014-03-29,02:13:41
链 接: http://bbs.pediy.com/showthread.php?t=186091


如果大家调试过某驱动就知道新版本的驱动已经改了很多很主要的一点就是只要我们在boot.ini里加入
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="MicrosoftWindows2003-debug"/fastdetect/noguiboot/debug/debugport=com1/baudrate=115200
启动游戏的时候就会导致蓝屏

tp://bbs.pediy.com/attachment.php?attachmentid=88178&d=1396029311')}}"
/>

这次测试我只是加入了/debug/debugport=com1并没有开启windbg也产生了系统错误0x99999999
可以得出一个结论就是某驱动对我们的启动模式有检测(地球人都知道)
首先我们来了解一下加入了/debug/debugport=com1对系统启动的时候做了些什么操作


所有命令详情请查看微软官方说明
http://msdn.microsoft.com/en-us/library/windows/hardware/ff556253(v=vs.85).aspx


实际上在windows内核初始化会对CPU做必要的初始化工作包括16位实模式切换到32位保护模式启动分页机制等然后通过启动配置信息(Boot.ini或BCD)得到windows系统的系统目录并加装系统内核文件而我们加入了/debug...这些就是告诉了系统要初始化内核调试引擎

上图表示了windows的初始化逻辑图

而内核调试引擎初始化主要在KdInitSystem这个函数里
  第一次调用KdInitSystem
  从图18-10可以看到,系统在启动过程中会两次调用内核调试引擎的初始化函数,KdInitSystem。第一次是在系统内核开始执行后由入口函数KiSystemStartup调用。
  调用时,KdInitSystem会执行以下动作。
  初始化调试器数据链表,使用变量KdpDebuggerDataListHead指向这个链表。
  初始化KdDebuggerDataBlock数据结构,该结构包含了内核基地址、模块链表指针、调试器数据链表指针等重要数据,调试器需要读取这些信息以了解目标系统。
  根据参数指针指向的LOADER_PARAMETER_BLOCK结构寻找调试有关的选项,然后保存到变量中(见下文)。
  对于XP之后的系统,调用KdDebuggerInitialize0来对通信扩展模块进行阶段0初始化。对于XP之前的版本,调用KdPortInitialize来初始化COM端口。如果使用的是串行通信方式,那么KDCOM中的KdDebuggerInitialize0函数会调用模块内的KdCompInitialize函数来初始化串行口。不论是KdPortInitialize还是KdCompInitialize函数,成功初始化COM口后,都会设置HAL模块中所定义的KdComPortInUse全局变量,记录下已被内核调试使用的COM端口,此后的串行口驱动程序(serial.sys)会检查这个变量,并跳过用于调试用途的串行端口,因此,在目标系统的设备管理器中我们看不到用于内核调试的串行端口。
  此外,KdInitSystem会初始化以下全局变量:
  KdPitchDebugger:布尔类型,用来标识是否显式抑制内核调试。当启动选项中包含/NODEBUG选项时,这个变量会被设置为真。
  KdDebuggerEnabled:布尔类型,用来标识内核调试是否被启用。当启动选项中包含/DEBUG或/DEBUGPORT而且不包含/NODEBUG时,这个变量会被设置为真。
  KiDebugRoutine:函数指针类型,用来记录内核调试引擎的异常处理回调函数,当内核调试引擎活动时,它指向KdpTrap函数,否则指向KdpStub函数。
  KdpBreakpointTable:结构数组类型,用来记录代码断点,每个元素为一个BREAKPOINT_ENTRY结构,用来描述一个断点,包括断点地址。
  对于WindowsXP之前的版本,还会初始化如下变量。
  KdpNextPacketIdToSend:整数类型,用来标识下一个要发送的数据包ID,KdInitSystem将这个变量初始化为0x80800000|0x800,即INITIAL_PACKET_ID(初始包)或SYNC_PACKET_ID(同步包)。
  KdpPacketIdExpected:整数类型,用来标识期待收到的下一个数据包ID,初始化为0x80800000,即INITIAL_PACKET_ID。
  对于XP和之后的系统,当使用串行通信方式时,在KDCOM.DLL中定义了两个同样用途的变量KdCompNextPacketIdToSend和KdCompPacketIdExpected。
  对于WindowsVista,会初始化如下变量。
  KdAutoEnableOnEvent:布尔类型,如果调试设置中的启动策略为AUTOENABLE,则设为真。
  KdIgnoreUmExceptions:布尔类型,代表了处理用户态异常的方式,如果调试设置中的启动策略包含/noumex,则设为真,含义是忽略用户态异常。
关于KdInitSystem第一次被调用,还有如下两点值得说明。第一,只有当0号CPU执行KiSystemStartup函数时才会调用KdInitSystem,所以它不会被KiSystemStartup多次调用。第二,不管系统是否启用内核调试,调用都会发生。



代码:
当我们启动/debug模式启动时候
KdPitchDebugger=FALSE;
KdDebuggerEnabled=TRUE;
KiDebugRoutine=KdpTrap;

0:kd>ddKdPitchDebugger
808948a800000000000000000000000000000000
0:kd>ddKdDebuggerEnabled
808a5b9000000001000000000000001400000000

0:kd>ddKiDebugRoutine
8089aa8c809cf27afffff0000000000ffffff000

0:kd>u809cf27a
nt!KdpTrap:
809cf27a8bffmovedi,edi
809cf27c55pushebp
809cf27d8becmovebp,esp
[/code]

这个3个全局变量都可以成为我们判断是否启动了内核调试引擎的标志相信逆过以前某驱动的朋友都知道他早期版本就是循环调用KdDisableDebugger来清0KdDebuggerEnabled达到防止调试的目的靠着这样的思路我们做以下测试
//让KdPitchDebugger=TRUE

代码:
0:kd>edKdPitchDebugger1
0:kd>ddKdPitchDebugger
808948a800000001000000000000000000000000
//恢复KiDebugRoutine=KdpStub
0:kd>uKdpStub
nt!KdpStub:
80824e888bffmovedi,edi
80824e8a55pushebp
80824e8b8becmovebp,esp
//得到KdpStub的地址0x80824e88然后修改
0:kd>edKiDebugRoutine80824e88
//查看新的KiDebugRoutine地址
0:kd>ddKiDebugRoutine
8089aa8c80824e88fffff0000000000ffffff000
8089aa9c0000000fffffffffffffffefffffffff
0:kd>u80824e88
nt!KdpStub:
80824e888bffmovedi,edi
80824e8a55pushebp
80824e8b8becmovebp,esp
[/code]

//最后我们恢复KdDebuggerEnabled说明一下因为如果我们先恢复KdDebuggerEnabled就会导致windbg收不到消息了所以最后才给他赋值

代码:
0:kd>edKdDebuggerEnabled0
0:kd>ddKdDebuggerEnabled
808a5b9000000000000000000000001400000000
808a5ba000000000000000000000000000000000
[/code]

这时候我们点击windbg的中断(Ctrl+Break)已经断不下来了启动游戏让某驱动加载还是蓝屏系统错误0x9999999

很明显了某驱动检测内核引擎并不是靠读取这几个标志位的值所以我们还要继续深入分析

内核对话
Windows启动内核调试后,主要做了以下几个工作
1.建立连接
2.调试器读取目标系统信息,初始化调试引擎(目标机)。
3.内核调试引擎通过状态变化信息包通知调试器加载初始模块的调试符号(目标机)。
4.调试器端发送中断包,将目标系统中断到调试器,交互调试后又恢复执行的过程。
5.因断点命中,目标系统中断到调试器的过程。
6.内核中的模块输出调试字符串(DbgPrint)到调试器
《软件调试》(490-491)总结的表如下:



内核调试引擎
内核调试引擎相当于再目标系统中进行调试的一个代理,调试引擎代表调试器来访问和控制目标系统。
内核调试的几个关键函数:
KdEnterDbugger
它用于冻结内核,调用后首先会禁止中断,对于多处理器的系统,它会将当前CPU的IRQL升高到HIGH_LEVEL并冻结所有的其他的CPU。锁定调试调试通信端口,调用KdSave让通信扩展模块保存通信状态,并将全局变量KdEnteredDebugger设置为真。当KdEnterDebugger执行后,整个系统进入一种简单的单任务状态,当前的CPU只执行当前的线程,其他CPU处于冻结状态。
KdExitDebugger恢复内核运行,主要工作有调用KdRestore让通信扩展模块恢复通信状态,对锁定的通信端口解锁。调用KeThawExecution来恢复系统进入正常的运行状态,包括恢复中断,降低当前CPU的IRQL,对多CPU系统,恢复其他CPU。
KdpReportExceptionStateChangeCPU报告异常类状态变化
KdpReportLoadSymbolsStateChangeCPU报告符号加载类异常
KdpSendWaitContinue函数用来发送信息包,与调试器对话。
KeUpdateSystemTime函数在每次更新系统时间时会检查全局变量KdDebuggerEnable来判断内核调试引擎是否被启动,如果为真则调用KdPollBreakIn函数来查看调试器是否发送了终端命令,如果是,则调用DbgBreakPointWithStatus触发断点异常,中断到调试器。
kdpTrap来处理内核态的异常。当内核态中发生异常时,KiDispatchException函数会调用全局变量KiDebugRoutine所指向的函数。当调试引擎启用时,这个变量的值是函数KdpTrap的地址。所以一旦异常发生时,系统就会调用KdpTrap。KdpTrap调用KdpReport向调试器报告异常。
KiSaveProcessorControlState保存CPU的控制状态
KiRestoreProcessorControlState恢复CPU状态
DbgPrint,DbgPrintEx,vDbbgPirntEx打印调试信息
//经过以上的了解我们大概知道了windbg和内核调试引擎的一些交互的流程和机制更多的一些结构在《软件调试》一书中都有比较详细的说明


这里我们要注意的是当收到复位包的时候清0KdDebuggerNotPresent冻结内核的时候KdEnteredDebugger会赋值KdEnteredDebugger=TRUE

这里我们为了验证这2个全局变量是不是当内核调试引擎启动后才会KdDebuggerNotPresent=FALSE;KdEnteredDebugger=TRUE;
我们打开虚拟机不启用/debug模式启动然后开启windbg用本地内核调试查看内容


这样可以得出结论这2个全局变量也可以判断系统是否是调试模式启动
但在我测试的过程中发现我虽然ebKdEnteredDebugger0了操作系统还会把他置1我们知道置1的代码是KdEnterDebugger()这个函数所干的

代码:
0:kd>uKdEnterDebuggerl50
nt!KdEnterDebugger:
809cd36c8bffmovedi,edi
809cd36e55pushebp
809cd36f8becmovebp,esp
809cd371837d0800cmpdwordptr[ebp+8],0
809cd375742cjent!KdEnterDebugger+0x37(809cd3a3)
809cd377ff7508pushdwordptr[ebp+8]
809cd37ae83f200000callnt!KdpQueryPerformanceCounter(809cf3be)
809cd37fa388869e80movdwordptr[nt!KdTimerStop(809e8688)],eax
809cd3842b0580869e80subeax,dwordptr[nt!KdTimerStart(809e8680)]
809cd38a89158c869e80movdwordptr[nt!KdTimerStop+0x4(809e868c)],edx
809cd3901b1584869e80sbbedx,dwordptr[nt!KdTimerStart+0x4(809e8684)]
809cd396a390869e80movdwordptr[nt!KdTimerDifference(809e8690)],eax
809cd39b891594869e80movdwordptr[nt!KdTimerDifference+0x4(809e8694)],edx
809cd3a1eb0ejmpnt!KdEnterDebugger+0x45(809cd3b1)
809cd3a3832588869e8000anddwordptr[nt!KdTimerStop(809e8688)],0
809cd3aa83258c869e8000anddwordptr[nt!KdTimerStop+0x4(809e868c)],0
809cd3b153pushebx
809cd3b256pushesi
809cd3b3648b3520000000movesi,dwordptrfs:[20h]
809cd3baff1594108080calldwordptr[nt!_imp__KeGetCurrentIrql(80801094)]
809cd3c0ff750cpushdwordptr[ebp+0Ch]
809cd3c3888645050000movbyteptr[esi+545h],al
809cd3c9ff7508pushdwordptr[ebp+8]
809cd3cce8ab31e6ffcallnt!KeFreezeExecution(8083057c)
809cd3d1b980438b80movecx,offsetnt!KdpDebuggerLock(808b4380)
809cd3d68ad8movbl,al
809cd3d8e83fb8ebffcallnt!KeTryToAcquireSpinLockAtDpcLevel(80888c1c)
809cd3dd6a00push0
809cd3dfa220879e80movbyteptr[nt!KdpPortLocked(809e8720)],al
809cd3e4e81597ebffcallnt!KdSave(80886afe)
809cd3e95epopesi
809cd3ea8ac3moval,bl
809cd3ecc705885b8a8001000000movdwordptr[nt!KdEnteredDebugger(808a5b88)],1//重点在这里
809cd3f65bpopebx
809cd3f75dpopebp
809cd3f8c20800ret8
[/code]

我们就直接asmKdEnterDebugger这个函数让他对nt!KdEnteredDebugger置0

代码:
//windbg执行修改代码
0:kd>eb809cd3ecc705885b8a8000
//结果
809cd3ecc705885b8a8000000000movdwordptr[nt!KdEnteredDebugger(808a5b88)],0
[/code]

大家要注意我这样修改是为了方便马上得到结果才这样做的这样可能会对系统照成不稳定所以希望大家不要学我
//已经验证了KdEnteredDebugger不会被置1了我们运行游戏看看
0:kd>g
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
Tenio[Error]:Toremoveacomponentnotexist
Tenio[Error]:IComponent*=0x0130C00C
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
[Tenio_Error]Can'tfindComponent(33554448)CreatorFromDllList!
RuleModuleEntryStart---------------------------
LPDispachIoRequest=F662C008
RuleModuleEntryend-----------------------------

tp://bbs.pediy.com/attachment.php?attachmentid=88184&d=1396029349')}}"
/>
某驱动已经加载了而且并没有蓝屏这里我们得出结论了某驱动就是对KdEnteredDebugger的值进行判断是否开启了内核

调试引擎的因为我直接修改KdEnterDebugger()函数导致某驱动可能初始化出问题没出现游戏界面一直卡着了

但是能定位到他是检测什么的了我相信朋友们要绕过它PASS它FUCK它都有各自的奇招我就不献丑我如何逆向他的了


定位到他关键代码:


大概的流程是获取到KdEnteredDebugger地址然后创建MDL然后判断用MDL返回的指针来判断KdEnteredDebugger的值所以我用了个很简单的方法来绕过他就是最简单的HOOK


代码:
typedefPMDL(__stdcall*_MyIoAllocateMdl)(
__in_optPVOIDVirtualAddress,
__inULONGLength,
__inBOOLEANSecondaryBuffer,
__inBOOLEANChargeQuota,
__inout_optPIRPIrpOPTIONAL);

_MyIoAllocateMdlold_IoAllocateMdl;
[/code]


代码:
PMDLMyIoAllocateMdl(
__in_optPVOIDVirtualAddress,
__inULONGLength,
__inBOOLEANSecondaryBuffer,
__inBOOLEANChargeQuota,
__inout_optPIRPIrpOPTIONAL)
{
if(pKdEnteredDebugger==VirtualAddress)
{
VirtualAddress=pKdEnteredDebugger+0x20;//+0x20是让他读到其他的位置
}

returnold_IoAllocateMdl(VirtualAddress,Length,SecondaryBuffer,ChargeQuota,Irp);
}


//////////////////////////////////////////////////////////////////////////
//模块加载回调函数例程
VOIDLoadImageRoutine(INPUNICODE_STRINGFullImageName,
INHANDLEProcessId,//whereimageismapped
INPIMAGE_INFOImageInfo)
{
PMDLpMdl;
ULONGuHookAddr,uJmpAddr;
if(wcsstr(FullImageName->Buffer,L"TesSafe.sys")!=NULL)
{
KdPrint(("TesSafe.sys-------------加载-------------\r\n"));
KdPrint(("TesSafe.sys--->ImageBase0x%x\r\n",(ULONG)ImageInfo->ImageBase));
KdPrint(("TesSafe.sys--->ImageSize0x%x\r\n",(ULONG)ImageInfo->ImageSize));
KdPrint(("断点命令:bae10x%x+\r\n",(ULONG)ImageInfo->ImageBase));
KdBreakPoint();

InlineHook(IoAllocateMdl,MyIoAllocateMdl,(void**)&old_IoAllocateMdl);
  }
  return;
}
[/code]

编译后运行成功绕过了内核调试引擎检测



但是虚拟机不能下断点了呵呵老样子它还是回阻止你调试但这已经不是本章的内容了不蓝屏了朋友们可以慢慢一步一步的分析

总结:
1.通过了解win内核调试引擎的加载机制解决了我们想要解决的问题
2.
KdPitchDebugger
KdDebuggerEnabled
KiDebugRoutine
KdDebuggerNotPresent
KdEnteredDebugger
3.知道了以上5个全局变量都可以来判断内核调试是否开启
了解WINDBG和内核引擎是如何完成初始化操作的
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 11:52

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表