过TP学习手册文档格式.docx
《过TP学习手册文档格式.docx》由会员分享,可在线阅读,更多相关《过TP学习手册文档格式.docx(47页珍藏版)》请在冰点文库上搜索。
boolPanDuanProcessName(char*szName)//判断当前进程名的函数,参数为一个进程名
{
intnEProcess;
nEProcess=(int)PsGetCurrentProcess();
//得到当前进程结构,这里转换为了int型
charszProessaName[16];
strcpy(szProessaName,(char*)(nEProcess+0x174));
//将当前进程名复制到缓冲区
if(strcmp(szProessaName,szName)==0)//判断与传进来的进程名是否一致
{returntrue;
}
returnfalse;
}
intSearchFeature(intnAddr,char*pFeature,intnLeng)//搜索特征码,参数为起始地址,特征码,字节数
charszStatus[256]="
"
;
//缓冲区
inti=5000;
//遍历的字节数
while(i--)//循环递减次
{
RtlMoveMemory(szStatus,(char*)nAddr,nLeng);
//将传进来的地址处的长度为nLeng的数据移动到缓冲区
if(RtlCompareMemory(pFeature,szStatus,nLeng)==nLeng)//比较缓冲区内数据和传进来的特征码是否一致
{returnnAddr+nLeng;
//如果一致则返回特征码后面的地址}
nAddr++;
//不一致则地址自加继续循环
return0;
intGetSSDTFunctionAddr(intnSSDTIndex)//通过函数索引号来获得函数在表中的地址
intAddr;
__asm
movebx,nSSDTIndex
shlebx,2
moveax,KeServiceDescriptorTable
moveax,[eax]
addeax,ebx
movecx,[eax]
movAddr,ecx
returnAddr;
voidInLineHookEngine(intnRHookAddr,intnMyFunctionAddr)//挂钩,参数为要HOOK的地址,自已的HOOK函数地址
MemoryWritable();
intnJmpAddr=nMyFunctionAddr-nRHookAddr-5;
//计算要跳转地址的特征码
moveax,nRHookAddr
movbyteptrds:
[eax],0xe9//JMP
movebx,nJmpAddr
movdwordptrds:
[eax+1],ebx//跳转到。
。
MemoryNotWritable();
//开启写保护
voidUnInLineHookEngine(intnRHookAddr,char*szMacCode,intnLeng)//恢复,参数为要恢复的地址,码,长度
RtlMoveMemory((char*)nRHookAddr,szMacCode,nLeng);
//将原先的特征码写回去
voidMemoryWritable()//关闭写保护
__asm
cli
moveax,cr0
andeax,not10000h
movcr0,eax
voidMemoryNotWritable()//恢复写保护
{
moveax,cr0
oreax,10000h
movcr0,eax
sti
}
intGetFunCtionAddr(WCHAR*szFunCtionAName)//获得函数原地址,参数为函数名
UNICODE_STRINGFsRtlLegalAnsiCharacterArray_String;
RtlInitUnicodeString(&
FsRtlLegalAnsiCharacterArray_String,szFunCtionAName);
return(int)MmGetSystemRoutineAddress(&
FsRtlLegalAnsiCharacterArray_String);
#endif
然后再将实现NtOpenProcess的HOOK放到另一个头文件HookNtOpenProcess.h中:
#ifndefHOOKNTOPENPROCESS
#defineHOOKNTOPENPROCESS
intnNtOpenProcessAddr;
//作为函数地址
intnHookNtOpenProcessAddr;
//作为我们要挂钩的地址
intnHookNtOpenPrpcessJmp;
//TPHOOK的下一行地址
intnHookNtOpenPrpcessOldJmp;
//TPHOOK的地址
intnObOpenObjectByPointerAddr;
//TPHOOK的地址的未HOOK前的ObOpenObjectByPointer的地址
__declspec(naked)voidMyNtOpenProcess()//要跳到的函数
pushdwordptr[ebp-38h]//首先将改写的地址处的原代码执行
pushdwordptr[ebp-24h]
if(PanDuanProcessName("
DNF.exe"
)||PanDuanProcessName("
TenSafe.exe"
QQLogin.exe"
))
//判断NtOpenProcess是否为游戏进程在调用,也就是游戏或TP本身在调用NtOpenProcess
__asm
{
jmpnHookNtOpenPrpcessOldJmp////如果是DNF调用的,则跳到TP自己HOOK的代码执行
}
else{
callnObOpenObjectByPointerAddr//不是则执行NtOpenProcess被HOOK处的原函数代码
jmpnHookNtOpenPrpcessJmp//跳回到TPHOOK的代码的下一行继续执行
voidHookNtOpenProcess()
//nNtOpenProcessAddr=GetSSDTFunctionAddr(122);
//根据在表中的索引来获得它的地址,如果函数已经被其他程序给HOOK了,这里得到的就是HOOK后的地址,OD内的StrongOD插件就对NtOpenProcess进行了HOOK,因此这里得到的就不是源函数的地址,也就是错误的地址,这样如果先开了OD再加载我们写的驱动就会造成蓝屏,解决办法就是先过保护再开OD,或者用下面的函数获得函数原地址
nNtOpenProcessAddr=GetFunCtionAddr(L"
NtOpenProcess"
);
//获得函数源地址
charcode[7]={(char)0xff,(char)0x75,(char)0xc8,(char)0xff,(char)0x75,(char)0xdc,(char)0xe8};
//特征码
nHookNtOpenProcessAddr=SearchFeature(nNtOpenProcessAddr,code,7)-7;
//搜索特征码,-7处是我们要HOOK
//接上:
的地方,得到要挂钩的地址
DbgPrint("
nHookNtOpenProcessAddr=%x\n"
nHookNtOpenProcessAddr);
nHookNtOpenPrpcessJmp=nHookNtOpenProcessAddr+11;
nHookNtOpenPrpcessOldJmp=nHookNtOpenProcessAddr+6;
nHookNtOpenPrpcessJmp=%x\n"
nHookNtOpenPrpcessJmp);
nHookNtOpenPrpcessOldJmp=%x\n"
nHookNtOpenPrpcessOldJmp);
nObOpenObjectByPointerAddr=GetFunCtionAddr(L"
ObOpenObjectByPointer"
//获得ObOpenObjectByPointer地址
nObOpenObjectByPointerAddr=%x\n"
nObOpenObjectByPointerAddr);
InLineHookEngine(nHookNtOpenProcessAddr,(int)MyNtOpenProcess);
//挂钩函数,第一个参数为我们要HOOK
的地址,第二个参数为要跳到的函数地址
voidUnHookNtOpenProcess()//恢复HOOK函数,将特征码写回去就OK了
UnInLineHookEngine(nHookNtOpenProcessAddr,code,5);
//因为JMP指令只修改了五个字节,所以只需要恢
复五个就可以了
然后驱动的入口函数内调用HookNtOpenProcess();
,卸载例程内调用UnHookNtOpenProcess()就可以了。
这里用到了搜索特征码的技术,其实就是从NtOpenprocess的开始处逐字节递增来遍历,然后得到要HOOK的地址。
其实也没有什么可阐述的了,仔细看代码就可以了。
另外对NtOpenThread的处理和NtOpenProcess的处理是一样的。
对ReadProcessMemory的处理:
用OD随便附加一个进程,然后ctrl+g转到ReadProcessMemory:
如上图,该函数返回14个字节,可见压栈了14个字节的参数,如果在函数的开头就直接retn14,这样CE,OD等工具就不能读取到游戏的数据了,下面跟进它更深层的CALL看一下:
得到了它在SSDT中的索引号BA也就是第186号,用KD来看下186号为:
NtReadVirtualMemory,也就是ReadProcessMemory在内核中的函数,对比一下原函数和TPHOOK后:
可见TP在函数的头部前两句就进行了HOOK,我们可以做SSDT的HOOK,也就是非内联HOOK,然后执行TP未HOOK前的前两句,然后jmp到call805380e0也就是HOOK的下一行来执行。
还有一个问题就是TPHOOK的两句原代码:
Push1C这句是不会变的,而push804DAB8这句压栈的值会改变,所以这个值就不能直接拿来压栈,可以用首地址+偏移得到这个数据的地址,然后再取出该地址处的值,然后还要在游戏启动前加载驱动,也就是TP还没有HOOK前,因为它HOOK后就得不到这个原始数据了。
下面就来看具体实现吧:
在原先的Func.h头文件中再封装两个函数:
intSSDTHookEngine(intnSSDTIndex,intnFunctionAddr)//SSDTHOOK函数,参数为索引,假的函数地址
intnOldAddr;
//旧地址,作为TPHOOK的地址(函数首地址)
shlebx,2
moveax,KeServiceDescriptorTable
moveax,[eax]
addeax,ebx//得到函数在表中基址
movecx,[eax]//得到函数当前地址,也就是TPHOOK的地址
movnOldAddr,ecx//保存TPHOOK的地址
movecx,nFunctionAddr
mov[eax],ecx//假到函数地址赋给函数在表中的基址,实现SSDTHOOK
returnnOldAddr;
voidSSDTUnHookEngine(intnSSDTIndex,intnOldFunctionAddr)//恢复SSDTHOOK,索引,旧地址
movecx,nOldFunctionAddr
mov[eax],ecx
然后我们再添加一个用来实现HOOKNtReadVirtualMemory的头文件HookReadVirtualMemory.h:
#ifndefHOOKREAD
#defineHOOKREAD
intnNtReadVirtualMemoryAddr;
//作为NtReadVirtualMemoryAddr函数地址
intnNtReadVirtualMemoryAddr_3;
//函数三个字节后的地址处的数据
intnNtReadVirtualMemoryAddrJmp;
__declspec(naked)voidMyNtReadVirtualMemory()//假的函数
))
jmpnNtReadVirtualMemoryAddr//如果是游戏调用则跳回TP的原代码去执行
else{
push0x1c
pushnNtReadVirtualMemoryAddr_3//首先执行TP未HOOK函数前的两行
jmpnNtReadVirtualMemoryAddrJmp//跳转到原函数下一行去执行
VOIDHookReadVirtualMemory()
nNtReadVirtualMemoryAddr=GetSSDTFunctionAddr(186);
//根据索引得到函数地址
nNtReadVirtualMemoryAddr_3=nNtReadVirtualMemoryAddr+3;
//得到函数三个字节后的地址,一定要在开启
游戏前加载驱动,否则得不到正确数据
nNtReadVirtualMemoryAddr_3=*((int*)nNtReadVirtualMemoryAddr_3);
//得到该地址处的值,也就是push的数据
nNtReadVirtualMemoryAddrJmp=nNtReadVirtualMemoryAddr+7;
SSDTHookEngine(186,(int)MyNtReadVirtualMemory);
//SSDTHOOK
VOIDUnHookReadVirtualMemory()
SSDTUnHookEngine(186,nNtReadVirtualMemoryAddr);
//恢复
然后在入口函数中和卸载例程中调用就可以了。
对写内存函数NtWriteVirtualMemory的处理方法是一模一样的。
对付KiAttchProcess的内联hook:
调用KiAttchProcess的函数有两个:
1.KeAttchProcess
2.KeStackAttachProcess(切换进程用,读写外部进程内存API最终会调用到此内核函数)
这两个内核函数的功能是一样的,只是第一个函数较老,以后将有可能被淘汰。
它们的功能就是对外部进程进行读写操作,我们知道每个进程都独占4GB的虚拟内存,互不干扰,用这两个函数就可以实现进程A访问进程B的内存了。
OD附加一个进程进行调试就会调用它们,从而实现对被调试进程的读写,原理应该就是OD将自己的线程用该函数注入到目标进程的进程空间中。
这两个函数都调用了KiAttchProcess,而TP对KiAttchProcess进行了HOOK,我们先来讲一下对KeAttchProcess调用KiAttchProcess的处理:
虽然TP对XT工具有检测,不过我们可以先用XT来分析一下TP所做的hook:
除了对读写内存的函数进行了HOOK(NtOpenProcess和NtOpenTread的HOOKXT检测不到),可以看出TP对内核模块中的一个地址进行了HOOK:
804F8438,用windbg来看一下这个地址:
可以看出TP的此处HOOK的确是对KiAttachProcess进行的HOOK。
然后用KD转到这个地址去看一下:
TP对它的前7个字节进行了HOOK,转到它jmp的地址去看一下:
转到它HOOK的内部来看是为了说明另外一种方法,我们一定非要用以前的方法来进行HOOK,也可以到它的里面能过修改代码来实现,比如TP在这里调用了IoGetCurrentProcess来得到当前进程的EPROCESS结构,调用PsGetCurrentProcessId来得到当前调用KiAttchProcess的进程ID,根据得到的这两个结果来判断是哪个进程在调用而决定是放过还是HOOK。
因此我们可以针对IoGetCurrentProcess和PsGetCurrentProcessId来进行HOOK,让它们返回DNF的EPROCESS结构地址和进程ID,当然这只是讲了一种思路,这里并不会对这种思路深究。
在windbg中对KiAttchProcess下断看是谁会调用它:
bp804f8438,然后查看调用堆栈:
alt+6:
可以看出KeStackAttachProcess+7B的位置对它进行了调用。
用u804f8438804f8438+100命令显示原KiAttchProcess全部反汇编代码,然后复制出来,做为我们要写的假KiAttchProcess代码,要注意的是,在反汇编代码中有很多jn,jne,jnz,jmp等跳转,如果跳转到的地址是在当前函数内,而我们假的KiAttchProcess函数的地址和原KiAttchProcess函数地址是不同的,因此就要做好各个跳转的目的地的标识以方便跳转,详细的看代码就好了。
还有函数内有一些CALLxxxx,因此还到得到xxxx函数的地址。
如下:
kd>
u804f8438804f8438+100
nt!
KiAttachProcess:
804f84388bffmovedi,edi
804f843a55pushebp
804f843b8becmovebp,esp
804f843d53pushebx
804f843e8b5d0cmovebx,dwordptr[ebp+0Ch]
804f844166ff4360incwordptr[ebx+60h]
804f844556pushesi
804f84468b7508movesi,dwordptr[ebp+8]
804f844957pushedi
804f844aff7514pushdwordptr[ebp+14h]
804f844d8d7e34leaedi,[esi+34h]
804f845057pushedi
804f8451e81efdffffcallnt!
KiMoveApcState(804f8174)
804f8456897f04movdwordptr[edi+4],edi
804f8459893fmovdwordptr[edi],edi
804f845b8d463cleaeax,[esi+3Ch]
804f845e894004movdwordptr[eax+4],eax
804f84618900movdwordptr[eax],eax
804f84638d864c010000leaeax,[esi+14Ch]
804f8469394514cmpdwordptr[ebp+14h],eax
804f84