驱动的hook
相比Win32API的hook,这个貌似更加的流氓一些,其实原理是这样子的(下面的例子用OpenProcess举例),在我们调用OpenProcess函数的时候会调用ZwOpenProcess函数,还是通过汇编代码分析一下吧(随便找个程序进去就行),这样更清楚:
1 | 73975A00 > 8BFF mov edi,edi ; OpenProc.<ModuleEntryPoint> |
可以看到,我们在jmp之前做了点迷惑操作,具体为什么这么做,不需要了解,但是可以注意到,我们跳转到了KernelBa.OpenProcess
里面,我们跟进去:
1 | 754B3C70 >/$ 8BFF mov edi,edi ; OpenProc.<ModuleEntryPoint> |
前面的还是不需要关注,还是最后一句话,call了ntdll.NtOpenProcess
,那么我们继续跟进这个ntdll.NtOpenProcess
这个函数:
1 | 7707AB30 > B8 26000000 mov eax,0x26 |
可以看到,我们又call了一下edx,然后就返回了,这个edx是多少呢,是0x7708F160,我们继续看这个地址:
1 | 7708F160 - FF25 28B21277 jmp dword ptr ds:[Wow64Transition] ; wow64cpu.76FA7000 |
这是最深的一层了,没必要跟进了,其实这个函数通过汇编的分析,可以简单的得到一个结论,就是他需要一个eax的值,在我们调用OpenProcess的时候,这个eax的值是0x26,我截个图大家看看上一层:
发现在上一层有好多类似的代码,都是赋值eax,然后跳到这个Wow64Transition函数中去,这里我讲解的是SSDT,也就是kernel的(不是GDI和user32的)
SSDT就是系统服务描述符表,主要作用就是将我们的用户层API与内核层的Nt函数进行一一的对应,里面会存放一个指向这个NT开头函数的地址:
说白了就是OpenProcess其实调用了ZwOpenProcess,然后通过eax的值在SSDT表中指向了NtOpenprocess函数,最终调用内核层的代码,Wow64Transition这个函数应该是从用户层跳转的一个函数,这里不做过多的分析。
简单的分析到这里,我们可以画一张图来理顺一下思路(本人画图技术不精,PS一直都是很混)
在之前我们知道用户层的hook分为两种方式,一种是修改PE导入表,另一种是inlinehook,那么这里也主要分为这两种:
- 修改SSDT表的OpenProcess函数的地址,指向我们hook的地址
- 修改函数的跳转
为了简单起见,我主要讲解的是第一种修改SSDT表的方式。
这里我们开发一个hook的驱动程序,我准备的环境是win7,vs2013,wdk,ddk标准的开发环境,虽然有一点点的老了,但是还是可以用的,如果不熟悉搭建环境流程的和我以前学驱动的时候一样的话呢,那么可以联系我,我将这个环境配置到了VM虚拟机中,你只需要下载虚拟机导入就可以直接使用我配置好的环境。
首先我们导入ntddk.h
1 |
对了还有一点要说的是,我用的是C语言写的,我建议使用C语言,不要用CPP了,CPP写起来我总是遇到问题!!!虽然高级属性少了,但是会避免一些不必要的问题。
定义NTOpenProcess的原型:
1 | typedef NTSTATUS(*pfnNtOpenProcess)( |
定义一个全局变量,用来保存我们的被hook前真实的NtOpenprocess地址,这里为什么我没用到设备扩展,因为我们不是一个真实的wdm驱动,而是一个测试的nt驱动,为了简单,我就不创建驱动对象了。
1 | pfnNtOpenProcess OldNtOpenProcess; |
下面是重点:
1 | typedef struct _SERVICE_DESCRIPTOR_TABLE { |
主要讲解这一块,我们定义SSDT的结构,这个微软给了,我直接copy别人的,下面的是ntoskrnl.lib导出的一个全局变量,这个变量里面就是我们要修改的宏大的SSDT表!
定义我们的NtOpenprocess函数:
1 | NTSTATUS MyNtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesireAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientID) |
可以看到,我什么都不做,只是返回一个连接拒绝,当然我只是为了简单,其实真正的做法应该是我们遍历我们保护的进程,得到PID,然后如果PID等于要保护的话呢我们就返回拒绝,不是我们就让他执行原先的Nt函数。
下面就是hook代码,我参考别人的,我下面简单解释一下他的做法是什么:
1 | PVOID HookSSDTFunction(PVOID OldFunction, PVOID HookFuction) |
首先我们创建一块mdl空间,SSDT表的基地址到SSDT表的结尾,大小的话呢就是SSDT个数的4倍,主要是一个指针四个字节,我们用的32位的操作系统。
然后更新我们的MDL空间在非分页内存上,然后再锁住,防止蓝屏这个小可爱。
然后遍历SSDT表判断是不是我们要hook的地址,如果是的话呢我们就用这种原子级别的数据交换,修改要hook的函数,最终解锁MDL,并且释放,返回我们之前的函数地址,为了在驱动卸载的时候取消hook。
1 | VOID hook(PCWSTR sz) |
这段代码为了验证,但是实际上关键的就是OldNtOpenProcess = (pfnNtOpenProcess)HookSSDTFunction(NtOpenProcess, MyNtOpenProcess);
,MmGetSystemRoutineAddress这个函数是通过字符串获得地址,这个也就是XT这种工具可以帮助我们取消hookSSDT表的方法函数。
可以看到我们将之前的NtOpenprocess hook 成为了我们的函数。
驱动加载函数:
1 |
|
很简单,驱动一加载我们就hook,也不创建别的东西了。
驱动卸载函数:
1 |
|
也是,卸载的话呢设备对象也不管了,直接取消hook就可以了。
代码其实比较的简单,关键的也就没几行。
看一下在32位上实际的操作是什么样子的:
可以看到XT已经检测出来hook了,并且我们现在如果要用管理员方式运行任何东西,都会提示这个样子的对话框:
这个对话框是不是很熟悉,杀毒软件经常这么搞。
卸载之后XT也就给我们提示了,没有hook了。
过掉驱动
我们知道原理之后,我们只需要写一个简单的驱动,通过MmGetSystemRoutineAddress函数修改回我们之前的OpenProcess地址就可以了,在这里Wker给大家一些问题以及思路:
- 我们修改回来的话呢,驱动其实可以设置一个DPC的时钟,一直监视我们的NtOpenprocess的地址,如果发现被改回来的话呢还是可以进行设置回来的。
- 解决办法:用汇编修改新的NtOpenprocess代码,让他直接跳转到的旧的NtOpenProcess
- 如果我们用了汇编修改方法,他可以检测这个内存单元的字节,如果存在跳转,并且不是他之前的字节的话呢,他还是可以进行保护
留一个问题,希望读者可以通过思考得出如何过掉他的第二种保护手法。
那种内联的hook方法我就不讲了,这个的话呢还要牵扯到一些比较底层的东西,牵扯到CR0的一些问题,给大家一段代码,从网上摘录的,其实也比较简单,就是对CR0的位进行了修改:
1 | //禁用写保护,wp=0 |