09SSDT概述


SSDT概述

通过08内核编程HOOK_KiFastCall.md可以知道,用户层的函数调用都会进入到0环, 0环将服务函数的地址实现保存在SSDT表中. KiFastCallEntry函数会使用调用号找到函数的参数个数表和函数地址表, 并将用户栈的参数拷贝到内核栈,最后调用了系统服务表中的函数.

SSDT HOOK的原理很简单: 找到SSDT,将对应的函数地址进行替换,就完成HOOK了.

在进行HOOK的过程中,唯一需要注意的是: SSDT表是不可写的, 强行写入会产生内存访问异常, 但是也有方法使其变成可写:

  1. 关闭CR0寄存器中的WP位 这个位是用于控制是否开启页保护的, 当其被置1, CPU就会做写入检查, 当其置0,就不做检查.(02_寄存器.md)

  2. 使用MDL重新映射SSDT表的元素,这样也可以进行写入(04内核编程内核新概念.md)

HOOK 代码

#include <ntddk.h>

typedef struct _KSYSTEM_SERVICE_TABLE
{
   PULONG ServiceTableBase;   //函数地址表的首地址
   PULONG ServiceCounterTableBase;// 函数表中每个函数被调用的次数
   ULONG   NumberOfService;// 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
   UCHAR*   ParamTableBase; // 参数个数表首地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
   KSYSTEM_SERVICE_TABLE   ntoskrnl;// ntoskrnl.exe的服务函数,即SSDT
   KSYSTEM_SERVICE_TABLE   win32k; // win32k.sys的服务函数(GDI32.dll/User32.dll 的内核支持),即ShadowSSDT
   KSYSTEM_SERVICE_TABLE   notUsed1; // 不使用
   KSYSTEM_SERVICE_TABLE   notUsed2; // 不使用
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
typedef NTSTATUS(NTAPI*FnZwOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);



void OnUnLoad(DRIVER_OBJECT* driver);

void installHookSSDT();
void uninstallHook();
void disablePageWriteProtect();
void enablePageWriteProtect();
NTSTATUS NTAPI MyZwOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);


FnZwOpenProcess g_oldZwOpenProcess; // 原始ZwOpenProcess函数
ULONG           g_uPid; // 需要保护的进程ID, 这个PID可以通过内核通讯来修改.
KSERVICE_TABLE_DESCRIPTOR* g_pServiceTable = NULL;

NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{

   DbgBreakPoint();
   driver->DriverUnload = OnUnLoad;

   // 安装HOOK
   installHookSSDT();

   return STATUS_SUCCESS;
}

void OnUnLoad(DRIVER_OBJECT* driver)
{
   // 卸载HOOK
   uninstallHook();
}

void installHookSSDT()
{
   // 1. 找到SSDT表的首地址
   // 1.1 在分析KiFastCallEntry时, 可以看到系统从KPCR中取出了当前线程对象
   //     然后又从当前线程对象(KTHREAD)中取出了ServiceTable.因此,可以仿照
   //     这个做法.
   // 1.2 ServiceTable在KTHREAD的以下偏移:
   //       +0x0bc ServiceTable     : Ptr32 Void
   PETHREAD* pCurThread = PsGetCurrentThread();

   g_pServiceTable = (KSERVICE_TABLE_DESCRIPTOR*)
       (*(ULONG*)((ULONG_PTR)pCurThread + 0xBC));

   // 2. 找到函数在表中的位置(其位置就是调用号.)
   // 2.1 保存旧的函数地址(0xBE是ZwOpenProcess函数)
   g_oldZwOpenProcess =
       (FnZwOpenProcess)g_pServiceTable->ntoskrnl.ServiceTableBase[0xBE];

   // 3. 将内存分页设置为可写
   disablePageWriteProtect();
   // 4. 写入新函数地址到SSDT表中
   g_pServiceTable->ntoskrnl.ServiceTableBase[0xBE] = (PULONG)MyZwOpenProcess;
   // 5. 将内存分页属性恢复不可写
   enablePageWriteProtect();
}

void uninstallHook()
{
   if (g_oldZwOpenProcess)
   {
       // 1. 将内存分页设置为可写
       disablePageWriteProtect();
       // 2. 写入新函数地址到SSDT表中
       g_pServiceTable->ntoskrnl.ServiceTableBase[0xBE] = (PULONG)g_oldZwOpenProcess;
       // 3. 将内存分页属性恢复不可写
       enablePageWriteProtect();
   }
}

NTSTATUS NTAPI MyZwOpenProcess(PHANDLE ProcessHandle,
                               ACCESS_MASK DesiredAccess,
                               POBJECT_ATTRIBUTES ObjectAttributes,
                               PCLIENT_ID ClientId)
{
   if (ClientId->UniqueProcess == g_uPid)
   {
       DesiredAccess = 0; // 将访问权限置零
   }
   // 调用原始函数
   return g_oldZwOpenProcess(ProcessHandle,
                             DesiredAccess,
                             ObjectAttributes,
                             ClientId);
}

// 关闭内存页写入保护
void _declspec(naked) disablePageWriteProtect()
{
   _asm
   {
       push eax;
       mov eax, cr0;
       and eax, ~0x10000;
       mov cr0, eax;
       pop eax;
       ret;
   }
}

// 开启内存页写入保护
void _declspec(naked) enablePageWriteProtect()
{
   _asm
   {
       push eax;
       mov eax, cr0;
       or eax, 0x10000;
       mov cr0, eax;
       pop eax;
       ret;
   }
}
原文地址:https://www.cnblogs.com/ltyandy/p/11439441.html