Process protection under r0

Posted by ThermalSloth on Mon, 07 Mar 2022 09:45:51 +0100

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.

Topics: C++ Windows security Cyber Security