preface
The full name of SSDT is System Services Descriptor Table. This table is a link between the Win32 API of Ring3 and the kernel API of Ring0. SSDT not only contains a huge address index table, but also contains some other useful information, such as the base address of the address index, the number of service functions and so on. By modifying the function address of this table, you can Hook common Windows functions and APIs, so as to filter and monitor some concerned system actions. Some HIPS, anti-virus software, system monitoring and registry monitoring software often use this interface to realize their own monitoring module.
structure
ssdt is a table, that is, the system service descriptor table
kd> dd KeServiceDescriptorTable
The address pointed to by the first parameter stores all kernel functions
This parameter represents the number of kernel functions in the ssdt table
This parameter refers to a pointer pointing to an address, which indicates the number of parameters corresponding to the above kernel function. For example, the first parameter is 18, and the number of parameters is 18 / 4 = 6
Here, find the index of OpenProcess in SSDT table. First, bp OpenProcess
Broke at kerner32 OpenProcess. Here, OpenProcess will call ZwOpenProcess in ntdll to enter ring0, and NtOpenProcess will be called in ring0ZwOpenProcess
Hurry to find mov eax,0x7A, so the index of zwupenprocess here is 0x7A
Then find all kernel functions through KeServiceDescriptorTable and OpenProcess functions through kernel function + offset
In Windows operating systems above NT 4.0, there are two system service description tables by default. These two scheduling tables correspond to two different types of system services. The two scheduling tables are KeServiceDescriptorTable and KeServiceDescriptorTableShadow. KeServiceDescriptorTable is mainly used to process Kernel32 from Ring3 layer DLL, while KeServiceDescriptorTableShadow mainly handles system calls from user32 DLL and gdi32 DLL, and KeServiceDescriptorTable is in ntoskrnl Exe (Windows operating system kernel file, including kernel and executive layer) is exported, while KeServiceDescriptorTableShadow is not exported by Windows operating system.
All content about SSDT is completed through KeServiceDescriptorTable.
The structure of the SSDT table is represented by the structure body as follows:
typedef struct _KSERVICE_TABLE_DESCRIPTOR { KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe service function KSYSTEM_SERVICE_TABLE win32k; // win32k.sys service function (GDI32.dll/User32.dll Kernel support for) KSYSTEM_SERVICE_TABLE notUsed1; KSYSTEM_SERVICE_TABLE notUsed2; } KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
Each of them is a structure: KSYSTEM_SERVICE_TABLE . It is represented by the structure as follows:
typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; // Base address of SSDT (System Service Dispatch Table) PULONG ServiceCounterTableBase; // Used for checked builds, including the number of times each service in SSDT is called ULONG NumberOfService; // The number of service functions. NumberOfService * 4 is the size of the entire address table ULONG ParamTableBase; // Base address of SSPT(System Service Parameter Table) } KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
Call number
When entering the 0 ring, the call number is passed by eax, but this call number is not just an ordinary number as the index number. The system will represent it with 32-bit data and split it into 19:1:12 format, as follows:
Analyze the lower 12 bits of 0-11 to form a real index number. The 12th bit represents the service table number, and the 13-31 bits are not used. After entering the kernel, which table is called, it is determined by the twelfth bits in the calling number, the SSDT table is called for 0, and the ShadowSSDT table is invoked for 1.
CR4 register
Here, when the function is ready, the pointer of the function should be overwritten with the pointer of the original NtOpenProcess. However, it should be noted that we don't need permission to change our own code. If we change other people's code, it is likely that this memory is read-only and not writable.
In essence, the physical page corresponding to SSDT is read-only. There are two methods. We all know that the attribute of memory R/W bit of physical page is derived from PDE and PTE, so we can change the R/W attribute of PDE and PTE corresponding to SSDT and set the physical page to be readable and writable. Determine whether it is 2-9-9-12 paging or 10-10-12 paging through CR4 register.
if(RCR4 & 0x00000020) {//The description is 2-9-9-12 pagination KdPrint(("2-9-9-12 paging %p\n",RCR4)); KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) &0x007FFFF8)))); *(DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)) |= 0x02; KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) &0x007FFFF8)))); } else {//The description is 10-10-12 pagination KdPrint(("10-10-12 paging\n")); KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)))); *(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)) |= 0x02; KdPrint(("PTE2 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) &0x003FFFFC)))); }
CR0 register
Use PsGetCurrentThread() function to get the first address of the current KTHREAD.
However, it should be noted that the property of the memory page where the SSDT table is located is read-only and has no write permission, so you need to set the address to be writable in order to write your own function. The CR0 register is used to turn off the read-only property.
Briefly introduce the following CR0 register:
It can be seen that 32-bit registers are used here, while in CR0 register, we focus on three flag bits:
PE Whether to enable the protection mode. If it is set to 1, it will be enabled.
PG Whether to use paging mode. If it is set to 1, the paging mode will be turned on. When this flag is set to 1, the PE flag must also be set to 1, otherwise the CPU will report an exception.
When WP is 1, read-only memory pages cannot be modified. When WP is 0, read-only memory pages can be modified.
Therefore, during HOOK, as long as the WP position in the CR0 register is 0, the memory can be written.
//Turn off page read-only protection __asm { push eax; mov eax, cr0; and eax, ~0x10000; // 0 is obtained by inverting with 0x10000 mov cr0, eax; pop eax; ret; } //Open page read-only protection __asm { push eax; mov eax, cr0; or eax, 0x10000; mov cr0, eax; pop eax; ret; }
Implementation code
#include <ntddk.h> #include <ntstatus.h> // Record the address of the original function ULONG uOldNtOpenProcess; //Hossdt kernel //System service table typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; //First address of function address table PULONG ServiceCounterTableBase;//The number of times each function in the function table is called ULONG NumberOfService; //Number of service functions ULONG ParamTableBase; //First address of parameter number table }KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE; //Service descriptor typedef struct _KSERVICE_TABLE_DESCRIPTOR { KSYSTEM_SERVICE_TABLE ntoskrnl;//ntoskrnl.exe service function, SSDT KSYSTEM_SERVICE_TABLE win32k; //win32k.sys service function, ShadowSSDT KSYSTEM_SERVICE_TABLE notUsed1;//Temporarily useless 1 KSYSTEM_SERVICE_TABLE notUsed2;//Temporarily useless 2 }KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR; //Defines the type of the HOOK function typedef NTSTATUS(NTAPI* FuZwOpenProcess)( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId ); //Self written function declaration NTSTATUS NTAPI MyZwOpenProcess( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId ); // KeServiceDescriptorTable is ntoskrnl Exe exported global variables extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable; //This function of the recording system FuZwOpenProcess g_OldZwOpenProcess; //Service descriptor table pointer KSERVICE_TABLE_DESCRIPTOR* g_pServiceTable = NULL; //ID of the process to protect ULONG g_Pid = 1624; //Installation hook NTSTATUS HookNtOpenProcess(); //Unloading hook NTSTATUS UnHookNtOpenProcess(); //Turn off page write protection void ShutPageProtect(); //Turn on page write protection void OpenPageProtect(); //Unload driver void DriverUnload(DRIVER_OBJECT* obj); /***Drive entry main function***/ NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path) { KdPrint(("Drive started successfully!\n")); //Installation hook HookNtOpenProcess(); driver->DriverUnload = DriverUnload; return STATUS_SUCCESS; } //Unload driver void DriverUnload(DRIVER_OBJECT* obj) { //Unloading hook UnHookNtOpenProcess(); KdPrint(("Driver uninstallation succeeded!\n")); } NTSTATUS HookNtOpenProcess() { NTSTATUS Status; Status = STATUS_SUCCESS; //1. Turn off page read-only protection ShutPageProtect(); //2. Modify SSDT table uOldNtOpenProcess = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a]; KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] =(ULONG)MyZwOpenProcess; //3. Open page read-only protection OpenPageProtect(); return Status; } //Unloading hook NTSTATUS UnHookNtOpenProcess() { NTSTATUS status; status = STATUS_SUCCESS; //1. Turn off page read-only protection ShutPageProtect(); //2. Write the original function into the SSDT table KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] = uOldNtOpenProcess; //3. Open page read-only protection OpenPageProtect(); return status; } //Turn off page read-only protection void _declspec(naked) ShutPageProtect() { __asm { push eax; mov eax, cr0; and eax, ~0x10000; mov cr0, eax; pop eax; ret; } } //Open page read-only protection void _declspec(naked) OpenPageProtect() { __asm { push eax; mov eax, cr0; or eax, 0x10000; mov cr0, eax; pop eax; ret; } } //Self written function NTSTATUS NTAPI MyZwOpenProcess( _Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId ) { //When this process is a process to be protected if (ClientId->UniqueProcess == (HANDLE)g_Pid) { //Set to deny access DesiredAccess = 0; } //Call the original function return NtOpenProcess(ProcessHandle,DesiredAccess,ObjectAttributes,ClientId); }
The results are as follows
Welcome to the team official account: red team blue army.