DLL随机化是从Vista版本之后开始出现的,主要是为了增加漏洞的利用难度,但是XP系统DLL的基址都是固定的,那么出现了一个问题,如何让XP系统的DLL也随机化?这个问题是在我踏入公司的第一个月与 Leader闲谈时抛出的,说可以尝试去想想,由于当时刚毕业,样本都不会怎么分析,所以一直将这个问题抛之脑后,直到距离毕业差一个月就满一年的的时候,重拾了这个问题。起初这个问题,我是通过多次运行EXE发现有时候其基址会一样,有时不一样(在Win10系统),突然来的灵感,于是有了第一个标题的内容。然后在别人告知下知道有个“XX”的产品,于是花了3天下班时间。分析了一下,如果有不对的地方望告知,毕竟自己还很菜,难免很多地方理解不到位。注:逆向优秀的代码仿佛是和作者跨时代的交流……好是好,就是有点太累。可执行文件(EXE,后续都用此代替)的随机化其实比较简单,关键点在于是理解CONTROL_AREA、SEGMENT、FILE_OBJECT,三者结构体之间的关系(以后有时间再说...这个涉及映射、原型PTE的那套东西)。
只需要修改其中的_SEGMENT.BaseAddress即可让这个EXE加载地址变化。
以此类推我便在开机启动的时候尝试修改NTDLL的这个属性,结果带来的就是不断蓝屏……其实看一下蓝屏错误就能很容易找到问题。
既然修改了基址,那么你的基址内容就要申请,所以这个涉及到了VAD和BITMAP的知识(这个在被鸽的文章里面有介绍)。在这里大概说几句:1、VAD是给用户使用的API提供的接口,也是验证内存地址有效的一道关卡。
2、BITMAP是在VAD上的一层优化(快速分配),两者功能有交集(即保留内存分配信息)。此时有人会想既然有了位图,那么为什么还要有VAD呢?其实位图只是为了快速分配大内存,而且在Win7和Win10只有一部分才被投入使用。(因为它所在的地址也需要有效哦!)ExE的随机化如此简单吗?答案当然是不!在Win10中,对象内存重用的概念好像脱颖而出,比如:APC对象的复用,在exe映射的过程中,也涉及到了对象的重用。
重用就意为着地址不会改变(这就解释了开篇说EXE基址有时候变有时候不变的问题),当然还有一些标志问题。这里就简单抛砖引玉一下:在Win10中可以尝试想想exe内容不改变,多次运行基址会改变吗?改变一点内容,地址又会改变吗?等等。1、_SEGMENT.BaseAddress内容的提前内存占用
2、 _SEGMENT.BaseAddress地址改变
带着这两个猜想,于是就开始探究生活中活生生的例子“盾甲”。对于关键sys文件的确认,只要对MmMapViewOfSection下手就行,然后就会容易的定位到关键模块,称为B模块吧。
只看关键部分其实会没有意思,因为对于这么好的代码,难得多看几眼。所以就从B模块DriverEntry开始逆。主函数:
在驱动一开始就对当前系统版本进行了效验,于是得知该版本只适用于Major=5,Minor=1的版本。判断完版本后,就开始将partmgr.sys中movzx eax,byte ptr ds:[0xFFDFF051]的硬编码修改为movzx eax,byte ptr fs:[0x51],这一步我的猜测是为了代码的复用性因为别的版本FS基址会变化的(因为可能不止一个)。声明一下,get_mode_imagebase_size和get_sec_start_end_addr函数,都是逆完修复好idb,自己命名的名字,是有理论依据的!所以就不公开了。接着判断全局变量InitSafeBootMode是否开启安全模式,没有则查看注册表。
然后验证当前win32k.sys还没有加载(这里可以体现出这个函数的复用性)。前期验证工作完成后,便开始创建符号连接,初始化功能号函数。到这里和一般的驱动操作没什么区别。
接着利用固定的地址,初始化了一个链表。这里猜测可以是为了后续的同步操作。接着开始产生随机种子。产生的算法为[当前时间xor异常信息各个字段xor平台信息各个字段]。接着判断.data段起始地址的第一个字节。此时这里不调用。这里我没有去研究。
接着就调用IoRegisterBootDriverReinitialization函数来实现DLL随机化的操作。4
DriverReinitializationRoutine回调函数分析
在回调函数一开始,便获取其驱动的DEVICE_OBJECT对象,主要是为了获取其扩展结构体指针。
接着调用设备扩展中的函数初始化一些自定义结构体以及进行效验一些字段的正确性。首先调用设备扩展的第一个函数,初始化包含SSDT和SSSDT表以及一些自定义字段的结构体。其次验证第一个字段是否为3和最后一个字段是否为0x3F0。
设备结构体的定义都在A模块中,这个模块主要是完成很多的初始化操作。后续会进一步看到。在这个函数中,不难看出,主要申请了SSDT和SSSDT表的空间,并将其分配的地址保存在自己定义的结构体中。大致逆向的结构体如下(还有一些字段没有去尝试,所以请忽略):
接着会再次调用初始化随机种子、申请自己定义的BITMAP空间(用于快速分配内存和记录内存分配情况)等操作:
接着还初始化了一个AVL树,这里不做详细研究,因为不会影响关键内容。
接着大量的调用设备扩展中的第二个函数,这个函数具体是完成了自己申请的SSDT和SSSDT的一些函数HOOK。
在这个函数中,首先会初始化一些函数的调用号,然后对某些函数进行替换,比较复杂。因为它分了3层,2001一层,4002一层,6003一层。经过分析,2001主要是用于序列号低于1000的,6003用于序列号高于1000的。
对于中间一层主要用于处理一些比较特殊的序列号,而且所执行的相关操作有点像稀疏矩阵。因为大概看一下,它将比较高的序列号进行转换到1000内,然后只是简单的在6003层进行相应的设置标志。
5、接着调用设备扩展第三个函数,进行相关调用号标志位置位。
接着采用inlinehook的方式,对MmMapViewOfSection函数进行hook,这里利用的是微软在每个函数之上保留的5字节0xCC(微软用于热补丁的后手)。MmMapViewOfSection Hook完毕后便对ZwCreateSection函数进行hook,同时获取内核NT模块,对其中的PAGEVRFY节区的函数,进行逐个HOOK。
现在整个流程是讲解完毕了,接着就需要详细分析自定义的MmMapViewOfSection 函数是如何进行DLL随机化的。此处只对NTDLL的随机化进行讲解,其他的DLL不予讲解(处理流程还是不太一样) 绿色部分:验证和随机化部分
粉色部分:调用原来函数部分
黄色部分:重定位部分
只有文件的CONTROL_AREA中的FLAG标志,满足Image,才会进行随机化。(第一个条件)
这里可以看出对NTDLL的优待性。如果是NTDLL,则会对某一标志位进行置位影响后续操作。(第二个条件)
紧接着根据SECTION对象,获取CONTROL_AREA中的基址,FILE_OBJECT对象、映射的PFN数量以及根据进程ID获取进程对象等。接着判断进程对象中的SectionObject字段和当前section对象进行对比,这里是不相等的。然后并便进行AVL树查询,以及根据当前PFN数量分配位图,并计算其基址等。
1、计算位图大小(因为位图和VAD一样都是64K为一个单位,只有PTE是以页为单位)。2、将位图中根据1所计算出来的大小,都进行置1,这里的0x7800是对基址的限定。
此时便拥有了随机化的基址,其次便是对其进行修复重定位。
首先会有一个开启修复重定位的标志fix_mem_flag(自命名),在函数一开始部分,都是对其初始化为0的。
这里可以通过参数进行选择获取VirtualProtect的fProtect参数。
到此就结束了。纵观来看,其实思路比较简单,但是对其细节的把控至关重要,这里这可以体现出设计者的功底是如此的强大!!!
看雪ID:烟花易冷丶
https://bbs.pediy.com/user-home-847490.htm
*本文由看雪论坛 烟花易冷丶 原创,转载请注明来自看雪社区