研究驱动有一个多月了,准备实践一下,拿DNF的TX驱动开刀,说干就干,刚开始心里没底,在看雪论坛找了很多相关的帖子,但是好象都比较早,直接拿来用是不行,但是我觉得思路绝对是对的,只有摸着石头过河,先把我的方法贴出来请各位大牛指教,在最后随便请教一下其他问题..
我过TX驱动是为了能用windbg调试DNF,我把他分成几个部分来做:
1.恢复OpenProcess, OpenThread,ReadVirtualMemory,WriteVirtualMemory
2.修改读写DebugPort的相关API
3.修复AttachProcess
步骤应该和以前是一样,但是方法变了.
我做一个TX驱动保护恢复工具(由于要时不时的看看ssdt和对比反汇编索性做在一起了,后面有下载地址),在工具启动的时候(打开游戏之前)做一些数据初始化和收集工作,并在进入游戏之后在点恢复键做真正的恢复工作.
首先是工具启动做的事情:
1. ReadVirtualMemory,WriteVirtualMemory这两个函数现在没有保护,在工具启动的时候复制前16个字节保存:
ULONG AddrRead = (ULONG)KeServiceDescriptorTable->pSSDTBase + 0xBA * 4;
ULONG AddrWrite = (ULONG)KeServiceDescriptorTable->pSSDTBase + 0x115 * 4;
//记录NtReadVirtualMemory/NtWriteVirtualMemory 前16 字节
OrgRead[0] = *(PULONG)(*(PULONG)AddrRead);
OrgRead[1] = *(PULONG)(*(PULONG)AddrRead + 4);
OrgRead[2] = *(PULONG)(*(PULONG)AddrRead + 8);
OrgRead[3] = *(PULONG)(*(PULONG)AddrRead + 12);
OrgWrite[0] = *(PULONG)(*(PULONG)AddrWrite);
OrgWrite[1] = *(PULONG)(*(PULONG)AddrWrite + 4);
OrgWrite[2] = *(PULONG)(*(PULONG)AddrWrite + 8);
OrgWrite[3] = *(PULONG)(*(PULONG)AddrWrite + 12);
恢复OpenProcess(OpenThread是一样的,只是跳转的地址需要重新计算):
OpenProcess只修改了一处call ObOpenObjectByPointer, 直接用原始的地址去恢复直接蓝屏,修改call ObOpenObjectByPointer前的push代码跳转到自制代码也是蓝屏,甚至原本想修改e9的跳转地址再跳回来也是蓝屏,后来我把心一横,直接修改了OpenProcess的前两个push指令改成一个一个jmp,跳到我的自制代码,自制代码的结构是前16个字节做好堆栈直接jmp到一个写好的C函数,C函数的作用的是比较是否DNF.exe进程在调用OpenProcess,这里要先说一下在工具初始化的时候我分配了整个OpenProcess两倍加16个字节的代码数组也就是0x285*2+16,初始化的时候我已经复制了一份原始的OpenProcess到数组里面,然后在游戏运行的时候又复制了一份已经被HOOK了的OpenProcess代码在里面,所以C函数的作用是如果是DNF.exe的进程则进第二份被HOOK了的代码,否则进原始的代码:
UCHAR MyThreadRoot[ThreadLength*2+16], MyProcessRoot[ProcessLength*2+16];
// 游戏加载前,保存一次原始的函数代码
BufferCode(MyThreadRoot+16, (ULONG)OldThread, ThreadLength);
BufferCode(MyProcessRoot+16, (ULONG)OldProcess, ProcessLength);
// 游戏加载后,保存一次被HOOK了的函数代码
BufferCode(MyThreadRoot + ThreadLength + 16, (ULONG)OldThread, ThreadLength);
BufferCode(MyProcessRoot + ProcessLength + 16, (ULONG)OldProcess, ProcessLength);
// 修改OpenProcess前几个字节跳到自己的MyOpenProcess
ProcessCode[0] = *((BYTE*)OldProcess+0);
ProcessCode[1] = *((BYTE*)OldProcess+1);
ProcessCode[2] = *((BYTE*)OldProcess+2);
ProcessCode[3] = *((BYTE*)OldProcess+3);
ProcessCode[4] = *((BYTE*)OldProcess+4);
ProcessCode[5] = *((BYTE*)OldProcess+5);
addr1 = (ULONG)(UCHAR*)(MyProcessRoot) - (ULONG)OldProcess - 5;
*(BYTE*)((ULONG)OldProcess) = 0xe9;
*((BYTE*)((ULONG)OldProcess)+1) = *(BYTE*)(&addr1);
*((BYTE*)((ULONG)OldProcess)+2) = *((BYTE*)(&addr1)+1);
*((BYTE*)((ULONG)OldProcess)+3) = *((BYTE*)(&addr1)+2);
*((BYTE*)((ULONG)OldProcess)+4) = *((BYTE*)(&addr1)+3);
*((BYTE*)((ULONG)OldProcess)+5) = 0x90;
// 下面是上面跳转到的自制代码
// 这里相当于前面提到的代码数组的前16个字节,需要再跳到C函数进行上面提到的那个判断然后在选// 择跳到原始代码还是被HOOK了的代码
addr1 = (ULONG)IsDNFprocess - (ULONG)MyProcessRoot - 5;
*(BYTE*)((ULONG)MyProcessRoot) = 0xe8;
*((BYTE*)((ULONG)MyProcessRoot)+1) = *(BYTE*)(&addr1);
*((BYTE*)((ULONG)MyProcessRoot)+2) = *((BYTE*)(&addr1)+1);
*((BYTE*)((ULONG)MyProcessRoot)+3) = *((BYTE*)(&addr1)+2);
*((BYTE*)((ULONG)MyProcessRoot)+4) = *((BYTE*)(&addr1)+3);
addr1 = (ULONG)(MyProcessRoot + ProcessLength + 16) - (ULONG)(UCHAR*)(MyProcessRoot + 7) - 6;
*((BYTE*)((ULONG)MyProcessRoot)+5) = 0x85;
*((BYTE*)((ULONG)MyProcessRoot)+6) = 0xc0;
*((BYTE*)((ULONG)MyProcessRoot)+7) = 0x0f;
*((BYTE*)((ULONG)MyProcessRoot)+8) = 0x84;
*((BYTE*)((ULONG)MyProcessRoot)+9) = *(BYTE*)(&addr1);
*((BYTE*)((ULONG)MyProcessRoot)+10) = *((BYTE*)(&addr1)+1);
*((BYTE*)((ULONG)MyProcessRoot)+11) = *((BYTE*)(&addr1)+2);
*((BYTE*)((ULONG)MyProcessRoot)+12) = *((BYTE*)(&addr1)+3);
// 下面是那个判断是否是DNF.exe进程的C函数
int IsDNFprocess() {
char Name[16];
if(GetProcessName(Name)) {
if (strcmp(Name,"DNF.exe")) {
dprintf("不是指定进程在调用!\n");
return 1;
}
else {
dprintf("是指定进程在调用!\n");
return 0;
}
}
else {
dprintf("未获取到进程名!\n");
return 2;
}
}
// 下面是上面IsDNFprocess函数调用到的几个函数
ULONG GetProcessNameOffset() {
int i=0;
PEPROCESS curproc;
DWORD procNameOffset;
curproc = PsGetCurrentProcess();
for(; i< 4096; i++) {
if( !strncmp( "System", (PCHAR) curproc + i, strlen("System") )) {
procNameOffset = i;
return procNameOffset;
}
}
return 0;
}
BOOL GetProcessName( PCHAR theName ) {
PEPROCESS curproc;
char *nameptr;
ULONG i;
KIRQL oldirql;
if( g_ProcessNameOffset ) {
curproc = PsGetCurrentProcess();
nameptr = (PCHAR) curproc + g_ProcessNameOffset;
strncpy( theName, nameptr, 16 );
theName[15] = '\0'; /**//* NULL at end */
return TRUE;
}
return FALSE;
}
还有一个重要的处理是将被HOOK了OpenProcess中call ObOpenObjectByPointer地址提取出来重新计算代码数组第二段的call ObOpenObjectByPointer(因为e8是相对地址所以要再计算一次..)
接下来是恢复DebugPort:
还是老办法将nt!_eprocess+0xbc转移到其他地址,我尝试了0x70,0x74,我发现经常被修改,如果将DebugPort对象地址保存到里面被修改之后再由nt!PsGetProcessDebugPort返回出来直接蓝屏,我用的0x78这个地址好象没怎么被修改.
找DebugPort相关是函数我是直接KD调试虚拟机,然后先设置硬件写断点,然后在虚拟机里面随便调试一个进程,ba w4 nt!_eprocess+0xdc,找出所有修改了这个地址的API,然后再找读的,读和写是一个方法.
找到之后我整理了一下有如下几个(我看到有人找的和我找的很多不一样,我百思不得其解,这也是我最后要请教的问题):
// 自己跟踪出来的地址(下面用的0x74我已经换成了0x78)
写
1 nt!DbgkpSetProcessDebugObject+0x6a 8063a930
8063a98e 8b450c mov eax,dword ptr [ebp+0Ch]
8063a991 8b4d14 mov ecx,dword ptr [ebp+14h]
8063a994 8987bc000000 mov dword ptr [edi+0BCh],eax
Change: 898774000000 {0x89,0x87,0x74,0x00,0x00,0x00}
2 nt!DbgkCopyProcessDebugPort+0xf
80639993 8b4508 mov eax,dword ptr [ebp+8]
80639996 83a0bc00000000 and dword ptr [eax+0BCh],0
读
1 nt!DbgkpSetProcessDebugObject+0x5c 8063a930
8063a980 c645ff01 mov byte ptr [ebp-1],1
8063a984 ffd6 call esi
8063a986 399fbc000000 cmp dword ptr [edi+0BCh],ebx
Change: 399f74000000 {0x39,0x9f,0x74,0x00,0x00,0x00}
2 nt!DbgkpMarkProcessPeb+0x48 806398fa
80639937 897dfc mov dword ptr [ebp-4],edi
8063993a 33c0 xor eax,eax
8063993c 39bebc000000 cmp dword ptr [esi+0BCh],edi
Change: 39be74000000 {0x39,0xbe,0x74,0x00,0x00,0x00}
3 nt!DbgkCreateThread+0x12b 8063b08c
8063b1b1 399ebc000000 cmp dword ptr [esi+0BCh],ebx
Change: 399e74000000 {0x39,0x9e,0x74,0x00,0x00,0x00}
4 nt!DbgkpQueueMessage+0x81: 80639bec
80639c64 8b4508 mov eax,dword ptr [ebp+8]
80639c67 8b80bc000000 mov eax,dword ptr [eax+0BCh]
Change: 8b8074000000 {0x8b,0x80,0x74,0x00,0x00,0x00}
5 nt!PsGetProcessDebugPort+0xe 8052874c
8052874f 8bec mov ebp,esp
80528751 8b4508 mov eax,dword ptr [ebp+8]
80528754 8b80bc000000 mov eax,dword ptr [eax+0BCh]
Change: 8b8074000000 {0x8b,0x80,0x74,0x00,0x00,0x00}
6 nt!DbgkForwardException+0x44 8063aee0
8063af1e 8b81bc000000 mov eax,dword ptr [ecx+0BCh]
Change: 8b8174000000 {0x8b,0x81,0x74,0x00,0x00,0x00}
7 nt!PspExitThread+0x28c 805c938a
805c9606 7408 je nt!PspExitThread+0x286 (805c9610)
805c9608 8b4de0 mov ecx,dword ptr [ebp-20h]
805c960b e862a5f5ff call nt!ObfDereferenceObject (80523b72)
805c9610 399fbc000000 cmp dword ptr [edi+0BCh],ebx
Change: 399f74000000 {0x39,0x9f,0x74,0x00,0x00,0x00}
8 nt!DbgkExitThread+0x26 8063b42a
8063b441 f6804802000004 test byte ptr [eax+248h],4
8063b448 7551 jne nt!DbgkExitThread+0x71 (8063b49b)
8063b44a 8b89bc000000 mov ecx,dword ptr [ecx+0BCh]
Change: 8b8974000000 {0x8b,0x89,0x74,0x00,0x00,0x00}
9 nt!KiDispatchException+0x18d 804fd94e
804fdacc 64a124010000 mov eax,dword ptr fs:[00000124h]
804fdad2 8b4044 mov eax,dword ptr [eax+44h]
804fdad5 39b8bc000000 cmp dword ptr [eax+0BCh],edi
Change: 39b874000000 {0x39,0xb8,0x74,0x00,0x00,0x00}
10 nt!DbgkpCloseObject+0x3e:
8063a7bd 8b5d08 mov ebx,dword ptr [ebp+8]
8063a7c0 81c3bc000000 add ebx,0BCh
Change: 81c374000000 {0x81,0xc3,0x74,0x00,0x00,0x00}
然后是修改相关API,我没有一个一个找特征码我怕漏了,我直接计算了KernelBase和KernelSize比如0x804d8000, 0x1f000000,我就在这区间里面直接替换。比如第10个API nt!DbgkpCloseObject我直接搜索区间里面所有的81c3bc000000替换为81c378000000,其他同理.
最后是恢复KiAttachProcess
我查看的时候这里没有特殊的保护,所以我在工具初始化的时候保存了原始地址,然后直接恢复了该地址.
工具的下载地址(目前xp下能用):
bk_ssdtview.rar
结果是:我已经能用windbg附加DNF,但是还是收不到调试消息,我查看了debugport已经成功转移到了eprocess+0x78并且未被清,所有的API也已经使用了该地址,但是windbg还是显示挂起30秒那个提示,请各位给点提示!
随便请那位大哥给个邀请码,感谢!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课