郁金香外挂技术-郁金香灬老师

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

郁金香终身VIP管理员QQ150330575项目合作(有实力的+)视频教程+每月更新+QQ群
飞郁视频分享(每周更新)
查看: 6972|回复: 3

过 DNF TP 反WinDbg双机调试 -转

[复制链接]
发表于 2018-2-6 19:03:11 | 显示全部楼层 |阅读模式
本帖最后由 郁金香灬老师 于 2018-2-8 17:37 编辑

帖子涨点人气。

正文:
在论坛搜索了下发现去年的时候有人发过一篇过TesSafe反双机调试的帖子,但是现在已经过时了,并且帖子里面也没提到怎么处理被IAT HOOK的两个函数。在这里呢,我就给大家彻底的讲明白吧。

先开PCHunter64.exe工具看看游戏干了什么。



从图片可以看到游戏启动的时候对ntkrnlmp.exe中的kdcom.KdSendPacket和kdcom.KdReceivePacket两个函数进行了HOOK,通过函数名称就能猜测到函数的用途,一个发包,一个收包,都是com口用来通信的。

怎样恢复这两个函数呢,方面有很多种,列举两个。

1、自己映射内核文件ntkrnlmp.exe,然后通过PE结构遍历ntkrnlmp.exe的导入表,获取导入表中KdSendPacket、KdReceivePacket两个函数的地址在ntkrnlmp.exe中的偏移,然后利用这个偏移修改系统正常运行的ntkrnlmp.exe导入表中的两个函数地址。

但是这里还有一个问题就是,导入表中KdReceivePacket这个位置有代码校验,只要被修改就会重启,所以还得绕过代码校验。所以不推荐这种方法。

2、通过图片可以看到被IATHOOK的两个函数被挂钩后的新地址分别为0xB07920E6,0xB07920F6,相差16个字节。既然不想直接去恢复导入表,那么我们可以在这两个被挂钩后的地址中再跳回正常的KdSendPacket、KdReceivePacket。或许有人又有疑问,这两个挂钩地址处没有代码校验吗,答案是也有校验,只要修改前面几个字节,同样会重启。但是游戏只检测了前面的几个字节,所以只要在靠后一点的地方修改就不会被检测到。



修改后


另外一个也是同样处理函数


这样两个被IATHOOK的函数就相当于被恢复了。

但是上面我们是用工具进行恢复的,而TP启动的时候就已经调用了KdDisableDebugger禁止了WinDbg双机调试,这时候就算恢复了上面的两个函数,同样也无法双机调试。

之所以先讲怎样处理上面的两个函数是因为在TP驱动加载的时候首先进行了IATHOOK,然后调用KdDisableDebugger,如果你先处理KdDisableDebugger,而没有恢复上面两个函数的话,系统会直接重启。

因为要赶在TP调用KdDisableDebugger之前恢复IATHOOK,那么就需要设置一个系统回调函数,然后在加载TP驱动的时候进入我们的回调函数,这时TP已经加载完成,但是还没有开始运行,所以我们可以直接定位到 挂钩地址 处 写入跳转代码到正常的函数。

2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74
76
78
80
82
//恢复两个被iat hook的函数

VOID RenewIATHook(ULONG ImageBase)         
{
    // 模块:KDCOM.dll,函数名称:KdSendPacket,函数地址:ba5a91b2,Hint:0007
    //模块:KDCOM.dll,函数名称:KdReceivePacket,函数地址:ba5a8f4c,Hint:0004

    ULONG uKdSend = 0xba5a91b2;     //KdSendPacket的地址硬编码
    ULONG uKdReceive = 0xba5a8f4c;  //KeReceivePacket地址

    //因为这两个函数不是导出的,所以我为了省事,就直接这样写了

    ULONG uJmpKdSend = ImageBase + 0x20EE;
    //KdSendPacket挂钩函数地址 = ImageBase + 0x20EE; 硬编码

    ULONG uJmpKdReceive = ImageBase + 0x20FE;

    ULONG jmpAddr1 = uKdSend - uJmpKdSend - 5;
    ULONG jmpAddr2 = uKdReceive - uJmpKdReceive - 5;

    __asm
    {  
        cli
            pushad
            mov     eax,uJmpKdSend
            mov     byte ptr [eax],0xE9
            mov     ebx,jmpAddr1
            mov     dword ptr [eax+1],ebx

            mov     eax,uJmpKdReceive
            mov     byte ptr [eax],0xE9
            mov     ebx,jmpAddr2
            mov     dword ptr [eax+1],ebx

            popad
            sti
    }
}


ULONG strEnd(PWCHAR dest,PWCHAR sub)
{
    if (dest == NULL && sub == NULL)
        return 0;
    int ulDest = wcslen(dest);
    int ulSub = wcslen(sub);

    if (ulSub == 0 || ulSub > ulDest)
        return 0;
    return !wcscmp(&dest[ulDest - ulSub],sub);
}

VOID NotifyRoutine(IN PUNICODE_STRING  FullImageName,
    IN HANDLE  ProcessId, // where image is mapped
    IN PIMAGE_INFO  ImageInfo)
{
              //这个strEnd是一个判断字符串是不是以***结束
         //我记得wdk中有一个函数可以判断的,但是记不起来了,知道的回复下啊
    if (strEnd(FullImageName->Buffer,L"TesSafe.sys"))
    {
                                 //判断加载的驱动是不是TP
        KdPrint(("TesSafe.sys:ImageBase:%08x          size: %06x",
ImageInfo->ImageBase,ImageInfo->ImageSize));
        RenewIATHook((ULONG)ImageInfo->ImageBase);  
    }
}


#pragma alloc_text("INIT")
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegPath){
    NTSTATUS status;
    UNREFERENCED_PARAMETER(RegPath);

    DriverObject->DriverUnload = DriverUnload;
    for (int i=0;i<irp_mj_maximum_function;i++)
        DriverObject->MajorFunction = DriverDispatch;


    KdPrint(("Init"));
    PsSetLoadImageNotifyRoutine(NotifyRoutine);

    return STATUS_SUCCESS;
}


通过上面的代码就可以对IATHOOK的两个函数进行恢复

编译上面的代码,然后重启虚拟机,加载编译好的驱动运行。

下面就来处理TP调用KdDisableDebugger函数来禁止调试

WinDbg中输入bp KdDisableDebugger

启动DNF登陆器,然后WinDbg断下,



直接修改首行代码 ret 返回即可。

eb 804f8886 0xC3

单步走一下,返回上层函数。

2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
b094dfd9 57              push    edi
b094dfda 8b782c          mov     edi,dword ptr [eax+2Ch]
b094dfdd 33f1            xor     esi,ecx
b094dfdf 33f9            xor     edi,ecx
b094dfe1 eb4b            jmp     TesSafe+0x702e (b094e02e)
b094dfe3 803d6da295b000  cmp     byte ptr [TesSafe+0x1326d (b095a26d)],0
b094dfea 7516            jne     TesSafe+0x7002 (b094e002)
b094dfec 688a4d6e43      push    436E4D8Ah
b094dff1 6876426e57      push    576E4276h
b094dff6 e873caffff      call    TesSafe+0x3a6e (b094aa6e)
b094dffb c6056da295b001  mov     byte ptr [TesSafe+0x1326d (b095a26d)],1
b094e002 85ff            test    edi,edi
b094e004 7404            je      TesSafe+0x700a (b094e00a)
b094e006 ffd7            call    edi           //call KdDisableDebugger
b094e008 eb24            jmp     TesSafe+0x702e (b094e02e)
b094e00a 803d6ea295b000  cmp     byte ptr [TesSafe+0x1326e (b095a26e)],0
b094e011 751b            jne     TesSafe+0x702e (b094e02e)
b094e013 6812010000      push    112h
b094e018 68e64d6e43      push    436E4DE6h
b094e01d 6873426e57      push    576E4273h
b094e022 e865caffff      call    TesSafe+0x3a8c (b094aa8c)
b094e027 c6056ea295b001  mov     byte ptr [TesSafe+0x1326e (b095a26e)],1
b094e02e 803e00          cmp     byte ptr [esi],0
b094e031 75b0            jne     TesSafe+0x6fe3 (b094dfe3)

//跳回去,继续call KdDisableDebugger  ,应该是个while循环,所以把这里nop掉
//ew b094e031 0x9090

b094e033 5f              pop     edi
b094e034 5e              pop     esi
b094e035 c3              ret



输入g,继续运行,WinDbg再次断下。

查看调用堆栈:TesSafe+0x7124

2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
b094e112 a16ceb95b0      mov     eax,dword ptr [TesSafe+0x17b6c (b095eb6c)]
b094e117 8b402c          mov     eax,dword ptr [eax+2Ch]
b094e11a 330568eb95b0    xor     eax,dword ptr [TesSafe+0x17b68 (b095eb68)]
b094e120 7404            je      TesSafe+0x7126 (b094e126)
b094e122 ffd0            call    eax         //call KdDisableDebugger
b094e124 eb24            jmp     TesSafe+0x714a (b094e14a)
b094e126 803d72a295b000  cmp     byte ptr [TesSafe+0x13272 (b095a272)],0
b094e12d 751b            jne     TesSafe+0x714a (b094e14a)
b094e12f 6882010000      push    182h
b094e134 68e64d6e43      push    436E4DE6h
b094e139 6873426e57      push    576E4273h
b094e13e e849c9ffff      call    TesSafe+0x3a8c (b094aa8c)
b094e143 c60572a295b001  mov     byte ptr [TesSafe+0x13272 (b095a272)],1
b094e14a 8b0d64a295b0    mov     ecx,dword ptr [TesSafe+0x13264 (b095a264)]
//跳到这里,dd b095a264的值是8055d664
//kd> u 8055d664
//nt!KiDebugRoutine:            

b094e150 85c9            test    ecx,ecx
b094e152 740f            je      TesSafe+0x7163 (b094e163)
b094e154 a168a295b0      mov     eax,dword ptr [TesSafe+0x13268 (b095a268)]
//这里eax = nt!KdpStub:

b094e159 85c0            test    eax,eax
b094e15b 7406            je      TesSafe+0x7163 (b094e163)
b094e15d 3901            cmp     dword ptr [ecx],eax
b094e15f 7402            je      TesSafe+0x7163 (b094e163)
b094e161 8901            mov     dword ptr [ecx],eax
//根据上面的ecx,跟eax的值来看 应该是设置debug函数之类的。
//测试后,发现这条代码执行后,windbg失去通讯。所以直接nop掉


b094e163 c3              ret
b094e164 cc              int     3
b094e165 cc              int     3



输入g,继续运行,这时WinDbg输出
Shutdown occurred at (Wed Apr 24 11:59:41.193 2013 (UTC + 8:00))...unloading all symbol tables.
Waiting to reconnect...

看样子好像是重启了? 在切换到虚拟机发现并没有重启。

这个地方经过测试发现如果没有恢复上面的两个IATHOOK,系统就直接死掉了,如果恢复了,虽然windbg输出了上面的信息,但切换到虚拟机之后发现游戏已经到了 输入账号密码 界面。

到了这一步,仍然没完,将虚拟机断下来,再次下bp KdDisableDebugger断点。

发现系统再次断下,因为我们已经在KdDisableDebugger的第一行代码ret了。所以不用担心WinDbg会断开,输入g,再次运行,再次断下。

发现这里应该有一个定时器在不断的调用KdDisableDebugger。

[table=98%,rgb(27, 36, 38)]
[tr][td]2
4
6
8
10
12
14
16
18
20
22
24
[/td][td]这里怎么处理,直接把call eax nop掉吗,很不幸,哪条代码也有校验,一改就重启了。不过哪个位置不能改,我们可以改别的位置。在这个函数头部b0965112 的位置直接 jmp b0965124 跳过call eax即可。

这样就可以完全的解决了TP的反双机调试。

以前的那篇文章里面提到在登陆游戏过程中还会调用KdDisableDebugger,但我测试之后发现直到进入游戏里面也没有再断下来过。。。



然后,可以去邪恶了。。。。

源码也贴上吧,都是用硬编码写的,自己修改修改就可以了。





x64系统过TP大概分两步,首先要过双机调试,然后要过应用层调试。

1、过双机调试,这里也分两步
(1)首先要保证debug模式下启动游戏不蓝屏。

我也是第一次研究TP,对这之前的保护不了解,不过看网上所说这个启动蓝屏似乎是最近几个月新加的。
要过这个需要对内核调试引擎有一定的了解,不过还好我们是站在巨♂人♀的肩膀上,在论坛上找到了篇帖子
比较详细的分析了系统启动时内核调试引擎初始化的几个标志。
TP只是检测了其中一个(待定),KdEnteredDebugger,它通过MDL映射来判断这个标志是不是True,如果是就蓝屏,解决方法我照抄了那篇帖子,直接在Hook一下IoAllocateMdl,
把判断的地址改到一个恒为False的地方这样就可以绕过了
[table=98%,rgb(27, 36, 38)]
[tr][td]2
4
6
8
10
12
14
16
[/td][td]不过这样做带来一个问题,蓝屏是不蓝屏了,TP的驱动模块也能加载,但只能启动登陆客户端Client.exe,在登陆后启动DNF.exe时TP会再进行一次检测,
这次就会造成虚拟机卡死,估计还是和调试模式的检测有关。


我用了个折中的解决办法,就是用户态调试的时候不进入Debug模式,这样就能启动DNF.exe了,不过实在是不方便

(2)使双机调试能下断点
关于双机调试中断点原理和异常的处理流程,我是看了《软件调试》这本书和http://www.xfocus.net/articles/200412/765.html这片帖子,有了些了解。
对于TP来说,他是不断调用KdDisableDebuger()这个函数来清零KdDebuggerEnabled,调试引擎就是在KeUpdateSystemTime()这个函数里不断检测这个标志来确定异常处理的流程

解决方法,一开始我是直接HooK KdDisableDebuger(),开头
[table=98%,rgb(27, 36, 38)]
[tr][td]2
[/td][td]直接STATUS_SUCCESS然后返回,不让它做其他处理,但是毫无效果,还是下不了断点,不知道是怎么回事……

后来想了个办法,直接修改KeUpdateSystemTime()里面检测的地方,和上面一样,让它检测其它地址……
[table=98%,rgb(27, 36, 38)]
[tr][td]2
4
6
8
10
[/td][td]要改的地方有3处,我只贴了一处的代码。这样就能下各种断点了。

2、双机调试之后就是用户态调试了
x64系统上TP目前做的保护还不是特别多,这也是我选x64入手的原因之一
TP修改了DebugObject中ValidAccessMask一项,这个就是调试权限,代码中我们恢复下就可以了。

windbg下输入以下命令就可以定位到ValidAccessMask,下硬件断点就能找到ValidAccessMask清零的地方了。
至于如何在自己的代码中定位这个变量,我是通过SSDT表查找NtCreateDebugObject这个内核函数地址,里面定位DbgkDebugObjectType
再根据下面的结构体加几个偏移地址(+0x040 + 0x01c)来找到ValidAccessMask。应该有更好的定位方法……
[table=98%,rgb(27, 36, 38)]
[tr][td]2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74
76
78
80
82
[/td][td]从清零代码那里的windbg信息来看,没有地址标号的提示(TesSafe+xxxx),这代码似乎不是在一个驱动模块里?
关于恢复的方法,大体上能想到3种:
1、开一个IoTimer或者DpcTimer或者干脆开个线程,不断对ValidAccessMask地址写入它原来的值
2、自己代码内利用调试寄存器下硬件断点,然后hook IDT 1号中断服务子程,在里面恢复ValidAccessMask
3、自己代码内利用调试寄存器下硬件断点,定位到上面的清零代码,nop之(我在windbg里直接nop掉是可以的,没有模块自校验)
目前我尝试了第一种方法,开了个IoTimer,虽说1s一次会造成一些概率问题,但测试而已,简单粗暴就好。
[table=98%,rgb(27, 36, 38)]
[tr][td]2
4
6
[/td][td]至于为什么会有后两种想法,是因为我觉得其作用不仅仅在于次,通过调试寄存器和1号中断应该还可以作很多事情,不仅仅是写个0x1f000f而已……
不过虽说想法很好,我还没开始实践……这中间似乎涉及到多核CPU还有用户栈内核栈切换的许多知识……

好了,现在可以开OD附加了,那么问题来了,学挖掘……哦不…………
还是先请前辈们看看附件中的效果图,第一张是开另一个任意程序调试的OD,一切正常。

第二张是附加了DNF.exe,反汇编窗口基本全是0,右键看不到模块信息……
这个就是传说中的DebugPort清零吗?如果是的话,我在自己研究下恢复,如果不是,哪是什么问题造成了这种现象?

另外从图上游戏界面可以看到,DNF检测到了非法模块,这个不知道会有什么影响……


上传的附件:

</irp_mj_maximum_function;i++)
郁金香外挂教程,学习中...
回复

使用道具 举报

发表于 2018-2-6 22:40:00 | 显示全部楼层
也许大家也是研究腾讯游戏的爱好者,对腾讯的游戏都有过这样的体会 例如OD与CE无法进行如以下操作:
无法附加进程,
无法打开进程,
游戏进程被隐藏无法在工具中查看到,
内存无法读取代码
内存修改后游戏掉线
无法双机进行调试
出现SX非法模块提示 `
其实以上说的这么多限制 都是因为TP保护造成的.其实这些东西研究了很久后,发现其实就是黑色老大常说的APIHOOK这方面. 7%32E1F)%
例如DNF的TP保护就是HOOK了以下几个API函数来禁止上面刚才说的那些:
NtOpenThread //这是TP防止调试器在它体内创建线程
NtOpenProcess //这是TP防止OD等在进程列表看到游戏进程
KiAttachProcess //这是TP防止其他软件附加它
NtReadVirtualMemory //这是TP防止别人读取它的内存
NtWriteVirtualMemory //这是TP防止别人在它的内存里面乱写乱画
KDCOM.dll:KdReceivePacket //这是TP这两个是COM串口的接受和发送数据
KDCOM.dll:KdSendPacket //这是TP主要用来方式别人双机调试,TP使用了KdDisableDebugger来禁用双机调试.
TP通过将以上这几个API进行HOOK后 来保护游戏, 看过独立团第四版本易语言辅助教程的人 应该知道 以上的那几个API函数 开头是 Nt 的吧
Nt开头的是ntdll.dll库中的函数,也正是黑色衬衣老大在第四版本易语言辅助教程中有一篇课程是讲 SSDTHOOK与恢复这方面的.
那么TP保护它比较变态,并对debugport进行了疯狂的清零操作甚至还包括EPROCESS+70\+74\+78等几处位置处理的手段通常都是向64端口写入FE导致计算机被重启。
下面我简单看说下以上关键的几个APIHOOK:
1.KiAttachProcess 函数
2.NtReadVirtualMemory 内存函数
3.NtWriteVirtualMemory 内存函数
4.NtOpenThread 线程函数
5.NtOpenProcess 进程函数
那么前3个函数是可以直接SSDT恢复的 第四版本易语言辅助教程老大讲了如何恢复的 不明白的可以自己去看教程。
第4个函数是有监视,如果直接恢复的话电脑会即刻重启.(TP蛮变态)
第5个函数和ring3有驱动通信,直接恢复这个函数的话 游戏会在1分钟内弹出SX非法模块提示.

既然我们现在知道了TP保护的保护特点和这几个API分析后的结果.
接下来就是要做出相应的解除TP保护(也就是这些APIHOOK)
下面我在梳理一下头绪给出相应的解决方案
1.首先直接恢复 第1、2、3处的SSDT表中的HOOK
2.绕过4、5处的HOOK 不采用直接恢复
3.将TP保护程序中的debugport清零的内核线程干掉 停止该线程继续运行.
4.恢复硬件断点
但是要有一个先后的逻辑顺序
因为内核有一个线程负责监视几个地方,必须要先干掉它。
但是这个内容我写在了处理debugport清零的一起,也就是第3步。所以大家在照搬源码的时候注意代码执行次序。
下面我们就开始写解除TP保护的代码,因为本人喜欢C++ 所以是c++编写,如果是使用易语言的话 就自己翻译过来吧
先从简单的工作讲起,恢复1、2、3处的HOOK
KiAttachProcess函数的处理的代码:
复制代码
//////////////////////////////////////////////////////////////////////
// 名称: Nakd_KiAttachProcess
// 功能: My_RecoveryHook_KiAttachProcess的中继函数
// 参数:
// 返回:
//////////////////////////////////////////////////////////////////////
static NAKED VOID Nakd_KiAttachProcess()
{
__asm
{
mov edi,edi
push ebp
mov ebp,esp
push ebx
push esi
mov eax,KiAttachProcessAddress //注意这个是全局变量 BYTE*
add eax,7
jmp eax
}
}
//////////////////////////////////////////////////////////////////////
// 名称: RecoveryHook_KiAttachProcess
// 功能: 解除游戏保护对_KiAttachProcess函数的HOOK(DNF)
// 参数:
// 返回: 状态
//////////////////////////////////////////////////////////////////////
NTSTATUS My_RecoveryHook_KiAttachProcess()
{
BYTE *KeAttachProcessAddress = NULL; //KeAttachProcess函数地址
BYTE *p;
BYTE MovEaxAddress[5] = {0xB8,0,0,0,0}; //
BYTE JmpEax[2] = {0xff,0xe0};
KIRQL Irql;
//特征码
BYTE Signature1 = 0x56, //p-1
Signature2 = 0x57, //p-2
Signature3 = 0x5F, //p-3
Signature4 = 0x5E, //p+5
Signature5 = 0xE8; //p第一个字节
//获得KeAttachProcess地址,然后通过特征码找到
//KiAttachProcess的地址
KeAttachProcessAddress = (BYTE*)MyGetFunAddress(L"KeAttachProcess");
if (KeAttachProcessAddress == NULL)
{
KdPrint(("KeAttachProcess地址获取失败\n"));
return FAILED_TO_OBTAIN_FUNCTION_ADDRESSES;
}
//将p指向KeAttachProcess函数开始处
p = KeAttachProcessAddress;
while (1)
{
if ((*(p-1) == Signature1) &&
(*(p-2) == Signature2) &&
(*(p+5) == Signature3) &&
(*(p+6) == Signature4) &&
(*p == Signature5))
{
//定位成功后取地址
KiAttachProcessAddress = *(PULONG)(p+1)+(ULONG)(p+5);
break;
}
//推动指针
p++;
}
//计算中继函数地址
*(ULONG *)(MovEaxAddress+1)=(ULONG)Nakd_KiAttachProcess;
WPOFF(); //清除CR0
//提升IRQL中断级
Irql=KeRaiseIrqlToDpcLevel();
//写入
RtlCopyMemory(KiAttachProcessAddress,MovEaxAddress,5);
RtlCopyMemory(KiAttachProcessAddress+5,JmpEax,2);
//恢复Irql
KeLowerIrql(Irql);
WPON(); //恢复CR0
return STATUS_SUCCESS;
}
郁金香外挂教程,学习中...
回复 支持 反对

使用道具 举报

发表于 2018-2-6 22:53:05 | 显示全部楼层
一:相关基础知识:

根据软件调试这本书上说的,Windows启动过程如下图:


        系统一共调用了两次KdInitSystem()函数。

第一次调用KdInitSystem会初始化一下全局变量:

1.KdPitchDebugger:布尔类型,用来表示是否显式抑制内核调试。当启动选项中包含/DEBUG选项时,这个变量会被设置为真。
2.KdDeBuggerEnable:布尔类型,用来表示内核调试是否被启用。当启动选项中包含/DEBUG或者/DEBUGPORT 而且不包含/NODEBUG时,这个变量会被设置为真。
3.KiDebugRoutine:函数指针类型,用来记录内核调试引擎的异常处理回调函数,当内核调试引擎活动时,只想KdpTrap函数,否则指向KdpStub函数。
4.KdpBreakpointTable:结构数组类型,用来记录代码断点,每一个元素为BREAKPOINT_ENTRY结构,用来描述一个断点,包括断点地址。
5.
可见KdDebuggerNotPresent也是一个判断是否是内核调试状态的标志。

6.根据先前大牛写的这个帖子http://bbs.pediy.com/showthread.php?t=186091
讲过KdEnteredDebugger也是一个判断是否为内核调试状态的标志。

7.如果是内核调试状态,KiDebugRoutine指向KdpTrap函数,如果不是内核调试状态则指向KdpStub函数。

综合上述,上述7点中的任何一点可以判断是否处于内核调试状态。

二:接下来看一下*P对内核做的手脚。

1.解决*P加载蓝屏的方法:

根据上面提到的帖子上有给出解决方案,因为*P会访问KdEnteredDebugger的内容所以Hook IoAllocMdl,当访问KdEnteredDebugger的时候让他去访问其他始终为0的地址。具体可以看上面帖子的地址。

2.解决完1之后,在虚拟机启动*P就不会蓝屏了,*P会有两个IAT Hook 两个通讯函数:KdSendPacket 和KdReceivePacket,反汇编一下这两个函数:



解决方案:
解析内核文件的PE结构,获取两个发送函数的地址,得到地之后把地址赋值给自己驱动的全局变量,然后用全局变量替换这两个jmp的地址。详见源码。
3.众所周知,*P是调用KdDisableDebugger函数来禁止内核调试的。所以下断KdDisableDebugger。并将KdDisableDebugger的头部改成ret:



运行*P后,Tp执行到KdDisableDebugger时就会断下来,单步走两步,发现了*P的第一个调用KdDisableDebugger的函数:

直接jmp 到958aea2e

这里的[edi] = 8416cd2c  ==> KdDebuggerEnabled

这是上面7条中之一,检测是否是内核调试的标志。

我尝试着将比较的0改为1,然后G~

4.接下来进入了第二个调用KdDisableDebugger的地方:


根据分析查看[958E4278h]这里存放的是KiDebugRoutine函数地址,

[958E427Ch]存放的是KdpStub地址

,根据汇编可知:
*P取出KiDebugRoutine地址,然后将KdpStub赋值给KiDebugRoutine,之后返回。

解决方案:
修改[958E427Ch]中的内容为KdpTrap地址

Go起来~

5.来到了第三处调用KdDisableDebugger的地方:


这里这个CALL貌似是IAT HOOK 两个通讯函数的过程,因为push 的两个地址内容是


正是IAT HOOK *P自己实现的函数。

三:综合一下整个过程。重新整理一下所有解决方案。就是替换上述基础知识中的7个内核变量。

首先打开IDA,搜索全部KdDeBuggerEnable和KdPitchDebugger调用:分别有下面几处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//1. KeUpdateSystemTime
//
//nt!KeUpdateSystemTime + 0x417:
//84096d65 33c9            xor     ecx, ecx
//84096d67 8d542420        lea     edx, [esp + 20h]
//
//nt!KeUpdateSystemTime + 0x41d :
//84096d6b 803d2c1d188400  cmp     byte ptr[nt!KdDebuggerEnabled(84181d2c)], 0      <--ul_KdDebuggerEnabled_1
//84096d72 7464            je      nt!KeUpdateSystemTime + 0x48a (84096dd8)
//
//
//2. KeUpdateRunTime
//
//nt!KeUpdateRunTime + 0x149:
//840970c2 803d2c1d188400  cmp     byte ptr[nt!KdDebuggerEnabled(84181d2c)], 0      <--ul_KdDebuggerEnabled_2
//840970c9 7412            je      nt!KeUpdateRunTime + 0x164 (840970dd)
//
//3. KdCheckForDebugBreak
//
//kd > uf kdcheckfordebugbreak
//nt!KdCheckForDebugBreak:
//840970e9 803d275d148400  cmp     byte ptr[nt!KdPitchDebugger(84145d27)], 0        <--ul_KdPitchDebugger_1
//840970f0 7519            jne     nt!KdCheckForDebugBreak + 0x22 (8409710b)
//
//nt!KdCheckForDebugBreak + 0x9 :
//840970f2 803d2c1d188400  cmp     byte ptr[nt!KdDebuggerEnabled(84181d2c)], 0      <--ul_KdDebuggerEnabled_3
//840970f9 7410            je      nt!KdCheckForDebugBreak + 0x22 (8409710b)
//
//
//4. KdPollBreakIn
//
//kd > uf KdPollBreakIn
//nt!KdPollBreakIn:
//8409711f 8bff            mov     edi, edi
//84097121 55              push    ebp
//84097122 8bec            mov     ebp, esp
//84097124 51              push    ecx
//84097125 53              push    ebx
//84097126 33db            xor     ebx, ebx
//84097128 381d275d1484    cmp     byte ptr[nt!KdPitchDebugger(84145d27)], bl       <--ul_KdPitchDebugger_2
//8409712e 7407            je      nt!KdPollBreakIn + 0x18 (84097137)
//
//nt!KdPollBreakIn + 0x11:
//84097130 32c0            xor     al, al
//84097132 e9d2000000      jmp     nt!KdPollBreakIn + 0xea (84097209)
//
//nt!KdPollBreakIn + 0x18 :
//84097137 885dff          mov     byte ptr[ebp - 1], bl
//8409713a 381d2c1d1884    cmp     byte ptr[nt!KdDebuggerEnabled(84181d2c)], bl     <--ul_KdDebuggerEnabled_4
//84097140 0f84c0000000    je      nt!KdPollBreakIn + 0xe7 (84097206)


将几处分别替换成我们驱动中定义的全局变量就OK。详见源码~

上述几处修改完毕之后,双机调试就可以正常下断了~

最后说说此次更新后的*P,TesSafe.sys并不是真正的驱动,而是一个加载工具,它NEW出来一片新的内存,新内存才是真正功能函数。那么如何寻找真正的*P驱动呢,我用的方法是找到*P HOOK 的NtReadVirtualMemory 地址,然后向上搜索PE特征~

补充一下获取*P真正基址的方法: HOOK KdDisableDebugger
1
2
3
4
5
6
7
8
9
10
11
12
13
__asm
        {
            push eax;
            push ebx;
            mov  eax, [ebp + 4];
            add  eax, 4;
            mov  ebx, [eax];
            mov  ul_ret, ebx;
            pop  ebx;
            pop  eax;
        }
ul_TP_RelBaseAddress = ul_ret - 0x47277;
DbgPrint("Tp 真正加载地址:%p\n", ul_TP_RelBaseAddress);


因为KdDisableDebugger 不止一次会被调用,所以要做好处理~

PS:代码只是测试写的,没有好好写,大家凑合看个思路就好了~

顺便给自己博客打个广告:http://blog.csdn.net/zfdyq0

最后上个图~祝大家新年快乐~


重新整理了一下代码,Win7 32直接加载驱动就能用
其他系统自己调一下细节即可~
郁金香外挂教程,学习中...
回复 支持 反对

使用道具 举报

 楼主| 发表于 2018-2-8 17:43:22 | 显示全部楼层
1、过双机调试,这里也分两步
(1)首先要保证debug模式下启动游戏不蓝屏。

我也是第一次研究TP,对这之前的保护不了解,不过看网上所说这个启动蓝屏似乎是最近几个月新加的。
要过这个需要对内核调试引擎有一定的了解,不过还好我们是站在巨♂人♀的肩膀上,在论坛上找到了篇帖子
比较详细的分析了系统启动时内核调试引擎初始化的几个标志。
TP只是检测了其中一个(待定),KdEnteredDebugger,它通过MDL映射来判断这个标志是不是True,如果是就蓝屏,解决方法我照抄了那篇帖子,直接在Hook一下IoAllocateMdl,
把判断的地址改到一个恒为False的地方这样就可以绕过了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PMDL newIoAllocateMdl(
        __in_opt PVOID  VirtualAddress,
        __in ULONG  Length,
        __in BOOLEAN  SecondaryBuffer,
        __in BOOLEAN  ChargeQuota,
        __inout_opt PIRP  Irp  OPTIONAL)
{

        if (VirtualAddress == KdEnteredDebugger)
        {
                //DbgPrint("[KdEnteredDebugger] address: %p\n", KdEnteredDebugger);
                VirtualAddress = (PUCHAR)KdEnteredDebugger + 0x30;  //据观察,+0x30 的位置恒为0
        }

        return oldIoAllocateMdl(VirtualAddress, Length, SecondaryBuffer, ChargeQuota, Irp);
}

不过这样做带来一个问题,蓝屏是不蓝屏了,TP的驱动模块也能加载,但只能启动登陆客户端Client.exe,在登陆后启动DNF.exe时TP会再进行一次检测,
这次就会造成虚拟机卡死,估计还是和调试模式的检测有关。

我用了个折中的解决办法,就是用户态调试的时候不进入Debug模式,这样就能启动DNF.exe了,不过实在是不方便

(2)使双机调试能下断点
关于双机调试中断点原理和异常的处理流程,我是看了《软件调试》这本书和http://www.xfocus.net/articles/200412/765.html这片帖子,有了些了解。
对于TP来说,他是不断调用KdDisableDebuger()这个函数来清零KdDebuggerEnabled,调试引擎就是在KeUpdateSystemTime()这个函数里不断检测这个标志来确定异常处理的流程

解决方法,一开始我是直接HooK KdDisableDebuger(),开头
1
2
xor rax, rax
ret

直接STATUS_SUCCESS然后返回,不让它做其他处理,但是毫无效果,还是下不了断点,不知道是怎么回事……

后来想了个办法,直接修改KeUpdateSystemTime()里面检测的地方,和上面一样,让它检测其它地址……
1
2
3
4
5
6
7
8
9
10
//替换KeUpdateSystemTime函数中两个KdDebuggerEnabled变量地址为DummyKdDebuggerEnabled
        KUSTPatchAddr1 = SearchAddressBySig((PUCHAR)KeUpdateSystemTimeAddr + 0x100, 0x100, UPSig1, sizeof(UPSig1)) ;
        if (KUSTPatchAddr1 != NULL)
        {
            KUSTPatchAddr1 += sizeof(UPSig1);
            //64位汇编,变量均为相对rip地址,下同
            DisableWriteProtect64();
            *(PULONG)KUSTPatchAddr1 = (PUCHAR)pDummyKdDebuggerEnabled - ((PUCHAR)KUSTPatchAddr1 - 2) - 6; //指令长度为6
            EnableWriteProtect64();
        }

要改的地方有3处,我只贴了一处的代码。这样就能下各种断点了。

2、双机调试之后就是用户态调试了
x64系统上TP目前做的保护还不是特别多,这也是我选x64入手的原因之一
TP修改了DebugObject中ValidAccessMask一项,这个就是调试权限,代码中我们恢复下就可以了。

windbg下输入以下命令就可以定位到ValidAccessMask,下硬件断点就能找到ValidAccessMask清零的地方了。
至于如何在自己的代码中定位这个变量,我是通过SSDT表查找NtCreateDebugObject这个内核函数地址,里面定位DbgkDebugObjectType
再根据下面的结构体加几个偏移地址(+0x040 + 0x01c)来找到ValidAccessMask。应该有更好的定位方法……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
dq DbgkDebugObjectType
dt _OBJECT_TYPE fffffa80`24e33250

清零前:

1: kd> dt _OBJECT_TYPE_INITIALIZER fffffa80`24e33250+0x040
nt!_OBJECT_TYPE_INITIALIZER
   +0x000 Length           : 0x70
   +0x002 ObjectTypeFlags  : 0x8 ''
   +0x002 CaseInsensitive  : 0y0
   +0x002 UnnamedObjectsOnly : 0y0
   +0x002 UseDefaultObject : 0y0
   +0x002 SecurityRequired : 0y1
   +0x002 MaintainHandleCount : 0y0
   +0x002 MaintainTypeList : 0y0
   +0x002 SupportsObjectCallbacks : 0y0
   +0x004 ObjectTypeCode   : 0
   +0x008 InvalidAttributes : 0
   +0x00c GenericMapping   : _GENERIC_MAPPING
[COLOR="Red"]   +0x01c ValidAccessMask  : 0x1f000f[/COLOR]
   +0x020 RetainAccess     : 0
   +0x024 PoolType         : 0 ( NonPagedPool )
   +0x028 DefaultPagedPoolCharge : 0
   +0x02c DefaultNonPagedPoolCharge : 0x58
   +0x030 DumpProcedure    : (null)
   +0x038 OpenProcedure    : (null)
   +0x040 CloseProcedure   : 0xfffff800`01f0ddb0     void  nt!DbgkpCloseObject+0
   +0x048 DeleteProcedure  : 0xfffff800`01d66fe0     void  nt!CmpConfigureProcessors+0
   +0x050 ParseProcedure   : (null)
   +0x058 SecurityProcedure : 0xfffff800`01dd25f0     long  nt!SeDefaultObjectMethod+0
   +0x060 QueryNameProcedure : (null)
   +0x068 OkayToCloseProcedure : (null)


清零后:
0: kd> dt _OBJECT_TYPE_INITIALIZER fffffa80`24e51250+0x040
nt!_OBJECT_TYPE_INITIALIZER
   +0x000 Length           : 0x70
   +0x002 ObjectTypeFlags  : 0x8 ''
   +0x002 CaseInsensitive  : 0y0
   +0x002 UnnamedObjectsOnly : 0y0
   +0x002 UseDefaultObject : 0y0
   +0x002 SecurityRequired : 0y1
   +0x002 MaintainHandleCount : 0y0
   +0x002 MaintainTypeList : 0y0
   +0x002 SupportsObjectCallbacks : 0y0
   +0x004 ObjectTypeCode   : 0
   +0x008 InvalidAttributes : 0
   +0x00c GenericMapping   : _GENERIC_MAPPING
[COLOR="Red"]   +0x01c ValidAccessMask  : 0[/COLOR]
   +0x020 RetainAccess     : 0
   +0x024 PoolType         : 0 ( NonPagedPool )
   +0x028 DefaultPagedPoolCharge : 0
   +0x02c DefaultNonPagedPoolCharge : 0x58
   +0x030 DumpProcedure    : (null)
   +0x038 OpenProcedure    : (null)
   +0x040 CloseProcedure   : 0xfffff800`01eb5db0     void  nt!DbgkpCloseObject+0
   +0x048 DeleteProcedure  : 0xfffff800`01d0efe0     void  nt!CmpConfigureProcessors+0
   +0x050 ParseProcedure   : (null)
   +0x058 SecurityProcedure : 0xfffff800`01d7a5f0     long  nt!SeDefaultObjectMethod+0
   +0x060 QueryNameProcedure : (null)
   +0x068 OkayToCloseProcedure : (null)

清零代码:
fffff880`0bcdc4cc 54              push    rsp
fffff880`0bcdc4cd 33c0            xor     eax,eax
fffff880`0bcdc4cf 87434c          xchg    eax,dword ptr [rbx+4Ch]
fffff880`0bcdc4d2 33c0            xor     eax,eax
fffff880`0bcdc4d4 874350          xchg    eax,dword ptr [rbx+50h]
fffff880`0bcdc4d7 33c0            xor     eax,eax
[COLOR="Red"]fffff880`0bcdc4d9 87435c          xchg    eax,dword ptr [rbx+5Ch] // ValidAccessMask  [/COLOR]
fffff880`0bcdc4dc 833d9585000000  cmp     dword ptr [fffff880`0bce4a78],0
fffff880`0bcdc4e3 0f8544feffff    jne     fffff880`0bcdc32d
fffff880`0bcdc4e9 33c9            xor     ecx,ecx
fffff880`0bcdc4eb ff15df6b0000    call    qword ptr [fffff880`0bce30d0]
fffff880`0bcdc4f1 488b4c2440      mov     rcx,qword ptr [rsp+40h]
fffff880`0bcdc4f6 4833cc          xor     rcx,rsp
fffff880`0bcdc4f9 e822570000      call    fffff880`0bce1c20
fffff880`0bcdc4fe 488b5c2468      mov     rbx,qword ptr [rsp+68h]
fffff880`0bcdc503 4883c450        add     rsp,50h
fffff880`0bcdc507 5f              pop     rdi
fffff880`0bcdc508 c3              ret

从清零代码那里的windbg信息来看,没有地址标号的提示(TesSafe+xxxx),这代码似乎不是在一个驱动模块里?
关于恢复的方法,大体上能想到3种:
1、开一个IoTimer或者DpcTimer或者干脆开个线程,不断对ValidAccessMask地址写入它原来的值
2、自己代码内利用调试寄存器下硬件断点,然后hook IDT 1号中断服务子程,在里面恢复ValidAccessMask
3、自己代码内利用调试寄存器下硬件断点,定位到上面的清零代码,nop之(我在windbg里直接nop掉是可以的,没有模块自校验)
目前我尝试了第一种方法,开了个IoTimer,虽说1s一次会造成一些概率问题,但测试而已,简单粗暴就好。
1
2
3
4
5
6
VOID OnTimer(DEVICE_OBJECT  *DeviceObject, PVOID  Context)
{
        DisableWriteProtect64();
        *g_pValidAccessMask = 0x1f000f;
        EnableWriteProtect64();
}

至于为什么会有后两种想法,是因为我觉得其作用不仅仅在于次,通过调试寄存器和1号中断应该还可以作很多事情,不仅仅是写个0x1f000f而已……
不过虽说想法很好,我还没开始实践……这中间似乎涉及到多核CPU还有用户栈内核栈切换的许多知识……

好了,现在可以开OD附加了,那么问题来了,学挖掘……哦不…………
还是先请前辈们看看附件中的效果图,第一张是开另一个任意程序调试的OD,一切正常。

第二张是附加了DNF.exe,反汇编窗口基本全是0,右键看不到模块信息……
这个就是传说中的DebugPort清零吗?如果是的话,我在自己研究下恢复,如果不是,哪是什么问题造成了这种现象?

另外从图上游戏界面可以看到,DNF检测到了非法模块,这个不知道会有什么影响……
郁金香外挂教程,学习中...
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

限时限量优惠

QQ|小黑屋|手机版|郁金香外挂技术-郁金香灬老师 ( 苏ICP备10059359号 )

GMT+8, 2020-6-2 11:54 , Processed in 1.003445 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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