学习笔记-SSDT_HOOK

0x00 SSDT:

  SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。

  内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable,另一个是KeServieDescriptorTableShadow。

  KeServiceDescriptorTable 主要是处理来自 Ring3 层得 Kernel32.dll 中的系统调用,而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,并且 KeServiceDescriptorTable 在 ntoskrnl.exe(Windows 操作系统内核文件,包括内核和执行体层)是导出的,而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出,而关于 SSDT 的全部内容则都是通过 KeServiceDescriptorTable 来完成的。

SSDT即系统服务描述符表,它的结构如下:

// KSYSTEM_SERVICE_TABLE 和 KSERVICE_TABLE_DESCRIPTOR
// 定义 SSDT 结构
typedef struct _KSYSTEM_SERVICE_TABLE
{
    PULONG  ServiceTableBase;                               // SSDT (System Service Dispatch Table)的基地址
    PULONG  ServiceCounterTableBase;                        // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
    ULONG   NumberOfService;                                // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
    ULONG   ParamTableBase;                                 // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
    KSYSTEM_SERVICE_TABLE   ntoskrnl;                       // ntoskrnl.exe 的服务函数
    KSYSTEM_SERVICE_TABLE   win32k;                         // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
    KSYSTEM_SERVICE_TABLE   notUsed1;
    KSYSTEM_SERVICE_TABLE   notUsed2;
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

0x01 API执行流程:

  Ntdll.dll 中的 API 是一个简单的包装函数,当 Kernel32.dll 中的 API 通过 Ntdll.dll 时,会完成参数的检查,再调用一个中断(int 2Eh 或者 SysEnter 指令),从而实现从 Ring3 进入 Ring0 层,并且将所要调用的服务号(即 SSDT 数组中的索引值)存放到寄存器 EAX 中,并且将参数地址放到指定的寄存器(EDX)中,再将参数复制到内核地址空间中,再根据存放在 EAX 中的索引值来在 SSDT 数组中调用指定的服务。

nt!ZwOpenProcess:
840736cc b8be000000      mov     eax,0BEh
840736d1 8d542404        lea     edx,[esp+4]
840736d5 9c              pushfd
840736d6 6a08            push    8
840736d8 e8b1190000      call    nt!KiSystemService (8407508e)
840736dd c21000          ret     10h

 kd> !idt 2e

 Dumping IDT:

 2e: 8407508e nt!KiSystemService

 

  在上面调试中可以看到在 Ring0 下的 ZwOpenProcess将 0BEh 放入了寄存器 eax 中,然后调用了系统服务分发函数 KiSystemService,而 KiSystemService 函数则是根据 eax 寄存器中的索引值,然后再 SSDT 数组中找到索引值为 eax 寄存器中存放的值得那个 SSDT 项,最后就是根据这个 SSDT 项中所存放的系统服务的地址来调用这个系统服务了,比如在这里就是调用 KeServiceDescriptorTable[0BEh] 处所保存的地址所对应的系统服务了 ,也就是调用 Ring0 下的 NtOpenProcess了。

0x01 SSDT HOOK原理

  如图可通过 ZwOpenProcess 获取 NtOpenProcess 索引号 0BEh,通过 KeServiceDescriptorTable 可获得 SSDT  首地址 840b5d9c。可以根据  SSDT 系统服务地址= SSDT 首地址 + 4 * 索引号(Address = 840b5d9c + 4 * 0BEh = 840b6094;),然后 840b6094->84249aa0 这个就是 NtOpenProcess 系统服务函数所在。我们需要做的就是将 84249aa0 地址替换成我们的Hook函数地址。

  SDT Hook前后,NtOpenProcess 的当前地址会发生变化。变化后的当前地址:9d31b050 为我们自定义的Hook函数(即 HookNtOpenProcess)的地址。之后每次执行 NtOpenProcess 的时候,都会根据执行“当前地址”所指向的函数了,由于SSDT所在的物理页是只读、可执行的,需要修改页属性为可写(1.通过页表基址直接修改;2.通过修改CR0寄存器修改;3.通过MDL修改;)。

0x3 SSDT HOOK 思路汇总

1.加载驱动,获取需要保护进程的PID;

2.保存系统的原NtOpenProcess函数地址 ;

3.修改内存保护属性使得SSDT表可读可写 ;

4.修改SSDT表中NtOpenProcess函数处的地址为我们自定义Hook函数的地址,使调用时执行自定义的函数;

5.函数中做判断,如果是我们要保护的进程就直接返回,不让其获得句柄,其他则执行原NtOpenProcess函数;

6.驱动卸载,还原被Hook的地址;

#include <ntddk.h>

// ntdll!NtWriteFile:
// 001b : 77656a68 b88c010000      mov     eax, 18Ch
// 001b : 77656a6d ba0003fe7f      mov     edx, offset SharedUserData!SystemCallStub(7ffe0300)
// 001b : 77656a72 ff12            call    dword ptr[edx]
// 001b : 77656a74 c22400          ret     24h
// 001b : 77656a77 90              nop
// 
//call    dword ptr[edx]:
//ntdll!KiFastSystemCall:
// 001b : 776570b0 8bd4            mov     edx, esp
// 001b : 776570b2 0f34            sysenter



// SSDT HOOK 实现进程保护

// 定义修复和恢复页属性的函数
PMDL MDSystemCall;
PVOID *MappedSCT;


// SSDT表结构体
typedef struct _ServiceDesriptorEntry
{
    ULONG *ServiceTableBase;        // SSDT (System Service Dispatch Table)的基地址
    ULONG *ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
    ULONG NumberOfServices;         // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
    UCHAR *ParamTableBase;          // SSPT(System Service Parameter Table)的基地址
}SSDTEntry, *PSSDTEntry;

// 导入SSDT
NTSYSAPI SSDTEntry KeServiceDescriptorTable;

// 被保护程序的PID
ULONG g_uProtectPID = 0;

// OpenProcess函数原型
typedef NTSTATUS(NTAPI *NTOPENPROCESS)(__out PHANDLE  ProcessHandle,
    __in ACCESS_MASK  DesiredAccess,
    __in POBJECT_ATTRIBUTES  ObjectAttributes,
    __in_opt PCLIENT_ID  ClientId
    );

// 原OpenProcess函数地址
NTOPENPROCESS g_OldOpenProcess = NULL;


// 自定义HookNtProcess函数
NTSTATUS NTAPI HookNtOpenProcess(__out PHANDLE  ProcessHandle,
    __in ACCESS_MASK  DesiredAccess,
    __in POBJECT_ATTRIBUTES  ObjectAttributes,
    __in_opt PCLIENT_ID  ClientId)
{
    // 判断是不是要针对的进程PID
    if ((ULONG)ClientId->UniqueProcess == g_uProtectPID)
    {
        return STATUS_ABANDONED;
    }
    // 过滤完成再次调用原来的NtOpenprocess
    return g_OldOpenProcess(
        ProcessHandle,
        DesiredAccess,
        ObjectAttributes,
        ClientId
    );
}


void _declspec(naked)OffMemoryProtect()
{
//     MDSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices * 4);
//     if (!MDSystemCall)
//       return STATUS_UNSUCCESSFUL;
//     MmBuildMdlForNonPagedPool(MDSystemCall);
//     MDSystemCall->MdlFlags = MDSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
//     MappedSCT = MmMapLockedPages(MDSystemCall, KernelMode);

    // 关闭内存保护
    __asm { 
        push eax;
        mov eax, CR0;
        and eax, not 0x10000;
        mov CR0, eax;
        pop eax;
        ret;
    }
}


void  _declspec(naked)OnMemoryProtect()
{
//     if (MDSystemCall)
//     {
//       MmUnmapLockedPages(MappedSCT, MDSystemCall);
//       IoFreeMdl(MDSystemCall);
//    }

    // 恢复内存保护
    __asm { 
        push eax;
        mov eax, CR0;
        or eax, 0x10000;
        mov CR0, eax;
        pop eax;
        ret;
    }

}

// 开启Hook
void OnHook()
{
    // 保存环境,OpenProcess函数在SSDT表中的第0xBE项(190)
    g_OldOpenProcess = (NTOPENPROCESS)KeServiceDescriptorTable.ServiceTableBase[0xBE];
    // 开始Hook前需要修改内存属性,修改完地址后恢复内存属性
    OffMemoryProtect();
    KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)HookNtOpenProcess;
    OnMemoryProtect();
}

// 关闭Hook
void OffHook()
{
    // 修改地址前,先修改内存属性,改完后还原内存属性
    OffMemoryProtect();
    KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)g_OldOpenProcess;
    OnMemoryProtect();
}


// 驱动卸载函数
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
    OffHook();
    DbgPrint("------驱动卸载------\r\n");
}


// 驱动入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
    // DbgBreakPoint();
    DbgPrint("------驱动加载------\r\n");
    g_uProtectPID = 3632;
    // 开启Hook
    OnHook();
    pDriver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

0x4 测试结果

 

猜你喜欢

转载自www.cnblogs.com/Schicksal/p/11437769.html