过TP学习手册.docx
- 文档编号:11674895
- 上传时间:2023-03-30
- 格式:DOCX
- 页数:47
- 大小:551.06KB
过TP学习手册.docx
《过TP学习手册.docx》由会员分享,可在线阅读,更多相关《过TP学习手册.docx(47页珍藏版)》请在冰豆网上搜索。
过TP学习手册
过NtOpenProcess:
其实这个函数TP只hook了一个地方,只是郁金香对比的数据太多了,超出了NtOpenProcess的范围,找到函数的结尾可以计算出它只有0x283个字节,因此只要对比0x283个字节的数据即可,通过对比只有一处被TP给HOOK了:
还是用一样的方法在上面两行来HOOK,然后JMP到TPhook的下一行执行,下面来看具体的实现吧:
首先我们先来封装几个功能函数放到一个头文件Func.h中:
#ifndefFUNC
#defineFUNC
extern"C"longKeServiceDescriptorTable;//导出SSDT表
intGetSSDTFunctionAddr(intnSSDTIndex);//通过索引号获得函数地址
boolPanDuanProcessName(char*szName);//判断当前进程名的函数,参数为一个进程名
intSearchFeature(intnAddr,char*pFeature,intnLeng);//搜索特征码,参数为起始地址,特征码,字节数
voidInLineHookEngine(intnRHookAddr,intnMyFunctionAddr);//挂钩函数
voidUnInLineHookEngine(intnRHookAddr,char*szMacCode,intnLeng);//恢复HOOK
intGetFunCtionAddr(WCHAR*szFunCtionAName);//获得函数原地址,参数为函数名
voidMemoryWritable();//关闭写保护
voidMemoryNotWritable();//恢复写保护
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;//计算要跳转地址的特征码
__asm
{
moveax,nRHookAddr
movbyteptrds:
[eax],0xe9//JMP
movebx,nJmpAddr
movdwordptrds:
[eax+1],ebx//跳转到。
。
。
}
MemoryNotWritable();//开启写保护
}
voidUnInLineHookEngine(intnRHookAddr,char*szMacCode,intnLeng)//恢复,参数为要恢复的地址,码,长度
{
MemoryWritable();
RtlMoveMemory((char*)nRHookAddr,szMacCode,nLeng);//将原先的特征码写回去
MemoryNotWritable();
}
voidMemoryWritable()//关闭写保护
{
__asm
{
cli
moveax,cr0
andeax,not10000h
movcr0,eax
}
}
voidMemoryNotWritable()//恢复写保护
{
__asm
{
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()//要跳到的函数
{
__asm
{
pushdwordptr[ebp-38h]//首先将改写的地址处的原代码执行
pushdwordptr[ebp-24h]
}
if(PanDuanProcessName("DNF.exe")||PanDuanProcessName("TenSafe.exe")||PanDuanProcessName("QQLogin.exe"))
//判断NtOpenProcess是否为游戏进程在调用,也就是游戏或TP本身在调用NtOpenProcess
{
__asm
{
jmpnHookNtOpenPrpcessOldJmp////如果是DNF调用的,则跳到TP自己HOOK的代码执行
}
}
else{
__asm
{
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;//TPHOOK的下一行地址
nHookNtOpenPrpcessOldJmp=nHookNtOpenProcessAddr+6;//TPHOOK的地址
DbgPrint("nHookNtOpenPrpcessJmp=%x\n",nHookNtOpenPrpcessJmp);
DbgPrint("nHookNtOpenPrpcessOldJmp=%x\n",nHookNtOpenPrpcessOldJmp);
nObOpenObjectByPointerAddr=GetFunCtionAddr(L"ObOpenObjectByPointer");//获得ObOpenObjectByPointer地址
DbgPrint("nObOpenObjectByPointerAddr=%x\n",nObOpenObjectByPointerAddr);
InLineHookEngine(nHookNtOpenProcessAddr,(int)MyNtOpenProcess);//挂钩函数,第一个参数为我们要HOOK
//接上:
的地址,第二个参数为要跳到的函数地址
}
voidUnHookNtOpenProcess()//恢复HOOK函数,将特征码写回去就OK了
{
charcode[7]={(char)0xff,(char)0x75,(char)0xc8,(char)0xff,(char)0x75,(char)0xdc,(char)0xe8};
UnInLineHookEngine(nHookNtOpenProcessAddr,code,5);//因为JMP指令只修改了五个字节,所以只需要恢
//接上:
复五个就可以了
}
#endif
然后驱动的入口函数内调用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函数,参数为索引,假的函数地址
{
MemoryWritable();
intnOldAddr;//旧地址,作为TPHOOK的地址(函数首地址)
__asm
{
movebx,nSSDTIndex
shlebx,2
moveax,KeServiceDescriptorTable
moveax,[eax]
addeax,ebx//得到函数在表中基址
movecx,[eax]//得到函数当前地址,也就是TPHOOK的地址
movnOldAddr,ecx//保存TPHOOK的地址
movecx,nFunctionAddr
mov[eax],ecx//假到函数地址赋给函数在表中的基址,实现SSDTHOOK
}
MemoryNotWritable();
returnnOldAddr;
}
voidSSDTUnHookEngine(intnSSDTIndex,intnOldFunctionAddr)//恢复SSDTHOOK,索引,旧地址
{
MemoryWritable();
__asm
{
movebx,nSSDTIndex
shlebx,2
moveax,KeServiceDescriptorTable
moveax,[eax]
addeax,ebx
movecx,nOldFunctionAddr
mov[eax],ecx
}
MemoryNotWritable();
}
然后我们再添加一个用来实现HOOKNtReadVirtualMemory的头文件HookReadVirtualMemory.h:
#ifndefHOOKREAD
#defineHOOKREAD
intnNtReadVirtualMemoryAddr;//作为NtReadVirtualMemoryAddr函数地址
intnNtReadVirtualMemoryAddr_3;//函数三个字节后的地址处的数据
intnNtReadVirtualMemoryAddrJmp;//TPHOOK的下一行地址
__declspec(naked)voidMyNtReadVirtualMemory()//假的函数
{
if(PanDuanProcessName("DNF.exe")||PanDuanProcessName("TenSafe.exe")||PanDuanProcessName("QQLogin.exe"))
{
__asm
{
jmpnNtReadVirtualMemoryAddr//如果是游戏调用则跳回TP的原代码去执行
}
}
else{
__asm
{
push0x1c
pushnNtReadVirtualMemoryAddr_3//首先执行TP未HOOK函数前的两行
jmpnNtReadVirtualMemoryAddrJmp//跳转到原函数下一行去执行
}
}
}
VOIDHookReadVirtualMemory()
{
nNtReadVirtualMemoryAddr=GetSSDTFunctionAddr(186);//根据索引得到函数地址
nNtReadVirtualMemoryAddr_3=nNtReadVirtualMemoryAddr+3;//得到函数三个字节后的地址,一定要在开启
//接上:
游戏前加载驱动,否则得不到正确数据
nNtReadVirtualMemoryAddr_3=*((int*)nNtReadVirtualMemoryAddr_3);//得到该地址处的值,也就是push的数据
nNtReadVirtualMemoryAddrJmp=nNtReadVirtualMemoryAddr+7;//TPHOOK的下一行地址
SSDTHookEngine(186,(int)MyNtReadVirtualMemory);//SSDTHOOK
}
VOIDUnHookReadVirtualMemory()
{
SSDTUnHookEngine(186,nNtReadVirtualMemoryAddr);//恢复
}
#endif
然后在入口函数中和卸载例程中调用就可以了。
对写内存函数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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- TP 学习 手册