函数,就是Inline HOOK这些函数。当我们用OD调试器还是CE搜索器,要打开它的进程并对其进行读写内存时,它都会禁止操作那进程,从而导致我们操作失败。比如程序对NtOpenProcess()函数HOOK后,防止打开进程,OD调试器就看不到那进程,对NtDebugActiveProcess()函数HOOK后,OD调试器虽然能看到进程,但是不能调试成功的。 2, 他们有可能会在内核下建立系统内核线程不停的检测它们HOOK代码的地方会不会被恢复。还有可能会检测他们HOOK的代码到底有没有被调用。如果没有被调用,则认为是我们在它前面跳过去了,这样它们肯定就检测出了异常,他们还有可能会在内核下建立内核线程不时的对其保护的进程的EPROCESS的DebugPort清0,让我们的OD调试器收不到任何调试事件信息。
3.2解决方案
基于以上问题,我是这么设计的,我们的应用层OD插件HOOK OD进程空间的ntdll.dll动态库的KiFastSystemCall()函数,使其跳到我们自己的HxKiFastSystemCall(),然后对OD进程调用的相应的API函数进行过滤处理,也就是改变系统调用ID。然后在Windows系统内核下加入我们自己的系统调用函数和调试驱动。当OD调用比较关键的函数,就会进入我们自己的系统调用。所以对于别的驱动保护HOOK的关键函数就不起用了,对于一直清除EPROCESS的DebugPort的线程的检测,我们自己在内核下可以不操作要调试进程EPROCESS的Debugport。我们移到另一个结构体中。这样也就可以防止它一直对DebugPort调试端口清0。
3.3系统需求分析
根据以上的分析,为了克服现行OD调试器存在的不足,得到新系统的功能如下:
1, 我们利用OD调试器插件库,HOOK OD进程空间的ntdll.dll的KiFastSystemCall(),它是一切用户层API要进行系统调用的最后入口,然后当OD进程调用API函数时,只要这个API函数里会调用系统函数调用,它都会跳到我们自己的HxKiFastSystemCall()函数。
2, 在我们自己实现的HxKiFastSystemCall()函数中,对OD进程调用的API函数进行过滤处理,比如如果是调用OpenProcess()打开进程,ReadVirtualMemory()读进程内存,WriteVirtualMemory()写进程内存,DebugActiveProcess()附加进程,WaitForDebugEvent()等待调试事件等等关键函数,我们会在这里改变它们的系统调用ID,然后自己调用sysenter指令进入Ring0,sysenter指令是intel CPU公司专门设计的快速系统调用,它会改变EIP寄存器,使其指向KiFastCallEntry()函数,因此在当前系统中,只要发生过系统调用,不管是哪个进程在要求调用系统调用,在Ring0中首先被执行的是KiFastCallEntry()函数,然后它会根据我们传来的系统调用Id从KeServiceDescriptorTable或
10
者KeServiceDescriptorTableShadow表中去取得相应的系统调用函数运行。 3, 由于我们内核驱动里面用到的很多内核函数很底层,MS提供的DDK中并没有导出这些函数。比如这些函数有NtReadVirtualMemory(),NtWriteVirtualMemory(),它们都要调用的MmCopyVirtualMemory()函数,这个函数微软提供的ntoskrnl.lib静态库文件就没有导出,还有很多比如MiProtectVirtualMemory(),PsGetNextProcess(),ObDuplicateObject(),等等没有导出。我们不能在内核驱动中直接调用,我们可以采用暴力搜索内存找特征码然后得到当前系统这些函数的加载地址,然后加以调用。这种方法很费时而且通用性不好。我采用另一种方法,我们应用程序自己解析微软提供给我们的PDB符号文件搜索得到这些未导出函数的地址,然后传进内核驱动这些内核函数的地址,我们内核驱动才可以加以调用这些未导出的函数。
4, 我们要在Windows内核下加入我们自己的系统调用表,Windows内核下有两种系统调用表,
KeServiceDescriptorTable和KeServiceDescriptorTableShadow表。我们自己在非分页内存中分配一个服务表, 先拷贝KeServiceDescriptorTable和KeServiceDescriptorTableShadow两个表,然后把我们要加上的系统调用函数地址加进去,然后写进KeServiceDescriptorTable和KeServiceDescriptorTableShadow表中,下次如果OD进程调用我们感兴趣的系统调用的话,KiFastCallEntry()函数就会根据服务Id从这两个表中找到我们的系统调用,然后调用之。
5, 我们自己的系统调用很多地方可以参考WRK。WRK是Windows Server 2003系统的内核。
跟XP还是有点区别的。对于很重要的内核函数KeStackAttachProcess()和KiAttachProcess (),这两个是Windows系统内核切换到别的进程空间中的关键函数。由于有的游戏也有HOOK,我们要自己实现之用来跳过HOOK,这几个函数我是用IDA把Windows Xp3的NtKernelpa.exe逆向出来的,功能基本已实现。
6, 有的保护驱动会在内核下建立内核线程一直对它要保护的进程的EPROCESS的DebugPort端口一直清0,这样会导致OD调试器收不到任何调试事件信息。我是这么做的,因为我们OD调试器调用的API与调试有关的系统内核函数几乎都是我们自己在内核驱动中实现的函数,所以打算把操作EPROCESS的DebugPort的代码地方都改下。改成操作另一个结构体,里面专门对应要调试的进程的EPROCESS进程环境块和调试对象(DebugObject)。比如NtDebugActiveProcess->DbgkpSetProcessDebugObject()里面会对要调试进程的EPROCESS的DebugPort设置成NtCreateDebugObject()的调试对象。这里不们不把被调试进程的EPROCESS的DebugPort设置成指向那调试对象,而是移到上面说的结构中去。
7, 我们此时还要HOOK 一些常见的Windows内核函数,比如建立线程时,结束线程时,加载DLL时,怎么会把调试事件信息通知到我们建立的这种对应的结构体中。我们要Hook 这些常见的函数像建立线程时的PspUserThreadStartup(),它里面会调用DbgkCreateThread()向被调试进程EPROCESS的DebugPort发消息,我们在这里要处理,还有就是PspExitThread(),NtMapViewOfSection()等等很多函数都要处理的。
8, 我们还要Hook Windows的内核异常处理机制,比如我们用OD调试器经常F2
11
下的断点,都是int 3断点,我们要这CPU 执行Int 3这个指令时向相应的EPROCESS的DebugPort发送调试事件信息,我们就要HOOK Windows的异常处理函数。
3.4系统流程图
如下图 3-3为整个系统整体流程图:
12
4系统概要设计
4.1 应用程序模块
当我们把我们写的OD插件放入OD调试器目录下的Plugin文件夹中后,
OD调试器启动时就会调用ODBG_Plugindata()这个回调函数,我们在这里调用HxLoadKrnl()函数负责加载我们的内核驱动,我们的插件在OD调试器插件菜单中加入了以下两个菜单选项,BeginHook和CacelHook,当用户点击BeginHook后,会调用HxKrnl_Init()这个函数,这个函数里面主要是调用DeviceIoControl()这个函数向我们的内核驱动依次发送相关命令。先是发送HXIOCTL_INIT这个命令,要求我们的内核驱动从Windows内核下得到ntkrnlpa.exe,hal.dll内核模块的加载地址,然后返回给我们的OD应用层插件;接着发送HXIOCTL_GET_FNNUMBERLIST命令,会取得内核国未导出函数的个数,然后发送HXIOCTL_GET_FNLIST命令,会取得这些内核未导出函数的名字,之后我们OD插件解析内核PDB文件得到这些未导出函数的地址,然后再次向我们的驱动发送HXIOCTL_SET_DRIVER命令,通知驱动开启把这些函数地址设置到函数指针变量中。接着OD插件会发送HXIOCTL_BEGIN_ADDSERVICE命令,要求驱动在Windows系统内核下加入我们自己的系统调用表。最后OD插件会
13
调用Hook()函数HOOK OD进程空间ntdll.dll的KiFastSystemCall()函数,之后当OD进程再次调用API时就会进入我们的函数,我们HxKiFastSystemCall()函数里面会对其系统调用ID进行判断,如果是我们感兴趣的系统调用ID,我们在这里把它改变成我们在内核添加的对应的系统调用ID,然后调用sysenter指令一样的进入Ring0,Ring0的响应函数KiFastCallEntry()会根据我们传进来的系统调用ID找到我们对应的系统调用然后调用之。
4.2 内核驱动模块
内核驱动模块有一部分是专门和我们的OD应用层插件通讯用的,当我们的
OD插件调用DeviceIoControl()函数向我们驱动发送相关命令时,我们会在HxCtrl()这个函数中做相关处理,对于HXIOCTL_INIT命令,我们会调用HxGetKenelNameAndLoadAddr()这个函数遍历系统当前所有加载的内核列表中找到ntkrnlpa.exe和hal.dll,然后返回当前的加载地址,返回给我们的应用层OD插件,对于HXIOCTL_GET_FNNUMBERLIST命令,我们返回内核未导出函数的个数,对于HXIOCTL_GET_FNLIST命令,我们返回这些未导出函数的名字,对于HXIOCTL_SET_DRIVER命令,我们把OD插件得到的内核未导出的函数地址设置到函数指针变量中来,对于HXIOCTL_BEGIN_ADDSERVICE命令,我们在Windows内核下加入我们自己的系统调用表,对于HXIOCTL_FREE这个命令,我们做好一些卸载善后操作,因为OD插件通知我们的内核驱动要退出了。内核驱动还有一部分就是实现的这些系统调用函数和调试模块。
14