【转】散谈游戏保护那点事~就从_TP开始入手吧
转自看雪:http://bbs.pediy.com/showthread.php?t=126802
声明:本文只为研究技术,请所有童鞋切勿使用本文之方法做下那天理难容罪恶不舍之坏事。
既是研究游戏保护,那么总要有一个研究对象。本文就以TMD_TP这款游戏保护为例进行分析讲解。请勿对号入座,如有雷同之处。纯属反汇编引擎之错误,不关我的事!
转载请注明出处
关键字:DNF 驱动保护
鉴于最近很多同学找上门来求解这那问题,反正这东西又不是绝密档案,放在我手里大半个月了,还不如放出来让大家一起进步算了。另外都是取之看雪还之看雪罢了。
索性我也就公布一个全套的方案。绝无其他意思,所以还请同道中人嘴下留情。切勿背地使坏!
在正式开篇之前我要感谢看雪ID:十年寒窗 在我最困惑的时候,他给予了最大的帮助!另外还有一位和我同岁的神秘人物也给予了不小的帮助,感谢你们。
废话了半天,正式开始吧。
tmd_TP也就是国内比较流行的游戏D_N*F的游戏保护。
它在ring0层一共HOOK了几个地方和一些其他的工作。来达到保护的目的
下面是简报:
使用了KdDisableDebugger来禁用双机调试
.text:010025F0 jz short loc_1002622
.text:010025F2 call sub_10022A4
.text:010025F7 call ds:KdDisableDebugger
.text:010025FD push offset byte_10022EC
.text:01002602 push esi
.text:01002603 push offset byte_10022DC
.text:01002608 push edi
.text:01002609 push dword_100CF24
并对debugport进行了疯狂的清零操作
甚至还包括EPROCESS+70\+74\+78等几处位置
处理的手段通常都是向64端口写入FE导致计算机被重启
.text:01001665 mov al, 0FEh .text:01001667 out 64h, al ; AT Keyboard controller 8042. .text:01001667 ; Resend the last transmission .text:01001669 popa .text:0100166A retn
下面简单看下他关键的几个HOOK:
KiAttachProcess
NtReadVirtualMemory
NtWriteVirtualMemory
NtOpenThread
NtOpenProces
根据上面的分析,下面给出相应的解决方案
1.直接恢复 第1、2、3处HOOK
2.绕过4、5处HOOK
3.将debugport清零的内核线程干掉
4.恢复硬件断点
但是要有一个先后的逻辑顺序
因为内核有一个线程负责监视几个地方,必须要先干掉它。
但是这个内容我写在了处理debugport清零的一起,也就是第3步。所以大家在照搬源码的时候
注意代码执行次序
先从简单的工作讲起,恢复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; }
NtReadVirtualMemory和
NtWriteVirtualMemory的处理
注意这里,我对他们俩开头的第2句PUSH的处理
我直接写入了push 0x78563412
大家可以根据自己的地址来硬编码一次。
或者干脆这样使用
////////////////////////////////////////////////////////////////////// // 名称: My_RecoveryHook_NtReadAndWriteMemory // 功能: 解除游戏保护对NtReadVirtualMemory和 // NtWriteVirtualMemory的HOOK // 参数: // 返回: ////////////////////////////////////////////////////////////////////// NTSTATUS My_RecoveryHook_NtReadAndWriteMemory() { BYTE Push1Ch[2] = {0x6a,0x1c}; //0~2字节 BYTE PushAdd[5] = {0x68,0x12,0x34,0x56,0x78}; //NtReadVirtualMemory[物理机] //BYTE PushAdd2[5] = {0x68,0xf0,0x6f,0x4f,0x80}; //NtWriteVirtualMemory[物理机] KIRQL Irql; BYTE *NtReadVirtualMemoryAddress = NULL; //NtReadVirtualMemory的地址 BYTE *NtWriteVirtualMemoryAddress = NULL; //NtWriteVirtualMemory的地址 //从SSDT表中获取NtReadVirtualMemory函数地址 NtReadVirtualMemoryAddress = (BYTE*)myGetCurrentAddress(0xBA); if (NtReadVirtualMemoryAddress == NULL) { KdPrint(("NtReadVirtualMemory函数地址获取失败! \n")); return FAILED_TO_OBTAIN_FUNCTION_ADDRESSES; } //从SSDT表中获取NtWriteVirtualMemory函数地址 NtWriteVirtualMemoryAddress = (BYTE*)myGetCurrentAddress(0x115); if (NtWriteVirtualMemoryAddress == NULL) { KdPrint(("NtWriteVirtualMemory函数地址获取失败! \n")); return FAILED_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 return STATUS_SUCCESS; }
好了,下面来处理
NtOpenProcess和
NtOpenThread
这两个函数的处理上不能太鲁莽了。
手法要风骚一点细腻一点了
介于篇幅的原因,我只贴出来前者的处理方法,后者雷同
细微之处大家自行修改。我总不能真的给你方法又给你工具。眼看着自己变成教唆犯
//NtOpenProcess用到的全局变量[为了方便堆栈平衡的处理使用全局变量] PEPROCESS processEPROCESS = NULL; //保存访问者的EPROCESS ANSI_STRING p_str1,p_str2; //保存进程名称 BYTE *ObOpenObjectByPointerAddress = NULL; //ObOpenObjectByPointer的地址 BYTE *p_TpHookAddress = NULL; //TP的HOOK函数地址 BYTE *p_ReturnAddress = NULL; //返回到的地址 BYTE *p_MyHookAddress = NULL; //我们的HOOK函数在哪写入 #define DNF_EXE "DNF.exe" //要检索的进程名 ////////////////////////////////////////////////////////////////////// // 名称: Nakd_NtOpenProcess // 功能: My_RecoveryHook_NtOpenProcess的中继函数 // 参数: // 返回: ////////////////////////////////////////////////////////////////////// static NAKED VOID Nakd_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 { push dword ptr [ebp-38h] push dword ptr [ebp-24h] push p_ReturnAddress mov eax,p_TpHookAddress jmp eax } } else { __asm { push dword ptr [ebp-38h] push dword ptr [ebp-24h] push p_ReturnAddress mov eax,ObOpenObjectByPointerAddress jmp eax } } } ////////////////////////////////////////////////////////////////////// // 名称: My_RecoveryHook_NtOpenProcess // 功能: 解除游戏保护对NtOpenProcess的HOOK // 参数: // 返回: 状态 ////////////////////////////////////////////////////////////////////// NTSTATUS My_RecoveryHook_NtOpenProcess() { BYTE *NtOpenProcessAddress = NULL; //NtOpenProcess的地址 BYTE *p = NULL; //临时 TOP5CODE *top5code = NULL; //保存5字节内容 BYTE JmpAddress[6] = {0xE9,0,0,0,0,0x90}; KIRQL Irql; //获取NtOpenProcess的地址 NtOpenProcessAddress = (BYTE*)MyGetFunAddress(L"NtOpenProcess"); if (NtOpenProcessAddress == NULL) { KdPrint(("NtOpenProcess地址获取失败\n")); return FAILED_TO_OBTAIN_FUNCTION_ADDRESSES; } //获取ObOpenObjectByPointer的地址 ObOpenObjectByPointerAddress = (BYTE*)MyGetFunAddress(L"ObOpenObjectByPointer"); if (ObOpenObjectByPointerAddress == NULL) { KdPrint(("ObOpenObjectByPointer地址获取失败\n")); return FAILED_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; //将一条JMP Nakd_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 return STATUS_SUCCESS; }
处理之后:
简而言之其原理就是,任何人调用了NtOpenProcess的时候会先进入
Nakd_NtOpenProcess函数,我们判断。如果是游戏进程访问的话,就有可能是验证之类的
我们转到它自己的函数里面。让它保持与ring3层的通信。否则的话,嘿嘿……
接下来是第3步处理debugport清零的这块了。
我想绝大多数人关心的都是这里了
网络上能搜多到的办法几乎都失效了
有办法的人又不肯放出来,急眼了就自己想了个土办法
虽然不那么时尚。但是绝对的奏效。
由于代码凌乱不堪,简单说下其原理。
我们定位内核模块TxxxSxxx.sys的首地址
然后根据特征码遍历整个模块找到我们需要的地方,然后干掉他们。
那么我们又如何能够通过人工的判断出来到底是哪里在作怪呢
利用syser或Start SoftICE对EPROCESS+BC处设置断点。就可以一层一层的追溯上去了
到底如何用他们,我想大家自己多花点时间在看雪和GOOGLE或者BAIDU上面是不会吃亏的。
由于ZwQuerySystemInformation函数的使用非常繁琐。而且篇幅有限。所以我只给出关键代码,至于这个函数如何使用。大家可以自己在搜索引擎找“枚举内核模块”
////////////////////////////////////////////////////////////////////// // 名称: MyEnumKernelModule // 功能: 枚举内核模块 // 参数: str:内核模块名称 // moduleadd:该模块地址[传出] // modulesie:该模块大小[传出] // 返回: ////////////////////////////////////////////////////////////////////// NTSTATUS MyEnumKernelModule(IN CHAR* str,OUT ULONG *moduleadd,OUT ULONG *modulesie) { NTSTATUS status = STATUS_SUCCESS; ULONG n = 0; ULONG i = 0; PSYSTEM_MODULE_INFORMATION_ENTRY module = NULL; PVOID pbuftmp = NULL; ANSI_STRING ModuleName1,ModuleName2; BOOLEAN tlgstst= FALSE; //如果找到了指定模块则设置为TRUE //利用11号功能枚举内核模块 status = ZwQuerySystemInformation(11, &n, 0, &n); //申请内存 pbuftmp = ExAllocatePool(NonPagedPool, n); //再次执行,将枚举结果放到指定的内存区域 status = ZwQuerySystemInformation(11, pbuftmp, n, NULL); module = (PSYSTEM_MODULE_INFORMATION_ENTRY)((PULONG )pbuftmp + 1 ); //初始化字符串 RtlInitAnsiString(&ModuleName1,str); // n = *((PULONG)pbuftmp ); for ( i = 0; i < n; i++ ) { RtlInitAnsiString(&ModuleName2,&module[i].ImageName); //DbgPrint("%d\t0x%08X 0x%08X %s\n",module[i].LoadOrderIndex,module[i].Base,module[i].Size,module[i].ImageName); if (RtlCompareString(&ModuleName1,&ModuleName2,TRUE) == 0) { DbgPrint("MyEnumKernelModule:%s:%0X \n",ModuleName2.Buffer,module[i].Base); *moduleadd = module[i].Base; *modulesie = module[i].Size; tlgstst = TRUE; break; } } ExFreePool(pbuftmp); if tlgstst == FALSE) { return FAILED_TO_OBTAIN_FUNCTION_ADDRESSES; } return status; } ////////////////////////////////////////////////////////////////////// // 名称: My_Recovery_Debugport // 功能: 恢复游戏对debugport的清零操作 // 参数: // 返回: ////////////////////////////////////////////////////////////////////// NTSTATUS My_Recovery_Debugport() { NTSTATUS stats; BYTE *sd1 = NULL,*sd2 = NULL,*pd = NULL; ULONG ModuleSize,ModuleAddress,i,number = 0; BYTE *p; KIRQL Irql; BYTE C390[2] = {0xc3,0x90}; //获取指定的内核模块地址和字节数 stats = MyEnumKernelModule("\\??\\c:\\windows\\system32\\tessafe.sys",&ModuleAddress,&ModuleSize); if (stats == FAILED_TO_OBTAIN_FUNCTION_ADDRESSES) { return FAILED_TO_OBTAIN_FUNCTION_ADDRESSES; } KdPrint(("Address:%0X Sie:%d \n",ModuleAddress,ModuleSize)); //特征码 /* sd1特征 p-1:18 p-2:87 p-3:DB p-4:33 p-5:07 p-6:03 p :33 p+1:C0 p+7:3B p+8:D8 sd2特征 p-1:07 p-2:87 p-3:c0 p-4:33 p+14:89 p+15:1c p+16:38 */ //将P指向内核模块开始处 p = (BYTE*)ModuleAddress + 20; for (i = 0; i < ModuleSize - 20; i++,p++) { //sd1 if ((*(p-1) == 0x18) && (*(p-2) == 0x87) && (*(p-3) == 0xDB) && (*(p-4) == 0x33) && (*(p-5) == 0x07) && (*(p-6) == 0x03) && (*p == 0x33) && (*(p+1) == 0xC0) && (*(p+7) == 0x3B) && (*(p+8) == 0xD8) ) { KdPrint(("--SD1 -- %0X \n",(ULONG)p)); sd1 = p; number+=1; //记录已经获取一个特征 } //sd2 if ((*(p-1) == 0x07) && (*(p-2) == 0x87) && (*(p-3) == 0xC0) && (*(p-4) == 0x33) && (*(p+14)== 0x89) && (*(p+15)== 0x1C) && (*(p+16)== 0x38) && (*p == 0xA1)) { KdPrint(("--SD2 -- %0X \n",(ULONG)p)); sd2 = p; number+=1; //记录已经获取一个特征 } //pd if ((*(p-2) == 0xE3) && (*(p-3) == 0xC1) && (*(p-7) == 0xF3) && (*(p-8) == 0x33) && (*(p-10)== 0xEB) && (*(p-11)== 0xC1) && (*(p+1) == 0xF3) && (*(p+2) == 0x42) && (*(p+3) == 0x3B) && (*(p+4) == 0xD1) && (*p == 0x33)) { KdPrint(("--PD -- %0X \n",(ULONG)p)); pd = p; number+=1; //记录已经获取一个特征 } if (number >= 3) { KdPrint(("特征 %d ---退出\n",number)); break; } } //首先干掉监视函数 while (1) { if ((*(pd-1) == 0xcc) && (*(pd-2) == 0xcc)) { KdPrint(("pd首地址:%0X \n",(ULONG)pd)); WPOFF(); //清除CR0 //提升IRQL中断级 Irql=KeRaiseIrqlToDpcLevel(); //写入 RtlCopyMemory(pd,C390,2); //恢复Irql KeLowerIrql(Irql); WPON(); //恢复CR0 break; } pd--; } //干掉2个SD while (1) { if ((*(sd1-1) == 0xcc) && (*(sd1-2) == 0xcc)) { KdPrint(("sd1首地址:%0X \n",(ULONG)sd1)); WPOFF(); //清除CR0 //提升IRQL中断级 Irql=KeRaiseIrqlToDpcLevel(); //写入 RtlCopyMemory(sd1,C390,2); //恢复Irql KeLowerIrql(Irql); WPON(); //恢复CR0 break; } sd1--; } while (1) { if ((*(sd2-1) == 0xcc) && (*(sd2-2) == 0xcc)) { KdPrint(("sd2首地址:%0X \n",(ULONG)sd2)); WPOFF(); //清除CR0 //提升IRQL中断级 Irql=KeRaiseIrqlToDpcLevel(); //写入 RtlCopyMemory(sd2,C390,2); //恢复Irql KeLowerIrql(Irql); WPON(); //恢复CR0 break; } sd2--; } return STATUS_SUCCESS; }
最后,处理一下硬件断点就可以了
这里我们使用到了SSDT HOOK
分别HOOK了 SSDT 表中索引为 0xD5和0x55的函数。由于这里比较简单
我想10个人有9个人懂得SSDT HOOK的。所以直接给出源码,不做原理分析了
//处理硬件断点时 ULONG uNtSetContextThreadAddress; ULONG uNtGetContextThreadAddress; ULONG TenNtSetContextThread, TenNtGetContextThread; ////////////////////////////////////////////////////////////////////// // 名称: _MyNtGetThreadContext // 功能: 两个SSDT HOOK伪造函数的中继函数 // 参数: // 返回: ////////////////////////////////////////////////////////////////////// static NAKED NTSTATUS Nakd_NtGetThreadContext(HANDLE hThread, PCONTEXT pContext) { __asm { jmp dword ptr[TenNtGetContextThread] } } static NAKED NTSTATUS Nakd_NtSetThreadContext(HANDLE hThread, PCONTEXT pContext) { __asm { jmp dword ptr[TenNtSetContextThread] } } ////////////////////////////////////////////////////////////////////// // 名称: MyNtGetThreadContext && MyNtSetThreadContext // 功能: NtGetThreadContext与NtSetThreadContext函数被SSDT HOOK的伪造函数 // 参数: // 返回: ////////////////////////////////////////////////////////////////////// NTSTATUS MyNtGetThreadContext(HANDLE hThread, PCONTEXT pContext) { if ( _stricmp((const char*)PsGetProcessImageFileName(PsGetCurrentProcess()),DNF_EXE) ) { return Nakd_NtGetThreadContext(hThread, pContext); } return STATUS_UNSUCCESSFUL; } NTSTATUS MyNtSetThreadContext(HANDLE hThread, PCONTEXT pContext) { if ( _stricmp((const char*)PsGetProcessImageFileName(PsGetCurrentProcess()),DNF_EXE) ) { return Nakd_NtSetThreadContext(hThread, pContext); } //DbgPrint("Dr7:%08X\n", pContext->Dr7); if ( pContext->Dr7 == 0x101 ) { return Nakd_NtSetThreadContext(hThread, pContext); } return STATUS_UNSUCCESSFUL; } ////////////////////////////////////////////////////////////////////// // 名称: My_Recovery_HardwareBreakpoint // 功能: 通过对set与get进行SSDT HOOK来恢复硬件断点 // 参数: // 返回: ////////////////////////////////////////////////////////////////////// NTSTATUS My_Recovery_HardwareBreakpoint() { KIRQL Irql; //获取地址 uNtSetContextThreadAddress = (ULONG)KeServiceDescriptorTable->ServiceTableBase+0xD5 * 4; uNtGetContextThreadAddress = (ULONG)KeServiceDescriptorTable->ServiceTableBase+0x55 * 4; TenNtSetContextThread = *(ULONG*)uNtSetContextThreadAddress; TenNtGetContextThread = *(ULONG*)uNtGetContextThreadAddress; KdPrint(("Set地址:%0X\n",TenNtSetContextThread)); KdPrint(("Get地址:%0X\n",TenNtGetContextThread)); KdPrint(("Process:%0X \n",(ULONG)p_MyHookAddress)); KdPrint(("Thread:%0X \n",(ULONG)t_MyHookAddress)); WPOFF(); //清除CR0 //提升IRQL中断级 Irql=KeRaiseIrqlToDpcLevel(); //完成SSDT HOOK *(ULONG*)uNtGetContextThreadAddress = (ULONG)MyNtGetThreadContext; *(ULONG*)uNtSetContextThreadAddress = (ULONG)MyNtSetThreadContext; //恢复Irql KeLowerIrql(Irql); WPON(); //恢复CR0 return STATUS_UNSUCCESSFUL; }
另外还有一些功能型的函数一并给出,省的大家迷糊
我也算服务到位了,再看上面代码迷糊的时候。看这里找找
看看有没有能用到的,或者翻一下我以往的帖子。里面应该有
//保存5字节代码的结构 #pragma pack(1) typedef struct _TOP5CODE { UCHAR instruction; //指令 ULONG address; //地址 }TOP5CODE,*PTOP5CODE; #pragma pack( ) //ssdt表结构 typedef struct _ServiceDescriptorTable { PVOID ServiceTableBase; //System Service Dispatch Table 的基地址 PVOID ServiceCounterTable; //包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新。 unsigned int NumberOfServices;//由 ServiceTableBase 描述的服务的数目。 PVOID ParamTableBase; //包含每个系统服务参数字节数表的基地址-系统服务参数表 }*PServiceDescriptorTable; //由SSDT索引号获取当前函数地址 //NtOpenProcess [[KeServiceDescriptorTable]+0x7A*4] extern PServiceDescriptorTable KeServiceDescriptorTable; ////////////////////////////////////////////////////////////////////// // 名称: MyGetFunAddress // 功能: 获取函数地址 // 参数: 函数名称字符串指针 // 返回: 函数地址 ////////////////////////////////////////////////////////////////////// ULONG MyGetFunAddress( IN PCWSTR FunctionName) { UNICODE_STRING UniCodeFunctionName; RtlInitUnicodeString( &UniCodeFunctionName, FunctionName ); return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName ); } ////////////////////////////////////////////////////////////////////// // 名称: myGetCurrentAddress // 功能: 获取SSDT表中指定函数的当前地址 // 参数: index:指定函数在表中的索引号 // 返回: 地址 ////////////////////////////////////////////////////////////////////// ULONG myGetCurrentAddress(IN ULONG index) { ULONG SSDT_Cur_Addr; __asm { push ebx push eax mov ebx,KeServiceDescriptorTable mov ebx,[ebx] mov eax,index shl eax,2 add ebx,eax mov ebx,[ebx] mov SSDT_Cur_Addr,ebx pop eax pop ebx } return SSDT_Cur_Addr; } VOID WPOFF() { __asm { cli mov eax,cr0 and eax,not 10000h mov cr0,eax } } VOID WPON() { __asm { mov eax,cr0 or eax,10000h mov cr0,eax sti } }
记在最后面的话:大家要善用搜索引擎(建议学习google hacking技巧),勤做笔记,最后要说的依然是感谢,感谢GOOGLE\BAIDU\PEDIY\DEBUGMAN。还有那些默默发帖的人~
如果有时间的话,我会将其他几个游戏保护的分析资料也放出来
什么GPK\HP\HS的。大家不要催不要急,一定会放出来的。等到我觉得这些东西都没有挑战性的时候那么也就不会再有资料陆续放出来了……
如果有好东西记得与我分享哈