【51CTO.com 专家特稿】作者:宇文
我们知道,应用程序总是离不开系统内核所提供的服务,比如它要使用内存的时候,只要跟操作系统申请就行了,而不用自己操心哪里有空闲的内存空间等问题,实际上,这些问题是由操作系统的内核来代劳的。站在***的角度讲,如果能够控制内核,实际上就是控制了内核之上的各种应用程序。本文将向您介绍如何建立内核级钩子来控制操作系统向上提供的各种低级功能。有了内核级钩子,我们不但能够控制、监视其他程序并过滤有关数据,还能用其实现Rootkit本身及其它程序的隐形。
本文首先回顾系统调用表和内存保护方面的知识,然后讲解如何实现内核钩子,最后对一些重要的内核函数进行了简要的说明。
 
一、系统调用表
系统调用表又称系统服务表或者服务描述符表,是Windows 内核在进行各种系统操作时所需的一个函数指针表。也就是说,这个表中存放的是提供系统服务的各种函数的地址。当然,该表所指向的都是系统自身的一些函数,但是,如果我们对它做了手脚后,就可以让它指向我们自己的函数。这正是本文要讲解的重点。
读者一定要注意,修改系统调用表及替换内核函数时,会对系统全局产生影响,稍有不慎就会导致系统崩溃。所以,下手之前,最好对表中的各个函数要有足够的认识,然后才好用我们自己的函数替换这些内核函数的方法。你对它们了解得越多越深,在实现内核钩子的时候就越顺手。但话又说回来,这个系统调用表中的表项实在是太多了,有的指向字符串操作,有的指向客户机/服务器操作,等等。所以要在短时间内了解所有表项是不可能的,所以下文中对它们只做有选择的、概括的介绍。
 
二、内存保护
 
现代的Windows操作系统通常将系统调用表所在内存页设为只读来提供保护。如果不能克服这个问题,实施内核钩子技术就是痴人说梦。因为试图向只读内存写入数据也即修改只读内存区时,立刻就会蓝屏。为此,先让我们来了解一下内存保护方面的有关知识。
内存描述符表是内存保护的一大关键,具体定义详见微软DDK中的ntddk.h头文件,我们这里仅做简要介绍:
typedef struct _MDL {struct _MDL *Next;CSHORT Size;CSHORT MdlFlags;struct _EPROCESS *Process;PVOID MappedSystemVa;PVOID StartVa;ULONG ByteCount;ULONG ByteOffset;
} MDL, *PMDL;
#define MDL_MAPPED_TO_SYSTEM_VA 0x0001
#define MDL_PAGES_LOCKED 0x0002
#define MDL_SOURCE_IS_NONPAGED_POOL 0x0004
#define MDL_ALLOCATED_FIXED_SIZE 0x0008
#define MDL_PARTIAL 0x0010
#define MDL_PARTIAL_HAS_BEEN_MAPPED 0x0020
#define MDL_IO_PAGE_READ 0x0040
#define MDL_WRITE_OPERATION 0x0080
#define MDL_PARENT_MAPPED_SYSTEM_VA 0x0100
#define MDL_FREE_EXTRA_PTES 0x0200
#define MDL_IO_SPACE 0x0800
#define MDL_NETWORK_HEADER 0x1000
#define MDL_MAPPING_CAN_FAIL 0x2000
#define MDL_ALLOCATED_MUST_SUCCEED 0x4000
#define MDL_MAPPING_FLAGS (MDL_MAPPED_TO_SYSTEM_VA | \MDL_PAGES_LOCKED | \MDL_SOURCE_IS_NONPAGED_POOL | \MDL_PARTIAL_HAS_BEEN_MAPPED | \MDL_PARENT_MAPPED_SYSTEM_VA | \MDL_SYSTEM_VA | \MDL_IO_SPACE )
内存描述符表(MDL)的作用是将虚拟内存映射成物理页。如果将系统调用表所在内存页的MDL的MDLFlags成员设为MDL_MAPPED_TO_SYSTEM_VA 并且该页面被锁定的话,那么就可以使用内核钩子技术了。以下代码将可以达此目的:
#pragma pack(1)
typedef struct ServiceDescriptorEntry
{unsigned int *ServiceTableBase;unsigned int *ServiceCounterTableBase;unsigned int NumberOfServices;unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
PVOID* NewSystemCallTable;
PMDL pMyMDL = MmCreateMdl( NULL,KeServiceDescriptorTable.ServiceTableBase,KeServiceDescriptorTable.NumberOfServices * 4 );
MmBuildMdlForNonPagedPool( pMyMDL );
pMyMDL->MdlFlags = pMyMDL->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
NewSystemCallTable = MmMapLockedPages( pMyMDL, KernelMode );
好了,我们现在可以通过NewSystemCallTable来新建系统调用表了。系统调用表如下图所示。
图1  系统调用表示意图
进行挂钩时,可以使用以下宏:
#define HOOK_INDEX(function2hook) *(PULONG)((PUCHAR)function2hook+1)
#define HOOK(functionName, newPointer2Function, oldPointer2Function )  \oldPointer2Function = (PVOID) InterlockedExchange( (PLONG)
&NewSystemCallTable[HOOK_INDEX(functionName)], (LONG) newPointer2Function)
#define UNHOOK(functionName, oldPointer2Function)  \InterlockedExchange( (PLONG) &NewSystemCallTable[HOOK_INDEX(functionName)], (LONG)
oldPointer2Function)
使这些宏后,钩子技术会变得更简单,也更安全。因为InterlockedExchange 是原子函数,不会要求中止中断,所以交换指针的方式是安全的;另外,它也不需要用一个宏挂钩之后用另一个宏卸载钩子,所以也更方便。下图向我们展示了拦截系统调用表的过程。
图2  系统调用表拦截技术示意图
系统调用表数据结构KeServiceDescriptorTable不仅含有ntdll.dll 的全部函数指针,还存有系统调用表的基地址和表的大小,当建立我们自己的内存描述符表的时候,这些信息是不可或缺的。利用MDL_MAPPED_TO_SYSTEM_VA 标志,我们可以建立一个不可页出(即不会被换到内存之外)的MDL ,这样我们就可以将其锁定,并把返回的地址用于我们自己的系统调用表,重要的是,这个系统调用表是可写的。
三、定义钩子函数
内核钩子主要有三部分组成:要钩取的函数(在下文中称为目标函数)、替代要钩取的函数的函数(在下文中成为钩子函数)和系统调用表。前面部分介绍了系统调用表的问题,下面开始介绍钩子函数。一般说来,当定义自己的钩子函数时,可以先到DDK 的头文件中找到所想要的函数的原型,然后,稍加修改就能把目标函数变成钩子函数了。
例如,ZwMapViewOfSection 是一个内核函数,允许应用程序把从动态链接库导出的函数映射至内存。如果我们想要钩住这个内核函数,那么可以到ntddk.h头文件中查看其函数原型,如下所示:
NTSYSAPI
NTSTATUS
NTAPI
ZwMapViewOfSection(IN HANDLE SectionHandle,IN HANDLE ProcessHandle,IN OUT PVOID *BaseAddress,IN ULONG ZeroBits,IN ULONG CommitSize,IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,IN OUT PSIZE_T ViewSize,IN SECTION_INHERIT InheritDisposition,IN ULONG AllocationType,IN ULONG Protect );
有了函数原型,我们就可以确定指向目标函数的指针了,如下所示:
typedef NTSTATUS (*ZWMAPVIEWOFSECTION)(IN HANDLE SectionHandle,IN HANDLE ProcessHandle,IN OUT PVOID *BaseAddress,IN ULONG ZeroBits,IN ULONG CommitSize,IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,IN OUT PSIZE_T ViewSize,IN SECTION_INHERIT InheritDisposition,IN ULONG AllocationType,IN ULONG Protect );
ZWMAPVIEWOFSECTION OldZwMapViewOfSection;
钩子函数如下所示:
NTSTATUS NewZwMapViewOfSection(IN HANDLE SectionHandle,IN HANDLE ProcessHandle,IN OUT PVOID *BaseAddress,IN ULONG ZeroBits,IN ULONG CommitSize,IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,IN OUT PSIZE_T ViewSize,IN SECTION_INHERIT InheritDisposition,IN ULONG AllocationType,IN ULONG Protect )
{NTSTATUS status;DbgPrint("comint32: NewZwMapViewOfSection called.");//我们可以对输入为所欲为,既可以马上返回,也可以继续执行原函数status = OldZwMapViewOfSection(SectionHandle,ProcessHandle,BaseAddress,ZeroBits,CommitSize,SectionOffset OPTIONAL,ViewSize,InheritDisposition,AllocationType,Protect );// 我们可以在此对输出为所欲为,想返回什么,就返回什么return status;
}
好了,钩子技术的三大件已经准备好了。现在,我们就可以像下面这样使用它们:
HOOK( ZwMapViewOfSection, NewZwMapViewOfSection, OldZwMapViewOfSection );
如果你打算使用DriverUnload ()的话,可千万不要忘了卸载钩子。
四、内核函数系列
经过上面的介绍,我们已经了解了系统调用表有关知识,也已知道如何拦截系统调用表中的函数,下面,我们再来了解一下我们要钩取的函数:目标函数。这方面,如果我们不仅了解系统调用表中有哪些函数,还知道这些函数的工作机制就最好了。但实际上,ntdll.dll 中的导出函数有好几百个,别说一个一个的探究,就是把它们都列出来,看着看着头都大了。幸运的是,我们不必了解每个函数,只要了解其所在的系列就行了。为什么这么说?因为微软已经按照函数的功能对Ntdll.dll的导出函数进行了分组,并冠以意义明确的前缀,所以根据函数系列的前缀就能明白它们的大体功能了。下面对这些函数系列进行简单的介绍:
1.KiEtw系列:本系列内核函数用于系统内核,这些函数只能从内核的内部进行调用,常用的有:KiUserCallbackDispatcher、KiRaiseUserExceptionDispatcher、KiUserApcDispatcher、KiUserExceptionDispatcher等。
2.Csr系列:此系列函数用于客户机和服务器运行时,如果您想拦截客户机/服务器方面的操作,那么就需要对Csr系列内核函数做进一步的了解。常见的有:CsrClientCallServer、CsrCaptureMessageBuffer、CsrConnectClientToServer和CrsNewThread等。
3.Ldr系列:本系列内核函数用于加载程序管理器,如果你打算拦截加载程序的话,那么请进一步考察这组以Ldr为前缀的函数,常用的有:LdrInitializeThunk、LdrLockLoaderLock、LdrUnlockLoaderLock、LdrGetDllHandle、LdrGetProcedureAddress等。
4.Dbg系列:本系列内核函数用于调试管理,如果打算拦截调试操作的话,那么请进一步考察这组以Dbg为前缀的函数,常用的函数包括:、DbgBreakPoint、DbgUserBreakPoint、DbgPrint和DbgUiConnectToDbg等。
5.Etw系列:本系列内核函数用于追踪窗口事件,如果你打算拦截追踪之类的操作的话,那么请进一步考察这组以Etw为前缀的函数。常用的函数包括:EtwTraceEvent、EtwEnableTrace、EtwGetTraceEnableLevel和EtwGetTraceEnableFlags等。
6.Rtl系列:本系列内核函数用于运行时库,以Rtl为前缀的函数可以完成多种操作,例如字符串、线程、资源、临界区、安全对象的初始化和使用,内存、进程异常和数据类型的处理,还用于完成定时器、堆、IPv4和IPv6方面的操作,以及压缩和解压缩等。
7.Pfx系列:本系列内核函数用于ANSI字符串操作,如果你打算拦截ASNI串表方面的操作的话,就需要进一步了解这些函数。常用的包括:PfxInitialize、PfxRemovePrefix、PfxInsertPrefix、PfxFindPrefix等。
8.Zw系列:本系列内核函数用于文件和注册表方面的操作,比如文件操作、注册表操作、访问进程、事件操作、令牌操作、进程操作和端口操作等。
这里介绍的只是内核函数中的一部分,限于篇幅其他部分在此不作介绍。
六、结束语
 
本文深入介绍了系统调用表和内存保护方面的知识,并介绍了实现钩子函数的方法,最后对一些重要的内核函数进行了简要的说明。有了内核级钩子,我们不但能够控制、监视其他程序并过滤有关数据,还能达到隐藏Rootkit本身及其它程序的目的。需要说明的是,尽管可以通过内核钩子技术来实现rootkit所需的一些功能,但是,现实中的rootkit通常组合使用多种其它技术,如进程注射、分层驱动过滤等。更多的技术,将在后文中分别加以介绍。

转载于:https://blog.51cto.com/security/77964

如何建立内核级钩子控制操作系统实现程序隐身相关推荐

  1. 【梅哥的Ring0湿润插入教程】重磅第三课:Ring0下的PE Loader及重加载内核秒杀一切内核级钩子(上篇)...

    [梅哥的Ring0湿润插入教程] Email:mlkui@163.com 转载请注明出处,谢绝喷子记者等,如引起各类不适请自觉滚J8蛋! 第三课:Ring0下PE Loader及重加载内核绕过一切内核 ...

  2. 操作系统--用户级线程和内核级线程

    在多线程操作系统中,各个系统的实现方式并不相同.在有的系统中实现了用户级线程,有的系统中实现了内核级线程 1.内核级线程: (1)线程的创建.撤销和切换等,都需要内核直接实现,即内核了解每一个作为可调 ...

  3. 哈工大李治军老师操作系统笔记【10】:内核级线程实现(Learning OS Concepts By Coding Them !)

    文章目录 0 回顾 1 实现 1.1 int 0x80 fork(中断入口) 1.2 进入核心态 1.3 system_call(中断切换中间三段) 1.4 中断出口 1.3 switch_to 1. ...

  4. 操作系统--用户级线程与内核级线程

    一.多进程是操作系统基本图像 进程都是在内核进行 二.用户级线程 2.1线程引入 可以切指令不切表,也就是资源不动,指令执行分开,更加轻量化,从而提高效率,保留并发优点,避免进程切换代价,也就引入了线 ...

  5. linux内核网络钩子函数使用,Linux内核IOCTL网络控制框架实现实例分析

    4.6.inet_ioctl函数 由于inet_ioctl函数内容分支很多,但功能.处理不难理解,所以我把一些不常见的内容都省去,挑简单重要的说,完全在于抛砖引玉: static int inet_i ...

  6. 操作系统(李治军) L12内核级线程的实现

    只有支持了进程才可以管理CPU 图:五段论 整个过程:从用户栈->内核栈->TCB->TCB完成切换(TCB用switch_to完成TCB的切换完成内核栈的切换)->内核栈切换 ...

  7. 哈工大操作系统学习笔记五——内核级线程实现

    哈工大os学习笔记五(内核级线程实现) 文章目录 哈工大os学习笔记五(内核级线程实现) 一. 中断入口.中断出口(前后两段) 1. 从int中断进入内核(中断入口第一段) 2.中断出口(最后一段) ...

  8. 操作系统——用户级线程和内核级线程(L10,L11,L12)

    用户级线程: 不同的进程的映射表不同,当一个进程中断了,我们需要切换到另外一个线程,就需要我们切换指令执行序列,切换与设备等各种资源,这样的效率是很低的 线程保留了并发的特点,又避免了进程切换的代价. ...

  9. OS / 线程的 3 种实现方式(内核级,用户级 和 混合型)

    1 .线程的 3 种实现方式 在传统的操作系统中,拥有资源和独立调度的基本单位都是进程.在引入线程的操作系统中,线程是独立调度的基本单位,进程是资源拥有的基本单位.在同一进程中,线程的切换不会引起进程 ...

最新文章

  1. 资料分享:送你一本《数据结构(C语言版)》电子书!
  2. CentOS 7.1云服务器 配置FTP服务器vsftpd
  3. VS2010 连接SQLSERVER数据库步骤
  4. 枚举遍历法,你能循环遍历所有的枚举值吗?
  5. 学习笔记(43):Python实战编程-事件处理简介
  6. H3C交换机设置DHCP中继,配合Linux 服务器为多VLAN提供DHCP地址分配服务
  7. c++读取excel_Java 嵌入 SPL 轻松实现 Excel 文件合并
  8. Netty学习总结(4)——图解Netty之Pipeline、channel、Context之间的数据流向
  9. 青年会会训的一些探究
  10. 最后2天,BDTC 2019早鸟票即将售罄,超强阵容及议题抢先曝光!
  11. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_17-CMS前端工程创建-单页面应用介绍...
  12. 信息系统项目管理师考试大纲(第2版)
  13. mysql 查询父子关系_递归查询具有父子关系的表
  14. 从模型制作(3dmax)到网页显示(babylonjs)全过程介绍
  15. 记一道面试算法题: 某学校需要一个能给学领前儿童自动出三个数加减法的算术题的程序(此处省略一万个字)
  16. 机器人视觉硬件方案设计对比
  17. element上传图片的时候额外参数
  18. 结婚仪式的过程(供朋友们参考)
  19. 80核处理器_最受欢迎的处理器 酷睿i5-9400F果然霸榜了
  20. 识别不了移动硬盘的解决方法

热门文章

  1. linux 快组描述符,Linux 进程描述符 task struct
  2. 暗棋单机版_中国暗棋游戏下载-中国暗棋下载v1.0.0 安卓版-单机手游网
  3. python 操作excel神器_【转】多图+代码 | 详解Python操作Excel神器openpyxl的各种操作!...
  4. rvest | 网络爬虫初步——使用CSS选择器
  5. 继续教育自动听课软件_绵阳继续教育 自动挂机学习软件 v2019.5.31
  6. 分支限界算法c语言_算法学习计划
  7. jmeter 登录用户并发压力测试案例_测试模型构建及场景用例设计
  8. 职业高中计算机网络试讲稿,《初识我的电脑》试讲稿+答辩
  9. 一个漂亮的Bootstrap查找查询框,带下拉选择按钮
  10. 注册表把html设置成桌面,[注册表] 将Windows 10默认应用程序设置页面添加到桌面右键菜单中...