从hook开始聊聊那些windows内核数据结构
总览:
一、IAT HOOK: 以下代码是DLL注入+iathook,通过测试procexp中的kill功能并没有使用OpenProcess函数,所以需要逆向看一看他是如何结束的进程,下回与大家一起讨论,64的注入是成功,但是记得要改DWORD等32位整型变量,整体思路是不变的。 // 因为ZwCreateThreadEx没有定义,所以自己定义一个伪函数 typedef DWORD(WINAPI* FnZwCreateThreadEx)(PHANDLE ThreadHandle,ACCESS_MASK DesiredAccess,LPVOID ObjectAttributes,HANDLE ProcessHandle,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,ULONG CreateThreadFlags,SIZE_T ZeroBits,SIZE_T StackSize,SIZE_T MaximunStackSize,LPVOID pUnkown); FnZwCreateThreadEx MyZwCreateThreadEx; // DLL的路径 const char DllPath[MAX_PATH] = { "C:UsersAdministratordocumentsvisual studio 2013ProjectsTextDebugTerminateProcessHook.dll" }; const char DllPath1[MAX_PATH] = { "C:UsersAdministratordocumentsvisual studio 2013ProjectsTextDebugHookDll.dll" }; // 需要声明的变量 HANDLE hProc = NULL; HANDLE RemoteHandle = NULL; 注入源码: HMODULE hNtdHandle = LoadLibrary(L"ntdll.dll"); // 获取地址给伪函数(因为ZwCreateThreadEx没有声明) MyZwCreateThreadEx = (FnZwCreateThreadEx)GetProcAddress(hNtdHandle,"ZwCreateThreadEx"); // 其实没必要这样写,不过更为规范一些 auto pFinAddress = GetProcAddress(GetModuleHandle(L"Kernel32.dll"),"LoadLibraryA"); // 第一次点击按钮,开启保护(只执行一次), 第二次点击按钮会挂起远程线程(暂停保护),第三次会在恢复...... if ((IntHookFlag == FALSE) && (IntHookFlag == TRUE)) { IntHookFlag = TRUE; // 挂起远程线程 SuspendThread(RemoteHandle); SetDlgItemText(IDC_STATIC3,L"×"); } else { IntHookFlag = FALSE; // 恢复远程线程 ResumeThread(RemoteHandle); SetDlgItemText(IDC_STATIC3,L"√"); } // IAT Hook 自我保护未开启 if (OneIntHookFlag == FALSE) { // 标记为真 OneIntHookFlag = TRUE; // 1. 获取被注入句柄 // HANDLE hProc = FindWindow(L"CalcFrame",NULL); hProc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,5000); if (!hProc) { AfxMessageBox(L"FindWindow() failuer"); return; } // DLL名称大小 SIZE_T dwSize = strlen(DllPath1) + 1; // 2. 被注入进程申请内存空间 auto pDlladdress = VirtualAllocEx(hProc,NULL,dwSize,MEM_COMMIT,PAGE_READWRITE); if (!pDlladdress) { CloseHandle(hProc); AfxMessageBox(L"VirtualAllocEx() failuer"); return; } // 3. 写入内存数据 if (!WriteProcessMemory(hProc,pDlladdress,DllPath1,&dwSize)) { VirtualFree(pDlladdress,MEM_RELEASE); CloseHandle(hProc); AfxMessageBox(L"WriteProcessMemory() failuer"); return; } // 补:这个地方创建信号量来传递Pid; DWORD m_Pid = GetCurrentProcessId(); // HANDLE pProcess = OpenProcess(PROCESS_ALL_ACCESS,m_Pid); HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE,PAGE_READWRITE,0x10,L"Pid"); LPVOID hMapFile = MapViewOfFile(hMap,FILE_MAP_ALL_ACCESS,0); // DLL里面接收PID memcpy(hMapFile,&m_Pid,sizeof(HANDLE)); // 4. 远程注入 LoadLibraryA获取函数地址可以直接函数名(编译器会帮助你获取VA),当然也可以GetProcess自己来获取VA DWORD dwTid = 0; // LoadLibrary(L""); RemoteHandle = CreateRemoteThread(hProc,(LPTHREAD_START_ROUTINE)LoadLibraryA,(LPVOID)pDlladdress,NULL); // HANDLE RemoteHandle = NULL; // DWORD dwStatu = MyZwCreateThreadEx(&RemoteHandle,PROCESS_ALL_ACCESS,hProc,(LPTHREAD_START_ROUTINE)pFinAddress,NULL); SetDlgItemText(IDC_STATIC3,L"√"); DWORD error = GetLastError(); // 5. 这个地方就不等待执行后在返回了WaitForSingleObjectEx(); // 6. 关闭远程句柄(只是关闭了本进程获取到的句柄,引用计数-1) CloseHandle(hProc); DLL源码: // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" // Save New Function Address BYTE g_NewAddress[5] = { 0xE9 }; // Save Old Function Address BYTE g_OldAddress[5] = {}; // Save WriteAttrib DWORD g_OldAttrib = 0; // Save ProtectProcessPid DWORD g_Pid = 0; // Statement : Camouflage Function HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess,_In_ BOOL bInheritHandle,_In_ DWORD dwProcessId); // Statement : Instanll Hook void InstallHook(); // Statement : UnInstall Hook void UnInstallHook(); // True OpenProcess typedef HANDLE (WINAPI* FnOpenProcess)( _In_ DWORD dwDesiredAccess,_In_ DWORD dwProcessId ); FnOpenProcess FOpenProcess; BOOL APIENTRY DllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved ) { switch (ul_reason_for_call) { // 被远程线程创建时候会被调用 case DLL_PROCESS_ATTACH: { ::MessageBox(NULL,L"X64任务管理器",L"注入",NULL); // 安装HOOK InstallHook(); } break; case DLL_PROCESS_DETACH: { // 卸载HOOK UnInstallHook(); } break; } return TRUE; } // Implementation : Camouflage Function HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess,_In_ DWORD dwProcessId) { /* 做个过滤,因为我们需要保护进程只有一个,被保护的PID是多少? 我们可以做用映射,信号量等把Pid传到被注入进程中。 */ HANDLE hProce = NULL; if (dwProcessId == g_Pid) { // 先回复 UnInstallHook(); // 打开权限默认为NULL 拒绝访问 hProce = OpenProcess(NULL,bInheritHandle,dwProcessId); // 在安装 InstallHook(); return hProce; } else { // 先回复 UnInstallHook(); // 调用正确的OPenProcess hProce = OpenProcess(dwDesiredAccess,dwProcessId); // 在安装 InstallHook(); // 返回正确的句柄 return hProce;//FOpenProcess(dwDesiredAccess,dwProcessId); } } // Implementation : Instanll Hook void InstallHook() { // 前奏工作 HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS,L"Pid"); LPVOID hAddr = MapViewOfFile(hMap,0); g_Pid = *(DWORD*)hAddr; // 1. 保存原来的地址(是指令长度) memcpy(g_OldAddress,OpenProcess,5); FOpenProcess = (FnOpenProcess)OpenProcess; // 2. 计算偏移 DWORD dwOffset = (DWORD)MyOpenProcess - (DWORD)OpenProcess - 5; // 3. 数组填充新偏移 // memcpy(&g_NewAddress[1],&dwOffset,4); 切记不可用这种方式 当年这个BUG卡了好久 内存大小端字符排序 内存拷贝是正向的 偏移错误 *(DWORD *)(g_NewAddress + 1) = dwOffset; VirtualProtect(OpenProcess,5,PAGE_EXECUTE_READWRITE,&g_OldAttrib); // 4. 写入地址 memcpy(OpenProcess,g_NewAddress,5); VirtualProtect(OpenProcess,g_OldAttrib,&g_OldAttrib); } // Implementation : UnInstall Hook void UnInstallHook() { VirtualProtect(OpenProcess,&g_OldAttrib); // 写回去就行了 memcpy(OpenProcess,g_OldAddress,&g_OldAttrib); } 二、ObjectHook: 如上图所示:这就是Windows内核对象数据结构,表示注册表、进程、线程等等。对象数据结构当然是对象管理器管理,所有的对象内部都会有OBJECT_HEADER的结构体,用来维护生命周期。 对象头上面是对象头引导,如OBJECT_HEADER_QUOTA_INFO、OBJECT_HEADER_HANDLE_INFO还有后面两个,描述了相关对象额外的属性,这些结构体对应着OBJECT_HEADER中的成员变量,如下介绍: 图中OBJECT_HEADER结构体中前两个PointerCount与HandleCount是引用计数。 +0x10指向的是_OBJECT_CREATE_INFORMATION 在创建CreateProcess时候会给该结构体申请空间,后面会填充该结构体信息。 我们重点看一下偏移为+0x008 OBJECT_TYPE,表示对于通用属性(对象的通用属性)存储,如下图所示: OBJECT_TYPE.TypeInfo指向了OBJECT_TYPE_INITIALIZER结构体,这个结构体包含特定对象类型的函数,对象管理器用于各类的执行操作,如下图所示: 你会发现我用红色框框标记了上图的一些成员,所谓的ObjectHook就是他们(替换地址),这些函数过程会在特定时机被调用。 如何找到对象头? 到底对不对?我们来测试一下? OBJECT_TYPE了+0x28就是_OBJECT_TYPE_INITIALIZER。windbg下面能找到,编写代码的时候如何获取OBJECT_HEADER呢? 先来看一下,OBJECT_HEADER + 0x18,是成员变量Boby,这是什么?这是对象主体,我们可以看到对象主体中OBJECT_DIRECTORY,DRIVER_OBJECT,DEVICE_OBJECT等对象结构,我们在编写windows内核编程的时候会创建驱动对象,设备对象,这样就好说了。 上面代码就是用汇编进行了的ObjectHook,如果理解了以上结构体概念,这些汇编代码应该没有难度。 为什么不用结构体去编程?因为我不想定义那么多结构体,部分结构体windows是没有公开的,需要自己在头文件中定义,但是代码中仍然给出了完整的结构体,可以用结构体实现,源码如下: 头文件定义: #include <ntddk.h> /*定义的结构体信息*/ typedef struct _OBJECT_TYPE_INITIALIZER { USHORT Length; USHORT type; PVOID ObjectTypeCode; PVOID InvalidAttributes; GENERIC_MAPPING GenericMapping; PVOID ValidAccessMask; PVOID RetainAccess; POOL_TYPE PoolType; PVOID DefaultPagedPoolCharge; PVOID DefaultNonPagedPoolCharge; PVOID DumpProcedure; PVOID OpenProcedure; PVOID CloseProcedure; PVOID DeleteProcedure; PVOID ParseProcedure; PVOID SecurityProcedure; PVOID QueryNameProcedure; USHORT OkayToCloseProcedure; } OBJECT_TYPE_INITIALIZER,*POBJECT_TYPE_INITIALIZER; typedef struct _OBJECT_TYPE { LIST_ENTRY TypeList; // : _LIST_ENTRY UNICODE_STRING Name; // : _UNICODE_STRING PVOID DefaultObject; // : Ptr32 Void ULONG Index; // : UChar ULONG TotalNumberOfObjects; // : Uint4B ULONG TotalNumberOfHandles; // : Uint4B ULONG HighWaterNumberOfObjects; // : Uint4B ULONG HighWaterNumberOfHandles; // : Uint4B OBJECT_TYPE_INITIALIZER TypeInfo; // : _OBJECT_TYPE_INITIALIZER PVOID TypeLock; // : _EX_PUSH_LOCK ULONG Key; // : Uint4B LIST_ENTRY CallbackList; // : _LIST_ENTRY } OBJECT_TYPE,*POBJECT_TYPE; typedef struct _OBJECT_CREATE_INFORMATION { ULONG Attributes; HANDLE RootDirectory; KPROCESSOR_MODE ProbeMode; ULONG PagedPoolCharge; ULONG NonPagedPoolCharge; ULONG SecurityDescriptorCharge; PVOID SecurityDescriptor; PSECURITY_QUALITY_OF_SERVICE SecurityQos; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; } OBJECT_CREATE_INFORMATION,*POBJECT_CREATE_INFORMATION; typedef struct _OBJECT_HEADER { //对象头部的指针计数,对对象头指针引用的计数 LONG_PTR PointerCount; union { //句柄引用计数 LONG_PTR HandleCount; PVOID NextToFree; }; POBJECT_TYPE Type; //OBJECT_HEADER_NAME_INFO相对于此结构的偏移 UCHAR NameInfoOffset; //OBJECT_HEADER_HANDLE_INFO相对于此结构的偏移 UCHAR HandleInfoOffset; //OBJECT_HEADER_QUOTA_INFO相对于此结构的偏移 UCHAR QuotaInfoOffset; UCHAR Flags; union { //创建对象是用于创建对象附加头的结构 //里面保存了和附加对象头类似的信息 PVOID ObjectCreateInfo; PVOID QuotaBlockCharged; }; PSECURITY_DESCRIPTOR SecurityDescriptor; QUAD Body; } OBJECT_HEADER,*POBJECT_HEADER; // 获取头信息 #define OBJECT_TO_OBJECT_HEADER(o) CONTAINING_RECORD((o),OBJECT_HEADER,Body) #define CONTAINING_RECORD(address,type,field) ((type*)(((ULONG_PTR)address)-(ULONG_PTR)(&(((type*)0)->field)))) 代码实现: #include "HookHead.h" VOID UnLoadDriver() { } NTSTATUS MyDeleteProcedure(); NTSTATUS MaDefaultFunction(DEVICE_OBJECT* pDeviceObj,IRP* Irp) { Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DriverEntry(DRIVER_OBJECT* pDeviceObj,UNICODE_STRING* RegistryPath) { pDeviceObj->DriverUnload = UnLoadDriver; for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i) { pDeviceObj->MajorFunction[i] = MaDefaultFunction; } // 1. 获取OBJECT_HEAD __asm { // 保存环境 pushad; pushfd; // 1 设备对象就是 OBJECT_BODY 也就是 -0x18是OBJECT_HREAD -0x10是OBJECT_TYPE(OBJECT_HANDLE + 0x8) lea eax,pDeviceObj; sub eax,0x10; // 2 获取到OBJECT_TYPE之后 地址加上0x3c则是DeleteProcdure(其实已经在OBJECT_TYPE_INITIALZER结构体中) lea eax,[eax + 0x3c]; // 3 DeleteProcdure地址替换成的MyDeleteProcedure地址 lea esi,MyDeleteProcedure; mov eax,esi; // 恢复环境 popad; popfd; } // 2. 获取OBJECT_TYPE // 3. 替换相对应的函数 } NTSTATUS MyDeleteProcedure() { } 三、SsdtHook: 先有个基本的概念,下面来看一张图: 我们发现不论是System support process,还是Service processes他们都会进入内核模式之前都会经过Ntdll.dll模块。然后通过系统服务调度程序到接口,到微内核,到HAL(驱动硬件相关联的地方)。 用OD随便跟踪函数: 上面调用的函数有点奇怪,并不是想象中的CALL [EDX],而是wow64cpu函数。这是用户模式下实现的,作为 ntdll.dll 和内核之间的层。如果你的应用程序是32位,为了能在64位的系统上运行起来,就会调用这个函数兼容,Windows下的一个子系统。 该函数有三种返回值: 1、64位运行在64位系统下,不是WOW64模式,return 0; 2、32位运行在64位系统下,WOW64模式,return 1; 3、32位运行在32位系统下,return 0; 有点像傀儡进程一样,函数过程大概是这样,原进程会被创建(包括注册表),然后判断该程序信息,如果不是64位在C盘下(具体位置记不清楚),Temp的文件夹下创建一个64位的线程,修改注册表信息等(不太准确,只是以前逆向的时候观察过整个的过程)。 MOV EAX,0x22(0x23) --> 保存调用号 ssdt表中的序号 EAX寄存器保存保存函数的调用号,其实到内核以后就靠调用号来确认调用的是哪个函数。 对windows内核比较熟悉的应该知道,用户的堆栈与内核的堆栈不是同一个堆栈空间,而且用户层没有权限去访问内核层的数据,通过什么进入内核层?一条汇编指令 SYSENTER,如下图所示: cs:ip执行这一条汇编指令之后,你将进入到内核。如何做到的呢?其实在SYSENTRY指令之前,先会用edx保存esp的值,执行SYSENTER时候会读取特殊寄存器MSR模组寄存器,如下图所示: 没有名字,只有编号,通过以下两条汇编指令对MSR进行操作:
详细: KiFastCallEntry函数大家有兴趣可以分析下,那么对上面的流程更为清晰,怎样去分析呢,windbg下就可以,如下图所示: 竟然进入到了内核层,把用户栈的内容拷贝到内核栈,但是拷贝多少个字节?参数个数? 通过eax在用户层保存的序号,就能找到函数地址。通过调用号作为序号,就能找到参数个数,个数*4就是总字节,其实这张表就是SSDT,还有一张表叫ShadowSSDT,专门用于保存和用户界面相关服务,内核中还有两张没有使用的表,如下图所示(两个结构体)。 _KSYSTEM_SERVICE_TABLE便是SSDT的结构体,通过上图我们知道了SSDT的结构,另一个结构体保存了这四张表。 windbg如何找到ssdt? 使用结构体解析地址看一下,如下图所示: 图中标红便对应着结构体成员值,函数地址表首地址、每个函数被调用次数、服务函数个数191个,参数表地址。 HOOK的是什么? 其实HOOK的就是函数地址表中的地址,当内核层通过调用号找到ssdt中的索引号,调用的是我们自己的函数地址即可。 到底是不是这样?经过测试确实是这样,下图是源码测试图: 我们用ark工具看一下,如下图所示: 源码如下: 头文件: #pragma once #include <ntddk.h> #define CTL_SSDT_ENABLE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_OUT_DIRECT,FILE_ANY_ACCESS) #define CTL_SSDT_DISABLE CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,FILE_ANY_ACCESS) typedef struct _KSERVICE_TABLE_DESCRIPTOR { KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe的服务函数,即SSDT KSYSTEM_SERVICE_TABLE win32k; // win32k.sys的服务函数(GDI32.dll/User32.dll 的内核支持),即ShadowSSDT KSYSTEM_SERVICE_TABLE notUsed1; // 不使用 KSYSTEM_SERVICE_TABLE notUsed2; // 不使用 }KSERVICE_TABLE_DESCRIPTOR,*PKSERVICE_TABLE_DESCRIPTOR; typedef struct _KSYSTEM_SERVICE_TABLE { PULONG ServiceTableBase; // 函数地址表的首地址 PULONG ServiceCounterTableBase; // 函数表中每个函数被调用的次数 ULONG NumberOfService; // 服务函数的个数,NumberOfService * 4 就是整个地址表的大小 UCHAR* ParamTableBase; // 参数个数表首地址 } KSYSTEM_SERVICE_TABLE,*PKSYSTEM_SERVICE_TABLE; // 伪函数(Hook的函数) typedef NTSTATUS(NTAPI*FnNtOpenProcess)(PHANDLE ProcessHandle,POBJECT_ATTRIBUTES ObjectAttributes,PCLIENT_ID ClientId); // 保存旧的地址 FnNtOpenProcess g_OldNtOpenProcess; // 定义KeServieDescriptorTableShadow KSERVICE_TABLE_DESCRIPTOR* g_ServiceTab = NULL; // 保存被保护进程PID HANDLE g_Pid = 0; 驱动层: #include "SSDTHookHead.h" // 声明:驱动卸载 VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj); // 声明:默认初始化 NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj,IRP* Irp); // 声明:控制码 NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj,IRP* Irp); // 声明:安装HOOK VOID InstallHook(); // 声明:卸载HOOK VOID UnInstallHook(); // 声明:关闭分页保护 NTSTATUS ShudowMemoryPageProtect(); // 声明:开启分页保护 NTSTATUS StartMemoryPageProtect(); // Hook实现函数 NTSTATUS MyOpenProcess(PHANDLE ProcessHandle,PCLIENT_ID ClientId); // 声明:入口点 NTSTATUS DriverEntry(DRIVER_OBJECT* pDriverObj,UNICODE_STRING* RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DEVICE_OBJECT* pDeviceObj = NULL; UNICODE_STRING DevName; UNICODE_STRING SymbolicLinkName; NTSTATUS Status = STATUS_SUCCESS; RtlInitUnicodeString(&DevName,L"DeviceSsdtHook"); RtlInitUnicodeString(&SymbolicLinkName,L"DosDevicesSymbolicLinkName"); // DbgBreakPoint(); pDriverObj->DriverUnload = DriverUnLoad; for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i) { pDriverObj->MajorFunction[i] = DefaultFunction; } // 设备对象 Status = IoCreateDevice(pDriverObj,&DevName,FILE_DEVICE_UNKNOWN,&pDeviceObj); if (!NT_SUCCESS(Status)) return Status; // 使用缓冲区的方式进行3环与0环通讯 pDriverObj->Flags = DO_BUFFERED_IO; // 符号对象暴露给三环使用 Status = IoCreateSymbolicLink(&SymbolicLinkName,&DevName); if (!NT_SUCCESS(Status)) return Status; pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlCode; return STATUS_SUCCESS; } VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj) { UNICODE_STRING DeleteSymblolicLinkName; RtlInitUnicodeString(&DeleteSymblolicLinkName,L"DosDevicesSymbolicLinkName"); IoDeleteSymbolicLink(&DeleteSymblolicLinkName); IoDeleteDevice(pDeviceobj->DeviceObject); } // 实现:默认初始化 NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj,IRP* Irp) { UNREFERENCED_PARAMETER(pDeviceObj); Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; } // 实现:控制码 NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj,IRP* Irp) { // DbgBreakPoint(); UNREFERENCED_PARAMETER(pDeviceObj); // 通过Irp栈数据获取控制码 // NTSTATUS nStatus = STATUS_SUCCESS; PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp); ULONG uControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode; // 通过MDL获取需要保护的Pid // g_Pid = (HANDLE)MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority); PVOID Pbuf = Irp->AssociatedIrp.SystemBuffer; RtlCopyMemory((PVOID)&g_Pid,Pbuf,sizeof(ULONG)); switch (uControlCode) { case CTL_SSDT_ENABLE: { // DbgBreakPoint(); InstallHook(); } break; case CTL_SSDT_DISABLE: { UnInstallHook(); } break; default: break; } Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp,IO_NO_INCREMENT); return STATUS_SUCCESS; } // 实现:安装HOOK VOID InstallHook() { // DbgBreakPoint(); // 1.1 获取当前线程 PETHREAD pThread = PsGetCurrentThread(); // 1.2 线程结构体 +0xbc 获取的是 ServiceTable g_ServiceTab = (KSERVICE_TABLE_DESCRIPTOR*)(*(ULONG*)((ULONG_PTR)pThread + 0xbc)); // 1.3 获取SSDT地址基址且保存原始的函数VA g_OldNtOpenProcess = (FnNtOpenProcess)g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE]; // 1.4 替换修改地址(这个地方先要关闭页保护) /* 只介绍其中的三种: PE - 是否启用保护模式,置1则启用 PG - 是否使用分页模式,置1则开启分页模式,此标志置1时,PE标志也必须置1,否则CPU报异常. WP - WP==1时,不能修改只读的内存页,WP==0 时,可以修改只读的内存页. */ ShudowMemoryPageProtect(); g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)MyOpenProcess; StartMemoryPageProtect(); } // 实现:卸载HOOK VOID UnInstallHook() { ShudowMemoryPageProtect(); g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)g_OldNtOpenProcess; // DbgBreakPoint(); StartMemoryPageProtect(); } // 实现:关闭分页保护 NTSTATUS ShudowMemoryPageProtect() { __asm { pushad; pushfd; mov eax,cr0; // 前提内存保护一定是开启的 WP = 1 否则..就给开启了 and eax,~0x10000; mov cr0,eax; popfd; popad; } } // 实现:开启分页保护 NTSTATUS StartMemoryPageProtect() { __asm { pushad; pushfd; mov eax,cr0; or eax,0x10000; mov cr0,eax; popfd; popad; } } // 实现:HOOK函数 NTSTATUS MyOpenProcess(PHANDLE ProcessHandle,PCLIENT_ID ClientId) { // 访问权限PROCESS_ALL_ACCESS改为NULL if (ClientId->UniqueProcess == g_Pid) { DbgBreakPoint(); DesiredAccess = 0; } return g_OldNtOpenProcess(ProcessHandle,DesiredAccess,ObjectAttributes,ClientId); } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |