Rookit技术之SSDT_Hook

Rookit技术之SSDT_Hook

0x0 SSDT简介

SSDT全称为System Services Descriptor Table,中文为系统服务描述符表,ssdt表就是把ring3的Win32 API和ring0的内核API联系起来。SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。SSDT通过修改此表的函数地址可以对常用windows函数进行hook,从而实现对一些核心的系统动作进行过滤、监控的目的。一些HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块(引用百度)

SSDT_Hook即挂钩系统服务描述表中函数.这是一种古老的技术,相传是在病毒中首次使用这样的技术.恶意软件为了提高自己的权限,来进行底层的挂钩,于是SSDT这个表被利用了.
我们知道,用户层调用函数都会调用到ntdll.dll中,其实这个ntdll.dll就是用户层到内核层的接口.里边维护着两张表-KeServiceDescriptorTable 和KeServiceDescriptorTableShadow.
前者简称SSDT,其中是函数地址数组,多数操作系统进程相关的函数都在其中,后者简称shadowSSDT,是用户界面相关的函数地址数组,比如来自user32.dll或者GDI32.dll的函数

0x1 测试环境

系统: 虚拟机 Windows 7 32bit
工具: Windbg,驱动加载工具等

这个技术可以稳定的在windows 32位的系统下进行,32位系统其实被各种Hook技术被用烂了,所以现在在64位上的Hook技术被微软限制了

0x2 达到目标

Hook NtOpenProcess函数,使我们的目标程序(计算器程序)不能被其他任务使用这个函数获取句柄,结束这个程序.

0x3 主要思路

1.加载驱动,获取需要保护进程的PID
2.保存系统的老的NtOpenProcess函数地址
3.修改内存保护属性使得SSDT表可读可写
4.修改SSDT表中NtOpenProcess函数处的地址为我们自己写的函数的地址,调用时候执行我们的函数
5.函数中做判断,如果是我们要保护的进程就直接返回,不让其获得句柄,如果不是,就主动调用老的系统NtOpenProcess函数
6.驱动卸载,还原被Hook的地址

0x4 关键代码

#include <ntddk.h>


//SSDT HOOK
//Hook  NtOpenProcess 使进程对象不会再次被打开

//SSDT表结构体
typedef struct _ServiceDesriptorEntry
{
    ULONG *ServiceTableBase;        // 服务表基址
    ULONG *ServiceCounterTableBase; // 计数表基址
    ULONG NumberOfServices;         // 表中项的个数
    UCHAR *ParamTableBase;          // 参数表基址
}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()
{
    __asm { //关闭内存保护
        push eax;
        mov eax, cr0;
        and eax, ~0x10000;
        mov cr0, eax;
        pop eax;
        ret;
    }
}
//开启内存保护裸函数
void  _declspec(naked)OnMemoryProtect()
{
    __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("Driver Unload\n");
}


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

0x5 测试效果

为了简单,直接在驱动中写死了PID,所以我们在任务管理器中查看其PID,然后在写到驱动中.
这里写图片描述
加载驱动,使用任务管理器结束”计算器”程序
这里写图片描述
这里写图片描述
卸载驱动,再次结束程序测试,正常结束了计算器.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章