软件工程详谈内核的InlineHook实现.docx

上传人:b****6 文档编号:5639856 上传时间:2022-12-29 格式:DOCX 页数:21 大小:38.41KB
下载 相关 举报
软件工程详谈内核的InlineHook实现.docx_第1页
第1页 / 共21页
软件工程详谈内核的InlineHook实现.docx_第2页
第2页 / 共21页
软件工程详谈内核的InlineHook实现.docx_第3页
第3页 / 共21页
软件工程详谈内核的InlineHook实现.docx_第4页
第4页 / 共21页
软件工程详谈内核的InlineHook实现.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

软件工程详谈内核的InlineHook实现.docx

《软件工程详谈内核的InlineHook实现.docx》由会员分享,可在线阅读,更多相关《软件工程详谈内核的InlineHook实现.docx(21页珍藏版)》请在冰豆网上搜索。

软件工程详谈内核的InlineHook实现.docx

软件工程详谈内核的InlineHook实现

前置知识:

汇编驱动windbg函数参数调用

关键词:

堆栈平衡inlinehook

详谈内核三步走InlineHook实现

(一)Inlinehook原理

Inlinehook通俗的说就是对函数执行流程进行修改,达到控制函数过滤操作的目的。

理论上我们可以在函数任何地方把原来指令替换成我们的跳转指令,也确实有些人在inline

的时候做的很深,来躲避inline的检测,前提是必须对函数的流程和指令非常熟悉,且这种深层次的inlline不具有通用性,稳定性也是问题。

本文讨论的是具有通用性的两类inline的实现。

Inlinehook原理:

解析函数开头的几条指令,把他们Copy到数组保存起来,然后用一个调用我们的函数的几条指令来替换,如果要执行原函数,则在我们函数处理完毕,再执行我们保存起来的开头几条指令,然后调回我们取指令之后的地址执行。

用下图来解释:

原函数:

开头指令A

指令B

Inline后:

JMPMyFunction

指令B

MyFunction:

处理函数

JMPResumeFunction

ResumeFunction:

开头指令A

JMP回去

整个Inlinehook的过程就大体这样,中间牵扯到对函数的检查,地址的获取就直接调用函数即可。

本文所要讨论的两类Inlinehook都是基于上面原理。

说明三点:

1、堆栈平衡是重中之重,参数压栈也需要格外注意

2、R0模式下内存是不允许写的,需要去除写保护,设置CR0寄存器

3、提高中断级别到DPC,禁止线程切换

(二)inlinehook应用

Inlinehook可分为两类:

(1)inline导出函数,选择ObReferenceObjectByHandle做例子。

(2)inline未导出函数,选择KiInsertQueueApc做例子。

导出函数前几个字节可以利用windbg自己查看是什么内容,而未导出函数就需要自己解析指令确定需要hook几个字节,其间还有很多问题需要注意。

当大家真正的弄懂了我这篇文章,回头再看inlinehook就会觉得inline也不过如此。

下面通过2个例子来讲inlinehook的使用(这部分知识网上也有很多,但都很零散不系统,本文部分思路及代码的确参考了网上资源,有抄袭之嫌,希望读者谅解。

我一直强调“授人以鱼不如授人以渔”,代码并不重要,关键是思想。

1、inlinehookObReferenceObjectByHandle保护进程

ObReferenceObjectByHandle属于ntoskrnl.exe导出函数,在内核中调用频繁。

NtCreateProcess创建进程需要调用ObReferenceObjectByHandle,NtTerminateProcess需要调用ObReferenceObjectByHandle,基于这我们就可以利用Hook来保护进程同时屏蔽进程的创建。

效果:

已经运行的记事本任务管理器无法结束

流程:

HookObReferenceObjectByHandle------DetourMyObReferenceObjectByHandle----------UnHookObReferenceObjectByHandle

核心代码分析如下:

//=======================================inlineHOOKObReferenceObjectByHandle===========================

//ObReferenceObjectByHandle是ntoskrnl.exe导出函数,采用HOOK前五个字节的方式

//字节型数据unsignedchar

ULONGCR0VALUE;

BYTEOriginalBytes[5]={0};//保存原始函数前五个字节

BYTEJmpAddress[5]={0xE9,0,0,0,0};//跳转到HOOK函数的地址

externPOBJECT_TYPE*PsProcessType;

NTKERNELAPINTSTATUSObReferenceObjectByHandle(

INHANDLEHandle,

INACCESS_MASKDesiredAccess,

INPOBJECT_TYPEObjectTypeOPTIONAL,

INKPROCESSOR_MODEAccessMode,

OUTPVOID*Object,

OUTPOBJECT_HANDLE_INFORMATIONHandleInformationOPTIONAL

);

//HOOK函数

NTSTATUSDetourMyObReferenceObjectByHandle(

INHANDLEHandle,

INACCESS_MASKDesiredAccess

INPOBJECT_TYPEObjectTypeOPTIONAL,

INKPROCESSOR_MODEAccessMode,

OUTPVOID*Object,

OUTPOBJECT_HANDLE_INFORMATIONHandleInformationOPTIONAL);

//

//hook流程HookObReferenceObjectByHandle---DetourMyObReferenceObjectByHandle---UnHookObReferenceObjectByHandle

voidHookObReferenceObjectByHandle()

{

//赋值前面定义的数组

KIRQLIrql;

KdPrint(("[ObReferenceObjectByHandle]:

0x%x",ObReferenceObjectByHandle));//地址验证

//保存函数前五个字节内容

RtlCopyMemory(OriginalBytes,(BYTE*)ObReferenceObjectByHandle,5);

//保存新函数五个字节之后偏移

*(ULONG*)(JmpAddress+1)=(ULONG)DetourMyObReferenceObjectByHandle-((ULONG)ObReferenceObjectByHandle+5);

//开始inlinehook

//关闭内存写保护

_asm

{

pusheax

moveax,cr0

movCR0VALUE,eax

andeax,0fffeffffh

movcr0,eax

popeax

}

//提升IRQL中断级

Irql=KeRaiseIrqlToDpcLevel();

//函数开头五个字节写JMP

RtlCopyMemory((BYTE*)ObReferenceObjectByHandle,JmpAddress,5);

//恢复Irql

KeLowerIrql(Irql);

//开启内存写保护

__asm

{

pusheax

moveax,CR0VALUE

movcr0,eax

popeax

}

}

_declspec(naked)NTSTATUSOriginalObReferenceObjectByHandle(INHANDLEHandle,

INACCESS_MASKDesiredAccess,

INPOBJECT_TYPEObjectTypeOPTIONAL,

INKPROCESSOR_MODEAccessMode,

OUTPVOID*Object,

OUTPOBJECT_HANDLE_INFORMATIONHandleInformationOPTIONAL)

{

_asm

{

movedi,edi

pushebp

movebp,esp

moveax,ObReferenceObjectByHandle

addeax,5

jmpeax

}

}

NTSTATUSDetourMyObReferenceObjectByHandle(

INHANDLEHandle,

INACCESS_MASKDesiredAccess,

INPOBJECT_TYPEObjectTypeOPTIONAL,

INKPROCESSOR_MODEAccessMode,

OUTPVOID*Object,

OUTPOBJECT_HANDLE_INFORMATIONHandleInformationOPTIONAL)

{

NTSTATUSstatus;

//调用原函数

status=OriginalObReferenceObjectByHandle(Handle,DesiredAccess,ObjectType,AccessMode,Object,HandleInformation);

if((status==STATUS_SUCCESS)&&(DesiredAccess==1))

{

if(ObjectType==*PsProcessType)

{

if(_stricmp((char*)((ULONG)(*Object)+0x174),"notepad.exe")==0)

{

ObDereferenceObject(*Object);

returnSTATUS_INVALID_HANDLE;

}

}

}

returnstatus;

}

voidUnHookObReferenceObjectByHandle()

{

//把五个字节再写回到原函数

KIRQLIrql;

//关闭写保护

_asm

{

pusheax

moveax,cr0

movCR0VALUE,eax

andeax,0fffeffffh

movcr0,eax

popeax

}

//提升IRQL到Dpc

Irql=KeRaiseIrqlToDpcLevel();

RtlCopyMemory((BYTE*)ObReferenceObjectByHandle,OriginalBytes,5);

KeLowerIrql(Irql);

//开启写保护

__asm

{

pusheax

moveax,CR0VALUE

movcr0,eax

popeax

}

}

驱动加载后,结束记事本程序如下:

(图一)

详细分析:

1、ObReferenceObjectByHandle分析

NTSTATUS 

  ObReferenceObjectByHandle(

    IN HANDLE  Handle,

    IN ACCESS_MASK  DesiredAccess,

    IN POBJECT_TYPE  ObjectType  OPTIONAL,

    IN KPROCESSOR_MODE  AccessMode,

    OUT PVOID  *Object,

    OUT POBJECT_HANDLE_INFORMATION  HandleInformation  OPTIONAL

    );

函数原型如上,由句柄获取对象指针,函数返回值:

STATUS_SUCCESS调用成功

STATUS_OBJECT_TYPE_MISMATCH

STATUS_ACCESS_DENIED权限不够

STATUS_INVALID_HANDLE无效句柄

调用NtTerminateProcess需要调用ObReferenceObjectByHandle,因此我们通过对函数返回值进程修改来达到保护进程。

但是NtCreateProcess(最终调用的PspCreateProcess)同样调用这个函数,如果不加区分的话,创建进程同样被禁止了,那么如何区分到底是谁在调用呢。

参考WRK,我发现可以通过第二个参数DesiredAccess来判别,创建进程和结束进程第二个参数明显不同,PROCESS_CREATE_PROCESS和PROCESS_TERMINATE,问题就解决了。

PspCreateProcess位于WRK-v1.2\base\ntos\ps\create.c

调用ObReferenceObjectByHandle代码:

Status=ObReferenceObjectByHandle(ParentProcess,

                                           PROCESS_CREATE_PROCESS,

                                           PsProcessType,

                                           PreviousMode,

                                           &Parent,

                                           NULL);

NtTerminateProcess位于WRK-v1.2\base\ntos\ps\psdelete.c

调用ObReferenceObjectByHandle代码:

st=ObReferenceObjectByHandle(ProcessHandle,

                                   PROCESS_TERMINATE,

                                   PsProcessType,

                                   KeGetPreviousModeByThread(&Self->Tcb),

                                   &Process,

                                   NULL);

DesiredAccess参数说明:

#definePROCESS_TERMINATE        (0x0001)//winnt

#definePROCESS_CREATE_THREAD    (0x0002)//winnt

#definePROCESS_SET_SESSIONID    (0x0004)//winnt

#definePROCESS_VM_OPERATION     (0x0008)//winnt

#definePROCESS_VM_READ          (0x0010)//winnt

#definePROCESS_VM_WRITE         (0x0020)//winnt

//begin_ntddkbegin_wdmbegin_ntifs

#definePROCESS_DUP_HANDLE       (0x0040)//winnt

//end_ntddkend_wdmend_ntifs

#definePROCESS_CREATE_PROCESS   (0x0080)//winnt

#definePROCESS_SET_QUOTA        (0x0100)//winnt

#definePROCESS_SET_INFORMATION  (0x0200)//winnt

#definePROCESS_QUERY_INFORMATION(0x0400)//winnt

#definePROCESS_SET_PORT         (0x0800)

#definePROCESS_SUSPEND_RESUME   (0x0800)//winnt

2、函数调用说明

C语言中我们调用一个函数就直接写函数名就可以,但是实际是进行了下面的操作:

把函数参数压入堆栈,压入函数返回地址,调用函数,为新函数开辟堆栈空间申请局部变量,

恢复堆栈保持堆栈平衡

(_stdcall调用方式)汇编代码就是:

Push参数4

Push参数3

Push参数2

Push参数1

Call函数;call指令同时完成2个操作,一是把返回地址压入堆栈,二跳转到调用函数入口地址

Pushebp

Movebp,esp

Subesp,XX;开辟栈帧空间

……

Addesp,XX

Popebp

Retn;恢复堆栈平衡

堆栈详细情况:

ESP

局部变量

EBP

返回地址

参数1

参数2

参数3

参数4

堆栈是由高地址到低地址。

参数就通过EBP来去,四字节对齐的

参数4----------------------EBP+0x14

参数3----------------------EBP+0x10

参数2----------------------EBP+0xc

参数1---------------------EBP+0x8

局部变量则通过Ebp-XX来获取

因此inline的时候要时刻考虑堆栈平衡,破坏了堆栈平衡就会导致函数崩溃。

我通常inlinehook的思路就是三步走:

HOOK函数-----DetourMy处理函数----------UnHook函数

处理函数中对返回结果或者中间数据进行修改处理,然后调用原始函数。

由于在我们处理的时候原始函数已经被hook了,所以我自己构造了一个原始函数,但是由于参数在我们hook前已经压人堆栈了,所以这里我们不用重新开辟栈帧,因此声名函数类型为_declspec(naked)

有人就会问那么你调用处理函数的时候,参数不是重复压栈了,这里请注意,我们是通过JMP方式跳转到我们处理函数入口地址的,而不是Call的形式,所以并没有执行上面所说的函数调用过程,参数仍然是原始函数的。

也就是说在真个inlinehook过程中我们不能破坏原始栈帧的EBP。

关于函数调用很栈帧的相关联系可能比较难理解,我也在尽肯能的用通俗的话来解释清楚,有什么不理解的地方或者个人见解欢迎大家跟我交流。

2、inlinehookKiInsertQueueApc对抗插APC杀进程

KiInsertQueueAPc为内核未导出函数,我下面提供的代码可以作为未导出函数inline的通用模板来使用,大家根据自己需要进行修改,基于inlineObReferenceObjectByHandle已经把原理分析了,这部分我就不详加分析,仍然采用的但不走,Hook函数---DetourMy函数---UnHook函数

直接看核心代码:

//===================inlinehookKiInsertQueueApc====================

//KiInsertQueueApc为内核未导出函数,可以从导出函数KeInsertQueueApc定位

//修改KiInsertQueueApc开头5字节

//处理函数思路:

apc-->kthread---apc_state--eprocess--进程名字

//HookKiInsertQueueApc---DetourMyKiInsertQueueApc---UnHookKiInsertQueueApc

ULONGCR0VALUE;

ULONGg_KiInsertQueueApc;

BYTEJmpAddress[5]={0xE9,0,0,0,0};//跳转到HOOK函数的地址

BYTEOriginalBytes[5]={0};//保存原始函数前五个字

VOIDFASTCALLDetourMyKiInsertQueueApc(INPKAPCApc,INKPRIORITYIncrement);

VOIDWPOFF()

{

_asm

{

pusheax

moveax,cr0

movCR0VALUE,eax

andeax,0fffeffffh

movcr0,eax

popeax

cli

};

}

VOIDWPON()

{

__asm

{

sti

pusheax

moveax,CR0VALUE

movcr0,eax

popeax

};

}

//1、获取KiInsertQueueApc地址

ULONGGetFunctionAddr(INPCWSTRFunctionName)//PCWSTR常量指针,指向16位UNICODE

{

UNICODE_STRINGUniCodeFunctionName;

RtlInitUnicodeString(&UniCodeFunctionName

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 外语学习 > 韩语学习

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1