SSDT - Framework Writing and Hook Examples

Overview of SSDT Framework

The SSDT Hook framework is not what everyone thinks. Net framework, MVC framework, etc., it is not that complicated. After all, it is just a .cpp and a .h file, and then expose several APIs in it. That is OK~

SSDThook Review

ssdt is actually an array, then we need to get the first address of the array first, so this structure comes out.

typedef struct ServiceDescriptorEntry {
    
    
    unsigned int *ServiceTableBase;
    unsigned int *ServiceCounterTableBase;
    unsigned int NumberOfServices;
    unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;

ServiceTableBase, this parameter is the base address of the ssdt array. With it, we can find the correct function address by giving the offset of the specific function.
NumberOfService, this is the maximum value of the ssdt array, that is, the number of functions in the ssdt.
With this structure, you can find the address of the function you want to hook according to the offset. But before the hook, you need to modify the kernel protection, otherwise it will be a ruthless blue screen.

cli means to disable the occurrence of interrupts
sti means to allow the occurrence of interrupts
Cr0 This register saves the flag bit of the kernel protection, use 10000h to invert and perform an AND operation with him, so that the flag bit changes from 1 to 0, and the kernel can be modified.

void PageProtectOff()
{
    
    
    __asm{
    
    
        cli
            mov  eax,cr0
            and  eax,not 10000h
            mov  cr0,eax
        }
}

Of course, you have to modify it after you are done

void PageProtectOn()
{
    
    
    __asm{
    
    
        mov  eax,cr0
        or   eax,10000h
        mov  cr0,eax
        sti
    }
}

After modifying the kernel protection, you can write the function address defined by yourself in the ssdt. But for system security, we need to save the previous function address and restore it when we uninstall the driver, so as not to cause the system to not work properly.

Take the OpenProcess function as an example

You can view the offset address of the OpenProcess function through Tinder Sword or Kernel Detective

NTSTATUS ssdt_hook()
{
    
    
    O_NtOpenProcess=KeServiceDescriptorTable.ServiceTableBase[122];
    PageProtectOff();
    KeServiceDescriptorTable.ServiceTableBase[122]=(unsigned int)MyNtOpenProcess;
    PageProtectOn();
    return STATUS_SUCCESS;
}

In order to make the driver complete the specified task, for example, we want to protect the Notepad process from being opened by OpenProcess, so we filter it in our own NtOpenProcess function

Take a look at the declaration of the NtOpenProcess function

NTSTATUS MyNtOpenProcess (
    __out PHANDLE ProcessHandle,
    __in ACCESS_MASK DesiredAccess,
    __in POBJECT_ATTRIBUTES ObjectAttributes,
    __in_opt PCLIENT_ID ClientId
    )

Parameters: ClientId, which is a structure of the target process. The target process is the process that other processes want to open.

typedef struct _CLIENT_ID {
    
    
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID;

If you want to view the NtProcess offset of the kernel, you need to unlock the debugging restrictions in cmd, you need to unlock the restrictions in the BIOS, you have forgotten the bios password, you are replacing the chip, and you need to fill in the hole.

The process object of the process can be found through the first member UniqueProcess, which is eprocess, then how to get the structure of eprocess, so there is the following function.

NTSTATUS
    PsLookupProcessByProcessId(
    IN HANDLE ProcessId,
    OUT PEPROCESS *Process
    );

The uniqueProcess structure is as follows

lkd> dt _eprocess
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY
   +0x090 QuotaUsage       : [3] Uint4B
   +0x09c QuotaPeak        : [3] Uint4B
   +0x0a8 CommitCharge     : Uint4B
   +0x0ac PeakVirtualSize  : Uint4B
   +0x0b0 VirtualSize      : Uint4B
   +0x0b4 SessionProcessLinks : _LIST_ENTRY
   +0x0bc DebugPort        : Ptr32 Void
   +0x0c0 ExceptionPort    : Ptr32 Void
   +0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : Uint4B
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : Uint4B
   +0x114 ForkInProgress   : Ptr32 _ETHREAD
   +0x118 HardwareTrigger  : Uint4B
   +0x11c VadRoot          : Ptr32 Void
   +0x120 VadHint          : Ptr32 Void
   +0x124 CloneRoot        : Ptr32 Void
   +0x128 NumberOfPrivatePages : Uint4B
   +0x12c NumberOfLockedPages : Uint4B
   +0x130 Win32Process     : Ptr32 Void
   +0x134 Job              : Ptr32 _EJOB
   +0x138 SectionObject    : Ptr32 Void
   +0x13c SectionBaseAddress : Ptr32 Void
   +0x140 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch  : Ptr32 _PAGEFAULT_HISTORY
   +0x148 Win32WindowStation : Ptr32 Void
   +0x14c InheritedFromUniqueProcessId : Ptr32 Void
   +0x150 LdtInformation   : Ptr32 Void
   +0x154 VadFreeHint      : Ptr32 Void
   +0x158 VdmObjects       : Ptr32 Void
   +0x15c DeviceMap        : Ptr32 Void
   +0x160 PhysicalVadList  : _LIST_ENTRY
   +0x168 PageDirectoryPte : _HARDWARE_PTE_X86
   +0x168 Filler           : Uint8B
   +0x170 Session          : Ptr32 Void
   +0x174 ImageFileName    : [16] UChar
   +0x184 JobLinks         : _LIST_ENTRY
   +0x18c LockedPagesList  : Ptr32 Void
   +0x190 ThreadListHead   : _LIST_ENTRY
   +0x198 SecurityPort     : Ptr32 Void
   +0x19c PaeTop           : Ptr32 Void

After getting the EPROCESS structure of the process, you can use windbg to find the imagebase member in the base, the offset is 0x174, this member is represented as the name of the target process, and we can compare this name with the name of the process we want to protect, If it is the same, then directly return to the unsuccessful opening, which protects the process we want to protect.

    if(ProtectProcess(ClientId->UniqueProcess,"notepad.exe"))
    {
    
    
        KdPrint(("hooked %s",(char *)PsGetCurrentProcess()+0x174));
        return STATUS_UNSUCCESSFUL;
    }
 
 
status=PsLookupProcessByProcessId(ProcessId,&process_obj);
    if(!NT_SUCCESS(status))
    {
    
    
        KdPrint(("error code:%X---ProcessId:%d",status,ProcessId));
        return FALSE;
    }
    if(!strcmp((char *)process_obj+0x174,str_ProtectObjName))
    {
    
    
        ObDereferenceObject(process_obj);
        return TRUE;
    }

In order to keep other processes unaffected, after processing these things, the original function base address should be used for normal calls, so that the unprotected process can work normally

    
//KdPrint(("Hook Success!"));
    return ((MYNTOPENPROCESS)O_NtOpenProcess)(ProcessHandle,
        DesiredAccess,
        ObjectAttributes,
        ClientId);
}

In order to allow the system to work normally after the driver is uninstalled, we need to restore the previous function address saved to the ssdt structure, so the following code is available.

void UnHookSsdt()
{
    
    
    PageProtectOff();
    KeServiceDescriptorTable.ServiceTableBase[122]=O_NtOpenProcess;
    PageProtectOn();
}

The overall code is as follows:

#include "ntddk.h"
 
#pragma pack(1)
typedef struct ServiceDescriptorEntry {
    
    
    unsigned int *ServiceTableBase;
    unsigned int *ServiceCounterTableBase;
    unsigned int NumberOfServices;
    unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
 
NTSTATUS
    PsLookupProcessByProcessId(
    IN HANDLE ProcessId,
    OUT PEPROCESS *Process
    );
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
 
typedef NTSTATUS(*MYNTOPENPROCESS)(
    OUT PHANDLE             ProcessHandle,
    IN ACCESS_MASK          AccessMask,
    IN POBJECT_ATTRIBUTES   ObjectAttributes,
    IN PCLIENT_ID           ClientId );//定义一个指针函数,用于下面对O_NtOpenProcess进行强制转换
ULONG O_NtOpenProcess;
 
BOOLEAN ProtectProcess(HANDLE ProcessId,char *str_ProtectObjName)
{
    
    
    NTSTATUS status;
    PEPROCESS process_obj;
    if(!MmIsAddressValid(str_ProtectObjName))//这个条件是用来判断目标进程名是否有效
    {
    
    
        return FALSE;
    }
    if(ProcessId==0)//这个条件是用来排除System Idle Process进程的干扰
    {
    
    
        return FALSE;
    }
    status=PsLookupProcessByProcessId(ProcessId,&process_obj);//这句用来获取目标进程的EPROCESS结构
    if(!NT_SUCCESS(status))
    {
    
    
        KdPrint(("错误号:%X--进程ID:%d",status,ProcessId));
        return FALSE;
    }
    if(!strcmp((char *)process_obj+0x174,str_ProtectObjName))//进行比较
    {
    
    
        ObDereferenceObject(process_obj);//对象计数器减1,为了恢复对象管理器计数,便于回收
        return TRUE;
    }
    ObDereferenceObject(process_obj);
    return FALSE;
}
NTSTATUS MyNtOpenProcess (
    __out PHANDLE ProcessHandle,
    __in ACCESS_MASK DesiredAccess,
    __in POBJECT_ATTRIBUTES ObjectAttributes,
    __in_opt PCLIENT_ID ClientId
    )
{
    
    
    //KdPrint(("%s",(char *)PsGetCurrentProcess()+0x174));
    if(ProtectProcess(ClientId->UniqueProcess,"calc.exe"))
    {
    
    
        KdPrint(("%s can’t open it。。",(char *)PsGetCurrentProcess()+0x174));
        return STATUS_UNSUCCESSFUL;
    }
    //KdPrint(("Hook Success!"));
    return ((MYNTOPENPROCESS)O_NtOpenProcess)(ProcessHandle,//处理完自己的任务后,调用原来的函数,让其它进程正常工作
        DesiredAccess,
        ObjectAttributes,
        ClientId);
}
 
void PageProtectOff()//关闭页面保护
{
    
    
    __asm{
    
    
        cli
            mov  eax,cr0
            and  eax,not 10000h
            mov  cr0,eax
        }
}
void PageProtectOn()//打开页面保护
{
    
    
    __asm{
    
    
        mov  eax,cr0
        or   eax,10000h
        mov  cr0,eax
        sti
    }
}
 
void UnHookSsdt()
{
    
    
    PageProtectOff();
    KeServiceDescriptorTable.ServiceTableBase[122]=O_NtOpenProcess;//恢复ssdt中原来的函数地址
    PageProtectOn();
}
NTSTATUS ssdt_hook()
{
    
    
    //int i;
    //for(i=0;i<KeServiceDescriptorTable.NumberOfServices;i++)
    //{
    
    
    //  KdPrint(("NumberOfService[%d]-------%x",i,KeServiceDescriptorTable.ServiceTableBase[i]));
    //}
    O_NtOpenProcess=KeServiceDescriptorTable.ServiceTableBase[122];//保存原来的函数地址
    PageProtectOff();
    //将原来ssdt中所要hook的函数地址换成我们自己的函数地址
    KeServiceDescriptorTable.ServiceTableBase[122]=(unsigned int)MyNtOpenProcess;
    PageProtectOn();
    return STATUS_SUCCESS;
}
void DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    
    
    UnHookSsdt();
    KdPrint(("Driver Unload Success !"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegsiterPath)
{
    
    
    DbgPrint("This is My First Driver!");
    ssdt_hook();
    pDriverObject->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

Environment: VS2019+WDK

Guess you like

Origin blog.csdn.net/qq_42882717/article/details/116119366