系统调用-SSDT HOOK

admin 2023年11月27日11:57:27评论6 views字数 10567阅读35分13秒阅读模式

SSDT与SSDT Shadow

前面我们已经介绍了系统服务表-SystemServiceTable,并且了解到一个程序如何从3环进入0环找到我们想要的函数,今天在完成SSDT HOOK项目之前,还需要了解两个结构体KeServiceDescriptorTableKeServiceDescriptorTableShadow

来看一下两个的区别:

kd> dd KeServiceDescriptorTable
8055b3a0 804e3620 00000000 0000011c 80510868
8055b3b0 00000000 00000000 00000000 00000000
8055b3c0 00000000 00000000 00000000 00000000
8055b3d0 00000000 00000000 00000000 00000000
8055b3e0 00002710 bf80c401 00000000 00000000
8055b3f0 f8a49a80 f82b7be0 81df9a90 80701f40
8055b400 00000000 00000000 37bbd85c 00000000
8055b410 7128ad9c 01da202a 00000000 00000000
kd> dd KeServiceDescriptorTableShadow
8055b360 804e3620 00000000 0000011c 80510868
8055b370 bf9a1500 00000000 0000029b bf9a2210
8055b380 00000000 00000000 00000000 00000000
8055b390 00000000 00000000 00000000 00000000
8055b3a0 804e3620 00000000 0000011c 80510868
8055b3b0 00000000 00000000 00000000 00000000
8055b3c0 00000000 00000000 00000000 00000000
8055b3d0 00000000 00000000 00000000 00000000

KeServiceDescriptorTable结构体,存储了绿颜色的数据

KeServiceDescriptorTableShadow存储了下面两张表的数据

系统调用-SSDT HOOK

为什么会这样呢?

因为KeServiceDescriptorTable又称为SSDT,是Windows导出函数:

系统调用-SSDT HOOK

而KeServiceDescriptorTableShadow未导出,并且KeServiceDescriptorTableShadow的第二行如果没有一个gui程序调用了相关函数,我们是看不到具体的值的,如下:

系统调用-SSDT HOOK

只有当我们挂载了一个GUI程序,那么,第二个表才有值。

这里就不扩展了,因为不是我们的主要任务。

简单介绍了SSDT表之后,那么我们来简单的写个函数打印SSDT表的地址:

#include<ntddk.h>
#include<ntstatus.h>

//SSDT表
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
PULONG 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;

extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

//实现卸载函数和派遣函数
VOID DriverUnload(PDRIVER_OBJECT pdriver)
{
DbgPrint("EXIT!");

}


NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING reg_path)
{
DbgPrint("Welcome Driver,rn");

DbgPrint("KeServiceDescriptorTable: %p", KeServiceDescriptorTable);

pdriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;

}

系统调用-SSDT HOOK

补充-中断与异常方面的知识

在前面我们只是简单介绍了一下中断门,并没有介绍别的中断知识。这里给大家介绍在介绍一下其他的中断知识

不可屏蔽中断

什么是不可屏蔽中断CPUEFLAG之中有一个位,它是IF位。如果它被置0。如果有可屏蔽中断告诉CPU有中断来了,你能先执行我的代码呢?可是IF位是0,对不起,我听不见。左耳朵进,右耳朵出。反之,我会处理。常见的不可屏蔽中断有电脑长按关机、键盘输入等等。当非可屏蔽中断产生时,CPU在执行完当前指令后会里面进入中断处理程序,非可屏蔽中断不受那个位的影响,一旦发生,CPU必须处理。为了方便观看,给个EFLAG图解:

系统调用-SSDT HOOK

那么CPU是如何处理我们的不可屏蔽中断呢?我们先来看如下表格:

(IDT表)中断号

NMI

说明

0x2

不可屏蔽中断

80x86 中固定为 0x2

如果处理不可屏蔽中断,CPU会调用2号中断。涉及的IDT表和中断门的知识如果忘却请查看前面的教程。

可屏蔽中断

可屏蔽中断与之相反,就是可以屏蔽掉的中断。

那么CPU是如何处理我们的可屏蔽中断呢?我们先来看如下表格:

(IDT表)中断号

IRQ

说明

0x30

IRQ0

时钟中断

0x31-0x3F

IRQ1-IRQ15

其他硬件设备的中断

如果自己的程序执行时不希望CPU去处理这些中断,可以用CLI指令清空EFLAG寄存器中的IF位,用STI指令设置EFLAG寄存器中的IF位。
硬件中断与
IDT表中的对应关系并非固定不变的,可以参考白皮书的Chapter 10 Advanced Programmable Interrupt Controller(APIC)进行了解。

异常

异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页等。中断与异常之间有一些相似之处,但它们是不一样的:中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的;而异常来自于CPU本身,是CPU主动产生的。INT N虽然被称为“软件中断”,但其本质是异常,EFLAGIF位对INT N是无效。

异常处理

无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表。常见的异常处理程序如下表所示:

错误类型

(IDT表)中断号

页错误

0xE

段错误

0xD

除零错误

0x0

双重错误

0x8

有些异常比较特别,我们在来略微讲解一下:

缺页异常

缺页异常当PDE/PTE的P=0时或当PDE/PTE的属性为只读但程序试图写入的时就会触发。一旦发生缺页异常,CPU会执行IDT表中的0xE中断处理程序,由操作系统来接管。
你或许不清楚操作系统来接管。一个程序要是使用内存,必须有一个物理页。但物理页被挂上之后,这个物理页也不一定是永久属于你的。如果不经常用,操作系统看到后,就操作PDE/PTE的P位为0,把它放到称之为虚拟内存交换文件之中。毕竟内存宝贵,不能养“闲人”嘛。当这个程序过了好长时间又想要它了,结果发现PDE/PTE不合法,触发缺页异常。然后操作系统过来,通过查看PDE/PTE看看是不是我自己裁的员(如下图所示),如果是的话我再招一个物理页,然后把数据写进入,然后重新挂上,然后告诉CPU我处理妥善,继续干活,然后程序就像没啥事情一样正常使用。可以说,缺页异常无时无刻发生着。

系统调用-SSDT HOOK

控制寄存器

控制寄存器用于控制和确定CPU的操作模式。控制寄存器有Cr0Cr1Cr2Cr3Cr4Cr1被保留了,Cr3用于页目录表基址,其他的将继续详细讲解。

Cr0

Cr0是一个十分重要的寄存器,可以说它是总开关的集合体。如下图所示:

系统调用-SSDT HOOK

PE位是启用保护模式(Protection Enable)标志。若PE = 1是开启保护模式,反之为实地址模式。这个标志仅开启段级保护,而并没有启用分页机制。若要启用分页机制,那么PEPG标志都要置位。
PG位是启用分页机制。在开启这个标志之前必须已经或者同时开启PE标志。PG = 0PE = 0,处理器工作在实地址模式下。PG = 0PE = 1,处理器工作在没有开启分页机制的保护模式下。PG = 1PE = 0,在PE没有开启的情况下无法开启PGPG = 1PE = 1,处理器工作在开启了分页机制的保护模式下。
WP位对于Intel 80486或以上的CPU,是写保护(Write Proctect)标志。当设置该标志时,处理器会禁止超级用户程序(例如特权级0的程序)向用户级只读页面执行写操作;当CPL < 3的时候,如果WP = 0可以读写任意用户级物理页,只要线性地址有效。如果WP = 1可以读取任意用户级物理页,但对于只读的物理页,则不能写。

Cr2

当CPU访问某个无效页面时,会产生缺页异常,此时,CPU会将引起异常的线性地址存放在CR2中,如下图所示:

系统调用-SSDT HOOK

Cr4

Cr4的结构如下图所示:

系统调用-SSDT HOOK

VME用于虚拟8086模式。PAE用于确认是哪个分页,PAE = 1,是2-9-9-12分页,PAE = 010-10-12分页。PSE是大页是否开启的总开关,如果置0,就算PDE中设置了大页你也得是普通的页。

SSDT HOOK

前面知识补充完毕之后,我们可以开始写SSDT HOOK项目了。

什么是SSDT HOOK?

最简单的说法就是,我们将SSDT中的某个系统调用号替换成我们自己写的函数。这就是SSDT HOOK。

因此,我们要实现课程中的作业,也就是对TerminateProcess的HOOK,我们需要找到相对应的系统调用号。

TerminateProcess会调用NtTerminateProcess

系统调用-SSDT HOOK

NtTerminateProcess系统调用号为0x101。

系统调用-SSDT HOOK

驱动层代码

#include <ntifs.h>
#include <wdm.h>
#include <ntddk.h>
#include <ntstatus.h>

#define TerminateProcessIndex 0x101

PEPROCESS protectedProcess = NULL;

//重新定义NtTerminateProcess
typedef NTSTATUS(__stdcall* NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);

NtTerminateProcess oldNtTerminateProcess = NULL;

#define DEVICE_NAME L"\Device\MyTestDriver"
#define SYMBOLICLINE_NAME L"\??\MyTestDriver" //ring3用CreateFile打开设备时,用"\\.\MyTestDriver"//相当于起的别名

#define HOOK CTL_CODE(FILE_DEVICE_UNKNOWN,0x800/*0-0x7FF??*/,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define UNHOOK CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

NTSTATUS DEVICE_CONTROL_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS DEVICE_CREATE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS DEVICE_CLOSE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp);
//SSDT表
typedef struct _KSYSTEM_SERVICE_TABLE
{
PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
PULONG 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;

extern PKSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;


PDEVICE_OBJECT pDevice = NULL;

//实现卸载函数和派遣函数
VOID DriverUnload(PDRIVER_OBJECT pDeviceObject)
{
if (pDevice != NULL) {
IoDeleteDevice(pDevice);
UNICODE_STRING symLink = RTL_CONSTANT_STRING(SYMBOLICLINE_NAME);
IoDeleteSymbolicLink(&symLink);
}
if (oldNtTerminateProcess != NULL)
{
KeServiceDescriptorTable->ServiceTableBase[TerminateProcessIndex] = oldNtTerminateProcess;
}
DbgPrint("EXIT!");

}



typedef NTSTATUS(__stdcall* NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);



//恢复内存保护
void PageProtectOn()
{
__asm
{
mov eax, cr0;
or eax, 10000h;
mov cr0, eax;
sti;
}
}

//去掉内存保护
void PageProtectOff()
{
__asm
{
cli;
mov eax, cr0;
and eax, not 10000h;
mov cr0, eax;
}
}

NTSTATUS __stdcall HookNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
if (protectedProcess != NULL)
{
PEPROCESS pro = IoGetCurrentProcess();
PEPROCESS p;
if (ObReferenceObjectByHandle(ProcessHandle, NULL, *PsProcessType, KernelMode, &p, NULL) == STATUS_SUCCESS)
{
if (p == protectedProcess && pro != protectedProcess)
{
return STATUS_ACCESS_DENIED;
}
}
}
return oldNtTerminateProcess(ProcessHandle, ExitStatus);
}

void HookTerminateProcess()
{
PageProtectOff();
oldNtTerminateProcess = KeServiceDescriptorTable->ServiceTableBase[TerminateProcessIndex];
KeServiceDescriptorTable->ServiceTableBase[TerminateProcessIndex] = HookNtTerminateProcess;
PageProtectOn();
}


NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDeviceObject/*设备信息*/, PIRP pIrp/*参数信息*/)
{
DbgPrint("DispatchCreate ... n");
pIrp->IoStatus.Status = STATUS_SUCCESS;//getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0;//返回给3环多少数据,没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pdriver, PIRP pIrp)
{
DbgPrint("DispatchClose ... n");
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// 参数pIrp:I / O 管理器在接受到应用层的设备读写请求后, 将请求封装为一个IRP请求(包含IRP 头部和IO_STACK_LOCATION)
NTSTATUS IrpDeviceContrlProc(PDEVICE_OBJECT pdriver, PIRP pIrp)
{
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
PIO_STACK_LOCATION pIrpStack;
ULONG uIoControlCode;
PVOID pIoBuffer;
ULONG uInLength;
ULONG uOutLength;
ULONG uRead;
ULONG uWrite;

//设置临时变量的值
uRead = 0;
//uWrite = 0x12345678;

// 设置临时变量的值
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// 获取控制码
uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 获取缓冲区地址(输入和输出的缓冲区都是一个)
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
// Ring3 发送数据的长度
uInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Ring0 发送数据的长度
uOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

switch (uIoControlCode)
{
case HOOK:
{
RtlMoveMemory(&uRead, pIoBuffer, 4);
DbgPrint("HOOK");
if (PsLookupProcessByProcessId((HANDLE)uRead, &protectedProcess) == STATUS_SUCCESS)
{
DbgPrint("得到消息,目前 EPROCESS 地址为:0x%p", protectedProcess);
}
else
{
status = STATUS_INVALID_HANDLE;
}
break;
}
case UNHOOK:
{
protectedProcess = NULL;
break;
}
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}



//设置返回状态,否则默认是失败
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; //返回给3环多少字节数据,没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING reg_path)
{
DbgPrint("Welcome Driver,rn");

NTSTATUS status = 0;
ULONG uIndex = 0;
UNICODE_STRING DeviceName;
UNICODE_STRING SymbolicLinkName;
//PDEVICE_OBJECT pDeviceObject = NULL;
NTSTATUS nStatus;

// 创建设备
RtlInitUnicodeString(&DeviceName, DEVICE_NAME);

//创建设备
status = IoCreateDevice(pdriver, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevice);
if (status != STATUS_SUCCESS)
{
DbgPrint("创建设备失败! rn");
return status;
}

//设置交互数据方式
pDevice->Flags |= DO_BUFFERED_IO;

//创建符号链接名称,就是给该设备在三环起个能用的别名
RtlInitUnicodeString(&SymbolicLinkName, SYMBOLICLINE_NAME);

//创建符号链接
status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
if (status != STATUS_SUCCESS)
{
DbgPrint("创建符号链接失败!rn");
IoDeleteDevice(pDevice);
return status;
}

//设置派遣函数和卸载函数
pdriver->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc;
pdriver->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;
pdriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceContrlProc;
HookTerminateProcess();
pdriver->DriverUnload = DriverUnload;

return STATUS_SUCCESS;

}

应用层代码

#include "stdafx.h"
#include <windows.h>
#include <winioctl.h>
#include <stdlib.h>


#define HOOK CTL_CODE(FILE_DEVICE_UNKNOWN,0x800/*0-0x7FF保留*/,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define UNHOOK CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

#define SYMBOL_LINK_NAME L"\\.\MyTestDriver"

HANDLE g_Device;
int main(int argc, char* argv[])
{
//获取驱动链接对象句柄
g_Device=CreateFileW(SYMBOL_LINK_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if (g_Device==INVALID_HANDLE_VALUE)
{
puts("访问驱动符号链接失败!");
goto endproc;
}

DWORD pid;
DWORD outBuffer;
DWORD lbret;

puts("请输入需要保护的程序的 PID :");
scanf("%d",&pid);

if (DeviceIoControl(g_Device,HOOK,&pid,sizeof(DWORD),&outBuffer,sizeof(DWORD),&lbret,NULL))
{
puts("保护命令正在发送,请测试……");
}

CloseHandle(g_Device);
endproc:
system("pause");
return 0;
}


系统调用-SSDT HOOK


原文始发于微信公众号(loochSec):系统调用-SSDT HOOK

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年11月27日11:57:27
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   系统调用-SSDT HOOKhttps://cn-sec.com/archives/2242098.html

发表评论

匿名网友 填写信息