DLL随机化是从Vista版本之后开始出现的,主要是为了增加漏洞的利用难度,但是XP系统DLL的基址都是固定的,那么出现了一个问题,如何让XP系统的DLL也随机化?
这个问题的抛出是在我踏入公司的第一个月 Leader闲谈时提出的,说可以尝试去想想,由于当时刚毕业,样本都不会怎么分析,所以一直将一个问题抛入脑后,直到距离毕业只剩一个月快一年的的时候,重拾了这个问题。
起初这个问题,我是通过多次运行EXE发现有时候其基址会一样,有时不一样(在Win10系统),突然来的灵感,于是有了第一个标题的内容。然后在别人告知下知道有个“XX”的产品,于是花了3天下班时间(毕竟工作是样本分析...)分析了一下,如果有不对的地方望告知,毕竟自己还很菜,难免很多地方理解不到位。
注:逆向优秀的代码仿佛是和作者跨时代的交流...好是好,就是有点太累hhh....
可执行文件(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地址改变
带着这两个猜想,于是就开始探究生活中活生生的例子“盾甲”。
1. 关键SYS的确认
对于关键sys文件的确认,只要对MmMapViewOfSection下手就行,然后就会容易的定位到关键模块,称为B模块吧。
2. 撸起袖子加油逆
只看关键部分其实会没有意思,因为对于这么好的代码,难得多看几眼。所以就从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随机化的操作。
在回调函数一开始,便获取其驱动的DEVICE_OBJECT对象,主要是为了获取其扩展结构体指针。
接着调用设备扩展中的函数初始化一些自定义结构体以及进行效验一些字段的正确性。
首先调用设备扩展的第一个函数,初始化包含SSDT和SSSDT表以及一些自定义字段的结构体。其次验证第一个字段是否为3和最后一个字段是否为0x3F0。
设备扩展对象第一个函数分析:
设备结构体的定义都在A模块中,这个模块主要是完成很多的初始化操作。后续会进一步看到。
在这个函数中,不难看出,主要申请了SSDT和SSSDT表的空间,并将其分配的地址保存在自己定义的结构体中。
大致逆向的结构体如下(还有一些字段没有去尝试..所以请忽略):
接着会再次调用初始化随机种子、申请自己定义的BITMAP空间(用于快速分配内存和记录内存分配情况)等操作:
接着还初始化了一个AVL树,这里不做详细研究,因为不会影响关键内容。
接着大量的调用设备扩展中的第二个函数,这个函数具体是完成了自己申请的SSDT和SSSDT的一些函数HOOK。
在这个函数中,首先会初始化一些函数的调用号,然后对某些函数进行替换,比较复杂。因为它分了3层,2001一层,4002一层,6003一层。经过分析,2001主要是用于序列号低于1000的,6003用于序列号高于1000的,对于中间一层主要用于处理一些比较特殊的序列号,而且所执行的相关操作有点像稀疏矩阵。因为大概看一下,它将比较高的序列号进行转换到1000内,然后只是简单的在6003层进行相应的设置标志。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-6-25 18:46
被烟花易冷丶编辑
,原因: