参考  这两天对某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. [.net 多线程]ThreadPool的安全机制

    ThreadPool类,有两个方法我们没有用到,UnsafeQueueUserWorkItem 和UnsafeRegisterWaitForSingleObject. 为了完全理解这些方法,首先,我们 ...

  2. 类的互相包含------新标准c++程序设计

    #include<iostream> using namespace std; class A; class B{ public: void f(A* pt){}; } class A{ ...

  3. Google浏览器历史版和下载地址

    Google浏览器历史版本下载地址https://www.slimjet.com/chrome/google-chrome-old-version.php google webdriver下载地址分享 ...

  4. 【转】C#日期时间格式化

    源地址:https://www.cnblogs.com/polk6/p/5465088.html

  5. Python3入门之软件安装

    获得更多资料欢迎进入我的网站或者 csdn或者博客园 最近将我的Python笔记整理下,希望能对他人有所帮助,这是第一课,安装入门篇: windows下安装Python 1.下载,从这里下载:[下载链 ...

  6. django 部署到Ubuntu安装MYSQL56

    阿里云 Ubuntu 14.04 安装mysql 5.6 1.升级apt-get sudo apt-get update 2. 安装mysql5.6版本 apt-get install mysql-s ...

  7. js 三大家族之offset

    JS中的offset家族: 一.offsetWidth与offsetHeight: 获取的是元素的实际宽高 = width + border + padding 注意点: 1.可以获取行内及内嵌的宽高 ...

  8. Vue 使用 axios post请求后台数据时 404

    今天遇到Vue 使用 axios post请求后台数据时 404 使用postman 就能获取到 网上找了大半天 终于找到了解决方法,传送门:https://www.jianshu.com/p/b10 ...

  9. 112th LeetCode Weekly Contest Validate Stack Sequences

    Given two sequences pushed and popped with distinct values, return true if and only if this could ha ...

  10. HDU - 1520 树形DP入门题

    写了两种DP,第一种是按照自己习惯来xjb敲的,第二种参考别人 熟悉一下树形DP的套路 dp[i][]是维护i及以下的关系最优值的,所以我觉得两次DP记忆搜索之间不清-1应该是正确的(也就做了一次加法 ...