首页
论坛
专栏
课程

Hook原理

Editor 发布于 看雪学院 2018-06-21 18:18

Hook原理


对于会Hook的人来说,Hook其实也就那么回事.对于没有Hook过的人来说,会感觉Hook很高大上(其实也没毛病).

那么今天我们就来探讨一些Hook的原理是什么.

我认为任何Hook都可以分为以下三步(简称WFH):

需要Hook的是什么,在哪里(后面简称Where).

寻找到Hook的地方.(后面简称Find)

进行Hook.(后面简称Hook)

当然了同一个类型的Hook所在的地方一般是一样的.但寻找到Hook的地方,和进行Hook却会有许多不同的方法.我们要抓住的是不变的地方.

根据这3点我们举例来验证一下.


Hook API

第一个我尽量说得详细一些.

举例子:Hook API:OpenProcess 让win10 64位的任务管理器关闭不了任何程序.

1. Where.

Hook API:OpenProcess. 在kernelbase.dll里面.

2.Find.

方式1:

通过LoadLibrary加载kernelbase.dll模块基地址.

通过GetProcAddress获取OpenProcess的地址.

方式2:编程直接引用OpenProcess的地址,因为在Windows下3大模块user32.dll,kernelbase.dll,ntdll.dll的加载基地址在每个应用程序中都是一样的.

方式3:通过寻找目标的IAT找到OpenProcess

3.Hook.

方式1:通过注入dll到目标进程进行,可以替换kernelbase.dll里面的OpenProcess的前面5个字节为jmp跳转到我们自己的地址,也可以修改目标进程的IAT.

方式2:通过WriteProcessMemory写入代码,修改目标进程的OpenProcess跳转到我们的代码.

代码实例:F1+H1(Find的第二种方式,Hook的第一种方式,后面不再说明):

新建一个dll文件:


在dll文件里面写如下代码


如果你的win10是64位的就编译64位的,32位就编译32位的


//dllmain.cpp : 定义 DLL 应用程序的入口点。

DWORD oldProtect;

BYTE  JmpBtye[5];

BYTE  OldByte[5];

void*OpenProcessaddr;

boolH1_OpenProcess();

void UnHook();

BOOLAPIENTRY DllMain( HMODULE hModule,

DWORD  ul_reason_for_call,

LPVOID lpReserved

)

{

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

H1_OpenProcess();

break;

case DLL_PROCESS_DETACH:

UnHook();

break;

}

returnTRUE;

}

HANDLE MyOpenProcess(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwProcessId)

{

dwDesiredAccess &=~PROCESS_TERMINATE;//去掉关闭程序的权限

UnHook();//恢复Hook 任何调整到原来的地方执行.

HANDLE h=OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);

H1_OpenProcess();

returnh;

}

void*F1_OpenProcess()

{

//寻找到OpenProcess的地址

void*addr=0;

//加载kernel32.dll

HMODULE hModule=LoadLibraryA("kernelbase.dll");

//获取OpenProcess的地址

addr=(void*)GetProcAddress(hModule,"OpenProcess");

returnaddr;

}

void*F2_OpenProcess()

{

return(void*)OpenProcess;

}

boolH1_OpenProcess()

{

//1.开始寻找地址

void*addr=F1_OpenProcess();

OpenProcessaddr=addr;

//判断是否寻找成功

if(addr==0)

{

MessageBoxA(NULL,"寻找地址失败",NULL,0);

returnfalse;

}

//2.进行Hook

/*

一般代码段是不可写的,我们需要把其改为可读可写.

*/

VirtualProtect((void*)addr,5, PAGE_EXECUTE_READWRITE,&oldProtect);

//修改前面的5个字节为jmp 跳转到我们的代码.

//内联Hook 跳转偏移计算方式:跳转偏移=目标地址-指令地址-5

//jmp 的OpCode 为:0xE9

JmpBtye[0]=0xE9;

*(DWORD*)&JmpBtye[1]=(DWORD)((longlong)MyOpenProcess-(longlong)addr-5);

//保存原先字节

memcpy(OldByte, (void*)addr,5);

//替换原先字节

memcpy((void*)addr, JmpBtye,5);

}

void UnHook()

{

//恢复原先字节

memcpy((void*)OpenProcessaddr, OldByte,5);

//恢复属性

DWORD p;

VirtualProtect((void*)OpenProcessaddr,5, oldProtect, &p);

}

把dll注入任务管理器,因为注入不是我们主题,所以这里我只是简单的贴出代码,直接拿来用就可以

```

#include <windows.h>


//获取进程句柄

HANDLE GetThePidOfTargetProcess(HWND hwnd)

{

DWORD pid;

GetWindowThreadProcessId(hwnd, &pid);

HANDLE hProcee=::OpenProcess(PROCESS_ALL_ACCESS | PROCESS_CREATE_THREAD,0, pid);

returnhProcee;

}

//提升权限

void Up()

{

HANDLE hToken;

LUID luid;

TOKEN_PRIVILEGES tp;

OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);

tp.PrivilegeCount=1;

tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

tp.Privileges[0].Luid=luid;

AdjustTokenPrivileges(hToken,0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);

}

//进程注入

BOOLDoInjection(char*DllPath, HANDLE hProcess)

{

DWORD BufSize=strlen(DllPath)+1;

LPVOID AllocAddr=VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(hProcess, AllocAddr, DllPath, BufSize, NULL);

PTHREAD_START_ROUTINE pfnStartAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")),"LoadLibraryA");

HANDLE hRemoteThread;

hRemoteThread=CreateRemoteThread(hProcess, NULL,0, pfnStartAddr, AllocAddr,0, NULL);

if(hRemoteThread)

{

MessageBox(NULL, TEXT("注入成功"), TEXT("提示"), MB_OK);

returntrue;

}

else

{

MessageBox(NULL, TEXT("注入失败"), TEXT("提示"), MB_OK);

returnfalse;

}

}

intmain()

{

//这里填写窗口标题

HWND hwnd=FindWindowExA(NULL, NULL, NULL,"任务管理器");

Up();

HANDLE hP=GetThePidOfTargetProcess(hwnd);

//开始注入

//这里填写Dll路径

DoInjection("E:\\studio\\VS2017\\F2H1.MessageBox\\x64\\Release\\F2H1.MessageBox.dll", hP);

}

```

注入之后看效果

)

其实还有很多方式,剩下的方式你就可以自己慢慢尝试了.

SSDT Hook.

刚才说了用户层的Hook,接下来我们再说一下内核层的Hook,其实还是3歩曲.WFH

实现相似的功能,让所有程序关闭不了自己的程序.

1.Where

Windows 操作系统共有4个系统服务描述符.其中只用了两个,第一个是SSDT,第二个是ShadowSSDT

系统描述符结构如下:


typedef struct _KSYSTEM_SERVICE_TABLE

{

ULONG*ServiceTableBase;       //服务表基址 第一个表示SSDT 紧接着下一个ShadowSSDT

ULONG*ServiceCounterTableBase;//计数表基址

ULONG NumberOfServices;        //表中项的个数

UCHAR*ParamTableBase;         //参数表基址

}KSYSTEM_SERVICE_TABLE,*PKSYSTEM_SERVICE_TABLE;

SSDT Hook:NtOpenProcess,在ntkrnlpa.exe内核模块中的系统服务描述符表中的SSDT表中的第190号.

使用PCHunter32查看


2.Find


方式1:在Win7 32下,系统服务描述符表直接导出符号KeServiceDescriptorTable,可以直接获取其地址,然后通过其第一个ServiceTableBase就是SSDT的地址,接着找到第190号函数.

方式2:可以通过PsGetCurrentThread 获取ETHREAD结构,该结构的第一个字段KTHREAD有一个字段ServiceTable保存着系统描述符表的地址KeServiceDescriptorTable.通过其第一个ServiceTableBase就是SSDT的地址,接着找到第190号函数.


0: kd> u PsGetCurrentThread

nt!PsGetCurrentThread:

840473f164a124010000   mov     eax,dword ptr fs:[00000124h]  ;ETHREAD

840473f7c3              ret


3.Hook


方式1:替换找到的地方,换成我们自己的函数

方式2:获取找到的地方的函数指针,改变其代码跳转到自己的代码(其实就是inline Hook).

例子:F2H1

新建一个驱动程序:


2.代码如下:


#include <ntifs.h>

#pragma pack(1)

typedef struct _KSYSTEM_SERVICE_TABLE

{

ULONG*ServiceTableBase;       //服务表基址 第一个表示SSDT 紧接着下一个是ShadowSSDT

ULONG*ServiceCounterTableBase;//计数表基址

ULONG NumberOfServices;        //表中项的个数

UCHAR*ParamTableBase;         //参数表基址

}KSYSTEM_SERVICE_TABLE,*PKSYSTEM_SERVICE_TABLE;

#pragma pack()

void*OldNtProcess=0;

//导入系统描述符表

extern"C"NTSYSAPI KSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;

typedef NTSTATUS(NTAPI*NTOPENPROCESS)(PHANDLE  ProcessHandle,

ACCESS_MASK  DesiredAccess,

POBJECT_ATTRIBUTES  ObjectAttributes,

PCLIENT_ID  ClientId);

NTOPENPROCESS g_NtOpenProcess=NULL;

NTSTATUS NTAPI MyOpenProcess(

PHANDLE  ProcessHandle,

ACCESS_MASK  DesiredAccess,

POBJECT_ATTRIBUTES  ObjectAttributes,

PCLIENT_ID  ClientId

)

{

if(ClientId->UniqueProcess==(HANDLE)916)//指定保护的进程ID

{

returnSTATUS_ABANDONED;

}

returng_NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);

}

void OffProtect()

{

__asm {//关闭内存保护

push eax;

mov eax, cr0;

andeax, ~0x10000;//关闭CR0.WP位,关闭页保护

mov cr0, eax;

pop eax;

}

}

void OnProtect()

{

__asm {//恢复内存保护

push eax;

mov eax, cr0;

oreax,0x10000;//开启CR0.WP位,开启页保护

mov cr0, eax;

pop eax;

}

}

void*F1_NtOpenProcess()

{

return(void*)&KeServiceDescriptorTable.ServiceTableBase[190];

}

void*F2_NtOpenProcess()

{

PETHREAD eThread=PsGetCurrentThread();

PKSYSTEM_SERVICE_TABLE kServiceTable=(PKSYSTEM_SERVICE_TABLE)(*(ULONG*)((ULONG)eThread+0xbc));

return(void*)&kServiceTable->ServiceTableBase[190];

}

boolH1_NtOpenProcess()

{

OldNtProcess=F2_NtOpenProcess();//Find

g_NtOpenProcess=(NTOPENPROCESS)(*(ULONG*)OldNtProcess);//保存就地址

OffProtect();//由于SSDT表是只读的所以需要关闭页写入保护

(*(ULONG*)OldNtProcess)=(ULONG)MyOpenProcess;//写入自己的函数地址

OnProtect();//恢复

returntrue;

}

void UnHook()

{

OffProtect();//由于SSDT表是只读的所以需要关闭页写入保护

(*(ULONG*)OldNtProcess)=(ULONG)g_NtOpenProcess;//恢复函数

OnProtect();//恢复

}

void Unload(PDRIVER_OBJECT pDri)

{

UNREFERENCED_PARAMETER(pDri);

UnHook();

}

extern"C"NTSTATUS DriverEntry(PDRIVER_OBJECT pDri, PUNICODE_STRING pRegStr)

{

UNREFERENCED_PARAMETER(pRegStr);

pDri->DriverUnload=Unload;

H1_NtOpenProcess();

returnSTATUS_SUCCESS;

}


加载驱动程序(自己写的一个小工具,也可以网上下载)


)


4.查看效果



SYSENTRY Hook


这里我再说一些Hook,也是3歩曲WFH.但是我不再提供具体实现.


我们知道以前windows系统是通过int2e中断进入系统内核的,但是现在是通过cpu提供的一个功能sysentry进入系统的(32位是sysentry,64位是syscall).这是一个CPU指令,如果对该指令不知道的话,可以查看我另外一篇文章:


1.Where


SYSENTRY Hook:190号功能号,功能号保存在eax中.

SYSENTRY指令进入系统内核的地址保存在MSR寄存器里面的**IA32_SYSENTER_EIP**(0x176)号寄存器.


2.Find

通过指令rdmsr读取**IA32_SYSENTER_EIP**MSR寄存器.其中ecx保存的是读取msr的序号,也就是0x176号,返回的结果保存在edx:eax(64位,edx保存高32位,eax保存低32位).因为是32位系统,所以只需要eax的值即可.


3.Hook

通过wrmsr写入我们自己的地址,地址放在edx:eax(64位,edx保存高32位,eax保存低32位).即可完成Hook.


Object Hook


每一个不同的内核对象,都对应着一个不同的类型索引:TypeIndex.通过该索引可以找到该内核对象的类型:OBJECT_TYPE

1.Where

在内核对象的TypeInfo中.

2.Find

通过ObGetObjectType内核函数获取内核对象类型(OBJECT_TYPE)的OBJECT_TYPE中有一个字段TypeInfo(类型_OBJECT_TYPE_INITIALIZER),其中保存着,在创建内核对象,销毁内核对象的一系列构造函数.


对应结构:


//OBJECT_TYPE-->TypeInfo:_OBJECT_TYPE_INITIALIZER

ntdll!_OBJECT_TYPE

+0x000TypeList                     : _LIST_ENTRY

+0x010Name                         : _UNICODE_STRING

+0x020DefaultObject                : Ptr64 Void

+0x028Index                        : UChar

+0x02cTotalNumberOfObjects         : Uint4B

+0x030TotalNumberOfHandles         : Uint4B

+0x034HighWaterNumberOfObjects     : Uint4B

+0x038HighWaterNumberOfHandles     : Uint4B

+0x040TypeInfo                     : _OBJECT_TYPE_INITIALIZER //1.这个

+0x0b0TypeLock                     : _EX_PUSH_LOCK

+0x0b8Key                          : Uint4B

+0x0c0CallbackList                 : _LIST_ENTRY

ntdll!_OBJECT_TYPE_INITIALIZER

+0x000Length                       : Uint2B

+0x002ObjectTypeFlags              : UChar

+0x002CaseInsensitive              : Pos0,1Bit

+0x002UnnamedObjectsOnly         : Pos1,1Bit

+0x002UseDefaultObject             : Pos2,1Bit

+0x002SecurityRequired             : Pos3,1Bit

+0x002MaintainHandleCount         : Pos4,1Bit

+0x002MaintainTypeList             : Pos5,1Bit

+0x002SupportsObjectCallbacks     : Pos6,1Bit

+0x004ObjectTypeCode               : Uint4B

+0x008InvalidAttributes            : Uint4B

+0x00cGenericMapping               : _GENERIC_MAPPING

+0x01cValidAccessMask              : Uint4B

+0x020RetainAccess                 : Uint4B

+0x024PoolType                     : _POOL_TYPE

+0x028DefaultPagedPoolCharge     : Uint4B

+0x02cDefaultNonPagedPoolCharge : Uint4B

+0x030DumpProcedure                : Ptr64     void

+0x038OpenProcedure                : Ptr64    long//打开 回调函数

+0x040CloseProcedure               : Ptr64     void//关闭 回到函数

+0x048DeleteProcedure              : Ptr64     void

+0x050ParseProcedure               : Ptr64    long

+0x058SecurityProcedure         : Ptr64    long

+0x060QueryNameProcedure         : Ptr64    long//查询名称 回调函数

+0x068OkayToCloseProcedure         : Ptr64     unsigned char

3.Hook

根据找到的位置替换里面回调函数指针为我们自己写的函数即可.比如替换OpenProcedure.

IDT Hook

1.Where

在中断描述符表(IDT)中.

2.Find

idtr寄存器指向中断描述符表.通过idtr找到.

说明:idtr是一个48位寄存器,其中低16位保存中断描述符表长度.高32位是中断描述符表.的基地址.

3.Hook

通过构造一个中断门或者陷阱门,其中中断门或陷阱门的偏移地址写自己的地址.然后把中断门或者陷阱门写入都相应的IDT表项中.


总结:

从上面我们可以看到,其实Hook都是一样的,只是对应的地方不同,寻找的方法不同,替换(修改)的方法不同而已.

有的人可能就要反问了,SetWindowsHookEx,就不要知道Hook的地方在哪了,也不需要寻找.确实,这两歩不需要我们自己做,但并不代表不需要,这只是操作系统为我们做了而已,我们只需要提供一个回调函数即可.

所以下面我留下一个小测试:就是自己自己实现SetWindowsHookEx.

最新评论 (3)
chprain 2月前
1
受益匪浅
wx_我心依旧 1月前
2
受益匪浅
严启真 1月前
3
认真学习了,谢谢
登录后即可评论
 返回上一页