[原创]SSDT InlineHook学习笔记
最近学习SSDT InlineHook,看了很多论坛前辈的文章,收获很多,代码很长,也很完善,但觉得有点难理解,太完善的代码会禁锢人的思维,发现问题通过努力自己解决,岂不理解得更加深刻?纸上得来终觉浅,绝知此事要躬行。下面说说我对InlineHook的理解,以NtOpenProcess为例。先看下正常的NtOpenProcess的执行流程,如图1:

我们应该怎么Hook这个函数呢,最简单的方法只需要把函数头的push 0C4h改为jmp xxxxxxx就可以了,当调用NtOpenProcess的时候,就会跳到我们的Hook函数上,就可以为所欲为了。这就是InlineHook,改变函数内部执行流程的Hook就是InlineHook。Hook后的函数,如图2:

我们看到原函数的push 04Ch已经变为jmp f7c15600,这个跳转的地址就是HookedNtOpenProcess的地址。那么问题来了,我们如何把原函数头部的push指令改为jmp指令,使其跳转到我们的Hooked函数上呢。我们必须要取得两个函数的地址,由于SSDT表是导出的,所以取得NtOpenProcess非常简单.
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
PVOID ServiceTableBase;
PULONG ServiceCounterTableBase;
ULONG NumberOfService;
ULONG ParamTableBase;
}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE;
Extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable; //KeServiceDescriptorTable为导出函数
Address=(ULONG)KeServiceDescriptorTable->ServiceTableBase+0x7A*4;
//存放NtOpenProcess的数组下标为7A,
OldServiceAddress=*(ULONG*)Address;
//取得Address中的内容,为NtOpenProcess地址。HookedNtOpenProcess函数是我们定义的,直接取就可以,这样jmp的地址就可以计算了。
jmpaddr=(ULONG)HookedNtOpenProcess-(ULONG)RealNtOpenProcess-5;为什么要这样计算跳转地址呢,直接jmp MyNtOpenProcess不可以吗,当然不行。在16位汇编下,jmp指令是分段内转移和段间转移的。32位下内存为flat模式,不再区分段内段外,可以理解为都是段内转移,所以jmp指令执行的时候不需要转移的目的地址,需要的是偏移。我们需要以原函数的第二条指令为基址计算偏移(push 04Ch占5字节),也就是图1中push offset nt!ObWatchHandles+0x25c (804daaa8)指令,写成如下格式可能更好理解
jmpaddr=(ULONG)HookedNtOpenProcess-((ULONG)RealNtOpenProcess+5);
Jmpaddr就是跳转位移,跟jmp的机器码e9组合起来,就是最关键的跳转指令了。如下图所示,这我Hook完之后的函数头,大家注意,e965336577这个字节码,e9为jmp指令的字节码,65336577倒过来看77653365为相对于原函数的偏移量。

我们可以反推一下Hooked函数=原函数+偏移量+5,我们用WinDbg看下是否正确,如下图:

果然,已经来到了Hooked函数头部,我们的计算是正确的。具体函数如下。
ULONG OldServiceAddress; //原来NtOpenProcess的服务地址
VOID Hook(int *PID)
{ //HANDLE pid;
ULONG temp;
ULONG Address;
ULONG jmpaddr;
Address=(ULONG)KeServiceDescriptorTable->ServiceTableBase+0x7A*4; //0x7A为NtOpenProcess服务ID
DbgPrint("Address:0x%08X\n",Address);
MyPID=(HANDLE)*PID;
OldServiceAddress=*(ULONG*)Address; //保存原来NtOpenProcess的地址
RealNtOpenProcess=(NTOPENPROCESS)OldServiceAddress;
DbgPrint("RealNtOpenProcess:0x%08X\n",RealNtOpenProcess);
DbgPrint("MyNtOpenProcess:0x%08X\n",MyNtOpenProcess);
__asm //去掉关内存保护
{
cli
mov eax,cr0
and eax,not 10000h
mov cr0,eax
}
temp=*((ULONG*)Address);
DbgPrint("temp:%08X\n",temp);
jmpaddr=(ULONG)MyNtOpenProcess-(ULONG)RealNtOpenProcess-5;
__asm
{
mov ebx,temp
mov eax,jmpaddr
mov byte ptr ds:[ebx],0xE9 //在原来函数头加jmp
mov DWORD ptr ds:[ebx+1],eax
}
__asm //恢复开内存保护
{
mov eax,cr0
or eax,10000h
mov cr0,eax
sti
}
DbgPrint("hook\n");
}Hook函数完成了,接下来看下执行主体的实现方法。NtOpenProcess在内核中的调用非常频繁,所以我们需要判断哪些程序不能发现我们保护的进程。这个函数有多种写法, 我更喜欢用naked函数,这里又要普及一下naked关键字了。裸函数就是不为你添加任何汇编指令的函数,真正的全都要自己写的函数,使用这个关键字有几点需要特别注意:
1.使用 naked 关键字必须自己构建 EBP 指针
2.必须显示返回,用联汇编的ret不是c的return
3.不能直接引用参数,需要用寄存器如ESP+8
4.栈平衡要尤其注意
最简单的我们可以把它写成这样子,什么都不做,直接返回四个参数,这样也Hook NtOpenProcess。
NTSTATUS __declspec(naked)(__stdcall MyNtOpenProcess)(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId)
{
__asm
{
retn 0x10 //执行保护后的返回
}我们现在把它扩展一下,根据进程ID来保护进程不被OD发现并且不能被任务管理器终结。
我们关注下第四个参数:
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;UniqueProcess这个成员变量是调用者传入的进程PID,只需要根据这个PID来判断是否使我们要保护的进程就可以了,因为不能直接访问参数所以用寄存器访问。
__asm
{
push eax
mov eax,[esp+0x14] //获取ClientId,因为是第四个参数所以【esp+10】,但上面有push eax 所以再加4
mov eax,[eax] //取得UniqueProcess
mov PID,eax //不能在naked函数里定义局部变量
pop eax
}PID和Access我定义的是全局变量,保存esp 用ebp来存放局部变量,应该也可以但我并没有试验。取到了PID之后呢与需要保护的PID对比
if(PID==MyPID)
{
__asm
{
mov [esp+4],0 //返回空句柄
mov eax,0xC0000022L //返回值STATUS_ACCESS_DENIED 无法发现进程
retn 0x10 //执行保护后的返回
}
}如果是的话ProcessHandle置零,如果函数成功会返回进程对象,我们不让他成功,返回值设置为0xC0000022L,含义如下
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
这个函数相当于我们只把进程对象置零,并且返回无权限,这个Hookd函数就结束了。但这还没有完,当我们需要放行的时候,还需要调用原来的NtOpenProcess。
__asm
{
push 0x0C4
mov edx,RealNtOpenProcess
add edx,5
jmp edx
}因为我们之前Hook的时候已经把push 04Ch改为了jmp xxxxxxx,所以调用原来函数的时候需要实现它的前五个字节也就是push 04Ch,然后跳到它的下一条指令继续执行。这样这个函数就写完了,可能有点不直观,再来个流程图,如图5

这样清晰多了。我们看下效果吧,driverMoniter载入。

我们已经无法通过任务管理器关闭计算器了。再看下OD能不能发现我们的进程。

OD看不到计算器了,我们的hook成功了。
文笔不好加之水平有限,错漏再算难免,只为做个记录,请大家批评指正。
代码如下:
inlineHook.rar
