MySun179 发表于 2019-8-9 11:35:18

调试机制攻防

<div align="left"><div align="left"><div align="left"><div align="left"><div align="left"><b>3.1 </b><b><font face="宋体">调试机制</font></b></div><div align="left">调试机制就是指调试器的附加、调试器的调试事件接受、调试器的断点的触发等一系列的工作。</div><div align="left">游戏保护在保护游戏的时候,关键的保护部分就是对调试机制的防御。防止被调试可以说是整个游戏保护的全部重点核心,其他部分也都是为这个目的服务的。游戏保护对调试机制的防御也可以叫做对调试机制的破坏。</div><div align="left"><b> </b></div><div align="left"><b>3.2 </b><b><font face="宋体">游戏保护对调试机制的破坏方法</font></b></div><div align="left"><b>3.2.1</b><b><font face="宋体">调试附加破坏</font></b></div><div align="left">附加破坏有3种,一种是进程隐藏,一种是访问阻拦,一种附加线程拦截。</div><div align="left">进程隐藏:如GameGuard是通过断链的。</div><div align="left"></div><div align="left">访问阻拦:一般都是通过HOOK或者劫持实现的。</div><div align="left"></div><div align="left">附加线程拦截:</div><div align="left">通过前文说过对自身进程的内部的HOOK或者DLL_THREAD_ATTACH事件进行。</div><div align="left"> </div><div align="left">备注:</div>
<div align="left">一些游戏保护会在调试附加流程里加入一个多次Lock造成附加时,调试器直接卡住。</div><div align="left"><b>3.2.2</b><b><font face="宋体">调试事件破坏</font></b></div><div align="left">游戏保护对调试事件的破坏方式:DbgPort清0、线程从调试中隐藏。</div><div align="left">DbgPort清0:</div>
<div align="left">就是修改EPROCESS中的DebugObject指针指向的位置。</div><div align="left"></div><div align="left">线程从调试中隐藏:</div>
<div align="left">Windows调试结构的设计上允许具体的线程经过调试事件,可以通过API简单的设置。</div><div align="left"></div><div align="left"><b>3.2.3 </b><b><font face="宋体">调试断点的破坏</font></b></div><div align="left">方法有很多。</div>
<div align="left">第一种:IDT HOOK</div><div align="left"></div><div align="left">此法 简单明了,直接了当,不过兼容性容易出现问题。</div><div align="left">注意:部分私服的保护在中断监控上使用了VT技术,需要特别注意。</div><div align="left"> </div><div align="left">第二种:单步清0</div><div align="left">启动一个线程对自身进程其他所有的线程的单步标识清0。</div><div align="left"> </div><div align="left">第三种:硬断寄存器清0</div><div align="left">类似第二种。</div><div align="left"> </div><div align="left">其他种类:在事件派发或者产生的路径上做手脚,方法非常多。</div><div align="left"><b>3.3 </b><b><font face="宋体">检测调试</font></b></div><div align="left">检测调试与之前课程中的检测基本都是重复的,这里不再重复。</div><div align="left">重点是硬断检测</div><div align="left">通过2种方法检测硬断,一种是GetThreadContext主动检测。</div><div align="left">一种是通过任意SEH/VEH经过ZwContinue时检测硬断寄存器(此法不常见,只有少数私服保护使用)。</div><div align="left"><b>3.4 </b><b><font face="宋体">针对调试破坏与检测的攻击</font></b></div><div align="left">前面第一节已经将非IDT HOOK和大量劫持搞定了,这里不会重复前面处理过的内容。</div><div align="left"> </div><div align="left"><b>3.4.1</b><b><font face="宋体">针对附加破坏的攻击</font></b></div><div align="left"><b>A.</b><b>进程隐藏</b></div><div align="left">只要针对几个保护,特别处理一下就可以。</div><div align="left">处理方式有很多种,其中大体分成2类,一类是通过回调获取进程,然后在枚举的时候,插入。一类是通过其他枚举方式获取进程,完善枚举结果。</div>
<div align="left">这里举例一下,通过回调的模式。</div><div align="left"></div><div align="left">代码:</div><div class="blockcode"><blockquote>#include "stdafx.h"
#include "ApiTools.h"
#include "HookEngine.h"
#include "NtosReload.h"
#include "FixGameGuard.h"

HANDLE GameGuardProcessId=0;
HANDLE NpGameProcessId=0;
HANDLE GameMonProcessId=0;

T_ZwQuerySystemInformation OldNtQuerySystemInformation=NULL;
ULONG NtQuerySystemInformationSize=0;
int nNtQuerySystemInformationIndex=0;

BOOL IsGameGuardProcess(HANDLE ProcessId)
{

if (GameGuardProcessId&amp;&amp;GameGuardProcessId==ProcessId)
{
return TRUE;
}
if (GameMonProcessId&amp;&amp;GameMonProcessId==ProcessId)
{
return TRUE;
}
if (NpGameProcessId&amp;&amp;NpGameProcessId==ProcessId)
{
return TRUE;
}
return FALSE;
}
PEPROCESS GetGameGuardProcess()
{
NTSTATUS ns = STATUS_UNSUCCESSFUL;
if(NpGameProcessId)
{
PEPROCESS ProcessRet=NULL;
ns = PsLookupProcessByProcessId(NpGameProcessId,&amp;ProcessRet);
if (NT_SUCCESS(ns))
{
ObDereferenceObject(ProcessRet);
return ProcessRet;
}
}
return NULL;
}
VOID CheckGameGuardProcess(HANDLE ProcessId,HANDLE ParentId)
{
NTSTATUS ns = STATUS_UNSUCCESSFUL;
PEPROCESS ProcessObject=NULL;
ns = PsLookupProcessByProcessId((PVOID)ProcessId,&amp;ProcessObject);
if (NT_SUCCESS(ns))
{
PCHAR ImageName=NULL;
ImageName = PsGetProcessImageFileName(ProcessObject);
if (_stricmp(ImageName,"gameguard.des")==0)
{
GameGuardProcessId = ProcessId;
}
if (_stricmp(ImageName,"gamemon.des")==0)
{
GameMonProcessId = ProcessId;
NpGameProcessId = ParentId;
}
ObDereferenceObject(ProcessObject);
}
}
VOID DelGameGuardProcess(HANDLE ProcessId)
{
if (ProcessId==NpGameProcessId)
{
NpGameProcessId=0;
}
if (ProcessId==GameMonProcessId)
{
GameMonProcessId =0;
}
if (GameGuardProcessId==ProcessId)
{
GameGuardProcessId=0;
}
}


VOID
WatchGameGuardNotifyRoutine(
__in HANDLE ParentId,
__in HANDLE ProcessId,
__in BOOLEAN Create)
{

if(!Create)
{
DelGameGuardProcess(ProcessId);
}
else
{
CheckGameGuardProcess(ProcessId,ParentId);
}
}

BOOL InitFixGameGuard()
{
NTSTATUS ns;
ULONG_PTR ulAddress=0;
PsSetCreateProcessNotifyRoutine((PCREATE_PROCESS_NOTIFY_ROUTINE)WatchGameGuardNotifyRoutine,FALSE);
nNtQuerySystemInformationIndex = GetSyscallNumber("ZwQuerySystemInformation");
if (nNtQuerySystemInformationIndex)
{
ulAddress = m_NewNtos-&gt;NtosTable-&gt;Base;
ns = HookFunction(ulAddress,(ULONG_PTR)ProcNtQuerySystemInformation,(PVOID *)&amp;OldNtQuerySystemInformation,&amp;NtQuerySystemInformationSize);
if (NT_SUCCESS(ns))
{
return TRUE;
}
}
return FALSE;
}

NTSTATUS NTAPI ProcNtQuerySystemInformation(
IN SYSTEM_INFORMATION_CLASS SystemInfoClass,
OUT PVOID SystemInfoBuffer,
IN ULONG SystemInfoBufferSize,
OUT PULONG BytesReturned OPTIONAL
)
{
NTSTATUS ns = STATUS_UNSUCCESSFUL;
ns = OldNtQuerySystemInformation(SystemInfoClass,
SystemInfoBuffer,
SystemInfoBufferSize,
BytesReturned);
if (NT_SUCCESS(ns))
{
//这里处理NP的问题!!!!!!
if (SystemInfoClass==SystemProcessInformation||SystemInfoClass==SystemExtendedProcessInformation)
{
PSYSTEM_PROCESS_INFORMATION pInfo = NULL;
PEPROCESS gameProcess=NULL;
pInfo = (PSYSTEM_PROCESS_INFORMATION)SystemInfoBuffer;
gameProcess = GetGameGuardProcess();

if (gameProcess)
{
DbgPrint("processing np process hide!\r\n");
//先处理本进程的npgg.des的线程的问题
//KillNpThread();

while(pInfo)
{
if (pInfo-&gt;UniqueProcessId==NpGameProcessId)
{
//NP进程自己在列表里,防止MU这种2b货色!!!
DbgPrint("np is in list now\r\n");
return ns;
}
if (pInfo-&gt;NextEntryOffset==0)
{
break;
}
pInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)pInfo+pInfo-&gt;NextEntryOffset);
}
pInfo = (PSYSTEM_PROCESS_INFORMATION)SystemInfoBuffer;
while(pInfo)
{
WCHAR uName={0};
if (pInfo-&gt;ImageName.Buffer)
{
wcsncpy(uName,pInfo-&gt;ImageName.Buffer,MAX_PATH-2);
}
if (_wcsicmp(uName,L"CSRSS.EXE")==0)
{
CHAR aName={0};
ANSI_STRING asString;
UNICODE_STRING usString;
//找到了!!
pInfo-&gt;UniqueProcessId = NpGameProcessId;
strncpy(aName,PsGetProcessImageFileName(gameProcess),16);
RtlInitAnsiString(&amp;asString,aName);
RtlAnsiStringToUnicodeString(&amp;usString,&amp;asString,TRUE);
wcsncpy(pInfo-&gt;ImageName.Buffer,usString.Buffer,9);
RtlFreeUnicodeString(&amp;usString);
break;
}
if (pInfo-&gt;NextEntryOffset==0)
{
break;
}
pInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)pInfo+pInfo-&gt;NextEntryOffset);
}
}
}
}
return ns;
}</blockquote></div><div align="left"><b>A.</b><b>附加LOCK</b></div><div align="left">在Windows附加进程时,会使用一个FastMutex来确保自己一次只对一个进程进行附加操作。</div><div align="left"></div><div align="left">如果启动一个内核线程对这个FastMutex进行循环Acquire的话,就会造成附加卡死现象,也就是附加LOCK。</div><div align="left">对于这种LOCK的攻击,最简单的方式就是彻底和谐对FastMutex的Acquire和Release,在低版本的系统中(XP)可以直接通过HOOK的方式来解决,在高版本的系统里却不能简单的通过HOOK方式解决,而且由于FastMutex的地址不好定位其位置,这里引入一个全新的获取地址的方法,通过调试符号查找地址。</div>
<div align="left">示例:通过符号查找数据。</div><div align="left"></div><div align="left">关于枚举符号的资料:</div>
<div align="left"><a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms679318(v=vs.85).aspx" target="_blank">http://msdn.microsoft.com/en-us/library/windows/desktop/ms679318(v=vs.85).aspx</a></div><div align="left">代码:</div><div align="left">**** Hidden Message *****</div><div align="left"></div><div align="left">处理LOCK的思路:直接替换变量引用地址。</div><div align="left"></div><div align="left">代码:</div><div class="blockcode"><blockquote>#include "stdafx.h"
#include "KernelLoad.h"
#include "GetSetting.h"
#include "NtosReload.h"

FAST_MUTEX m_DbgFastMutex;
DWORD dwDbgkpProcessDebugPortMutex = 0;

VOID InitFixFastMutex()
{
BYTE *OldMutex=NULL;
BYTE *NewMutex=NULL;
int nLen=0;
dwDbgkpProcessDebugPortMutex = RtlGetSettingEx(L"DbgkpProcessDebugPortMutex");
if (dwDbgkpProcessDebugPortMutex)
{
//XP ,2003,vista,win2008,win7 应该是一样的处理,重定位成自己的FastMutex就行!!
DbgPrint("DbgkpProcessDebugPortMutex %08x\r\n",dwDbgkpProcessDebugPortMutex);
ExInitializeFastMutex(&amp;m_DbgFastMutex);
OldMutex = (BYTE *)dwDbgkpProcessDebugPortMutex;
NewMutex = (BYTE *)&amp;m_DbgFastMutex;
for (nLen=0;nLen&lt;sizeof(FAST_MUTEX);nLen++)
{
LdrProcessFixRefX((BYTE *)m_NewNtos-&gt;pNewNtosBase,(DWORD)&amp;(OldMutex),(DWORD)&amp;(NewMutex));
}
//pDbgFastMutex = (PFAST_MUTEX)dwDbgkpProcessDebugPortMutex;
//LdrProcessFixRefX((BYTE *)m_NewNtos-&gt;pNewNtosBase,(DWORD)&amp;(pDbgFastMutex-&gt;Count),(DWORD)&amp;(m_DbgFastMutex.Count));
//LdrProcessFixRefX((BYTE *)m_NewNtos-&gt;pNewNtosBase,(DWORD)&amp;(pDbgFastMutex-&gt;Owner),(DWORD)&amp;(m_DbgFastMutex.Owner));
//LdrProcessFixRefX((BYTE *)m_NewNtos-&gt;pNewNtosBase,(DWORD)&amp;(pDbgFastMutex-&gt;Contention),(DWORD)&amp;(m_DbgFastMutex.Contention));
//LdrProcessFixRefX((BYTE *)m_NewNtos-&gt;pNewNtosBase,(DWORD)&amp;(pDbgFastMutex-&gt;Event),(DWORD)&amp;(m_DbgFastMutex.Event));
//LdrProcessFixRefX((BYTE *)m_NewNtos-&gt;pNewNtosBase,(DWORD)&amp;(pDbgFastMutex-&gt;OldIrql),(DWORD)&amp;(m_DbgFastMutex.OldIrql));
}
}</blockquote></div><div align="left"><b>3.4.2</b><b><font face="宋体">针对调试事件破坏的攻击</font></b></div>
<div align="left">本节重点讲一下如何处理DbgPort清0,比较方便的方式是转移DbgPort的位置,网上流行的代码转移的位置充满蓝屏的冲突可能,稳定的转移应该选择EPROCESS中的空闲位置(系统初始化为0,而且也不使用,因此没有冲突)。</div><div align="left"></div><div align="left">在重载的内核里进行转移的代码:</div><div class="blockcode"><blockquote>#include "stdafx.h"
#include "NtosReload.h"
#include "GetSetting.h"
#include "ldasm.h"
#include "ApiTools.h"


WCHAR m_dbgport_symlist[]={
L"PspUserThreadStartup@",
L"PspExitThread@",
L"KiDispatchException@",
L"DbgkExitProcess@",
L"DbgkCreateThread@",
L"DbgkExitThread@",
L"DbgkForwardException@",//Thread!
L"DbgkpMarkProcessPeb@",
L"DbgkMapViewOfSection@",
L"DbgkUnMapViewOfSection@",
L"DbgkpCloseObject@",
L"DbgkpSetProcessDebugObject@",
L"DbgkpQueueMessage@",
L"DbgkCopyProcessDebugPort@",
L"DbgkClearProcessDebugObject@",
L"PspProcessDelete@",
L"PspTerminateAllThreads@",
L"PspTerminateProcess@",
L"PspCreateProcess@",
L"DbgkpPostFakeThreadMessages@",
};

DWORD dwDbgPortOffset=0;
DWORD dwNewDbgPortOffset=0;

DWORD FindDbgPortOffset()
{
DWORD dwStartAddress;
DWORD dwCurAddress;
DWORD Length=0;
PUCHAR pOpcode;
BYTE opCode[]={0x8B,0x80};
UNICODE_STRING usString;
PVOID pAddress=0;
RtlInitUnicodeString(&amp;usString, L"PsGetProcessDebugPort");
pAddress = MmGetSystemRoutineAddress(&amp;usString);
if (!pAddress)
{
return 0;
}
dwStartAddress = (DWORD)pAddress;
for(dwCurAddress = dwStartAddress; dwCurAddress &lt; dwStartAddress + PAGE_SIZE; dwCurAddress+=Length)
{
Length = SizeOfCode((PUCHAR)dwCurAddress, &amp;pOpcode);
//DbgPrint("opcode %02x\r\n",*pOpcode);
if (memcmp((void *)dwCurAddress,opCode,sizeof(opCode))==0)
{
return *(DWORD *)(dwCurAddress+2);
}
}
return 0;
}

VOID FixDbgPortOffset(PVOID pAddress)
{
DWORD dwStartAddress;
DWORD dwCurAddress;
DWORD Length=0;
PUCHAR pOpcode;
dwStartAddress = (DWORD)pAddress;
for(dwCurAddress = dwStartAddress; dwCurAddress &lt; dwStartAddress + PAGE_SIZE; dwCurAddress+=Length)
{
DWORD dwValue= *(DWORD*)dwCurAddress;
DWORD dwValue1 = *(DWORD *)(dwCurAddress+1);
DWORD dwValue2 = *(DWORD *)(dwCurAddress+2);

Length = SizeOfCode((PUCHAR)dwCurAddress, &amp;pOpcode);
//DbgPrint("opcode %02x\r\n",*pOpcode);
if (dwValue==0xCCCCCCCC)
{
//对于ntkrnlpa的内核,这种判断无压力,但是对于ntoskrnl的内核会出现问题,
//无法断定这样判断结尾是否是最好,但是目前看起来都这样是符合M$编译的二进制特征的~
//结尾判断!!
//DbgPrint("Find nop %08x\r\n",dwCurAddress);
break;
}
if (dwValue1==dwDbgPortOffset&amp;&amp;*pOpcode!=0x68)
{
//DbgPrint("Find Patch Offset\r\n");
*(DWORD *)(dwCurAddress+1)=dwNewDbgPortOffset;
}
if (dwValue2==dwDbgPortOffset)
{
//DbgPrint("Find Patch Offset\r\n");
*(DWORD *)(dwCurAddress+2)=dwNewDbgPortOffset;
}
}
}
VOID FixTerminate()
{
DWORD dwIndex= 0;
dwIndex = GetSyscallNumber("ZwTerminateProcess");
if (dwIndex)
{
PVOID pNewAddress = NULL;
pNewAddress = (PVOID)(m_NewNtos-&gt;NtosTable-&gt;Base);
DbgPrint("ZwTerminateProcess ssdt index %08x\r\n",dwIndex);
FixDbgPortOffset(pNewAddress);
}
}
BOOL InitFixDbgPort()
{
dwDbgPortOffset = FindDbgPortOffset();
if (dwDbgPortOffset)
{
DbgPrint("DebugPort: %08x\r\n",dwDbgPortOffset);
switch(*NtBuildNumber)
{
case 2600:
dwNewDbgPortOffset = 0x25C;
break;
case 3790:       
dwNewDbgPortOffset = 0x274;
break;
case 6000:
case 6001:
case 6002:
dwNewDbgPortOffset = 0x26C;
break;
case 7600:
case 7601:
dwNewDbgPortOffset = 0x09C;
break;
case 8102:
case 8250:
case 9200:
dwNewDbgPortOffset = 0xA4;
break;
default:
}
if (dwNewDbgPortOffset)
{
//继续走
int nSymIndex=0;
int nCount = sizeof(m_dbgport_symlist)/sizeof(m_dbgport_symlist);
DbgPrint("New DebugPort:%08x\r\n",dwNewDbgPortOffset);
FixTerminate();//Terminate里的问题!
for (nSymIndex=0;nSymIndex&lt;nCount;nSymIndex++)
{
//走起!!
DWORD dwAddress = RtlGetSettingEx(m_dbgport_symlist);
if (dwAddress)
{
PVOID pNewAddress =NULL;
DbgPrint("%ws %08x\r\n",m_dbgport_symlist,dwAddress);
//这里开始逐个处理,有一些需要XXOO
//首先每个进来的函数新内核版本都要走处理1个PAGE_SIZE内的DbgXXX的XX~
pNewAddress = (PVOID)GetKernelAddress((PVOID)dwAddress);
if(pNewAddress)
{
FixDbgPortOffset(pNewAddress);       
}

}
}
return TRUE;
}
}
return FALSE;
}</blockquote></div><div align="left">但仅仅这样子是不能顺利转发Dbg事件的,所以需要把Dbg事件也转发到重载的内核里来。</div><div align="left">转移代码:</div><div class="blockcode"><blockquote>#include "stdafx.h"
#include "GetSetting.h"
#include "HookEngine.h"
#include "ApiTools.h"

WCHAR m_redirect_symlist[]={
L"PspUserThreadStartup@",
L"PspExitThread@",
L"KiDispatchException@",
L"DbgkpCloseObject@",
L"PspProcessDelete@",
L"PspTerminateAllThreads@",
L"PspTerminateProcess@",
};

BOOL m_initRedirectDbg=FALSE;
VOID MakeJmp(PVOID pOld,PVOID pNew)
{
PVOID pSave=NULL;
ULONG SaveSize=0;
NTSTATUS ns = HookFunction((ULONG_PTR)pOld,(ULONG_PTR)pNew,&amp;pSave,&amp;SaveSize);
if (NT_SUCCESS(ns))
{
if (pSave)
{
M_FREE(pSave);
}
}
}


VOID InitRedirectDbg()
{
int nSymIndex=0;
int nCount = sizeof(m_redirect_symlist)/sizeof(m_redirect_symlist);
if (m_initRedirectDbg)
{
return ;
}
for (nSymIndex=0;nSymIndex&lt;nCount;nSymIndex++)
{
//走起!!
DWORD dwAddress = RtlGetSettingEx(m_redirect_symlist);
if (dwAddress)
{
PVOID pNewAddress =NULL;
DbgPrint("%ws %08x\r\n",m_redirect_symlist,dwAddress);
pNewAddress = (PVOID)GetKernelAddress((PVOID)dwAddress);
if(pNewAddress)
{
MakeJmp((PVOID)dwAddress,pNewAddress);       
}

}
}
m_initRedirectDbg = TRUE;
}</blockquote></div><div align="left">除了这种修改内核中数据访问位置的方式,还有一种方法就是自己搭建一个调试机制,也就是网上流传的自建调试机制的方法,这种方法对系统兼容性更好,而且不用搜索修改大量内核代码,更加问题,但是其多系统兼容性非常艰难,除非从中断开始一路自己实现。</div>
<div align="left">参考Reactos中ntos部分的代码(.WRK的代码也是可以的)。</div><div align="left"></div><div align="left">基本上的思路就是创建一个自己的DebugObject和Process的对应关系表,然后管理DebugObject——为了在高版本的系统中为了更好的做系统兼容,可能需要重写整个TrapHandler架构(从WRK或者Reactos中抄这些代码过来即可)。</div><div align="left"> </div>
<div align="left">对于线程从调试器中隐藏的处理,SSDT HOOK方式拦截NtSetInformationThread.</div><div align="left"></div><div align="left"><b>3.4.3</b><b><font face="宋体">针对断点破坏的攻击</font></b></div><div align="left">A、针对普通的清0单步和清0硬断破坏的攻击</div><div align="left">直接SSDT HOOK 拦截 NtSetThreadContext,去掉修改调试的ContextFlag,去掉EFLAGS中修改单步的设置。</div><div align="left">B、对IDT HOOK的攻击</div><div align="left">看雪上有一个攻击方式通过GDT HOOK来转换IDT</div><div align="left"><a href="http://bbs.pediy.com/showthread.php?t=142627" target="_blank">http://bbs.pediy.com/showthread.php?t=142627</a></div><div align="left">还有通过SwapContext 尾部切换IDT的(前面提过)。</div>
<div align="left"><b>还有一种比较通用而简单的方法就是使用VMX</b><b>技术,这里简单以Intel VT-x</b><b>下的IDT</b><b>转发为例子,举例一下。</b></div><div align="left"></div><div align="left"><b>完整代码:</b></div><div align="left"></div><div align="left"><b>3.4.4</b><b><font face="宋体">针对检测调试的攻击</font></b></div><div align="left">路径上的检测拦截因为重载内核和转移调试,已经彻底避免掉了,至于其他进程之类的检测,在前面的课程中已经讲过如何攻击,这里不重复了。针对硬断检测的GetThreadContext,可以采用SSDT HOOK NtGetThreadContext的方式。</div>
<div align="left"> </div><div align="left"><b>3.5 </b><b><font face="宋体">游戏保护对各种针对性攻击的处理方式</font></b></div><div align="left"><b>大部分游戏保护因为设计结构的原因,无法修改拦截策略,而逐渐将重点放在检测调试器和各种非法工具上去了。而少部分游戏保护系统,则进一步采取更深层更接近硬件的技术来进行新的保护,比如保护也使用VT</b><b>技术等等。</b></div>
<div align="left"><b>提供一个使用VT</b><b>技术的游戏保护demo:</b></div><br></div><div align="left"></div><div align="left">附件解压密码:**** Hidden Message *****</div></div></div></div>

zhang898600 发表于 2020-6-27 18:01:56

针对检测调试的攻击
页: [1]
查看完整版本: 调试机制攻防