本帖最后由 YFSafe 于 2022-8-28 02:34 编辑
首先我们说下hook,什么是hook?hook的英文已经说明了,hook在英文中是钩的意思,计算机取其意叫钩子,而我的理解叫拦截 大家应该写过ring3下的程序,估计也写过一些ring3的hook.例如有一个API是OpenProcess,功能是打开进程得到进程句柄,比如我们要结束一个进程,需要调用TerminateProcess这个api,就必须知道进程的句柄 根据以上原理,我们可以写一下结束进程的伪代码 [AppleScript] 纯文本查看 复制代码 push PID
push 0
push 114514 ;这个值代表权限
call OpenProcess
push 0
push eax
call TerminateProcess 执行这段代码,如果正常的话,对应这个pid的进程应该就会被结束.这里我们来设想一下:假如我是一个恶意软件,我要防止我自身被结束,那我的最好办法应该是什么? 在这里思考一下,然后我们继续往下走.
我们可以在call openprocess这里做一个修改,接管原来的openprocess,将每次调用的函数修改成我们自己的.写出如下代码: [AppleScript] 纯文本查看 复制代码 push PID
push 0
push 114514
call MyOpenProcess
push 0
push eax
call TerminateProcess myopenprocess是我们自己实现的函数.这个函数跟OpenProcess一样,也有三个参数,也有相同的返回值类型.在这个函数里,我把传入的三个参数(内核中的openprocess是四个参数,这里用用户层的openprocess讲原理)传递给真正的OpenProcess,而把OpenProcess的返回值做为myopenprocess的返回值,那么这套流程下来肯定也是可以执行的,写出如下的代码: [C++] 纯文本查看 复制代码 HANDLE MyOpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId)
{
return OpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId);
}
有一些帅气的同学可能要问了:这样做执行是可以执行,但是何必费力做一个包装函数,再调用原函数?这样的意义是什么? 现在这套流程下来非常正常,我们做的这个包装函数没有意义.但是现在我们假设,如果有一个恶意进程PID=1145,你需要写个结束程序来结束这个PID=1145的进程,这个恶意进程却给你安装了这个拦截.但是,它稍微的修改了下函数,变成这样: [C++] 纯文本查看 复制代码 HANDLE MyOpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId)
{
if(dwProcessId == 1145)
{
return NULL;
}
return OpenProcess(dwDesiredAccess,bInheritHandle,dwProcessId);
}
这样下来,会怎么样?你还能结束恶意进程吗? 当然不能 因为你如果打开PID为1145的进程时候,得不到正确的句柄返回值,返回值为NULL. 这个方法叫hook.我们也可以在OpenProcess的函数头进行hook. 而我把这种方法叫拦截.截获后,可阻止,可改变,可放行.hook,无非是截获流程,根据自己的需要替换,阻止,放行而已 r3层的r0层的hook原理一样,大道至简,现在很多教驱动开发的讲hook一上来就说内核,ssdt,弄的大家一头雾水.驱动hook就是截获api在内核的执行流程,然后根据自己的需要替换,阻止,放行. 理论上你会r3的hook,你一定会r0的hook
明白了HOOK原理后,那么内核hook就很简单了,只要我们在API函数执行流程走到内核后在内核流程中进行截断,处理,就是内核hook了 我们在内核流程的位置选择了SSDT表,那么,SSDT表是什么? SSDT(System Services Descriptor Table),系统服务描述符表。这个表就是一个把ring3的Win32 API和ring0的内核API联系起来。SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。 现在我们以OpenProcess来说明,看下SSDT表在函数执行流程中起的作用! 函数的执行流程以OpenProcess是这样.先说明,下面再解释 #r3 OpenProcess进入ntdll.dll的NtOpenProcess或ZwOpenProcess函数(是同一个地址) #进入内核 #进入Ntoskrnl.exe的ZwOpenProcess #根据ZwOpenProcess的索引值,在SSDT表中找到对应的地址,再根据SSDT表对应地址的数据(Ntoskrnl.exe的NtOpenProcess)执行函数 #也就是Ntoskrnl.exe的NtOpenProcess才是真正的执行主体 函数在由R3进R0后,要通过SSDT表来选择最终执行的内核函数.假设我们想在内核中HOOK OpenProcess这个函数,只需要修改ssdt表中存放的NtOpenProcess地址,放入我们自己写的newNtOpenProcess函数地址.在我们newNtOpenProcess函数中,进行了修改或替换或阻止或放行后执行真正的NtOpenProcess.这就是所谓的SSDT hook 简单说下ssdt表中 hook OpenProcess这个函数的步骤 1.先得到Ntoskrnl.exe的NtOpenProcess函数的地址(用MmGetSystemRoutineAddress函数得到内核导出函数地址) 2.再得到SSDT表中Ntoskrnl.exe的NtOpenProcess函数应该存放的位置 (SSDT表的开始地址) +(索引)* 4(每个函数的间隔) SSDT表的开始地址 = [KeServiceDescriptorTable->SSDT表的指针] 索引 = [Ntoskrnl.exe的zwOpenProcess地址+1] 3.自己建立一个新函数,参数和返回值要和Ntoskrnl.exe的NtOpenProcess函数一模一样 4.把自己的新函数地址写到SSDT表中我们算出来的存放位置处(mov[(SSDT表的开始地址) + (索引) * 4(每个函数的间隔)],新函数地址) 代码如下(只给出关键代码): 声明挂钩宏: [C++] 纯文本查看 复制代码 #pragma pack(1) //SSDT表的结构
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //Used only in checked build
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
PMDL m_MDL;
PVOID *m_Mapped;
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; //变量名是不能变的,因为是从外部导入
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
#define HOOK_SYSCALL(_Function, _Hook, _Orig ) \
_Orig = (PVOID) InterlockedExchange( (PLONG) &m_Mapped[SYSCALL_INDEX(_Function)], (LONG) _Hook) 声明需要被hook的函数: [C++] 纯文本查看 复制代码 //新加
NTSYSAPI NTSTATUS NTAPI
ZwOpenProcess(
OUT PHANDLE ProcessHandle,IN ACCESS_MASK DesiredAccess,IN POBJECT_ATTRIBUTES ObjectAttributes,IN PCLIENT_ID ClientId OPTIONAL
);
typedef (*ZWOPENPROCESS)(
OUT PHANDLE ProcessHandle,IN ACCESS_MASK DesiredAccess,IN POBJECT_ATTRIBUTES ObjectAttributes,IN PCLIENT_ID ClientId OPTIONAL
);
ZWOPENPROCESS RealZwOpenProcess=NULL;
ZWOPENPROCESS OldZwOpenProcess=NULL; 挂载钩子: [C++] 纯文本查看 复制代码 HOOK_SYSCALL(ZwOpenProcess,HookZwOpenProcess,RealZwOpenProcess); 恢复钩子: [C++] 纯文本查看 复制代码 PVOID Oldfun = NULL;
HOOK_SYSCALL(ZwOpenProcess,RealZwOpenProcess,Oldfun); HookZwOpenProcess的实现: [C++] 纯文本查看 复制代码 NTSTATUS
HookZwOpenProcess(
OUT PHANDLE ProcessHandle,IN ACCESS_MASK DesiredAccess,IN POBJECT_ATTRIBUTES ObjectAttributes,IN PCLIENT_ID ClientId OPTIONAL
)
{
if(ClientId.UniqueProcess == 1145)
{
return STATUS_ACCESS_DENIED;
}
return ((ZWOPENPROCESS)(RealZwOpenProcess))(ProcessHandle,DesiredAccess,ObjectAttributes,ClientId);
} |