关于几种hook的探究
2023-4-3 13:33:12 Author: 红队蓝军(查看原文) 阅读量:11 收藏

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(0L"1"L"1", MB_OK); // 调用MessageBox
    getchar(); // 进行DLL注入
    MessageBox(0L"1"L"1", MB_OK); // 调用MyMessageBox
    MessageBox(0L"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] = { 0xB80x00x00x00x00xFF0xE0 };
#else // x64.dll
BYTE OldCode[12] = { 0 };
BYTE NewCode[12] = { 0x480xB80x00x00x00x00x00x00x00x00xFF0xE0 };
#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(0L"2"L"2", MB_OK); // 修改弹框内容
    RtlCopyMemory(MessageBoxAddress, NewCode, 7); // 改为新硬编码,用于下一次hook
#else // x64.dll
    RtlCopyMemory(MessageBoxAddress, OldCode, 12);
    MessageBox(0L"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, 0NULL); // ntdll文件句柄
    HANDLE ntdllMapping = CreateFileMapping(ntdllFile, NULL, PAGE_READONLY | SEC_IMAGE, 00NULL); // 将ntdll映射到进程内存
    LPVOID ntdllMappingAddress = MapViewOfFile(ntdllMapping, FILE_MAP_READ, 000); // 指向起始地址的指针
    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(0L"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

课程链接如下

第三期免杀课程


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg2NDY2MTQ1OQ==&mid=2247507721&idx=1&sn=9661731dd721441f63c42ba1b982cc36&chksm=ce6765b5f910eca3f24520b03f93512716c5e8e84fc559d0ece01c2927abf5a6b8f4bac87680#rd
如有侵权请联系:admin#unsafe.sh