业界动态

网络安全编程:HOOK SSDT

发布日期:2021-07-31
来源:计算机与网络安全

  SSDT把用户层的Win32 API与内核层的Native API做了一个关联,而整个Native API都保存在SSDT中的一个函数指针数组中,只要修改函数指针数组中的某一项,就相当于HOOK了某个Native API函数。比如,修改SSDT中函数指针数组中的最后一个函数指针,就相当于HOOK了NtQueryPortInformationProcess()函数。

  下面HOOK一个比较熟悉的函数,即创建进程函数NtCreateProcessEx()。该函数在指针数组的第0x30项(该编号根据系统版本的不同而不同,是系统相关的)。通过编程获取SSDT表,然后找到Native API的函数指针数组,再修改其中第0x30项的内容为自己的函数地址。为了不影响进程的正常创建,在函数中调用NtCreateProcessEx()函数。代码如下:

  #include <ntddk.h>

  typedef struct _SERVICE_DESCRIPTOR_TABLE

  {

  PULONG ServiceTableBase;

  PULONG ServiceCounterTableBase;

  ULONG NumberOfServices;

  PUCHAR ParamTableBase;

  }SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

  extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

  typedef NTSTATUS (*NTCREATEPROCESSEX)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES,

  HANDLE, ULONG, HANDLE, HANDLE, HANDLE, ULONG);

  // 保存 NtCreateProcessEx 函数的地址

  NTCREATEPROCESSEX ulNtCreateProcessEx = 0;

  // 在指针数组中 NtCreateProcessEx 的地址

  ULONG ulNtCreateProcessExAddr = 0;

  VOID UN_PROTECT()

  {

  __asm

  {

  push eax

  mov eax, CR0

  and eax, 0FFFEFFFFh

  mov CR0, eax

  pop eax

  }

  }

  VOID RE_PROTECT()

  {

  __asm

  {

  push eax

  mov eax, CR0

  or eax, 0FFFEFFFFh

  mov CR0, eax

  pop eax

  }

  }

  VOID DriverUnload(PDRIVER_OBJECT pDriverObject)

  {

  UN_PROTECT();

  // 替换 NtCreateProcessEx 的地址为 MyNtCreateProcessEx

  *(PULONG)ulNtCreateProcessExAddr = (ULONG)ulNtCreateProcessEx;

  RE_PROTECT();

  }

  NTSTATUS

  MyNtCreateProcessEx(

  __out PHANDLE ProcessHandle,

  __in ACCESS_MASK DesiredAccess,

  __in_opt POBJECT_ATTRIBUTES ObjectAttributes,

  __in HANDLE ParentProcess,

  __in ULONG Flags,

  __in_opt HANDLE SectionHandle,

  __in_opt HANDLE DebugPort,

  __in_opt HANDLE ExceptionPort,

  __in ULONG JobMemberLevel

  )

  {

  NTSTATUS Status = STATUS_SUCCESS;

  KdPrint((“Enter MyNtCreateProcessEx! \r\n”));

  Status = ulNtCreateProcessEx(ProcessHandle,

  DesiredAccess,

  ObjectAttributes,

  ParentProcess,

  Flags,

  SectionHandle,

  DebugPort,

  ExceptionPort,

  JobMemberLevel);

  return Status;

  }

  VOID HookCreateProcess()

  {

  ULONG ulSsdt = 0;

  // 保存 NtCreateProcess 的地址

  // 获取 SSDT

  ulSsdt = (ULONG)KeServiceDescriptorTable->ServiceTableBase;

  // 获取 NtCreateProcessEx 地址的指针

  ulNtCreateProcessExAddr = ulSsdt + 0x30 * 4;

  // 备份 NtCreateProcessEx 的原始地址

  ulNtCreateProcessEx = (NTCREATEPROCESSEX) *(PULONG)ulNtCreateProcessExAddr;

  UN_PROTECT();

  // 替换 NtCreateProcessEx 的地址为 MyNtCreateProcessEx

  *(PULONG)ulNtCreateProcessExAddr = (ULONG)MyNtCreateProcessEx;

  RE_PROTECT();

  }

  NTSTATUS DriverEntry(

  PDRIVER_OBJECT pDriverObject,

  PUNICODE_STRING pRegistryPath

  )

  {

  NTSTATUS Status = STATUS_SUCCESS;

  pDriverObject->DriverUnload = DriverUnload;

  HookCreateProcess();

  return Status;

  }

  DriverEntry()中调用了HookCreateProcess()函数,该函数的作用是将指针数组中NtCreateProcessEx()函数的地址替换为MyNtCreateProcessEx()函数的地址。而MyNtCreateProcessEx()函数是用来取代NtCreateProcessEx()函数的函数,在这里的函数中调用了一条KdPrint()用于输出代码。整个HOOK的过程非常简单,只要找到指针数组的位置,保存原地址后修改为新的地址即可。代码中出现了两个函数,分别是UN_PROTECT()和RE_PROTECT()。这两个函数的作用是禁止和开启CPU向标志为只读的内存页进行写入的操作。执行UN_PROTECT后, CPU可以向标志为只读的内存页进行写入操作。当写入完成后,调用RE_PROTECT()函数恢复到原来的状态。把它放到虚拟机中,打开DebugView,然后加载该驱动,加载成功后随便运行一个可执行程序。可以看到,DebugView中显示了在MyNtCreateProcessEx()中的输出,如图1所示,说明HOOK成功了。

  图1  MyNtCreateProcessEx()函数的输出




电子技术图片.png

本站内容除特别声明的原创文章之外,转载内容只为传递更多信息,并不代表本网站赞同其观点。转载的所有的文章、图片、音/视频文件等资料的版权归版权所有权人所有。本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如涉及作品内容、版权和其它问题,请及时通过电子邮件或电话通知我们,以便迅速采取适当措施,避免给双方造成不必要的经济损失。联系电话:010-82306116;邮箱:aet@chinaaet.com。
HOOKSSDT