参考  这两天对某P双机调试的学习及成果 ,非常好的一篇分析贴。

本文在Win7 x86下的分析,在虚拟机中以/DEBUG模式启动TP游戏,系统会自动重启。

0x01 内核调试全局变量 

根据软件调试第十八章,windows启动过程中会调用两次KdInitSystem()函数

第一次调用KdInitSystem分别会初始化如下变量

1.KdPitchDebugger : Boolean 用来表示是否显示的抑制内核调试, 当启动项中包含 /NODEBUG选项时,这个变量会被置为 TRUE
2.KdDebuggerEnabled : Boolean 用来表示内核调试是否被启用。当启动项中包含 /DEBUG 或者/ DEBUGPORT 而且不包含/NODEBUG时,这个 变量置为TRUE
3.kiDebugRoutine : 函数指针类型 ,用来记录内核调试引擎的异常处理回调函数,当内核调试引擎活动时,指向KdpTrap函数,否则指向 KdpStub函数
4.KdpBreakpointTable : 结构体数组类型,用来记录代码断点。每一个元素为BREAKPOINT_ENTRY结构,用来描述一个断点,包括断点地 址。
5.KdDebuggerNotPresent也是判断内核调试状态的标志(收到复位包之后,将KdDebuggerNotPresent设置为0)
6.KdEnteredDebugger,在内核冻结时,会对KdEnterDebugger赋值,TP就是根据这个点进行MDL判断。详细可以参考 某驱动的内核调试检测学习内核调试引擎加载机制

当启用/debug模式的时候
     KdPitchDebugger = FALSE;
     KdDebuggerEnabled = TRUE;
     KiDebugRoutine -> KdpTrap  

windbg中我们查看这几个变量

kd> dd KdPitchDebugger             =>是否抑制内核调试
83f7fd27 00003c00 fe796000 ffffffff

kd> dd KdDebuggerEnabled         => 内核调试启用时为1
83fbbd2c 00000000 00000000 db1dbbbb

kd> dd KiDebugRoutine                => 内核引擎活动时指向KdpTrap ,否则指向KdpStub
83fc09bc 841834f2 83ed4d9c 00000000 00000191

先将KdPitchDebugger 置为1 表示抑制内核调试

kd> ed KdPitchDebugger 1
kd> dd KdPitchDebugger
83f7fd27 00000001 00003c00 fe796000 ffffffff

然后设置KiDebugRoutine 指向的指针 从KdpTrap 改成 KdpStub
kd> dd KiDebugRoutine
83fc09bc 841834f2 83ed4d9c 00000000 00000191

kd> ed KiDebugRoutine 83f339af
kd> dd KiDebugRoutine
83fc09bc 83f339af 83ed4d9c 00000000 00000191

最后恢复KdDebuggerEnabled 为 0,之所以最后设置,因为设置这个之后windbg就接受不到调试消息包了 表示内核调试未启用
kd> ed KdDebuggerEnabled 0

这样windbg就收不到消息包了。

在设置这几项之后,在启动游戏
还是重启,很显然检测内核引擎不仅仅是靠读取这几个值

0x02 Windows内核调试函数

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

内核调试几个关键函数

1.KdEnterDebugger
用于冻结内核,调试后首先会禁止中断,对于多处理器的系统,它会将当前CPU的IRQL升高到HIGH_LEVEL并冻结所有的其他的CPU,锁定调试器的通信端口,调用KdSave()让通信模块保存通信状态,并将全局变量KdEnteredDebugger设置为真,当KdEnterDebugger执行后,整个系统进入一种简单任务状态,当前的CPU只执行当前的线程,其他CPU出于冻结状态。

2.KdExitDebugger恢复内核运行,主要工作有调用KdRestore让通信扩展模块恢复通信状态,对锁定的通信端口解锁。调用KeThawExecution来恢复系统进入正常的运行状态,包括恢复中断,降低当前CPU的IRQL,对多CPU系统,恢复其他CPU。

3.KdpReportExceptionStateChange CPU报告异常类状态变化
4.KdpReportLoadSymbolsStateChange CPU报告符号加载类异常
5.KdpSendWaitContinue函数用来发送信息堡,与调试器对话

6.KeUpdateSystemTime 函数在每次更新系统时间时会检查全局变量KdDebuggerEnabled来判断内核调试引擎是否被启用,如果为真则调用KdUpdateRunTime,KeUpdateRun检测KdDebuggerEnabled,并且调用KdCheckForDebugger检测KdDebuggerEnabled和KdPitchDebugger并且调用KdPollBreakIn函数来查看调试器是否发送了中断命令,如果是,则调用DbgBreakPointWithStatus触发断点异常,中断到调试器。

7.KdpTrap来处理内核态的异常,当内核态发生异常时,KiDispatchException函数会调用全局变量KiDebugRoutine所指向的函数,当调试引擎启用时,这个变量的值是KdpTrap函数的地址,所以一旦异常发生时,系统就会调用KdpTrap。KdpTrap函数调用KdpReport向调试器报告异常。

8.KiSaveProcessorControlState 保存cpu的控制状态
9.KiRestoreProcessorControlState恢复CPU状态

10.DbgPrint、DbgPrintEx、vDbbggPrintEx打印调试信息

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

TP对于KdEnteredDebugger值用MDL映射,判断是否为真,如果为真则直接重启,我们选择Hook IoCreateMdl函数来过滤

0x03 解决内核调试

根据上面的几点,我们要解决的事情有

1.KdDebuggerEnabled 表示开启了调试, 我们要置为0

2.KdDebuggerEnabled置为0之后windbg就收不到消息了,这里我们要知道windbg怎么收到消息的

3.TP对于KdEnterDebugger的检测,这里用Hook IoAllocateMdl解决

4.TP会将kiDebugRoutine指向的地方,变成KdpStub,我们可以Hook KdpStub,跳转到KdpTrap,也可以在调试的时候修改其内容

5.TP调用KiDisableDebugger对于KdDebuggerEnabled清0,我们可以Hook KiDisableDebugger让其直接返回

一、我们首先看一下Windbg怎么收到消息的

ctrl+break断下来之后,输入kn,我们可以看到这几个函数的调用

KeUpdateSystemTimeAssist -> KeUpdateSystemTime -> KeUpdateRunTime -> KdCheckForDebugBreak -> RtlpBreakWithStatusInstruction -> 通过int 3  断下来

我们通过IDA查看这几个函数对于KiDebuggerEnabled和KdPitchDebugger的检测

1.在KeUpdateSystemTime中,对于KdDebuggerEnabled标志位的检测

2.在KeUpdateSystemTime中调用KeUpdateRunTime函数,检测KdDebuggerEnabled标志

3.然后继续反汇编KdCheckForDebugBreak函数,可以看到最KdDebuggerEnabled和 KdPitchDebugger的检测

4.可以看到调用了KdPollBreakIn() 函数,我们继续跟进kdPollBreakIn()函数,可以看到主要也是对KdPitchDebugger和KdDebuggerEnabled的检测,如果为0就直接退出,返回0

5.KdCheckForDebugBreak函数接着调用DbgBreakPointWithStatus() 函数

可以发现并没有做什么处理,直接向下执行RtlpBreakWithStatusInstruction()   可以看到我们的int 3 ,中断到调试器

我们只需要把这几个函数对于KdPitchDebugger和对于KdDebuggerEnabled检测的几个值替换成我们自己的地址,并设置对应的值,我们就可以继续的双机调试了

但是在最新的TesSafe中,还是失败了,估计又更新了一些点,我这里先研究这些已有的方法。

二、KdEnterDebugger
用于冻结内核,调试后首先会禁止中断,对于多处理器的系统,它会将当前CPU的IRQL升高到HIGH_LEVEL并冻结所有的其他的CPU,锁定调试器的通信端口,调用KdSave()让通信模块保存通信状态,并将全局变量KdEnteredDebugger设置为真,当KdEnterDebugger执行后,整个系统进入一种简单任务状态,当前的CPU只执行当前的线程,其他CPU出于冻结状态。

TP会使用IoAllocateMdl对KdEnterDebugger进行映射,检测当前的KdEnterDebugger值

我们通过Hook IoAllocateMdl来将映射地址更换到别的为0的地方

三、针对TP不停的调用KdDisableDebugger来清0 KdDebuggerEnabled来反调试,于是Hook KdDisableDebugger,让其直接返回。

(这里有个疑问,就是我们已经将KdDebuggerEnabled设置为0 了,为什么还要Hook 这个函数,让其不要清0??而且我对这个函数设置断点,更本就断不下来??)

四、处理KiDebugRoutine的问题

当内核引擎活动时,KiDebugRoutine这个函数指针是指向的KdpTrap,来处理我们调试是产生的异常,当我们将KiDebugRoutine指向了KdpStub之后,可以绕过对KiDebugRoutine的检测,但是内核调试引擎来处理我们触发的异常时,调用的不是KdpTrap,而变成了KdpStub,很显然不能继续进项调试,所以我们还需要做的一项工作就是hook KdpStub,让他跳转到KdpTrap,这个内核引擎可以正常工作,也可以绕过TP的检测。

并且TP也将KiDebugRoutine的值更换为KdpStub的地址

五、将当前KdDebuggerEnabled 设置为0

(因为之前对于windbg接受消息的四个地方已经换成我们的变量,所以此时windbg还能接收到消息)

0x04 WINDBG调试TP

思路弄清,开始调试

一、替换我们的全局变量 

kd> u KeUpdateSystemTime+0x417
nt!KeUpdateSystemTime+0x411:
83ec5d5f inc edi
83ec5d60 663bf8 cmp di,ax
83ec5d63 72ec jb nt!KeUpdateSystemTime+0x403 (83ec5d51)
83ec5d65 33c9 xor ecx,ecx
83ec5d67 8d542420 lea edx,[esp+20h]
83ec5d6b 803d002267a600 cmp byte ptr [PatchTPForDebug!bool_myKdDebuggerEnabled (a6672200)],
83ec5d72 je nt!KeUpdateSystemTime+0x48a (83ec5dd8)
83ec5d74 a1f841f883 mov eax,dword ptr [nt!KiPollSlotNext (83f841f8)] kd> u KeUpdateRunTime+0x149
nt!KeUpdateRunTime+0x149:
83ec60c2 803d002267a600 cmp byte ptr [PatchTPForDebug!bool_myKdDebuggerEnabled (a6672200)],
83ec60c9 je nt!KeUpdateRunTime+0x164 (83ec60dd)
83ec60cb a1ec41f883 mov eax,dword ptr [nt!KiPollSlot (83f841ec kd> u KdCheckForDebugBreak
nt!KdCheckForDebugBreak:
83ec60e9 803d1d2267a600 cmp byte ptr [PatchTPForDebug!bool_myKdPitchDebugger (a667221d)],
83ec60f0 jne nt!KdCheckForDebugBreak+0x22 (83ec610b)
83ec60f2 803d002267a600 cmp byte ptr [PatchTPForDebug!bool_myKdDebuggerEnabled (a6672200)],
83ec60f9 je nt!KdCheckForDebugBreak+0x22 (83ec610b) kd> u KdPollBreakIn
nt!KdPollBreakIn:
83ec611f 8bff mov edi,edi
83ec6121 push ebp
83ec6122 8bec mov ebp,esp
83ec6124 push ecx
83ec6125 push ebx
83ec6126 33db xor ebx,ebx
83ec6128 381d1d2267a6 cmp byte ptr [PatchTPForDebug!bool_myKdPitchDebugger (a667221d)],bl
83ec612e je nt!KdPollBreakIn+0x18 (83ec6137) nt!KdPollBreakIn+0x11:
83ec6130 32c0 xor al,al
83ec6132 e9d2000000 jmp nt!KdPollBreakIn+0xea (83ec6209)
83ec6137 885dff mov byte ptr [ebp-],bl
83ec613a 381d002267a6 cmp byte ptr [PatchTPForDebug!bool_myKdDebuggerEnabled (a6672200)],bl

第一步成功,我们已经将四个函数中的KdDebuggerEnabled和KdPitchDebugger换成我们自己的变量,这样在之后的KdDebuggerEnabled清0 的时候windbg也能接收到消息。

二、注册回调 

注册模块加载回调,当TesSafe.sys加载的时候能够确定其基地址

三、Hook IoAllocateMdl 

kd> u IoAllocateMdl
nt!IoAllocateMdl:
83ee04f5 e966f27822 jmp PatchTPForDebug!MyIoAllocateMdl (a666f760)
83ee04fa 83ec10 sub esp,10h
83ee04fd 8b550c mov edx,dword ptr [ebp+0Ch]

成功跳转到我们的模块

我们的fake函数是

 PMDL MyIoAllocateMdl(
__in_opt PVOID VirtualAddress,
__in ULONG Length,
__in BOOLEAN SecondaryBuffer,
__in BOOLEAN ChargeQuota,
__inout_opt PIRP Irp OPTIONAL)
{
PVOID pKdEnteredDebugger;
pKdEnteredDebugger = (PVOID)GetKdEnteredDebuggerAddr();
if (pKdEnteredDebugger == VirtualAddress)
{
VirtualAddress = (PVOID)((SIZE_T)pKdEnteredDebugger + 0x20); //+0x20 是让他读到其他的位置
} return old_IoAllocateMdl(VirtualAddress,Length,SecondaryBuffer,ChargeQuota,Irp);
15 }

我在12行的地方下了断点,当TesSafe加载的时候,就断在里面,走出去就能发现进入了TesSafe模块,在对KdDisableDebugger设置断点的时候却不能断下来

四、Hook  KdpStub

kd> u KdpStub
nt!KdpStub:
83f289af 8bff mov edi,edi
83f289b1 push ebp
83f289b2 8bec mov ebp,esp
83f289b4 push ebx kd> u KdpStub
nt!KdpStub:
83f289af e93efb2400 jmp nt!KdpTrap (841784f2) //Success
83f289b4 push ebx
83f289b5 push esi

五、Hook KdDisaableDebugger

让其头部直接返回

kd> u KdDisableDebugger
nt!KdDisableDebugger:
83f28846 6a01 push
83f28848 e806ffffff call nt!KdDisableDebuggerWithLock (83f28753)
83f2884d c3 ret
83f2884e cc int kd> u KdDisableDebugger
nt!KdDisableDebugger:
83f28846 nop
83f28847 c3 ret
83f28848 e806ffffff call nt!KdDisableDebuggerWithLock (83f28753)
83f2884d c3 ret
83f2884e cc int

六、改写KdDebuggerEnabled  为0

完成之后我们点开 英雄联盟

首先断下来了
TesSafe.sys -------------加载-------------
TesSafe.sys --->ImageBase 0xa6675000 - a6764000
TesSafe.sys --->ImageSize 0xef000
断点命令: ba e 1 0xa6675000+

我们对自己的MyIoAllocateMdl设置断点,如果有谁访问pKdEnteredDebugger,我们就会断下来
F10走几步就进入了 TesSafe.sys中

然后在F9的时候,windbg就失去联系,卡死

如果直接在虚拟机加载,不上windbg,界面是打开了,但是后面一直出于卡死状态

0x05 后续

可以猜想 , 还有对于
1.对于windbg的一些流程函数,tesSafe进行了判断,比如直接映射我们修改的那个地方,tesSafe直接判断那个点
    KdDebuggerEnabled 和 KdPitchDebugger 在KeUpdateSystemTime、KeUpdateTunTime、KdCheckForDebugBreak、
    KdPollBreakIn中的点直接MDL进行判断

tesSafe 检测KdDebuggerEnabled 开启就会gg ,所以我们会设置KdDebuggerEnabled的值

2.tesSafe设置定时器对我们修改了的地方进行覆盖

①做出的方案
1.我们对于自己的IoAllocateMdl进行了的判断,验证是否会进入,看tesSafe是否会做出判断

if(VirtualAddress==(PVOID)&bool_myKdDebuggerEnabled)
{
VirtualAddress = (PVOID)((SIZE_T)pKdEnteredDebugger + 0x20);
}
if(VirtualAddress==(PVOID)&bool_myKdPitchDebugger)
{
VirtualAddress = (PVOID)((SIZE_T)pKdEnteredDebugger + 0x20);
}

嗯..然后就是断点不会到达,这个地方不对

②没什么头绪

目前要解决的就是

1、目前断点就在IoAllocateMdl中断下来,其余的断点停不下来,最后不知道tesSafe对于哪里做出了判断,KdDisableDebugger不行,KdDisableDebuggerWithLock也不行

2.windbg失去了联系,在通信函数的某个地方,失去了联系

Win7 x86内核调试与TP反调试的研究的更多相关文章

  1. WinDbg调试流程的学习及对TP反调试的探索

    基础知识推荐阅读<软件调试>的第十八章 内核调试引擎 我在里直接总结一下内核调试引擎的几个关键标志位,也是TP进行反调试检测的关键位. KdPitchDebugger : Boolean ...

  2. 编译Android内核 For nexus 5 以及绕过Android的反调试

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/54880488 前面的博客中已经记录了Nexus 5手机的Android 4.4.4 ...

  3. PEB标记反调试方法

    PEB标记反调试方法 一丶PEB结构简介 PEB.简称进程环境快. 我们在讲DLL隐藏的时候已经说过了. 具体博客链接: https://www.cnblogs.com/iBinary/p/96018 ...

  4. Windows 32位-调试与反调试

    1.加载调试符号链接文件并放入d:/symbols目录下. 0:000> .sympath srv*d:\symbols*http://msdl.microsoft.com/download/s ...

  5. 【Python3爬虫】反反爬之解决前端反调试问题

    一.前言 在我们爬取某些网站的时候,会想要打开 DevTools 查看元素或者抓包分析,但按下 F12 的时候,却出现了下面这一幕: 此时网页暂停加载,也就没法运行代码了,直接中断掉了,难道这就能阻止 ...

  6. 【Python3爬虫】突破反爬之应对前端反调试手段

    一.前言 在我们爬取某些网站的时候,会想要打开 DevTools 查看元素或者抓包分析,但按下 F12 的时候,却出现了下面这一幕:   此时网页暂停加载,自动跳转到 Source 页面并打开了一个 ...

  7. C/C++ 程序反调试的方法

    C/C++ 要实现程序反调试有多种方法,BeingDebugged,NtGlobalFlag,ProcessHeap,CheckRemoteDebuggerPresent,STARTUPINFO,Is ...

  8. 华为手机内核代码的编译及刷入教程【通过魔改华为P9 Android Kernel 对抗反调试机制】

    0x00  写在前面 攻防对立.程序调试与反调试之间的对抗是一个永恒的主题.在安卓逆向工程实践中,通过修改和编译安卓内核源码来对抗反调试是一种常见的方法.但网上关于此类的资料比较少,且都是基于AOSP ...

  9. 修改Android手机内核,绕过反调试

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/57086486 0x1.手机设备环境 Model number: Nexus 5 O ...

随机推荐

  1. ComicEnhancerPro 系列教程十九:用JpegQuality看JPG文件的压缩参数

    作者:马健邮箱:stronghorse_mj@hotmail.com 主页:http://www.comicer.com/stronghorse/ 发布:2017.07.23 教程十九:用JpegQu ...

  2. 数据库索引与b+树

    数据库索引详解 索引 当我们在设计数据库的时候,对表的一些属性有时会加上索引,但索引为什么能提高检索速率呢?是不是用了索引就一定可以提高效率呢?不同索引之间有什么区别呢?搞懂这些问题是灵活运用索引的必 ...

  3. 转载:ResultMap和ResultType在使用中的区别

    在使用mybatis进行数据库连接操作时对于SQL语句返回结果的处理通常有两种方式,一种就是resultType另一种就是resultMap,下面说下我对这两者的认识和理解 resultType:当使 ...

  4. Day 3 Python 基础数据类型二

    1. INT 型 #1. 数字int #bit_length() 当十进制用二进制表示时,最少使用的位数. v =11 data = v.bit_length() print(data) 2. 布尔值 ...

  5. iOS系统各个版本的占比查询

    目的:为了向大多数看齐,我们要实时了解应用系统的使用占比 1.苹果官网查询各个系统的占比: Apple 2.各种设备各种系统的占比 第三方

  6. form表单提交回调函数

    form表单没有回调函数,不过可以通过jquery-form.js这个插件来实现回调函数: <form id="addform" class="form-horiz ...

  7. java 读取excel 2007 .xlsx文件 poi实现

    工作需要读取excel里面的行内容,使用java实现较为简单. 在最开始,尝试使用 jxl-2.6.12 来实现读取excel 的行内容.但是按照网上的方法,程序根本无法正确处理文件流.经过谷姐的一番 ...

  8. AJAX发送json,SpringMVC 接收JSON,@RequestBody

    需求:JQuery ajax前台,采用 POST请求 发送json,后台使用SpringMVC接收json并处理 前台: $.ajax({ url:"请求地址", type:&qu ...

  9. if __name__ == '__main__'是什么意思?如何理解?看到一个很有用的解答

    小明.py 朋友眼中你是小明(__name__ == '小明'), 你自己眼中你是你自己(__name__ == '__main__'), 你编程很好, 朋友调你去帮他写程序(import 小明, 这 ...

  10. TX2 上使用opencv 调用板载mipi摄像头

    使用命令测试 gst-launch-1.0 nvcamerasrc ! 'video/x-raw(memory:NVMM), width=(int)1920, height=(int)1080, fo ...