过TP保护的基本方案.docx
- 文档编号:27573406
- 上传时间:2023-07-02
- 格式:DOCX
- 页数:21
- 大小:306.12KB
过TP保护的基本方案.docx
《过TP保护的基本方案.docx》由会员分享,可在线阅读,更多相关《过TP保护的基本方案.docx(21页珍藏版)》请在冰豆网上搜索。
过TP保护的基本方案
过TP保护分析过程~【转帖】
本文只为研究技术,请所有童鞋切勿使用本文之方法做下那天理难容罪恶不舍之坏事。
既是研究游戏保护,那么总要有一个研究对象。
本文就以TMD_TP这款游戏保护为例进行分析讲解。
请勿对号入座,如有雷同之处。
纯属反汇编引擎之错误,不关我的事!
转载请注明出处
关键字:
DNF驱动保护
鉴于最近很多同学找上门来求解这那问题,反正这东西又不是绝密档案,放在我手里大半个月了,还不如放出来让大家一起进步算了。
另外都是取之看雪还之看雪罢了。
索性我也就公布一个全套的方案。
绝无其他意思,所以还请同道中人嘴下留情。
切勿背地使坏!
在正式开篇之前我要感谢看雪ID:
十年寒窗在我最困惑的时候,他给予了最大的帮助!
另外还有一位和我同岁的神秘人物也给予了不小的帮助,感谢你们。
废话了半天,正式开始吧。
tmd_TP也就是国内比较流行的游戏D_N*F的游戏保护。
它在ring0层一共HOOK了几个地方和一些其他的工作。
来达到保护的目的
下面是简报:
[url=javascript:
]复制代码[/url]
1.NtOpenThread//防止调试器在它体内创建线程
NtOpenProcess//防止OD等在进程列表看到它
KiAttachProcess//防止其他软件附加它
NtReadVirtualMemory//防止别人读取它的内存
NtWriteVirtualMemory//防止别人在它的内存里面乱写乱画
KDCOM.dll:
KdReceivePacket//这两个是COM串口的接受和发送数据
KDCOM.dll:
KdSendPacket//主要用来方式别人双机调试
使用了KdDisableDebugger来禁用双机调试
代码:
[url=javascript:
]复制代码[/url]
1..text:
010025F0jzshortloc_1002622
.text:
010025F2callsub_10022A4
.text:
010025F7callds:
KdDisableDebugger
.text:
010025FDpushoffsetbyte_10022EC
.text:
01002602pushesi
.text:
01002603pushoffsetbyte_10022DC
.text:
01002608pushedi
.text:
01002609pushdword_100CF24
并对debugport进行了疯狂的清零操作
甚至还包括EPROCESS+70\+74\+78等几处位置
图片:
1.jpg
处理的手段通常都是向64端口写入FE导致计算机被重启
代码:
[url=javascript:
]复制代码[/url]
1..text:
01001665moval,0FEh
.text:
01001667out64h,al;ATKeyboardcontroller8042.
.text:
01001667;Resendthelasttransmission
.text:
01001669popa
.text:
0100166Aretn
下面简单看下他关键的几个HOOK:
KiAttachProcess
图片:
2.jpg
NtReadVirtualMemory
图片:
3.jpg
NtWriteVirtualMemory
图片:
4.jpg
NtOpenThread
图片:
5.jpg
NtOpenProcess
图片:
6.jpg
引用:
其中,前3个直接恢复即可。
第4个有监视,直接恢复即刻重启
第5个和ring3有通信,直接恢复1分钟内SX非法模块
根据上面的分析,下面给出相应的解决方案
1.直接恢复第1、2、3处HOOK
2.绕过4、5处HOOK
3.将debugport清零的内核线程干掉
4.恢复硬件断点
但是要有一个先后的逻辑顺序
因为内核有一个线程负责监视几个地方,必须要先干掉它。
但是这个内容我写在了处理debugport清零的一起,也就是第3步。
所以大家在照搬源码的时候
注意代码执行次序
先从简单的工作讲起,恢复1、2、3处的HOOK
KiAttachProcess的处理
代码:
[url=javascript:
]复制代码[/url]
1.//////////////////////////////////////////////////////////////////////
//名称:
Nakd_KiAttachProcess
//功能:
My_RecoveryHook_KiAttachProcess的中继函数
//参数:
//返回:
//////////////////////////////////////////////////////////////////////
staticNAKEDVOIDNakd_KiAttachProcess()
{
__asm
{
movedi,edi
pushebp
movebp,esp
pushebx
pushesi
moveax,KiAttachProcessAddress//注意这个是全局变量BYTE*
addeax,7
jmpeax
}
}
//////////////////////////////////////////////////////////////////////
//名称:
RecoveryHook_KiAttachProcess
//功能:
解除游戏保护对_KiAttachProcess函数的HOOK(DNF)
//参数:
//返回:
状态
//////////////////////////////////////////////////////////////////////
NTSTATUSMy_RecoveryHook_KiAttachProcess()
{
BYTE*KeAttachProcessAddress=NULL;//KeAttachProcess函数地址
BYTE*p;
BYTEMovEaxAddress[5]={0xB8,0,0,0,0};//
BYTEJmpEax[2]={0xff,0xe0};
KIRQLIrql;
//特征码
BYTESignature1=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"));
returnFAILED_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
returnSTATUS_SUCCESS;
}
NtReadVirtualMemory和
NtWriteVirtualMemory的处理
注意这里,我对他们俩开头的第2句PUSH的处理
我直接写入了push0x78563412
大家可以根据自己的地址来硬编码一次。
或者干脆这样使用
代码:
[url=javascript:
]复制代码[/url]
1.//////////////////////////////////////////////////////////////////////
//名称:
My_RecoveryHook_NtReadAndWriteMemory
//功能:
解除游戏保护对NtReadVirtualMemory和
//NtWriteVirtualMemory的HOOK
//参数:
//返回:
//////////////////////////////////////////////////////////////////////
NTSTATUSMy_RecoveryHook_NtReadAndWriteMemory()
{
BYTEPush1Ch[2]={0x6a,0x1c};//0~2字节
BYTEPushAdd[5]={0x68,0x12,0x34,0x56,0x78};//NtReadVirtualMemory[物理机]
//BYTEPushAdd2[5]={0x68,0xf0,0x6f,0x4f,0x80};//NtWriteVirtualMemory[物理机]
KIRQLIrql;
BYTE*NtReadVirtualMemoryAddress=NULL;//NtReadVirtualMemory的地址
BYTE*NtWriteVirtualMemoryAddress=NULL;//NtWriteVirtualMemory的地址
//从SSDT表中获取NtReadVirtualMemory函数地址
NtReadVirtualMemoryAddress=(BYTE*)myGetCurrentAddress(0xBA);
if(NtReadVirtualMemoryAddress==NULL)
{
KdPrint(("NtReadVirtualMemory函数地址获取失败!
\n"));
returnFAILED_TO_OBTAIN_FUNCTION_ADDRESSES;
}
//从SSDT表中获取NtWriteVirtualMemory函数地址
NtWriteVirtualMemoryAddress=(BYTE*)myGetCurrentAddress(0x115);
if(NtWriteVirtualMemoryAddress==NULL)
{
KdPrint(("NtWriteVirtualMemory函数地址获取失败!
\n"));
returnFAILED_TO_OBTAIN_FUNCTION_ADDRESSES;
}
WPOFF();//清除CR0
//提升IRQL中断级
Irql=KeRaiseIrqlToDpcLevel();
//写入
RtlCopyMemory(NtReadVirtualMemoryAddress,Push1Ch,2);
RtlCopyMemory(NtReadVirtualMemoryAddress+2,PushAdd,5);
RtlCopyMemory(NtWriteVirtualMemoryAddress,Push1Ch,2);
RtlCopyMemory(NtWriteVirtualMemoryAddress+2,PushAdd,5);
//恢复Irql
KeLowerIrql(Irql);
WPON();//恢复CR0
returnSTATUS_SUCCESS;
}
好了,下面来处理
NtOpenProcess和
NtOpenThread
这两个函数的处理上不能太鲁莽了。
手法要风骚一点细腻一点了
介于篇幅的原因,我只贴出来前者的处理方法,后者雷同
细微之处大家自行修改。
我总不能真的给你方法又给你工具。
眼看着自己变成教唆犯
代码:
[url=javascript:
]复制代码[/url]
1.//NtOpenProcess用到的全局变量[为了方便堆栈平衡的处理使用全局变量]
PEPROCESSprocessEPROCESS=NULL;//保存访问者的EPROCESS
ANSI_STRINGp_str1,p_str2;//保存进程名称
BYTE*ObOpenObjectByPointerAddress=NULL;//ObOpenObjectByPointer的地址
BYTE*p_TpHookAddress=NULL;//TP的HOOK函数地址
BYTE*p_ReturnAddress=NULL;//返回到的地址
BYTE*p_MyHookAddress=NULL;//我们的HOOK函数在哪写入
#defineDNF_EXE"DNF.exe"//要检索的进程名
//////////////////////////////////////////////////////////////////////
//名称:
Nakd_NtOpenProcess
//功能:
My_RecoveryHook_NtOpenProcess的中继函数
//参数:
//返回:
//////////////////////////////////////////////////////////////////////
staticNAKEDVOIDNakd_NtOpenProcess()
{
//获得调用者的EPROCESS
processEPROCESS=IoGetCurrentProcess();
//将调用者的进程名保存到str1中
RtlInitAnsiString(&p_str1,(ULONG)processEPROCESS+0x174);
//将我们要比对的进程名放入str2
RtlInitAnsiString(&p_str2,DNF_EXE);
if(RtlCompareString(&p_str1,&p_str2,TRUE)==0)
{
//说明是DNF进程访问了这里
__asm
{
pushdwordptr[ebp-38h]
pushdwordptr[ebp-24h]
pushp_ReturnAddress
moveax,p_TpHookAddress
jmpeax
}
}
else
{
__asm
{
pushdwordptr[ebp-38h]
pushdwordptr[ebp-24h]
pushp_ReturnAddress
moveax,ObOpenObjectByPointerAddress
jmpeax
}
}
}
//////////////////////////////////////////////////////////////////////
//名称:
My_RecoveryHook_NtOpenProcess
//功能:
解除游戏保护对NtOpenProcess的HOOK
//参数:
//返回:
状态
//////////////////////////////////////////////////////////////////////
NTSTATUSMy_RecoveryHook_NtOpenProcess()
{
BYTE*NtOpenProcessAddress=NULL;//NtOpenProcess的地址
BYTE*p=NULL;//临时
TOP5CODE*top5code=NULL;//保存5字节内容
BYTEJmpAddress[6]={0xE9,0,0,0,0,0x90};
KIRQLIrql;
//获取NtOpenProcess的地址
NtOpenProcessAddress=(BYTE*)MyGetFunAddress(L"NtOpenProcess");
if(NtOpenProcessAddress==NULL)
{
KdPrint(("NtOpenProcess地址获取失败\n"));
returnFAILED_TO_OBTAIN_FUNCTION_ADDRESSES;
}
//获取ObOpenObjectByPointer的地址
ObOpenObjectByPointerAddress=(BYTE*)MyGetFunAddress(L"ObOpenObjectByPointer");
if(ObOpenObjectByPointerAddress==NULL)
{
KdPrint(("ObOpenObjectByPointer地址获取失败\n"));
returnFAILED_TO_OBTAIN_FUNCTION_ADDRESSES;
}
//将p指向NtOpenProcess函数开始处
p=NtOpenProcessAddress;
//用一个无限循环来判断给定的特征码来确定被HOOK位置
while
(1)
{
if((*(p-7)==0x50)&&
(*(p-0xE)==0x56)&&
(*(p+0xd)==0x50)&&
(*(p+0x16)==0x3b)&&
(*(p+0x17)==0xce)&&
(*p==0xE8)&&
(*(p+5)==0x8b)&&
(*(p+6)==0xf8))
{
KdPrint(("%0X\n",(ULONG)p));
break;
}
//推动指针向前走
p++;
}
//将top5code指向p的当前处
//用以取出call[地址]这5字节里面的地址
top5code=(TOP5CODE*)p;
p_TpHookAddress=(BYTE*)((ULONG)p+5+top5code->address);
//找到我们写入自定义函数的地址
p_MyHookAddress=p-6;
//保存调用ObOpenObjectByPointer函数以后的返回地址
p_ReturnAddress=p+5;
//将一条JMPNakd_NtOpenProcess写入到数组中
*(ULONG*)(JmpAddress+1)=(ULONG)Nakd_NtOpenProcess-((ULONG)p_MyHookAddress+5);
WPOFF();//清除CR0
//提升IRQL中断级
Irql=KeRaiseIrqlToDpcLevel();
//写入
RtlCopyMemory(p_MyHookAddress,JmpAddress,6);
//恢复Irql
KeLowerIrql(Irql);
WPON();//恢复CR0
returnSTATUS_SUCCESS;
}
处理之后:
图片:
7.jpg
简而言之其原理就是,任何人调用了NtOpenProcess的时候会先进入
Nakd_NtOpenProcess函数,我们判断。
如果是游戏进程访问的话,就有可能是验证之类的
我们转到它自己的函数里面。
让它保持与ring3层的通信。
否则的话,嘿嘿……
接下来是第3步处理debugport清零的这块了。
我想绝大多数人关心的都是这里了
网络上能搜多到的办法几乎都失效了
有办法的人又不肯放出来,急眼了就自己想了个土办法
虽然不那么时尚。
但是绝对的奏效。
由于代码凌乱不堪,简单说下其原理。
我们定位内核模块TxxxSxxx.sys的首地址
然后根据特征码遍历整个模块找到我们需要的地方,然后干掉他们。
那么我们又如何能够通过人工的判断出来到底是哪里在作怪呢
利用syser或StartSoftICE对EPROCESS+BC处设置断点。
就可以一层一层的追溯上去了
到底如何用他们,我想大家自己多花点时间在看雪和GOOGLE或者BAIDU上面是不会吃亏的。
由于ZwQuerySystemInformation函数的使用非常繁琐。
而且篇幅有限。
所以我只给出关键代码,至于这个函数如何使用。
大家可以自己在搜索引擎找“枚举内核模块”
代码:
[url=javascript:
]复制代码[/url]
1.//////////////////////////////////////////////////////////////////////
//名称:
MyEnumKernelModule
//功能:
枚举内核模块
//参数:
str:
内核模块名称
//moduleadd:
该模块地址[传出]
//modulesie:
该模块大小[传出]
//返回:
//////////////////////////////////////////////////////////////////////
NTSTATUSMyEnumKernelModule(INCHAR*str,OUTULONG*moduleadd,OUTULONG*modulesie)
{
NTSTATUSstatus=STATUS_SUCCESS;
ULONGn
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- TP 保护 基本 方案