系统调用篇之总结与提升,介绍 SSDT Hook 项目编写,隐藏 SSDT Hook 尝试和 GUI 线程的简单介绍。
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?系统调用篇学会了吗?练习做完了吗?没有的话就不要继续了。
🔒 华丽的分割线 🔒
篇章总结
由于本篇章十分简单,没学
APC
,完整的系统调用流程无法讲解。我们就用简单总结一下我们所学:我们首先直接调用我们的函数比如
OpenProcess
,它会经过层层参数校验,然后通过将服务号赋给
eax
,将堆栈赋给
esp
,通过
sysenter
或者中断门进入内核,经过层层处理,调用真正的内核函数,通过
APC
返回。既然学习了本篇,我们就做一个项目,实现进程只能通过自己关闭,不能通过他人调用关闭,也就是实现所谓的
SSDT Hook
。比如你打开一个记事本,只能通过点击关闭按钮或者菜单退出可以,而不能通过其他程序调用
TerminateProcess
关闭它。
项目代码分析
在分析项目源码之前,我们来看看效果:
如果没有写代码的话,就不要继续了。代码可以不和我一样,只要实现它的功能即可,本项目代码仅供参考。
我们的核心功能肯定需要在驱动上,单纯的3环是做不到的,实现
SSDT Hook
,还要修改它,为了代码的可读性和方便性,我们首先定义一下它的结构体和导入变量:
struct SSDT_ITEM
{
PULONG funcTable;
ULONG count;
ULONG limit;
PUCHAR paramTable;
};
extern struct SSDT_ITEM* KeServiceDescriptorTable;
我们阻止他人关闭要保护的程序,首先得知道它是怎样关闭程序的,如下是任务管理器关闭程序的关键函数的伪代码:
BOOL __thiscall CProcPage::KillProcess(CProcPage *this, DWORD a2, int a3)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
dwMessageId = 0;
v4 = FindProcInArrayByPID(*(this + 3), a2);
v5 = v4;
if ( !v4 || CProcPage::IsSystemProcess(this, a2, v4) )
return 0;
v7 = *(v5 + 35);
if ( v7 )
v8 = *(v7 + 8);
else
v8 = *(v5 + 2);
v9 = *(v5 + 72);
dwProcessId = v8;
if ( !a3 && CProcPage::QuickConfirm(this, 0x2717u, 0x2719u) != 6 )
return 0;
if ( v7 )
return VDMTerminateTaskWOW(dwProcessId, v9);
CPrivilegeEnable::CPrivilegeEnable(v13, L"SeDebugPrivilege");
v10 = OpenProcess(1u, 0, a2);
v11 = v10;
if ( v10 )
{
if ( TerminateProcess(v10, 1u) )
(*(*this + 24))(this);
else
dwMessageId = GetLastError();
CloseHandle(v11);
}
else
{
dwMessageId = GetLastError();
}
if ( dwMessageId )
{
DisplayFailureMsg(*(this + 1), 0x2721u, dwMessageId);
v12 = 0;
}
else
{
v12 = 1;
}
CPrivilegeEnable::~CPrivilegeEnable(v13);
return v12;
}
可以知道,任务管理器是通过
TerminateProcess
来关闭这个进程,如果失败会弹窗显示错误原因。要想
Hook
,就必须知道在
SSDT
的服务号,如下是跟踪流程,见如下分析,我们先定位到该函数:
; BOOL __stdcall TerminateProcess(HANDLE hProcess, UINT uExitCode)
public _TerminateProcess@8
_TerminateProcess@8 proc near ; CODE XREF: .text:7C839AB2↓j
; ConsoleIMERoutine(x)+102↓p
; DATA XREF: ...
hProcess = dword ptr 8
uExitCode = dword ptr 0Ch
mov edi, edi
push ebp
mov ebp, esp
cmp [ebp+hProcess], 0
jnz short loc_7C801E2E
push 6 ; dwErrCode
call _SetLastError@4 ; SetLastError(x)
jmp short loc_7C801E49
; ---------------------------------------------------------------------------
loc_7C801E2E: ; CODE XREF: TerminateProcess(x,x)+9↑j
push [ebp+uExitCode] ; ExitStatus
push [ebp+hProcess] ; ProcessHandle
call ds:__imp__NtTerminateProcess@8 ; NtTerminateProcess(x,x)
test eax, eax
jl short loc_7C801E43
xor eax, eax
inc eax
jmp short loc_7C801E4B
; ---------------------------------------------------------------------------
loc_7C801E43: ; CODE XREF: TerminateProcess(x,x)+22↑j
push eax ; Status
call _BaseSetLastNTError@4 ; BaseSetLastNTError(x)
loc_7C801E49: ; CODE XREF: TerminateProcess(x,x)+12↑j
xor eax, eax
loc_7C801E4B: ; CODE XREF: TerminateProcess(x,x)+27↑j
pop ebp
retn 8
_TerminateProcess@8 endp
上面的函数在
kernel32.dll
里面,可以看到它又调用了
NtTerminateProcess
,这个函数又在
ntdll.dll
中,如下所示:
; Exported entry 348. NtTerminateProcess
; Exported entry 1157. ZwTerminateProcess
; =============== S U B R O U T I N E =======================================
; __stdcall ZwTerminateProcess(x, x)
public _ZwTerminateProcess@8
_ZwTerminateProcess@8 proc near ; CODE XREF: LdrpGenericExceptionFilter(x,x)+9107↓p
; ___report_gsfailure+E1↓p ...
mov eax, 101h ; NtTerminateProcess
mov edx, 7FFE0300h
call dword ptr [edx]
retn 8
_ZwTerminateProcess@8 endp
最终,我们找到了它的服务号是
0x101
,剩下的我们就开始写代码了。
既然修改
SSDT
,我们就必须具有写权限,修改自身模块我们可以想改就改没啥问题,但是要修改其他模块的,就得小心行事了,因为我们不能确保那块内存我们具有写的权限。一种方式我们可以修改物理页属性,这个我就不赘述了,提供下面的代码仅供参考:
void SetReadWrite(UINT32 HookFunAddr, BOOLEAN writeable)
{
DWORD32 RCR4;
_asm
{
_emit 0x0F;
_emit 0x20;
_emit 0xE0; //mov eax,cr4
mov RCR4, eax;
}
if (RCR4 & 0x00000020)
{
// 2 - 9 - 9 - 12 分页
DbgPrint("2-9-9-12分页");
DWORD64* PDE = (DWORD64*)(0xC0600000 + ((HookFunAddr >> 18) & 0x3FF8));
if (writeable) *PDE |= 2; else *PDE &= ~2;
if (!(*PDE & 0x80)) //判断是否不为大页
{
DWORD64* PTE = (DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x7FFFF8));
if (writeable) *PDE |= 2; else *PDE &= ~2;
}
else
{
DbgPrint("大页");
}
}
else
{
// 10 - 10 - 12 分页
DbgPrint("10-10-12分页\n");
DWORD32* PDE = (DWORD32*)(0XC0300000 + (HookFunAddr >> 20) & 0xFFC);
if (writeable) *PDE |= 2; else *PDE &= ~2;
if (!(*PDE & 0x80)) //判断是否不为大页
{
DWORD32* PTE = (DWORD32*)(0xC0000000 + (HookFunAddr >> 10) & 0x3FFFFC);
if (writeable) *PDE |= 2; else *PDE &= ~2;
}
else
{
DbgPrint("大页");
}
}
}
本项目通过修改
CR0
的
WP
位实现绕过只读,如果忘却,请自行复习 保护模式篇——中断与异常和控制寄存器 中的控制寄存器的
CR0
,具体代码如下:
//恢复内存保护
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;
}
}
但是,上面的代码对于单核的没问题,对于操作系统多核的情况,还是存在隐患的。
下面我只需要写个实现
Hook
的和接管
Hook
的函数就行了,如下所示:
typedef NTSTATUS(__stdcall *NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);
NtTerminateProcess oldNtTerminateProcess = NULL;
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->funcTable[TerminateProcessIndex];
KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
PageProtectOn();
}
如果你使用修改物理页的方式代码的话,请不要这么写:
void HookTerminateProcess()
{
SetReadWrite(KeServiceDescriptorTable->funcTable, TRUE);
oldNtTerminateProcess = KeServiceDescriptorTable->funcTable[TerminateProcessIndex];
KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
SetReadWrite(KeServiceDescriptorTable->funcTable, FALSE);
}
如果你使用上面的代码,在
2-9-9-12
分页下,加载驱动后虚拟机将会死机,
Windbg
无法下断点。我仔细观察该物理页的属性,发现它的值如下为
0x4001e3
,根据
PAE
分页,该物理页可读可写,且为大页,只能特权用户访问,也就是说,这个地方本来就是可写的,你把它改为只读,就会出错。在
10-10-12
分页下未经测试,应该效果是一样的。
我们接下来看
HookNtTerminateProcess
函数,这个函数是用来实现
Hook
函数接管的,为什么我还要调用原来的
NtTerminateProcess
函数呢?是因为所有的3环程序都需要用到它,我必须调用它才行。
剩下的代码我就不再赘述了,如下是该项目的完整代码:
🔒 点击查看驱动代码 🔒
#include <ntifs.h>
#include <wdm.h>
#include <ntddk.h>
#define TerminateProcessIndex 0x101
UNICODE_STRING Devicename;
UNICODE_STRING SymbolLink;
#define DEVICE_NAME L"\\Device\\ProcessHook"
#define SYMBOL_LINK L"\\??\\ProcessHook"
//操作码:0x0-0x7FF 被保留,0x800-0xFFF 可用
#define Hook CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,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);
PEPROCESS protectedProcess = NULL;
struct SSDT_ITEM
{
PULONG funcTable;
ULONG count;
ULONG limit;
PUCHAR paramTable;
};
extern struct SSDT_ITEM* KeServiceDescriptorTable;
typedef NTSTATUS(__stdcall *NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);
NtTerminateProcess oldNtTerminateProcess = NULL;
//恢复内存保护
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->funcTable[TerminateProcessIndex];
KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
PageProtectOn();
}
PDEVICE_OBJECT dobj;
void UnloadDriver(PDRIVER_OBJECT DriverObject)
{
IoDeleteSymbolicLink(&SymbolLink);
IoDeleteDevice(dobj);
if (oldNtTerminateProcess!=NULL)
{
KeServiceDescriptorTable->funcTable[TerminateProcessIndex] = oldNtTerminateProcess;
}
DbgPrint("卸载成功!!!\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
RtlInitUnicodeString(&Devicename, DEVICE_NAME);
IoCreateDevice(DriverObject, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dobj);
RtlInitUnicodeString(&SymbolLink, SYMBOL_LINK);
IoCreateSymbolicLink(&SymbolLink, &Devicename);
DriverObject->Flags |= DO_BUFFERED_IO;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DEVICE_CREATE_Dispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DEVICE_CONTROL_Dispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DEVICE_CLOSE_Dispatch;
HookTerminateProcess();
DbgPrint("驱动加载完毕!!!\n");
return STATUS_SUCCESS;
}
NTSTATUS DEVICE_CONTROL_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION pIrqlStack;
ULONG uIoControlCode;
PVOID pIoBuffer;
ULONG uInLength;
ULONG uOutLength;
ULONG uRead;
//获取IRP教据
pIrqlStack = IoGetCurrentIrpStackLocation(pIrp);
//获取控制码
uIoControlCode = pIrqlStack->Parameters.DeviceIoControl.IoControlCode;
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
uInLength = pIrqlStack->Parameters.DeviceIoControl.InputBufferLength;
uOutLength = pIrqlStack->Parameters.DeviceIoControl.OutputBufferLength;
switch (uIoControlCode)
{
case Hook:
RtlMoveMemory(&uRead, pIoBuffer, 4);
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 DEVICE_CREATE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("Create Success!\n");
//设置返回状态,否则默认是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0; //返回给3环多少字节数据,没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DEVICE_CLOSE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("Close Success!\n");
//设置返回状态,否则默认是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0; //返回给3环多少字节数据,没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
🔒 点击查看应用代码 🔒
#include "stdafx.h"
#include <windows.h>
#include <winioctl.h>
#include <stdlib.h>
#define Hook CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define UnHook CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define SYMBOL_LINK_NAME L"\\\\.\\ProcessHook"
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 隐藏尝试
SSDT Hook
在现在是比较烂大街的技术,很多杀软是基于这个技术进行自保等技术,比如我们上面做的项目就是一个自保的实现。我们上面的项目很容易被
PCHunter
检测到:
既然有检测,肯定有对抗。根据系统调用的流程有一些对抗
ARK
SSDT Hook
隐藏思路,这里就提供一些思路,其他具体代码的实现需要自行完成。
据他人分析,
PCHunter
是通过重载内核文件使用
SSDT
表一个一个比对完成的,什么是内核重载将会在进程线程篇进行讲解,但其实不是,没那么简单,下面介绍的方法可以绕过内核重载检查
SSDT
。简单点说,内核重载就是把内核文件重新在内存展开,因为内核文件也遵守
PE
格式。我们从
IDA
看看
SSDT
表长啥样子,由于篇幅,只摘录一部分:
.data:0047BFA0 _KeServiceDescriptorTable dd 0 ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+11↓r
.data:0047BFA0 ; KeAddSystemServiceTable(x,x,x,x,x)+4D↓w ...
.data:0047BFA4 dword_47BFA4 dd 0 ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+53↓w
.data:0047BFA4 ; KeRemoveSystemServiceTable(x)+47↓w ...
.data:0047BFA8 dword_47BFA8 dd 0 ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+59↓w
.data:0047BFA8 ; KeRemoveSystemServiceTable(x)+4D↓w ...
.data:0047BFAC dword_47BFAC dd 0 ; DATA XREF: KeAddSystemServiceTable(x,x,x,x,x)+5F↓w
.data:0047BFAC ; KeRemoveSystemServiceTable(x)+53↓w ...
咱们逆向分析0环调用流程时,我们也做过思考题,如下所示:
APIService: ; CODE XREF: _KiBBTUnexpectedRange+18↑j
; _KiSystemService+6F↑j
mov edi, eax ; eax = 3环传来的服务号
shr edi, 8
and edi, 30h
mov ecx, edi ; 正好一个表的大小就是0x10,如果是GUI相关,就加;反之不加。
add edi, [esi+_KTHREAD.ServiceTable] ; edi = ServiceTable
mov ebx, eax ; eax = 3环传来的服务号
and eax, 0FFFh ; 去掉索引为12的位
cmp eax, [edi+8] ; 得到的结果与ServiceLimit比较
jnb _KiBBTUnexpectedRange ; 如果超出,说明越界,跳走
cmp ecx, 10h
jnz short loc_46660C ; 判断是否是调用 win32k.sys 的,不是的话跳走
mov ecx, ds:0FFDFF018h ; _DWORD
xor ebx, ebx
我们可以看到,咱们调用内核真实的函数地址是从当前线程取出的,也就是说,我们替换当前线程的
ServiceTable
,
PCHunter
就不会检测出来。
注意涉及
GUI
的就不是
KeServiceDescriptorTable
,而是
KeServiceDescriptorTableShadow
了。为了替换
ServiceTable
,我们首先申请一块内存,把
SSDTShadow
拷贝一份到里面,然后把该
Hook
的函数写入进去即可。我们测试一下(有些知识可能有点超前,可以自行学习玩进程线程篇后再来看不懂的代码):
#include <ntifs.h>
#include <wdm.h>
#include <ntddk.h>
#define TerminateProcessIndex 0x101
const int ThreadListAndSerivceTableSpace = 0x1b0 - 0xe0;
UNICODE_STRING Devicename;
UNICODE_STRING SymbolLink;
#define DEVICE_NAME L"\\Device\\ProcessHook"
#define SYMBOL_LINK L"\\??\\ProcessHook"
//操作码:0x0-0x7FF 被保留,0x800-0xFFF 可用
#define Hook CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,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);
PEPROCESS protectedProcess = NULL;
struct SSDT_ITEM
{
PULONG funcTable;
ULONG count;
ULONG limit;
PUCHAR paramTable;
};
extern struct SSDT_ITEM* KeServiceDescriptorTable;
typedef NTSTATUS(__stdcall* NtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);
NtTerminateProcess oldNtTerminateProcess = NULL;
struct SSDT_ITEM* mySSDT;
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()
{
mySSDT = ExAllocatePool(NonPagedPool, 0x40);
if (mySSDT == NULL)
{
DbgPrint("构建SSDT失败!");
return;
}
memset(mySSDT, 0, 0x40);
oldNtTerminateProcess = KeServiceDescriptorTable->funcTable[TerminateProcessIndex];
RtlCopyMemory(mySSDT, (INT)KeServiceDescriptorTable - 0x40, 0x40);
mySSDT->funcTable[TerminateProcessIndex] = HookNtTerminateProcess;
}
PDEVICE_OBJECT dobj;
void UnloadDriver(PDRIVER_OBJECT DriverObject)
{
IoDeleteSymbolicLink(&SymbolLink);
IoDeleteDevice(dobj);
if (mySSDT != NULL)
{
ExFreePool(mySSDT);
}
DbgPrint("卸载成功!!!\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = UnloadDriver;
RtlInitUnicodeString(&Devicename, DEVICE_NAME);
IoCreateDevice(DriverObject, 0, &Devicename, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dobj);
RtlInitUnicodeString(&SymbolLink, SYMBOL_LINK);
IoCreateSymbolicLink(&SymbolLink, &Devicename);
DriverObject->Flags |= DO_BUFFERED_IO;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DEVICE_CREATE_Dispatch;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DEVICE_CONTROL_Dispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DEVICE_CLOSE_Dispatch;
HookTerminateProcess();
DbgPrint("驱动加载完毕!!!\n");
return STATUS_SUCCESS;
}
NTSTATUS DEVICE_CONTROL_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION pIrqlStack;
ULONG uIoControlCode;
PVOID pIoBuffer;
ULONG uInLength;
ULONG uOutLength;
ULONG uRead;
//获取IRP教据
pIrqlStack = IoGetCurrentIrpStackLocation(pIrp);
//获取控制码
uIoControlCode = pIrqlStack->Parameters.DeviceIoControl.IoControlCode;
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
uInLength = pIrqlStack->Parameters.DeviceIoControl.InputBufferLength;
uOutLength = pIrqlStack->Parameters.DeviceIoControl.OutputBufferLength;
switch (uIoControlCode)
{
case Hook:
RtlMoveMemory(&uRead, pIoBuffer, 4);
if (PsLookupProcessByProcessId((HANDLE)uRead, &protectedProcess) == STATUS_SUCCESS)
{
DbgPrint("得到消息,目前 EPROCESS 地址为:0x%p", protectedProcess);
LIST_ENTRY* le = (INT)protectedProcess + 0x50;
LIST_ENTRY* pe = le;
while (1)
{
pe = le->Blink;
if (pe == le)
{
break;
}
*(UINT32*)((UINT32)pe - ThreadListAndSerivceTableSpace) = mySSDT;
}
DbgPrint("处理完毕!");
}
else
{
status = STATUS_INVALID_HANDLE;
}
break;
case UnHook:
protectedProcess = NULL;
LIST_ENTRY* le = (INT)protectedProcess + 0x50;
LIST_ENTRY* pe = le;
while (1)
{
pe = le->Blink;
if (pe == le)
{
break;
}
*(UINT32*)((UINT32)pe - ThreadListAndSerivceTableSpace) = KeServiceDescriptorTable;
}
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 DEVICE_CREATE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("Create Success!\n");
//设置返回状态,否则默认是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0; //返回给3环多少字节数据,没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DEVICE_CLOSE_Dispatch(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("Close Success!\n");
//设置返回状态,否则默认是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0; //返回给3环多少字节数据,没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
应用层代码同本篇小结项目。
PCHunter
还是能够检测到该种
SSDT Hook
。卸载该驱动模块后,
PCHunter
检测此方法会有后遗症,如下图所示:
其实我早已把我构造的假
SSDT
撤掉了。
GUI 线程转化
普通非
GUI
线程转化为
GUI
线程的时候,会调用
PsConvertToGuiThread
函数实现,为了方便理解,直接看其伪代码:
int __stdcall PsConvertToGuiThread()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v0 = KeGetCurrentThread();
v1 = v0;
if ( !v0->PreviousMode )
return 0xC000000D;
if ( !PspW32ProcessCallout )
return 0xC0000022;
if ( v0->ServiceTable != &KeServiceDescriptorTable )
return 0x4000001B;
v7 = v0->ApcState.Process;
if ( !v0->LargeStack )
{
v3 = MmCreateKernelStack(1, v0->InitialNode);
if ( !v3 )
{
ms_exc.registration.TryLevel = 0;
KeGetPcr()->NtTib.Self[1].Self = 8;
ms_exc.registration.TryLevel = -1;
return 0xC0000017;
}
NewIrql = KfRaiseIrql(1u);
v4 = KeSwitchKernelStack(v3, v3 - 12288);
KfLowerIrql(NewIrql);
MmDeleteKernelStack(v4, 0);
}
if ( PPerfGlobalGroupMask && (*(PPerfGlobalGroupMask + 4) & 1) != 0 )
{
v5[6] = *&v1[1].DebugActive;
v5[7] = *&v1[1].Iopl;
v5[0] = v1->StackBase;
v5[1] = v1->StackLimit;
v5[2] = 0;
v5[3] = 0;
v5[4] = 0;
v5[5] = 0;
v6 = -1;
PerfInfoLogBytes(1315, v5, 36);
}
result = PspW32ProcessCallout(v7, 1);
if ( result >= 0 )
{
v1->ServiceTable = &KeServiceDescriptorTableShadow;
result = PspW32ThreadCallout(v1, 0);
if ( result < 0 )
v1->ServiceTable = &KeServiceDescriptorTable;
}
return result;
}
大概流程就是创建更大的线程堆栈,换掉并删除原来的栈,经历过一定流程的时候,再换
ServiceTable
,而这个特征就是判断是否为
GUI
线程的最明显的特征。
下一篇
羽夏看Win系统内核——进程线程篇
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15831506.html