其实渣新学内核编程不过半个月时间,帖中难免有幼稚的想法和错误,遇到的问题我会用蓝色文字表述,还请各位前辈指正,感激不尽!
我把自己目前的进展和遇到的问题一起说说吧:
x64系统过TP大概分两步,首先要过双机调试,然后要过应用层调试。
1、过双机调试,这里也分两步
(1)首先要保证debug模式下启动游戏不蓝屏。
我也是第一次研究TP,对这之前的保护不了解,不过看网上所说这个启动蓝屏似乎是最近几个月新加的。
要过这个需要对内核调试引擎有一定的了解,不过还好我们是站在巨♂人♀的肩膀上,在看雪找到了篇帖子,比较详细的分析了系统启动时内核调试引擎初始化的几个标志。
TP只是检测了其中一个(待定), KdEnteredDebugger,它通过MDL映射来判断这个标志是不是True,如果是就蓝屏,解决方法我照抄了那篇帖子,直接在Hook一下IoAllocateMdl,
把判断的地址改到一个恒为False的地方这样就可以绕过了
不过这样做带来一个问题,蓝屏是不蓝屏了,TP的驱动模块也能加载,但只能启动登陆客户端Client.exe,在登陆后启动DNF.exe时TP会再进行一次检测,
这次就会造成虚拟机卡死,估计还是和调试模式的检测有关。
我用了个折中的解决办法,就是用户态调试的时候不进入Debug模式,这样就能启动DNF.exe了,不过实在是不方便
(2)使双机调试能下断点
关于双机调试中断点原理和异常的处理流程,我是看了《软件调试》这本书和http://www.xfocus.net/articles/200412/765.html这片帖子,有了些了解。
对于TP来说,他是不断调用KdDisableDebuger()这个函数来清零KdDebuggerEnabled,调试引擎就是在KeUpdateSystemTime()这个函数里不断检测这个标志来确定异常处理的流程
解决方法,一开始我是直接HooK KdDisableDebuger(),开头
直接STATUS_SUCCESS然后返回,不让它做其他处理,
但是毫无效果,还是下不了断点,不知道是怎么回事……
后来想了个办法,直接修改KeUpdateSystemTime()里面检测的地方,和上面一样,让它检测其它地址……
要改的地方有3处,我只贴了一处的代码。这样就能下各种断点了。
2、双机调试之后就是用户态调试了
x64系统上TP目前做的保护还不是特别多,这也是我选x64入手的原因之一
TP修改了 DebugObject中 ValIDAccessMask一项,这个就是调试权限,代码中我们恢复下就可以了。
windbg下输入以下命令就可以定位到 ValidAccessMask,下硬件断点就能找到 ValidAccessMask清零的地方。
至于如何在自己的代码中定位这个变量,我是通过SSDT表查找NtCreateDebugObject这个内核函数地址,里面定位 DbgkDebugObjectType
再根据下面的结构体加几个偏移地址( +0x040 + 0x01c)来找到 ValidAccessMask。应该有更好的定位方法……
从清零代码那里的windbg信息来看,没有地址标号的提示(TesSafe+xxxx),这代码似乎不是在一个驱动模块里?
关于恢复的方法,大体上能想到3种:
1、开一个IoTimer或者DpcTimer或者干脆开个线程,不断对 ValidAccessMask地址写入它原来的值
2、自己代码内利用调试寄存器下硬件断点,然后hook IDT 1号中断服务子程,在里面恢复 ValidAccessMask
3、 自己代码内利用调试寄存器下硬件断点,定位到上面的清零代码,nop之(我在windbg里直接nop掉是可以的,没有模块自校验)
目前我尝试了第一种方法,开了个IoTimer,虽说1s一次会造成一些概率问题,但测试而已,简单粗暴就好。
至于为什么会有后两种想法,是因为我觉得其作用不仅仅在于次,通过调试寄存器和1号中断应该还可以作很多事情,不仅仅是写个0x1f000f而已……
不过虽说想法很好,我还没开始实践……这中间似乎涉及到多核CPU还有用户栈内核栈切换的许多知识……
好了,现在可以开OD附加了,那么问题来了,学挖掘……哦不…………
还是先请前辈们看看效果图:
这是开另一个任意程序调试的OD,一切正常:
这是附加了DNF.exe,反汇编窗口基本全是0,右键看不到模块信息……
这个就是传说中的DebugPort清零吗?如果是的话,我在自己研究下恢复,如果不是,哪是什么问题造成了这种现象?
另外从图上游戏界面可以看到,DNF检测到了非法模块,这个不知道会有什么影响……
我把自己目前的进展和遇到的问题一起说说吧:
x64系统过TP大概分两步,首先要过双机调试,然后要过应用层调试。
1、过双机调试,这里也分两步
(1)首先要保证debug模式下启动游戏不蓝屏。
我也是第一次研究TP,对这之前的保护不了解,不过看网上所说这个启动蓝屏似乎是最近几个月新加的。
要过这个需要对内核调试引擎有一定的了解,不过还好我们是站在巨♂人♀的肩膀上,在看雪找到了篇帖子,比较详细的分析了系统启动时内核调试引擎初始化的几个标志。
TP只是检测了其中一个(待定), KdEnteredDebugger,它通过MDL映射来判断这个标志是不是True,如果是就蓝屏,解决方法我照抄了那篇帖子,直接在Hook一下IoAllocateMdl,
把判断的地址改到一个恒为False的地方这样就可以绕过了
[C++]
纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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(),开头
[Asm]
纯文本查看 复制代码
1
2
|
xor
rax, rax
ret
|
后来想了个办法,直接修改KeUpdateSystemTime()里面检测的地方,和上面一样,让它检测其它地址……
[C++]
纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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。应该有更好的定位方法……
[Asm]
纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
+0x01c ValidAccessMask : 0x1f000f
+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
+0x01c ValidAccessMask : 0
+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
fffff880`0bcdc4d9 87435c
xchg
eax
,
dword
ptr
[rbx+5Ch] // ValidAccessMask
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一次会造成一些概率问题,但测试而已,简单粗暴就好。
[C++]
纯文本查看 复制代码
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检测到了非法模块,这个不知道会有什么影响……