这篇文章主要讲述HookNtOpenProcess函数阻止暴力枚举进程以及学习过程中遇到的相关问题以及解决方法
大家遇到问题也可以直接在下面评论,咱们可以一起探索一下
文章一部分参考自博客http://www.cnblogs.com/yifi/p/4898406.html
为了阻止暴力枚举进程,前辈们对OpenProcess函数的底层调用过程分析中发现可以对SSDT表进行Hook,从而达到阻止暴力枚举进程的目的,现在我们学习一下前辈们的经验,同时,我们可以将这个方法用到其他代码中。
一、OpenProcess调用的主要过程:
①从自己的exe模块的导入表中取值
②Ntdll.dll模块的导出表中取得ZwOpenProcess函数(获取索引,进入Ring0层)
此处要注意一下,Ntdll.dll导出表中的ZwOpenProcess和NtOpenProcess两个函数其实是一个函数
/*
X86
0:003> u zwOpenProcess
ntdll!ZwOpenProcess:
77755dc8 b8be000000 mov eax,0BEh
77755dcd ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
77755dd2 ff12 call dword ptr [edx]
77755dd4 c21000 ret 10h
77755dd7 90 nop
0:003> u ntOpenProcess
ntdll!ZwOpenProcess:
77755dc8 b8be000000 mov eax,0BEh
77755dcd ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
77755dd2 ff12 call dword ptr [edx]
77755dd4 c21000 ret 10h
77755dd7 90 nop
*/
/*
x64
0:002> u ZwOpenProcess
ntdll!ZwOpenProcess:
00000000`76f81510 4c8bd1 mov r10,rcx
00000000`76f81513 b823000000 mov eax,23h
00000000`76f81518 0f05 syscall
00000000`76f8151a c3 ret
00000000`76f8151b 0f1f440000 nop dword ptr [rax+rax]
0:002> u NtOpenProcess
ntdll!ZwOpenProcess:
00000000`76f81510 4c8bd1 mov r10,rcx
00000000`76f81513 b823000000 mov eax,23h
00000000`76f81518 0f05 syscall
00000000`76f8151a c3 ret
00000000`76f8151b 0f1f440000 nop dword ptr [rax+rax]
*/
③进入Ring0层从Ntoskernel.exe模块的导出表,执行ZwOpenProcess(取索引,获得SSDT服务表索引)
/*
X86
kd> u nt!ZwOpenProcess
nt!ZwOpenProcess:
83e47cd8 b8be000000 mov eax,0BEh
83e47cdd 8d542404 lea edx,[esp+4]
83e47ce1 9c pushfd
83e47ce2 6a08 push 8
83e47ce4 e8d5190000 call nt!KiSystemService (83e496be)
83e47ce9 c21000 ret 10h
x64
kd> u ZwOpenProcess
nt!ZwOpenProcess:
fffff800`03e6e620 488bc4 mov rax,rsp
fffff800`03e6e623 fa cli
fffff800`03e6e624 4883ec10 sub rsp,10h
fffff800`03e6e628 50 push rax
fffff800`03e6e629 9c pushfq
fffff800`03e6e62a 6a10 push 10h
fffff800`03e6e62c 488d05dd2d0000 lea rax,[nt!KiServiceLinkage (fffff800`03e71410)]
fffff800`03e6e633 50 push rax
*/
④通过索引取得NtOpenProcess的地址(x86),或者通过索引取得NtOpenProcess的相对偏移(x64)
/*
x86
kd> u nt!NtOpenProcess
nt!NtOpenProcess:
8401eba1 8bff mov edi,edi
8401eba3 55 push ebp
8401eba4 8bec mov ebp,esp
8401eba6 51 push ecx
8401eba7 51 push ecx
8401eba8 64a124010000 mov eax,dword ptr fs:[00000124h]
8401ebae 8a803a010000 mov al,byte ptr [eax+13Ah]
8401ebb4 8b4d14 mov ecx,dword ptr [ebp+14h]
.......
x64
kd> u ntopenprocess
nt!NtOpenProcess:
fffff800`041473d0 4883ec38 sub rsp,38h
fffff800`041473d4 65488b042588010000 mov rax,qword ptr gs:[188h]
fffff800`041473dd 448a90f6010000 mov r10b,byte ptr [rax+1F6h]
fffff800`041473e4 4488542428 mov byte ptr [rsp+28h],r10b
fffff800`041473e9 4488542420 mov byte ptr [rsp+20h],r10b
fffff800`041473ee e84dfcffff call nt!PsOpenProcess (fffff800`04147040)
fffff800`041473f3 4883c438 add rsp,38h
fffff800`041473f7 c3 ret
*./
⑤执行NtIpenProcess
从上面的通过dbg看的汇编来看,x86情况下,NtOpenProcess的索引是0xbe,x64下,NtOpenProcess的索引是0x23,我们可以把该索引处的地址换成我们自己写的函数即可实现Hook。
二、关键步骤解释及代码片段
1、SSDT的获取
SSDT表的结构:
typedef struct _SYSTEM_SERVICE_DESCRIPOR_TABLE_X86_
{
PVOID ServiceTableBase; // 函数指针数组的基地址
PVOID UnKnow0;
ULONG32 NumberOfServices; // 函数的数量 0x191
PVOID Unknow1;
}SYSTEM_SERVICE_DESCRIPOR_TABLE_X86, *PSYSTEM_SERVICE_DESCRIPOR_TABLE_X86;
typedef struct _SYSTEM_SERVICE_DESCRIPTOR_TABLE_X64_
{
PVOID ServiceTableBase; // 函数指针数组的基地址
PVOID UnKnown0;
ULONG64 NumberOfServices; // 函数的数量
PVOID UnKnow1;
}SYSTEM_SERVICE_DESCRIPOR_TABLE_X64,*PSYSTEM_SERVICE_DESCRIPOR_TABLE_X64;
①Win7_x86
/*
kd> dd KeServiceDescriptorTable
83f77b00 83e8c43c 00000000 00000191 83e8ca84
*/
x86下我们可以直接从ntos模块的导出表中获得
BOOLEAN GetSSDTAddressInWin7_X86(ULONG32* SSDTAddress)
{
//从NtosKernel.exe 模块中的导出表获得该导出变量 KeServiceDescriptorTable
*SSDTAddress = 0;
*SSDTAddress = (ULONG32)GetExportVariableAddressFromNtosExportTableByVariableName(L"KeServiceDescriptorTable");
if (*SSDTAddress != 0)
{
return TRUE;
}
return FALSE;
}
PVOID GetExportVariableAddressFromNtosExportTableByVariableName(WCHAR * VariableName)
{
PVOID VariableAddress = NULL;
UNICODE_STRING v1 = { 0 };
if (VariableName&& wcslen(VariableName) > 0)
{
RtlInitUnicodeString(&v1, VariableName);
VariableAddress = MmGetSystemRoutineAddress(&v1);
}
return VariableAddress;
}
②Win_x64
x64下SSDT没有导出,但是我们可以通过特殊模块寄存器–sr得到。Msr寄存器可以控制CPU工作环境等信息。我们通过读取 C0000082 寄存器,能够得到 KiSystemCall64 的地址,然后从KiSystemCall64 的地址开始,往下搜索 0x500 字节左右(特征码是 4c8d15) ,就能得到KeServiceDescriptorTable 的地址了。
BOOLEAN GetSSDTAddressInWin7_X64(ULONG64* SSDTAddress)
{
/*
kd> rdmsr c0000082
msr[c0000082] = fffff800`03e85bc0
kd> u fffff800`03e85bc0 l 50
nt!KiSystemCall64:
......
nt!KiSystemServiceRepeat:
fffff800`03e85cf2 4c8d15478c2300 lea r10,[nt!KeServiceDescriptorTable (fffff800`040be940)]
fffff800`03e85cf9 4c8d1d808c2300 lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`040be980)]
*/
PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082);
PUCHAR EndSearchAddress = StartSearchAddress + PAGE_SIZE;
PUCHAR i = NULL;
UCHAR v1 = 0, v2 = 0, v3 = 0;
INT64 Offset = 0;
*SSDTAddress = 0;
for (i = StartSearchAddress; i < EndSearchAddress; i++)
{
if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
{
v1 = *i;
v2 = *(i + 1);
v3 = *(i + 2);
if (v1 == 0x4c && v2 == 0x8d && v3 == 0x15) // 验证特征码
{
memcpy(&Offset, i + 3, 4);
*SSDTAddress = (ULONG64)i + Offset + 7;
break;
}
}
}
if (*SSDTAddress == 0)
{
return FALSE;
}
return TRUE;
}
2、SSDT的获取
Win7_x86和Win7_x64的获取方法是一致的,都是从Ntdll的导出表中获得,只是细节稍有不同。
BOOLEAN GetSSDTFunctionIndexFromNtdllExportTableByFunctionNameInWIN7_X86(CHAR * FunctionName, ULONG * SSDTFunctionIndex)
{
BOOLEAN IsOk = FALSE;
PVOID MappingBaseAddress = NULL;
SIZE_T MappingViewSize = 0;
WCHAR FileFullPath[] = L"\\SystemRoot\\System32\\ntdll.dll";
ULONG32* AddressOfFunctions = NULL;
ULONG32* AddressOfNames = NULL;
UINT16* AddressOfNameOrdinals = NULL;
ULONG32 FunctionOrdinal = 0;
PVOID FunctionAddress = NULL;
ULONG32 i = 0;
CHAR* TravelFunctionName = NULL;
PIMAGE_NT_HEADERS NtHeader = NULL;
PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
#ifdef _WIN64
ULONG32 OffsetOfIndexInFunc = 4;
#else
ULONG32 OffsetOfIndexInFunc = 1;
#endif
IsOk = MappingPEFileInRing0Space(FileFullPath, &MappingBaseAddress, &MappingViewSize);
if (IsOk == FALSE)
{
return FALSE;
}
else
{
__try
{
NtHeader = RtlImageNtHeader(MappingBaseAddress);// 通过PE文件基地址获得NtHeader
if (NtHeader&&NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
// 导出表地址
ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((UINT8*)MappingBaseAddress +
NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
AddressOfFunctions = (ULONG32*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfFunctions);
AddressOfNames = (ULONG32*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfNames);
AddressOfNameOrdinals = (UINT16*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfNameOrdinals);
for (i = 0; i < ImageExportDirectory->NumberOfNames; i++)
{
TravelFunctionName = (CHAR*)((ULONG32)MappingBaseAddress + AddressOfNames[i]);
if (_stricmp(FunctionName, TravelFunctionName) == 0)
{
FunctionOrdinal = AddressOfNameOrdinals[i];
FunctionAddress = (PVOID)((UINT8*)MappingBaseAddress + AddressOfFunctions[FunctionOrdinal]);
/*
WIN7_x64
0:003> u zwopenprocess
ntdll!ZwOpenProcess:
00000000`76fe1510 4c8bd1 mov r10,rcx
00000000`76fe1513 b823000000 mov eax,23h
00000000`76fe1518 0f05 syscall
00000000`76fe151a c3 ret
00000000`76fe151b 0f1f440000 nop dword ptr [rax+rax]
WIN7_x86
0:003> u ntopenprocess
ntdll!ZwOpenProcess:
77c65dc8 b8be000000 mov eax, 0BEh
77c65dcd ba0003fe7f mov edx, offset SharedUserData!SystemCallStub(7ffe0300)
77c65dd2 ff12 call dword ptr[edx]
77c65dd4 c21000 ret 10h
77c65dd7 90 nop
*/
// 从函数地址偏移4个或1个字节得到此函数在SSDT表的索引
*SSDTFunctionIndex = *(ULONG32*)((UINT8*)FunctionAddress + OffsetOfIndexInFunc);
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
}
ZwUnmapViewOfSection(NtCurrentProcess(), MappingBaseAddress);
if (*SSDTFunctionIndex == -1)
{
return FALSE;
}
return TRUE;
}
//下面这一段摘自博客http://www.cnblogs.com/yifi/p/4898406.html
3.保存原先函数的地址
64位与32位不同 64为中存放的并不是SSDT函数的完整地址而是其相对于ServiceTableBase[Index]>>4 的数据(称它为偏移地址)
4.在32位中换掉Base中索引里的数据 NtOpenProcess Base[索引] == FakeNtOpenProcess –>HookSSDT,在32位中 直接替换就可以了但是………………….
/* 一系列的说明 作者:胡文亮 */
HOOK SSDT 就很简单了,首先获得待 HOOK 函数的序号
Index,然后通过公式把自己的代理函数的地址转化为偏移地址,然后把偏移地址的数据填入 ServiceTableBase[Index]。也许有些读者看到这里,已经觉得胜利在望了,我当时也是如此。但实际上我在这里栽了个大跟头,整整郁闷了很长时间!因为我低估了设计这套算法的工程师的智商,我没有考虑一个问题,为什么 WIN64 的 SSDT 表存放地址的形式这么奇怪?只存放偏移地址,而不存放完整地址?难道是为了节省内存?这肯定是不可能的,要知道现在内存白菜价。那么不是为了节省内存,唯一的可能性就是要给试图挂钩 SSDT 的人制造麻烦!要知道,WIN64 内核里每个驱动都 不在同一个 B 4GB 里,而 4 字节的整数只能表示 4GB的范围!所以无论你怎么修改这个值,都跳不出 ntoskrnl 的手掌心。如果你想通过修改这个值来跳转到你的代理函数, 那是绝对不可能的。 因为你的驱动的地址不 可能跟 l ntoskrnl 在同一个 B 4GB 里。然而,这位工程师也低估了我们中国人的智商, 在中国有两句成语, 这位工程师一定没听过, 叫 “明修栈道, 暗渡陈仓”以及“上有政策,下有对策” 。虽然不能直接用 4 字节来表示自己的代理函数所在的地址, 但是还是可以修改这个值的。 要知道, ntoskrnl 虽然有很多地方的代码通常是不会被执行的, 比如 KeBugCheckEx。 所以我的办法是: 修改这个偏移地址的值,使之跳转到 KeBugCheckEx ,然后在 x KeBugCheckEx 的头部写一个 2 12 字节的 mov - - jmp , 这是一个可以跨越 4GB B ! 的跳转, 跳到我们的函数里!
①Win7_x86
VOID HookSSDTWIN7_X86(PULONG32 ServiceTableBase,ULONG32 FunctionIndexInSSDT,ULONG32 FakeOpenProcess)
{
WPOFF();
ServiceTableBase[FunctionIndexInSSDT] = FakeOpenProcess;
WPON();
}
②Win7_x64
VOID HookSSDTWIN7_X64(PVOID ServerTableBase,ULONG32 SSDTFunctionIndex,PVOID FakeFunctionAddress,
PVOID OldFunctionAddress,ULONG32 ParameterCount, UCHAR* OldFunctionCode, ULONG32 PatchCodeLength)
{
ULONG32 KeBugOffsetInSSDT = 0;
WPOFF();
InLineHook(FakeFunctionAddress, OldFunctionAddress, OldFunctionCode, PatchCodeLength);
WPON();
KeBugOffsetInSSDT = CalcFunctionOffsetInSSDTInWIN7_X64(ServerTableBase,OldFunctionAddress, ParameterCount);
WPOFF();
((PULONG32)ServerTableBase)[SSDTFunctionIndex] = (ULONG32)KeBugOffsetInSSDT;
WPON();
__IsHook = TRUE;
}
三、完整代码
.h文件
#pragma once
#include
#include
typedef struct _SYSTEM_SERVICE_DESCRIPOR_TABLE_X86_
{
PVOID ServiceTableBase; // 函数指针数组的基地址
PVOID UnKnow0;
ULONG32 NumberOfServices; // 函数的数量 0x191
PVOID Unknow1;
}SYSTEM_SERVICE_DESCRIPOR_TABLE_X86, *PSYSTEM_SERVICE_DESCRIPOR_TABLE_X86;
/*
kd> dd KeServiceDescriptorTable
83f77b00 83e8c43c 00000000 00000191 83e8ca84
*/
typedef struct _SYSTEM_SERVICE_DESCRIPTOR_TABLE_X64_
{
PVOID ServiceTableBase; // 函数指针数组的基地址
PVOID UnKnown0;
ULONG64 NumberOfServices; // 函数的数量
PVOID UnKnow1;
}SYSTEM_SERVICE_DESCRIPOR_TABLE_X64,*PSYSTEM_SERVICE_DESCRIPOR_TABLE_X64;
/*
kd> dd keServiceDescriptorTable
fffff800`040c9940 03e92800 fffff800 00000000 00000000
fffff800`040c9950 00000191 00000000 03e9348c fffff800
*/
typedef NTSTATUS (*pfnNtOpenProcess)
( PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
);
NTSTATUS FakeOpenProcess
(PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
);
extern
char* PsGetProcessImageFileName(PEPROCESS EProcess);
extern
PIMAGE_NT_HEADERS
NTAPI
RtlImageNtHeader(PVOID BaseAddress);
// 公共函数
BOOLEAN GetSSDTFunctionIndexFromNtdllExportTableByFunctionNameInWIN7(CHAR* FunctionName, ULONG* FunctionIndex);
BOOLEAN MappingPEFileInRing0Space(PWCHAR FileFullPath, PVOID* MappingBaseAddress, PSIZE_T MappingViewSize);
VOID DriverUnload(PDRIVER_OBJECT DriverObject);
VOID POFF();
VOID WPON();
// Win7_x86
VOID HookSSDTWIN7_X86(PULONG32 ServiceTableBase, ULONG32 FunctionIndexInSSDT, ULONG32 FakeOpenProcess);
VOID UnHookSSDTWIN7_X86(PULONG32 ServiceTableBase, ULONG32 FunctionIndex, ULONG32 OldNtOpenProcess);
PVOID GetExportVariableAddressFromNtosExportTableByVariableName(WCHAR* VariableName);
BOOLEAN GetSSDTAddressInWin7_X86(ULONG32* SSDTAddress);
// Win7_x64
VOID InLineHook(ULONG64 FakeFunctionAddress, ULONG64 OldFunctionAddress, UCHAR* OldFunctionCode, ULONG32 PatchCodeLength);
VOID HookSSDTWIN7_X64(PVOID ServerTableBase, ULONG32 SSDTFunctionIndex, PVOID FakeFunctionAddress,
PVOID OldFunctionAddress, ULONG32 ParameterCount, UCHAR* OldFunctionCode, ULONG32 PatchCodeLength);
VOID UnHookSSDTWIN7_X64(PVOID ServiceTableBase, ULONG32 SSDTFunctionIndex, ULONG32 OldNtOpenProcessAddress,
PVOID OldFunctionAddress, UCHAR* OldFunctionCode, ULONG32 PatchCodeLength);
VOID UnInLineHook(PVOID OldFunctionAddress, UCHAR* OldFunctionCode, ULONG32 PatchCodeLength);
BOOLEAN GetSSDTAddressInWin7_X64(ULONG64* SSDTAddress);
ULONG32 CalcFunctionOffsetInSSDTInWIN7_X64(PVOID ServerTableBase, PVOID OldFunctionAddress, ULONG32 ParameterCount);
.c文件
#include "HookSSDTx86.h"
#define SEC_IMAGE 0x001000000
PVOID __OldNtOpenProcessAddress = NULL; // NtOpenProcess的绝对地址
ULONG32 __OldNtOpenProcessOffset = 0; // x64下在0x23处的数据(右移4位即得到NtOpenProcess的偏移地址)
PVOID __ServiceTableBase = NULL;
ULONG32 __FunctionIndexInSSDT = 0; // NtOpenProcess的索引
UCHAR __OldFunctionCode[15] = { 0 };
BOOLEAN __IsHook = FALSE;
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
DriverObject->DriverUnload = DriverUnload;
#ifdef _WIN64
CHAR FunctionName[] = "NtOpenProcess";
ULONG64 SSDTAddress = 0;
ULONG32 v1 = 0;
if (GetSSDTAddressInWin7_X64(&SSDTAddress) == FALSE)
{
return Status;
}
DbgPrint("Win7_X64 SSDTAddress is %p", SSDTAddress);
if (GetSSDTFunctionIndexFromNtdllExportTableByFunctionNameInWIN7(FunctionName, &__FunctionIndexInSSDT) == FALSE)
{
return Status;
}
DbgPrint("FunctionIndex is %p\r\n", __FunctionIndexInSSDT);
// 获取原先的OpenProcess的地址
__ServiceTableBase = ((PSYSTEM_SERVICE_DESCRIPOR_TABLE_X86)SSDTAddress)->ServiceTableBase;
__OldNtOpenProcessOffset = ((PULONG32)__ServiceTableBase)[__FunctionIndexInSSDT];
DbgPrint("__OldNtOpenProcessOffset is %p\r\n", __OldNtOpenProcessOffset);
// 右移4位,得到偏移地址v1
v1 = __OldNtOpenProcessOffset >> 4;
__OldNtOpenProcessAddress = (PVOID)((ULONG64)__ServiceTableBase + v1); // 获取绝对地址
DbgPrint("__OldNtOpenProcessAddress is %p\r\n", __OldNtOpenProcessAddress);
HookSSDTWIN7_X64(__ServiceTableBase, __FunctionIndexInSSDT,FakeOpenProcess,KeBugCheckEx,5,__OldFunctionCode,15);
#else
ULONG32 SSDTAddress = 0;
CHAR FunctionName[] = "NtOpenProcess";
if (GetSSDTAddressInWin7_X86(&SSDTAddress) == FALSE)
{
return Status;
}
DbgPrint("Win7_X86 SSDTAddress is %p\r\n", SSDTAddress);
if (GetSSDTFunctionIndexFromNtdllExportTableByFunctionNameInWIN7(FunctionName, &__FunctionIndexInSSDT) == FALSE)
{
return Status;
}
DbgPrint("FunctionIndex is %p\r\n", __FunctionIndexInSSDT);
// 获取原先的OpenProcess的地址
__ServiceTableBase = (PULONG32)((PSYSTEM_SERVICE_DESCRIPOR_TABLE_X86)SSDTAddress)->ServiceTableBase;
__OldNtOpenProcessAddress = ((PULONG32)__ServiceTableBase)[__FunctionIndexInSSDT];
HookSSDTWIN7_X86(__ServiceTableBase, __FunctionIndexInSSDT, FakeOpenProcess);
#endif
return STATUS_SUCCESS;
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
#ifdef _WIN64
if (__IsHook)
{
UnHookSSDTWIN7_X64(__ServiceTableBase, __FunctionIndexInSSDT, __OldNtOpenProcessOffset,KeBugCheckEx,__OldFunctionCode,15);
__IsHook = FALSE;
}
#else
UnHookSSDTWIN7_X86(__ServiceTableBase, __FunctionIndexInSSDT, __OldNtOpenProcessAddress);
#endif
DbgPrint("ByeDriver\r\n");
}
VOID WPOFF()
{
#if (defined(_M_AMD64) || defined(_M_IA64)) && !defined(_REALLY_GET_CALLERS_CALLER_)
_disable();
__writecr0(__readcr0() & (~(0x10000))); // 将cr0寄存器的第17位置0
#else
__asm
{
CLI; // 禁止所有中断发生
MOV EAX, CR0;
AND EAX, NOT 10000H;
MOV CR0, EAX;
}
#endif
}
VOID WPON()
{
#if (defined(_M_AMD64) || defined(_M_IA64)) && !defined(_REALLY_GET_CALLERS_CALLER_)
__writecr0(__readcr0() ^ 0x10000);// 将cr0寄存器的第17位置1
_enable();
#else
__asm
{
MOV EAX, CR0;
OR EAX, 10000H;
MOV CR0, EAX;
STI;
}
#endif
}
BOOLEAN GetSSDTFunctionIndexFromNtdllExportTableByFunctionNameInWIN7(CHAR * FunctionName, ULONG * SSDTFunctionIndex)
{
BOOLEAN IsOk = FALSE;
PVOID MappingBaseAddress = NULL;
SIZE_T MappingViewSize = 0;
WCHAR FileFullPath[] = L"\\SystemRoot\\System32\\ntdll.dll";
ULONG32* AddressOfFunctions = NULL;
ULONG32* AddressOfNames = NULL;
UINT16* AddressOfNameOrdinals = NULL;
ULONG32 FunctionOrdinal = 0;
PVOID FunctionAddress = NULL;
ULONG32 i = 0;
CHAR* TravelFunctionName = NULL;
PIMAGE_NT_HEADERS NtHeader = NULL;
PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
#ifdef _WIN64
ULONG32 OffsetOfIndexInFunc = 4;
#else
ULONG32 OffsetOfIndexInFunc = 1;
#endif
IsOk = MappingPEFileInRing0Space(FileFullPath, &MappingBaseAddress, &MappingViewSize);
if (IsOk == FALSE)
{
return FALSE;
}
else
{
__try
{
NtHeader = RtlImageNtHeader(MappingBaseAddress);// 通过PE文件基地址获得NtHeader
if (NtHeader&&NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
// 导出表地址
ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((UINT8*)MappingBaseAddress +
NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
AddressOfFunctions = (ULONG32*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfFunctions);
AddressOfNames = (ULONG32*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfNames);
AddressOfNameOrdinals = (UINT16*)((UINT8*)MappingBaseAddress + ImageExportDirectory->AddressOfNameOrdinals);
for (i = 0; i < ImageExportDirectory->NumberOfNames; i++)
{
TravelFunctionName = (CHAR*)((ULONG32)MappingBaseAddress + AddressOfNames[i]);
if (_stricmp(FunctionName, TravelFunctionName) == 0)
{
FunctionOrdinal = AddressOfNameOrdinals[i];
FunctionAddress = (PVOID)((UINT8*)MappingBaseAddress + AddressOfFunctions[FunctionOrdinal]);
/*
WIN7_x64
0:003> u zwopenprocess
ntdll!ZwOpenProcess:
00000000`76fe1510 4c8bd1 mov r10,rcx
00000000`76fe1513 b823000000 mov eax,23h
00000000`76fe1518 0f05 syscall
00000000`76fe151a c3 ret
00000000`76fe151b 0f1f440000 nop dword ptr [rax+rax]
WIN7_x86
0:003> u ntopenprocess
ntdll!ZwOpenProcess:
77c65dc8 b8be000000 mov eax, 0BEh
77c65dcd ba0003fe7f mov edx, offset SharedUserData!SystemCallStub(7ffe0300)
77c65dd2 ff12 call dword ptr[edx]
77c65dd4 c21000 ret 10h
77c65dd7 90 nop
*/
// 从函数地址偏移4个或1个字节得到此函数在SSDT表的索引
*SSDTFunctionIndex = *(ULONG32*)((UINT8*)FunctionAddress + OffsetOfIndexInFunc);
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
}
ZwUnmapViewOfSection(NtCurrentProcess(), MappingBaseAddress);
if (*SSDTFunctionIndex == -1)
{
return FALSE;
}
return TRUE;
}
BOOLEAN MappingPEFileInRing0Space(PWCHAR FileFullPath, PVOID* MappingBaseAddress, PSIZE_T MappingViewSize)
{
NTSTATUS Status;
UNICODE_STRING v1;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE FileHandle = NULL;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE SectionHandle = NULL;
if (FileFullPath == NULL&&MmIsAddressValid(FileFullPath))
{
return FALSE;
}
if (MappingBaseAddress == NULL&&MmIsAddressValid(MappingBaseAddress))
{
return FALSE;
}
RtlInitUnicodeString(&v1, FileFullPath);
InitializeObjectAttributes(&ObjectAttributes,
&v1,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,// 大小写不敏感,只能在内核模式下打开句柄
NULL,
NULL);
Status = IoCreateFile(&FileHandle,
GENERIC_READ | SYNCHRONIZE, // 一般读操作和等待对象操作
&ObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT, // 同步
NULL,
0,
CreateFileTypeNone,
NULL,
IO_NO_PARAMETER_CHECKING
);
if (!NT_SUCCESS(Status))
{
return FALSE;
}
ObjectAttributes.ObjectName = NULL;
Status = ZwCreateSection(&SectionHandle,
SECTION_QUERY | SECTION_MAP_READ,
&ObjectAttributes,
NULL,
PAGE_WRITECOPY,
SEC_IMAGE, // Specifies that the file that FileHandle specifies is an executable image file.
FileHandle);
if (!NT_SUCCESS(Status))
{
return FALSE;
}
ZwClose(FileHandle);
Status = ZwMapViewOfSection(SectionHandle,
NtCurrentProcess(),
MappingBaseAddress,
0,
0,
0,
MappingViewSize,
ViewUnmap,
0,
PAGE_WRITECOPY);
ZwClose(SectionHandle);
if (!NT_SUCCESS(Status))
{
return FALSE;
}
return TRUE;
}
BOOLEAN GetSSDTAddressInWin7_X86(ULONG32* SSDTAddress)
{
//从NtosKernel.exe 模块中的导出表获得该导出变量 KeServiceDescriptorTable
*SSDTAddress = 0;
*SSDTAddress = (ULONG32)GetExportVariableAddressFromNtosExportTableByVariableName(L"KeServiceDescriptorTable");
if (*SSDTAddress != 0)
{
return TRUE;
}
return FALSE;
}
BOOLEAN GetSSDTAddressInWin7_X64(ULONG64* SSDTAddress)
{
/*
kd> rdmsr c0000082
msr[c0000082] = fffff800`03e85bc0
kd> u fffff800`03e85bc0 l 50
nt!KiSystemCall64:
......
nt!KiSystemServiceRepeat:
fffff800`03e85cf2 4c8d15478c2300 lea r10,[nt!KeServiceDescriptorTable (fffff800`040be940)]
fffff800`03e85cf9 4c8d1d808c2300 lea r11,[nt!KeServiceDescriptorTableShadow (fffff800`040be980)]
*/
PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082);
PUCHAR EndSearchAddress = StartSearchAddress + PAGE_SIZE;
PUCHAR i = NULL;
UCHAR v1 = 0, v2 = 0, v3 = 0;
INT64 Offset = 0;
*SSDTAddress = 0;
for (i = StartSearchAddress; i < EndSearchAddress; i++)
{
if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
{
v1 = *i;
v2 = *(i + 1);
v3 = *(i + 2);
if (v1 == 0x4c && v2 == 0x8d && v3 == 0x15) // 验证特征码
{
memcpy(&Offset, i + 3, 4);
*SSDTAddress = (ULONG64)i + Offset + 7;
break;
}
}
}
if (*SSDTAddress == 0)
{
return FALSE;
}
return TRUE;
}
PVOID GetExportVariableAddressFromNtosExportTableByVariableName(WCHAR * VariableName)
{
PVOID VariableAddress = NULL;
UNICODE_STRING v1 = { 0 };
if (VariableName&& wcslen(VariableName) > 0)
{
RtlInitUnicodeString(&v1, VariableName);
VariableAddress = MmGetSystemRoutineAddress(&v1);
}
return VariableAddress;
}
VOID HookSSDTWIN7_X86(PULONG32 ServiceTableBase,ULONG32 FunctionIndexInSSDT,ULONG32 FakeOpenProcess)
{
WPOFF();
ServiceTableBase[FunctionIndexInSSDT] = FakeOpenProcess;
WPON();
}
VOID UnHookSSDTWIN7_X86(PULONG32 ServiceTableBase,ULONG32 FunctionIndex, ULONG32 OldNtOpenProcess)
{
WPOFF();
ServiceTableBase[FunctionIndex] = OldNtOpenProcess;
WPON();
}
NTSTATUS FakeOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
{
PEPROCESS EProcess = PsGetCurrentProcess(); //进程上下背景文
if (EProcess != NULL&&MmIsAddressValid(EProcess))
{
//通过EProcess 获得进程名称
char *ProcessImageName = PsGetProcessImageFileName(EProcess);
if (strstr(ProcessImageName, "EnumProcess") != 0)
{
return STATUS_ACCESS_DENIED; //黑名单
}
}
((pfnNtOpenProcess)__OldNtOpenProcessAddress)(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId); //白名单
}
VOID HookSSDTWIN7_X64(PVOID ServerTableBase,ULONG32 SSDTFunctionIndex,PVOID FakeFunctionAddress,
PVOID OldFunctionAddress,ULONG32 ParameterCount, UCHAR* OldFunctionCode, ULONG32 PatchCodeLength)
{
ULONG32 KeBugOffsetInSSDT = 0;
WPOFF();
InLineHook(FakeFunctionAddress, OldFunctionAddress, OldFunctionCode, PatchCodeLength);
WPON();
KeBugOffsetInSSDT = CalcFunctionOffsetInSSDTInWIN7_X64(ServerTableBase,OldFunctionAddress, ParameterCount);
WPOFF();
((PULONG32)ServerTableBase)[SSDTFunctionIndex] = (ULONG32)KeBugOffsetInSSDT;
WPON();
__IsHook = TRUE;
}
VOID InLineHook(ULONG64 FakeFunctionAddress,ULONG64 OldFunctionAddress,UCHAR* OldFunctionCode,ULONG32 PatchCodeLength)
{
UCHAR PatchCode[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
memcpy(PatchCode + 6, &FakeFunctionAddress, 8); // 远距离跳转到自己的函数
memcpy(OldFunctionCode, (PVOID)OldFunctionAddress, PatchCodeLength);// 保存原先内存的内容
memset((PVOID)OldFunctionAddress, 0x90, PatchCodeLength);// 将置为nop指令(空指令)要打补丁的内存
memcpy((PVOID)OldFunctionAddress, PatchCode, 14); //打补丁
}
ULONG32 CalcFunctionOffsetInSSDTInWIN7_X64(PVOID ServerTableBase, PVOID OldFunctionAddress,ULONG32 ParameterCount)
{
ULONG32 FunctionOffset = 0;
CHAR Bits[4] = { 0 };
CHAR v1 = 0;
ULONG32 i = 0;
// 将偏移地址右移4位,留出存放参数个数的位置(x64)
// 如果这里变成ServerTableBase - OldFunctionAddress 会直接导致虚拟机CPU关闭,让我调试了快两个小时
FunctionOffset = (ULONG32)((ULONG64)OldFunctionAddress - (ULONG64)ServerTableBase);
FunctionOffset = FunctionOffset << 4;
// 将最后的一个字节拷出来,把4位表示参数的数据放进去,然后再拷回去!
memcpy(&v1, &FunctionOffset, 1);
if (ParameterCount > 4)
{
ParameterCount -= 4;
}
else
{
ParameterCount = 0;
}
#define SETBIT(x,y) x|=(1<//将X的第Y位置1
#define CLRBIT(x,y) x&=~(1<//将X的第Y位清0
#define GETBIT(x,y) (x & (1 << y)) //取X的第Y位,返回0或非0
for (i = 0; i < 4; i++)
{
Bits[i] = GETBIT(ParameterCount, i);
if (Bits[i])
{
SETBIT(v1, i);
}
else
{
CLRBIT(v1, i);
}
}
// 将最后一个字节的内容还原
memcpy(&FunctionOffset, &v1, 1);
return FunctionOffset;
}
VOID UnHookSSDTWIN7_X64(PVOID ServiceTableBase,ULONG32 SSDTFunctionIndex,ULONG32 OldNtOpenProcessOffset,
PVOID OldFunctionAddress,UCHAR* OldFunctionCode,ULONG32 PatchCodeLength)
{
// 将KeBugCheckEx里面被替换的代码段修复
WPOFF();
UnInLineHook(OldFunctionAddress, OldFunctionCode, PatchCodeLength);
WPON();
// 将SSDT表里的NtOpenProcess的偏移修复
WPOFF();
((PULONG32)ServiceTableBase)[SSDTFunctionIndex] = OldNtOpenProcessOffset;
WPON();
}
VOID UnInLineHook(PVOID OldFunctionAddress,UCHAR* OldFunctionCode,ULONG32 PatchCodeLength)
{
memcpy(OldFunctionAddress, OldFunctionCode, PatchCodeLength);
}
四、在HookSSDT的过程中遇到的问题,现在写出来记录并解释一下。
第一个问题:
为什么不能像x86一样将SSDT表里的地址换成我们自己的函数的地址呢?
1.首先,x64已经和x86的SSDT表不一样了,x86里面的地址是绝对地址,而x64SSDT里面的地址是偏移地址(相对于SSDT表中的__ServiceTableBase的值,也就是函数指针数组的首地址)
2.x64SSDT表里面的4个字节数据(32位数据)中的左28位是偏移,右4位是和参数个数相关的。
如果想要把FakeOpenProcess放入SSDT表中的话,偏移地址必须足够小
我们可以计算一下FakeOpenProcess的绝对地址减去SSDT表中的__ServiceTableBase ,因为最右端的4位和参数个数相关,所以我们得看偏移是不是满足4*7位的,
通过Dbg调试,FakeOpenProcess的地址是0xfffff880-02f492e0 ,__ServiceTableBase 的地址是0xfffff800-03e81800 。
0xfffff880-02f492e0 - 0xfffff800-03e81800 = 0x7f-ff0c7ae0,现在可以看出,放不进SSDT表了,偏移地址实在太大了!
所以64位采取的方法是:使用FF25远跳转指令直接跳转到我们自己写的FakeOpenProcess,不过FF25指令要执行的地方啊,于是前辈们找了一个不常用的函数KeBugCheckEx,把”\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF”这段代码的8个字节修改成FakeOpenProcess的地址后,将这段代码放在KeBugCheckEx函数开始地方,将KeBugCheckEx的相对于__ServiceTableBase的偏移放在0x23索引处,这样,当ZwOpenProcess呼叫到NtOpenProcess后,因为原来0x23处的地址被替换,所以ZwOpenProcess呼叫的是KeBugCheckEx,在执行KeBugCheckEx时,首先执行的是我们所写的跳转指令,直接跳转到FakeOpenProcess。
第二个问题:
为什么一定要通过映射文件到内存获得函数的地址,从而获得函数在SSDT表的索引呢,为什么不能像KeBugCheckEx一样直接通过函数指针减去SSDT的基地址呢?
这是因为可能在我们去勾OpenProcess函数之前,可能已经有其他程序已经将这个函数勾掉了,我们只能通过映射文件获得没有被勾的SSDT,从而得到正确的函数地址。
第三个问题:
在将x64位的程序写好之后,放到64位虚拟机上运行,刚用Kmd程序加载运行,就弹出来这个窗口。
并且无法调试,因为还没来得及调试,虚拟机已经崩溃了。
起初我认为DriverEntry还没执行就崩了,可能是程序设置属性的问题,检查属性也没毛病。网上也没查到答案。
最后发现将大部分代码屏蔽之后,可以正常运行,意识到是代码的问题,又因为刚加载虚拟机就关闭的原因,所以无法通过Windbg调试,只能一段代码一段代码的屏蔽,慢慢逼近错误,最终让我找到了。
// FunctionOffset = (ULONG32)((ULONG64)ServerTableBase - (ULONG64)OldFunctionAddress);// 导致虚拟机直接关闭,不过晚上写博客的时候,修改成原来错误的状态,又可以调试了
FunctionOffset = (ULONG32)((ULONG64)OldFunctionAddress - (ULONG64)ServerTableBase);