【上篇】从零起步开发Rootkit:内核空间初探

  • A+
所属分类:逆向工程

点击上方蓝字“Tools”一起玩耍

1

写在前面

相信大部分人和我一样,觉得但凡与内核或者是rootkit沾边的项目,都认为需要很深厚的系统功底和内核开发经验才能驾驭,加上有关windows系统ring0的API又总是生涩不堪,而大部分windows rootkit程序往往闭源 ,所以往往让我们与rootkit越来越远。。。至少在我写出属于我自己的rootkit之前是这样认为的。


本文的目的就在于把rootkit推荐到各位面前,你就会发现,其实rootkit也不是那么高大上的玩意,跟随我的脚步一步步走,构建属于自己的rootkit木马,不再遥不可及。

    

rootkit一般来说分为两种,基于微软未公开函数(以Zw和以Nt开头的系统函数),以及基于系统未公开漏洞的的。前者在利用OS本身提供给开发者的API进行隐藏和侵入系统内核。所谓未公开,是指微软对于一些系统函数,并没有给出相关系统文档和函数说明和定义,(可能是这些函数功能太强大了,防止被一些别有用心的人利用);而后者利用栈溢出,UAF等漏洞向内核空间加载代码。后者隐蔽性,稳定性更好,开发成本自然更高。


本文要介绍的是基于SSDT挂钩的木马,属于前者。SSDT劫持原理实现相对简单,并且有较强的隐蔽性,适于入门,或快速开发自己需要的rootkit。

    

代码中有很多引用的地方,尤为引用了一个人的SSDT框架。由于SSDT劫持技术已经比较成熟,所以可能网上有很多类似代码 (不过大部分都不能编译运行)。


目前这个软件能在2003+360卫士无任何提示运行。

当然,SSDT对于大牛们来说是绝对的老技术了,分享到这里,是希望和不懂这个或者没接触过内核但又希望了解的朋友一起交流下。
当然本文会将源代码一起发出来,以供学习讨论。
链接: 
http://pan.baidu.com/s/1gdH9P3X 

密码: yp5b

程序效果图:

【上篇】从零起步开发Rootkit:内核空间初探



2

内核知识库:Windows调用过程

想要构建一个SSDT rootkit ,我们首先要思考的问题就是要何时介入一个系统正常的调用,并且劫持它,众所周知,我们的系统里面有各种各样的“表”,也就是系统调用表。


比如:

IAT用户态模块导入的Windows DLL      

IDT内核态硬件相关        

GDT内核态内存段信息        

SSDT内核态存储系统调用函数地址        

IRP内核态处理IRP驱动使用的函数

这些表直观的说,就是一个一个数组,这些数组存储例程的地址。


所谓例程,例程的作用类似于函数,但含义更为丰富一些。例程是某个系统在ring0对外提供的功能接口或服务的集合。比如操作系统的API、服务等就是例程


而我们要做的事情,就是将我们设计的例程地址代替现有的,合法的调用表地址,(也就是挂钩),通过这样的手段钩住系统函数,这样每当这个系统函数被调用的时候,实际上先调用的是我们的函数,通过我们预设的逻辑篡改通过的信息,达到隐藏自己或是隐藏木马程序的目的。
 

所以我们的逻辑的第一步应该是这样:

NtSystemFunc * NtQueryFile; 

 //系统原有函数指针
NtQueryFile  = TrojanNtQueryFile;

  //通过SSDT表替换成注入到内核空间的恶意函数


【上篇】从零起步开发Rootkit:内核空间初探


那么这张表到底怎么从内核“弄到”我们的驱动程序中呢?

回答是:我们只需要一个声明和一个extern导出。


我们先根据MSDN给出的结构声明一个SSDT表:

【上篇】从零起步开发Rootkit:内核空间初探

然后从ntoskrnl.exe将SSDT表“装”到我们的指针中就好。
//导出由 ntoskrnl.exe 所导出的 SSDT
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;



3

混淆内核的黑手:劫持调用表篇

而篡改一个系统调用表之前,我们

①需要去掉Windows的页保护,以便让我们修改SSDT表。
②备份原有SSDT表。

③安装钩子(SSDT表操作)。

看起来很头痛呢! 没关系,我们一步步来就好。(感谢@小宝马的爸爸 他的博客实现了一个有关SSDT操作的框架,而我的rootkit调用了他的框架)。


【上篇】从零起步开发Rootkit:内核空间初探


1 | 去页面保护

为了安全起见,Windows XP及其以后的系统将一些重要的内存页设置为只读属性,这样就算有权力访问该表也不能随意对其修改,例如SSDT、IDT等。但这种方法很容易被绕过,我们只要将这些部分修改为可写属性就可以了,不过当我们的事情做完后记得把它们恢复为只读属性,不然会造成一些很难预料到的后果。
 

cr0寄存器到486的处理器版本被加入了“写保护”(Write Protect,WP)位,WP位控制是否允许处理器向标记为只读属性的内存页写入数据。所以我i们要做的是修改cr0寄存器。

 我们用汇编实现:

【上篇】从零起步开发Rootkit:内核空间初探



2 | 备份正常SSDT表

前面提到,SSDT表实际上就是数组,所以我们只需要以操作数组的方法操作这张表就好。


【上篇】从零起步开发Rootkit:内核空间初探


相信大家从上面的图片注意到,我们需要得到一个索引,并通过修改索引指向的内容达到劫持系统函数的目的。SSDThook框架的作者所用的算法 涉及到了以Zw和以Nt开头的系统函数,此处我们先来讲讲这两者的关系。

首先要说的是,windows系统里面有两组Zw&Nt,分别位于ring3和ring0态。ring3态的Zw&Nt位于Ntdll。我们直接让kd告诉我们两者的关系吧:


【上篇】从零起步开发Rootkit:内核空间初探


可以看出,Zw和Nt在ring3下其实是一样的,按照网上面的说法叫做“Nt只是Zw的别名函数”。而在ring0态就不一样了。下面给出ring0的Zw实现:


【上篇】从零起步开发Rootkit:内核空间初探


可以看到Zw函数在ring0下实际上就是把7Ah(OpenProcess函数的索引号)传递给SSDT表 KeServiceDescriptorTable

[07Ah] 。而ring0中的Nt函数才是真正的函数实现。所以我们要篡改SSDT表,会导致调用了Zw函数去工作的进程被我们劫持和蒙骗,而直接调用Nt函数的驱动是不会被劫持的比如说冰刃(所幸绝大多数程序都是从Zw进行调用的)。


【上篇】从零起步开发Rootkit:内核空间初探


那么通过如上两个宏定义函数,通过服务名称(NtQuerySystemInformation)计算得到SSDT索引号,再通过索引号得到在KeServiceDescriptorTable中函数的虚拟地址。



3 | 安装钩子

第三个也就是我们的核心啦,安装SSDT钩子。


前文里面我们备份了SSDT里面所有内容,又能计算出要劫持函数的索引号和真实地址,现在要做的事情就是替换掉他,也就是挂钩。


【上篇】从零起步开发Rootkit:内核空间初探


这里的oldService 和newService都是地址变量,唯一的区别在于oldService是Zw型,而newService是Nt型的,这很好理解。我们两个宏定义的目的就是通过Zw函数找到Nt函数的地址,并将newServie赋值到KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[ZwFunc],这样就实现了我们的SSDT表操作了。

【上篇】从零起步开发Rootkit:内核空间初探

未完待续




【上篇】从零起步开发Rootkit:内核空间初探

本文始发于微信公众号(T00ls):【上篇】从零起步开发Rootkit:内核空间初探

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: