金沙注册网站-新金沙官网 金沙注册网站 新金沙官网Hook的几种实现

新金沙官网Hook的几种实现



API
Hook的几种实现

0x00 Hook技术

hook技术分为两块:

  • Ring3层的Hook,俗称应用层hook技术
  • Ring0层的Hook,俗称内核层Hook技术

Ring3层的hook又分为两种类型

  • Windows消息的hook
  • WindowsAPI的hook

如下图所示:

新金沙官网 1

hook1

新金沙官网 2

hook2

新金沙官网 3

hook1

关于Windows消息的Hook,相信很多朋友都有接触过的,因为一个SetWindowsHookEx即可以完成消息
Hook,在这里简要介绍一下消息 Hook,消息 Hook
是通过SetWindowsHookEx可以实现将自己的钩子插入到钩子链的最前端,而对于发送给被
Hook 的窗口(也有可能是所有的窗口,即全局
Hook)的消息都会被我们的钩子处理函数所捕获到,也就是我们可以优先于窗体先捕获到这些消息,Windows
消息 Hook 可以实现为进程内消息 Hook 和全局消息 Hook,对于进程内消息
Hook
,则可以简单的将 Hook 处理函数直接写在这个进程内,即是自己 Hook
自己,而对于用途更为广泛的全局消息 Hook,则需要将 Hook 处理函数写在一个
DLL 中,这样才可以让你的处理函数被所有的进程所加载(进程自动加载包含 Hook
消息处理函数的 DLL)。

对于 Windows 消息 Hook
呢,可以有个简单的邪恶应用,就是记录键盘按键消息,从而达到监视用户输入的键值信息的目的,这样,对于一些简单的用户通过键盘输入的密码就可以被
Hook 获取到,因为没当用户按下一个键时,Windows
都会产生一个按键消息(当然有按下,弹起等消息的区分),然后我们可以 Hook
到这个按键消息,这样就可以在 Hook
的消息处理函数中获取到用户按下的是什么键了。

不过消息的hook不是本文的重点。

本文要讲的SSDT hook呢其实是属于内核Hook,常见于病毒以及杀软中。

下图展示了内核hook的几个基本类型。

新金沙官网 4

Kernel hook

所谓的API
Hook,就是利用某种技术将API的调用转为我们自己定义的函数的调用。这种技术在实际项目里面的应用也是很广泛的。最近,我在做关于我们项目的自动化
测试的时候,就遇到了这种情况。在写测试代码之前,我们对测试代码有一些要求。1.
不能因为测试代码而修改原代码。2.
原有的模块是以dll格式输出的,在做测试的时候,要测的类和函数也只能使用dll的导出类或者函数,而不能将源文件重新编译。由于这些限制,导致测试用
例往往不能在普通的机器上运行。比如这样一个函数:

0x01 SSDT简介

SSDT全称System Service Descriptor
Table(系统描述符表),这个表用于将Ring3的Win32API和内核的API联系起来。

SSDT并不仅仅只包含一个庞大的地址索引表,它还包含一些有用的信息,如地址索引的基地址,服务函数的个数等。通过修改此表可以达到对一些关心的系统动作进行过滤以及监控的目的。

在NT4.0的windows操作系统中,默认存在两个系统服务描述符表,这两个描述符表对应了两类不同的系统服务,这两个表为:KeServiceDescriptorTable(SSDT)和KeServiceDescriptorTable(SSDT
Shadow)。其中SSDT负责处理来自Ring3层的Kernel32.dll的系统调用。而SSDT
Shadow则主要处理来自User32.dll和GDI32.dll的系统调用。同时SSDT在ntoskrnl.exe中是导出的,而SSDT
Shadow如其名是未被Windows所导出的,而关于SSDT的全部内容都是通KeServiceDescriptorTable来完成的。

以下截图说明,KeServiceDescriptorTable是在ntoskrnl.exe中被导出的:

新金沙官网 5

Export1

随后我们看看看看Windows操作系统的源码中如何定义KeServiceDescriptorTable的,通过观察WRK可知,

新金沙官网 6

KeServiceDescriptorTable in WRK

这么看还是有点蛋疼啊。改写以下变量的名称吧。

    typedef struct _KSYSTEM_SERVICE_TABLE{
        PULONG ServiceTableBase;            //SSDT的基地址指针
        PULONG ServiceCounterTableBase; //SSDT中每个服务被调用次数表的基地址指针
        ULONG NumberOfService;              //服务函数个数,NumberOfService*4就是整个地址表的大小
        ULONG ParamTableBase;               //SSPT的基地址
    }KSYSTEM_SERVICE_TABLE,*PKSYSTEM_SERVICE_TABLE;
    typedef struct _KSERVICE_TABLE_DESCRIPTOR{
        KSYSTEM_SERVICE_TABLE ntoskrnl;     //ntoskrnl.exe的服务函数
        KSYSTEM_SERVICE_TABLE win32k;       //win32k.sys的服务函数(GDI32/User32)
        KSYSTEM_SERVICE_TABLE notUsed1;
        KSYSTEM_SERVICE_TABLE notUsed2;
    }KSERVICE_TABLE_DESCRIPTOR,*PKSERVICE_TABLE_DESCRIPTOR;
    //导出由ntoskrnl.exe所导出的SSDT
    extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

从上述介绍可知,KeServiceDescriptorTable可以看做是一个数组,是4个KSYSTEM_SERVICE_TABLE结构形成的数组,而每个KSYSTEM_SERVICE_TABLE对应一个PE文件导出的服务描述符表,根据这些服务描述符表我们可以获得更加详细的服务信息。在应用层ntdll.dll中的API在这个系统服务描述表中都存在一个与之对应的服务,当我们的应用程序调用ntdll.dll里的API时最终可以调用对应的系统服务函数,通知给内核一个索引,内核通过该索引在SSDT中查找对应的服务,内核调用服务完成应用的API调用请求。

<!–

应用层调用Win32API流程

有了以上的SSDT基础后,我们再来看看在应用层调用Win32API(主要指ntdll.dll中的API)的流程,我们主要针对ntdll.dll中的NtQuerySystemInformation这个API的调用流程来进行阐述。

这里存在四个类似的API。

新金沙官网 7

4APIs

再给出这些API的调用流程。

新金沙官网 8

calling way

这里我们可以看到ntdll.dll中的nt和zw都会进入内核层去调用ntoskrnl的zw函数,而zw最终会调用nt函数,这个函数作为内核API最终去请求系统服务的执行。
用exescope工具可以打开ntdll.dll,看到NtQuerySystemInformation以及ZwQuerySystemInformation。

新金沙官网 9

ntdll1

新金沙官网 10

ntdll2

而实质上zw和nt都是同一函数,指向同一区域,入口地址相同。

因此Ntdll.dll中的API都是对内核API的封装,当Kernel32.dll中的API通过Ntdll.dll去调用系统API时,会进行参数检查,并调用中断(int
2Eh或SysEnter),从而从Ring3进入Ring0,并将所要调用的服务号,即SSDT数组的索引值,存放进寄存器EAX中,并且将参数地址放到指定的寄存器EDX中,再复制参数到内核地址空间,根据存放在EAX中的索引值来在SSDT数组中调用指定的服务。

经过上面步骤我们来到Ring0层。使用exescope看看ntoskrnl.exe中的ZwQuerySystemInformation以及NtQuerySystemInformation。

新金沙官网 11

ntoskrnl1

新金沙官网 12

ntoskrnl2

再反汇编ntoskrnl这个文件可以看到Zw函数中调用KiSystemService系统服务分发函数时往EAX中存放了索引号ADh。如图:

新金沙官网 13

ZwQSystemInfor

随后根据该索引值检索SSDT项,最后根据该SSDT项中所存放的系统服务地址来调用这个系统服务。在这里就是调用KeServiceDescriptorTable[ADh]处保存的地址对应的系统服务。那就是Ring0下的NtQuerySystemInformation。

Code highlighting produced by Actipro CodeHighlighter (freeware)

0x02 详解SSDT

这节内我们用WinDbg来调试XP系统,借此说明SSDT是个什么鬼。
据我们上文的结构定义可知,KeServiceDescriptorTable是一个指向4个KSYSTEM_SERVICE_TABLE结构首地址的指针,以下图为例吧。

新金沙官网 14

dd KeServiceDescriptorTable

这里的0x80563520这一行就是ntoskrnl对应的服务描述符表结构KSYSTEM_SERVICE_TABLE。那么第一个32位的0x804e58a0则是对应ntoskrnl对应的KSYSTEM_SERVICE_TABLE中的SSDT
Base
,即服务描述符表的首地址。通过对该首地址的dump,我们可以看到许多以128位为一组排列的服务描述符,每个描述符中的第一个32位(0x80591bfb)对应着系统服务的入口地址。通过对该入口地址的反汇编,可以看到这是SSDT第一个系统服务NtAcceptConnectPort函数。如图:

新金沙官网 15

NtAcceptConnectPort

那么知道了SSDT首地址,同时知道了索引,那么我们就可以通过索引来找到对应的系统服务入口地址了。通过计算“SSDT中系统服务地址所在的地址
= SSDT首地址 + 4 *
索引值”,可以推算出NtQuerySystemInformation的起始地址位0x80586ff1,对该地址进行反汇编,可得下图:

新金沙官网 16

NtQuerySystemInformation

由此可知,SSDT就是个保存Windows系统服务地址的数组。

–>int func()
{
    //Some initializing codes
    int hardware_code = get_hardware_code();
    if (is_valid_code(hardware_code))
    {
        //新金沙官网 17
    }
    //新金沙官网 18
    return ret;
}

0x03 SSDT hook原理

从上面的分析中我们可以看到SSDT数组中保存了系统服务的地址,如Ring0下的NtQuerySystemInformation系统服务地址,就保存在KeServiceDescriptorTable[ADh]中,既然hook就是取出这个地址后替换上我们的hook函数,在hook函数中执行原函数即可。

参考文献:

http://www.cnblogs.com/boyxiao/archive/2011/09/03/2164574.html

此处,函数get_hardware_code()是与特定平台相关的,在普通PC上运行肯定无法获得正确的结果。如果拿不到正确的结果,也就不能
对函数func()进行测试了。于是,我们就可以利用API
Hook技术,在测试代码里面,把所有对get_hardware_code()的调用换成我们自定义的函数
mock_get_hardware_code()的调用,这样,在我们自己定义的函数里面,可以返回一个有效的代码以保证原代码能够正确的往下执行。

经过研究,API Hook有这么几种方法。

  1. 改写函数的首地址。

这个是在《Windows核心编程》里面大师提到的API
Hook的方法之一。原理就是,首先获得要被Hook的函数的地址(比如get_hardware_code()),然后将其首地址之后的若干字节(通常
是5个字节)改成一条jmp指令,而jmp的目标地址就是自定义函数的地址(此处为mock_get_hardware_code())。这样,当函数每
次执行目标函数的时候,就会跳转到我们自定义的函数里面去。这种方法很简洁,据说在Win16的年代经常被使用。但是大师并不推荐,好像是因为这种方法在
多线程的环境下会有什么问题(具体的我忘记了,大家可以翻书看看)。

这样的话,只要我们能得到被调函数的地址,我们就可以随心所欲的修改。当然,由于大师的不推荐,我这种方法只是实现了一下,并未真正应用。

  1. 改写导入表

这个也是《Windows核心编程》里面提到的,也是大师所推荐的。具体来说,就是遍历当前进程里面的所有模块,对其中每一个模块查找它的导入表。
如果找到被测函数所在的dll,并且发现这个函数,那么就把这个地址修改成自定义函数的地址。关于如何从导入表中发现被测函数,我也总结了两种方式。1)
对于一般的C导出函数,可以直接通过比较地址的方式去找,这个也在核心编程上面有一个小例子。2)对于C++中的导出的类成员函数而言,由于C++的指向
成员函数的指针和普通的指针有所差别(我没仔细研究过,从网上查的),在将一个成员函数指针转化成普通的函数指针的时候编译通不过,因此我采取了第二种查
找方式,也就是查找函数名。这还有一个问题,由于C++的导出成员函数名都进行了修饰,类似于?MethodName@ClassName@…@Z
种怪异的名字,不过,只要知道类名和方法名,然后查找MethodName@ClassName
符串就行。如果找到了这样的函数,然后在修改它的地址就行了。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图