对XP盾甲ASLR实现机制的逆向分析

这篇文章是去年为某公司的面试准备的,虽然最终没有被录用,但是在这过程中收获很多,总结出这篇文章与大家分享。对于逆向分析我纯属于爱好者水平,而且之前主要以Android smali为主,对Win32经验并不多,文章中大部分内容都是现学现卖,最终没有被录用的原因是还有一个重要的地方我没有分析出来。不过以我有限的经验,无法再继续,在此也希望有大神指点一二,不胜感激!!


360出的XP盾甲,为Windows XP实现了Vista以上版本才有的ASLR机制,这个机制会使绝大多数溢出类漏洞失效或难以利用。这也是为什么360这么自信的宣传360盾甲。下面是我对此机制实现过程的逆向分析,看看360用了什么黑科技搞定这件事情的。

什么是ASLR?

对溢出类漏洞的攻击,会用到许多系统共享库,在XP及之前的系统,这些共享库的内存地址是固定的。构造溢出代码时,可以很方便的调用这些共享库的API。例如:LoadLibrary函数在XP下地址是0x7C80AEEB,直接调用即可。当然还有其它的漏洞利用方式,但几乎都必须依靠固定的地址实现。ASLR全称是Address Space Layout Randomization(地址空间布局随机化)。它通过随机化共享库的加载地址,使以上攻击失效或难以利用。

ASLR的效果

这里我写了一小段程序ASLRTest,用来输出各个库的加载地址。输出结果如下:

未使用XP盾甲:

Ntdll的加载地址:7C920000
Kernel32的加载地址:7C800000
LoadLibrary函数的地址:7C80AEEB
MSVCR120.dll的加载地址:10000000
system函数的地址:100808C2
本程序customFunc函数的地址:00401000
按任意键继续。

使用XP盾甲后:

Ntdll的加载地址:77040000
Kernel32的加载地址:76CD0000
LoadLibrary函数的地址:76CDAEEB
MSVCR120.dll的加载地址:6BEB0000
system函数的地址:6BF308C2
本程序customFunc函数的地址:00401000
按任意键继续。

寻找关键点

ASLR的机制涉及到系统底层的东西,估计360会在驱动上做文章,因此首要目标就是360的驱动文件。经过排除,发现只需要保留以下两个驱动,就可以实现ASLR。

C:WINDOWSsystem32drivershookport.sys
C:WINDOWSsystem32drivers360rosdrv.sys

对于ASLR来说,应该涉及到Windows的内存分配功能。我猜测360使用了HOOK技术,来改变Windows默认的内存分配功能。由于hookport.sys是最先加载的驱动,名字里也带有hook,因此刚开始我花了大量时间在hookport.sys上,后来发现其仅仅实现一些初始化的操作,并没有具体的实现代码。HOOK活动也没有在hookport.sys中体现出来(从后期分析看,仅HOOK了一个函数)。

转变思路,使用XueTr工具查看内核钩子,发现了一个重要的线索:

\"360的内核HOOK列表\"

原来360rosdrv.sys才是关键。360使用了Inline Hook,回调函数都指向360rosdrv.sys中的代码。在这些被HOOK的函数中,发现MmMapViewOfSection函数与内存分配有关系,因此重点对这个函数进行跟踪。

验证思路

首先验证一下是不是这个函数。根据XueTr检测结果,360修改了MmMapViewOfSection入口处的2个字节(其实不止这些,后面会说到)。内核加载过后,暂停运行。找到内核MmMapViewOfSection入口处,添加硬件写入断点,继续运行。一会就会找到做Inline Hook的代码,在0xBA5C2EA2处。

注意:这里使用360rosdrv.sys的内存映射地址定位,以下相同。其内存映射基址是0xBA5B6000。

以下是对做Inline Hook代码的分析:

.text:BA5C2E50
.text:BA5C2E50 ; =============== S U B R O U T I N E =======================================
.text:BA5C2E50
.text:BA5C2E50 ; 对MmMapViewOfSection做Inline Hook
.text:BA5C2E50 ; Attributes: bp-based frame
.text:BA5C2E50
.text:BA5C2E50 sub_BA5C2E50 proc near                  ; CODE XREF: sub_BA5B8518+11A
.text:BA5C2E50                                         ; sub_BA5B8518+20C
.text:BA5C2E50
.text:BA5C2E50 MemoryDescriptorList= dword ptr -10h
.text:BA5C2E50 var_C= byte ptr -0Ch
.text:BA5C2E50 var_B= dword ptr -0Bh
.text:BA5C2E50 var_4= dword ptr -4
.text:BA5C2E50 arg_0= dword ptr  8
.text:BA5C2E50 arg_4= dword ptr  0Ch
.text:BA5C2E50 arg_8= dword ptr  10h
.text:BA5C2E50
.text:BA5C2E50 mov     edi, edi
.text:BA5C2E52 push    ebp
.text:BA5C2E53 mov     ebp, esp
.text:BA5C2E55 sub     esp, 10h
.text:BA5C2E58 mov     eax, ___security_cookie
.text:BA5C2E5D xor     eax, ebp
.text:BA5C2E5F mov     [ebp+var_4], eax
.text:BA5C2E62 mov     eax, [ebp+arg_0]                ; MmMapViewOfSection入口地址到eax
.text:BA5C2E65 mov     ecx, [ebp+arg_8]                ; 取得保存Mm新入口地址的变量的地址。这里是0xBA5FC3E0
.text:BA5C2E68 lea     edx, [eax+2]
.text:BA5C2E6B mov     [ecx], edx                      ; 将Inline Hook后的MmMapViewOfSection的入口地址保存在360rosdrv.sys的0xBA5FC3E0处,这应该是个全局变量。由于原始Mm函数入口的2个字节已被修改,这里的新入口是旧入口加2个字节,即DAE2C(原入口地址为DAE2A,相对内核基址偏移,下同),经过后期分析,360还需要调用原始的Mm函数,因此需要这个地址。
.text:BA5C2E6D mov     ecx, [ebp+arg_4]                ; 回调函数地址,这里是0xBA5BA3DA
.text:BA5C2E70 push    ebx
.text:BA5C2E71 mov     ebx, [eax]                      ; 获取DAE2A处的4个字节(8B FF 55 8B)。
.text:BA5C2E73 sub     ecx, eax                        ; 计算回调函数地址与原MmMapViewOfSection地址的差,下面做Inline Hook跳转时使用。
.text:BA5C2E75 mov     [ebp+var_B], ecx
.text:BA5C2E78 lea     ecx, [ebp+MemoryDescriptorList]
.text:BA5C2E7B push    ecx                             ; int
.text:BA5C2E7C push    9                               ; Length
.text:BA5C2E7E add     eax, 0FFFFFFFBh                 ; 计算得到地址[MmMapViewOfSection-5],对Mm函数的Inline Hook是从其地址之前5个字节开始的。不知为啥,微软在Mm函数之前留了5个字节的空位置,被0xCC填充,正好够一个长jmp跳转。
.text:BA5C2E81 and     ebx, 0FFFFF9EBh
.text:BA5C2E87 push    eax                             ; VirtualAddress
.text:BA5C2E88 mov     [ebp+var_C], 0E9h ; \'
.text:BA5C2E8C or      ebx, 0F9EBh                     ; 这是DAE2A~DAE2B的目标值
.text:BA5C2E92 call    sub_BA5B7380                    ; 锁住内存
.text:BA5C2E97 test    eax, eax
.text:BA5C2E99 jz      short loc_BA5C2EAB
.text:BA5C2E9B push    esi
.text:BA5C2E9C push    edi
.text:BA5C2E9D mov     edi, eax
.text:BA5C2E9F lea     esi, [ebp+var_C]
.text:BA5C2EA2 movsd                                   ; 执行MmMapViewOfSction Inline Hook的地方,将DAE25~DAE28(相对内核基址)CC CC CC CC修改为E9 B0 F5 F4
.text:BA5C2EA3 movsb                                   ; 将DAE29的0xCC修改为0x39
.text:BA5C2EA3                                         ; 以上两句其实是jmp指令,跳转到360rosdrv的回调函数中。其中的跳转地址在上面代码中已经计算出来。
.text:BA5C2EA4 add     eax, 5                          ; 指针前进5个字节,到DAE2A
.text:BA5C2EA7 xchg    ebx, [eax]                      ; 将DAE2A~DAE2B的8B FF修改为EB F9
.text:BA5C2EA9 pop     edi
.text:BA5C2EAA pop     esi
......

以下是Inline Hook前后MmMapViewOfSection的代码变化:

修改前:

......
MEMORY:8066AE25 db 0CCh ; 
MEMORY:8066AE26 db 0CCh ; 
MEMORY:8066AE27 db 0CCh ; 
MEMORY:8066AE28 db 0CCh ; 
MEMORY:8066AE29 db 0CCh ; 
MEMORY:8066AE2A ; ---------------------------------------------------------------------------
MEMORY:8066AE2A
MEMORY:8066AE2A ; __stdcall MmMapViewOfSection(x, x, x, x, x, x, x, x, x, x)
MEMORY:8066AE2A _MmMapViewOfSection@40:                 ; CODE XREF: MEMORY:805CBD62
MEMORY:8066AE2A mov     edi, edi
MEMORY:8066AE2C push    ebp
MEMORY:8066AE2D mov     ebp, esp
MEMORY:8066AE2F sub     esp, 20h
MEMORY:8066AE32 mov     eax, [ebp+20h]
MEMORY:8066AE35 mov     ecx, [eax]
MEMORY:8066AE37 and     dword ptr [ebp-4], 0
......

\"修改前\"

修改后:

......
MEMORY:8066AE25 jmp     sub_BA5BA3DA ; 5个0xCC修改为了一句jmp指令
MEMORY:8066AE2A
MEMORY:8066AE2A ; =============== S U B R O U T I N E =======================================
MEMORY:8066AE2A
MEMORY:8066AE2A ; Attributes: bp-based frame
MEMORY:8066AE2A
MEMORY:8066AE2A ; __stdcall MmMapViewOfSection(x, x, x, x, x, x, x, x, x, x)
MEMORY:8066AE2A _MmMapViewOfSection@40 proc near        ; CODE XREF: MmInitializeProcessAddressSpace(x,x,x,x)+3D2
MEMORY:8066AE2A                                         ; NtSecureConnectPort(x,x,x,x,x,x,x,x,x)+3CE
MEMORY:8066AE2A
MEMORY:8066AE2A var_20= byte ptr -20h
MEMORY:8066AE2A var_8= dword ptr -8
MEMORY:8066AE2A var_4= dword ptr -4
MEMORY:8066AE2A arg_0= dword ptr  8
MEMORY:8066AE2A arg_4= dword ptr  0Ch
MEMORY:8066AE2A arg_8= dword ptr  10h
MEMORY:8066AE2A arg_C= dword ptr  14h
MEMORY:8066AE2A arg_10= dword ptr  18h
MEMORY:8066AE2A arg_14= dword ptr  1Ch
MEMORY:8066AE2A arg_18= dword ptr  20h
MEMORY:8066AE2A arg_1C= dword ptr  24h
MEMORY:8066AE2A arg_20= dword ptr  28h
MEMORY:8066AE2A arg_24= dword ptr  2Ch
MEMORY:8066AE2A
MEMORY:8066AE2A jmp     short loc_8066AE25 ; 修改为了一句短跳转,跳转到上面一句长跳转指令
MEMORY:8066AE2C push    ebp
MEMORY:8066AE2D mov     ebp, esp
MEMORY:8066AE2F sub     esp, 20h
MEMORY:8066AE32 mov     eax, [ebp+arg_18]
MEMORY:8066AE35 mov     ecx, [eax]
MEMORY:8066AE37 and     [ebp+var_4], 0
......

\"修改后\"

修改后的代码为两个跳转,通过这两个跳转最终跳到360rosdrv中的处理代码。如下图:

\"跳转指令\"

通过上面的分析,我使用的验证思路是,在360的Inline Hook代码处设置断点,待360的代码做完Inline Hook后,将修改后的代码还原,看看ASLR是否还有效。经过验证,代码还原后ASLR机制就失效了。这说明很有可能这里就是实现ASLR的关键地方。

经过搜索,发现MmMapViewOfSection函数的资料很少,只在第三方开源内核reactos上找到了定义:

NTSTATUS NTAPI MmMapViewOfSection(_In_ PVOID SectionObject,
_In_ PEPROCESS Process,
_Inout_ PVOID * BaseAddress,
_In_ ULONG_PTR  ZeroBits,
_In_ SIZE_T CommitSize,
_Inout_opt_ PLARGE_INTEGER SectionOffset,
_Inout_ PSIZE_T ViewSize,
_In_ SECTION_INHERIT InheritDisposition,
_In_ ULONG AllocationType,
_In_ ULONG Protect 
)

不过,经过跟踪发现NtMapViewOfSection最终调用了MmMapViewOfSection,而MSDN上说NtMapViewOfSection与ZwMapViewOfSection是一样的,下面是ZwMapViewOfSection:

NTSTATUS ZwMapViewOfSection(
  _In_         HANDLE SectionHandle,
  _In_         HANDLE ProcessHandle,
  _Inout_      PVOID *BaseAddress,
  _In_         ULONG_PTR ZeroBits,
  _In_         SIZE_T CommitSize,
  _Inout_opt_  PLARGE_INTEGER SectionOffset,
  _Inout_      PSIZE_T ViewSize,
  _In_         SECTION_INHERIT InheritDisposition,
  _In_         ULONG AllocationType,
  _In_         ULONG Win32Protect
);

很多资料都指向了BaseAddress参数,这个参数默认是0,但是修改后,能够修改加载地址。因此重点对这个参数进行跟踪。

注意:由于之前我的分析记录将BaseAddress写成了ImageBase,因此BaseAddress名称在下文中会用ImageBase代替。

对Hook函数的分析

经过以上的Inline Hook后,对MmMapViewOfSection的调用会跳转到0xBA5BA3DA。看来0xBA5BA3DA处的函数是关键了。

注意:这里我把360的0xBA5BA3DA处的Hook函数命名为MmHook,下面会以这个名称代替。

MmHook函数流程

MmHook的流程比较绕,不过还是能看的出其大体的框架。刚开始,做了一些初始化工作。然后重点判断了当前内存域是内核域还是用户域,如果是内核域,则继续判断模块是否是符合修改基址的要求。如果是用户域,则判断模块是否已经被处理过。如果被处理过,则直接调用原始的MmMapViewOfSection函数。否则,进入基址的处理过程。代码分析如下:

.text:BA5BA3DA mov     edi, edi
.text:BA5BA3DC push    ebp
.text:BA5BA3DD mov     ebp, esp
.text:BA5BA3DF sub     esp, 30h
.text:BA5BA3E2 or      [ebp+var_14], 0FFFFFFFFh
.text:BA5BA3E6 push    ebx
.text:BA5BA3E7 push    esi
.text:BA5BA3E8 mov     esi, ds:ExGetPreviousMode
.text:BA5BA3EE push    edi
.text:BA5BA3EF xor     edi, edi
.text:BA5BA3F1 mov     [ebp+var_1], 0
.text:BA5BA3F5 mov     [ebp+var_3], 0
.text:BA5BA3F9 mov     [ebp+var_5], 0
.text:BA5BA3FD mov     [ebp+var_2], 0
.text:BA5BA401 mov     [ebp+var_4], 0
.text:BA5BA405 mov     [ebp+var_10], edi
.text:BA5BA408 mov     [ebp+ImageBase], edi            ; 以上是做初始化工作,将局部变量清零
.text:BA5BA40B call    esi ; ExGetPreviousMode         ; 查询当前的内存域是内核进程还是用户进程。
.text:BA5BA40D mov     ebx, [ebp+arg_8]                ; 取得模块基址的指针保存到ebx
.text:BA5BA410 cmp     al, 1
.text:BA5BA412 jnz     short loc_BA5BA44A              ; 如果是内核域,则跳转
.text:BA5BA414 mov     eax, [ebx]                      ; 判断当前为用户域进程(如果为内核域进程,则需要额外判断是否跳过修改基址的操作),此句将模块基址保存到eax
.text:BA5BA416 cmp     eax, edi                        ; 判断eax是否为0,为0则跳转到基址处理分支。eax保存了模块基址指针,未作处理的模块,基址为0,需要做基址处理。否则eax保存处理过后的基址地址。
.text:BA5BA418 jz      short loc_BA5BA452              ; 这个分支貌似是内存基址处理分支,大部分用户态模块和少数内核态模块都经此分支,内核态貌似有ntdll.dll进行了特殊处理,将7C920000基址改为了77920000,并将此地址保存到了全局变量BA5FBB7C处。
.text:BA5BA41A cmp     eax, offset off_10000           ; 比较模块基址与0x10000的大小
.text:BA5BA41F jnb     short loc_BA5BA44A              ; 如果模块基址大于0x10000,则跳转,之后会不做任何处理,调用原始Mm函数
......

对于符合要求的内核模块和用户模块,则会跳转到0xBA5BA452分支,从这个分支起,开始做基址的处理,期间还做了大量的判断工作,以区分不同的情况。

接着往下分析,我找到了四个调用原始MmMapViewOfSection函数的地方:

0xBA5BA5C4
0xBA5BA7D7
0xBA5BA8C6
0xBA5BA92F

其中只有0xBA5BA8C6处的调用,修改了MmMapViewOfSection的ImageBase参数。而且ImageBase与模块加载基址吻合,也就是说,之前的猜测是正确的,ImageBase的修改确实引起了模块加载基址的变化。并且,我猜测这个分支上面有计算地址的代码。从0xBA5BA8C6往上溯源,可以在0xBA5BA854处找到计算函数0xBA5C32DC。在分析这个函数之前,先得准备些基础的内容。

内存的位图管理方式

地址的计算,涉及到Windows内核的一种内存分配管理方式。这个方式网上资料很少(可能我搜索姿势不对),手头也没有相关的书籍。在看了好多零零散散的文章后,大概摸清了这种内存分配管理方式。

系统把内存分为很多区块,每个区块有固定的大小(具体到本文是0x10000)。然后开辟一个位图空间(Bitmap),这个空间的每一位,都代表相应的区块是否被占用。我们假设区块大小为0x10000,那么一个4字节(32位)位图空间,就可以管理0x4 * 0x8 * 0x10000 = 0x200000——也就是2KB大小的内存。这个位图空间如下图:

\"位图\"

上图标识为1的区块表示已被占用。如果我们需要3个区块的内存空间,从0索引位开始搜索,那么我们会得到8~10区块。

当然,我们还可以从指定索引位置搜索。比如,我们还是需要3个区块的内存空间,但我们不从0索引位开始搜索,我们指定从第20索引位开始搜索,这样我们得到的区块是20~22。这种方式是实现地址随机的关键,下面会用到。

我们再来看一个实例,假设我们需要管理640MB(0x280)的内存空间,每个内存块是64KB(0x10000)字节大小。那么我们需要640 * 1024 / 64 = 10240位,即10240 / 8 = 1280(0x500)字节的空间,来表示这640MB空间那些块用过,哪些没有用过。这也是360的做法,具体会在下文说到。

Windows内核提供了以下管理函数来实现这种内存管理方式:

VOID RtlInitializeBitMap(
  _Out_  PRTL_BITMAP BitMapHeader,
  _In_   PULONG BitMapBuffer,
  _In_   ULONG SizeOfBitMap
);
初始化一个位图空间。PRTL_BITMAP结构体是返回值,保存了位图空间的大小以及地址。

ULONG RtlFindClearBits(
  _In_  PRTL_BITMAP BitMapHeader,
  _In_  ULONG NumberToFind,
  _In_  ULONG HintIndex
);
寻找连续的指定大小的未使用的内存索引。BitMapHeader是使用RtlInitializeBitMap初始化的位图空间结构体。NumberToFind代表需要寻找的连续区块的个数。HintIndex代表起始的搜索位置,默认是从位图空间第0位开始搜索。之前我们说到过自定义搜索起始位置,这个参数就是用来实现这个功能的。

ULONG RtlFindClearBitsAndSet(
  _In_  PRTL_BITMAP BitMapHeader,
  _In_  ULONG NumberToFind,
  _In_  ULONG HintIndex
);
寻找并分配连续的指定大小的未使用的内存索引。比上面的函数多了一个分配的动作,如果能找到连续的区块,则将这些区块位置为1,表示已占用。

当然还有其它的,不过360主要使用了上面三个函数。

360内存分配的流程及地址随机化的实现

初始化工作,创建位图空间及单字节随机值

360rosdrv在初始化时,创建了一个可管理640MB大小内存的位图空间,用于模块内存加载地址的分配管理。同时,还创建了一个单字节的随机值HintIndex,这个随机值用于之后的地址随机化。以下是代码分析:

.text:BA5C326A
.text:BA5C326A ; =============== S U B R O U T I N E =======================================
.text:BA5C326A
.text:BA5C326A ; 生成RTL_BITMAP中Buffer所需的存储空间(0x500(1280)字节,0x2800(10240)位),同时产生HintIndex这个决定基址模块的随机值
.text:BA5C326A
.text:BA5C326A sub_BA5C326A    proc near               ; CODE XREF: sub_BA5BC8FA+53
.text:BA5C326A                 call    sub_BA5C27B8    ; 生成随机值
.text:BA5C326F                 push    4D49424Dh       ; Tag
.text:BA5C3274                 push    500h            ; NumberOfBytes
.text:BA5C3279                 and     eax, 0FFh       ; 取生成的随机数的末尾字节(HintIndex只需一个字节)
.text:BA5C327E                 push    0               ; PoolType
.text:BA5C3280                 mov     HintIndex, eax  ; 全局变量,内存块起始搜索位置。通过修改这个数值,可以达到模块基址随机变化的目的。
.text:BA5C3285                 call    ds:ExAllocatePoolWithTag ; 分配0x500(1280)字节的内存,用于存储位图空间数据
.text:BA5C328B                 test    eax, eax
.text:BA5C328D                 jnz     short loc_BA5C3290
.text:BA5C328F                 retn
.text:BA5C3290 ; ---------------------------------------------------------------------------
.text:BA5C3290
.text:BA5C3290 loc_BA5C3290:                           ; CODE XREF: sub_BA5C326A+23
.text:BA5C3290                 push    esi
.text:BA5C3291 位图大小为0x2800(10240)位
.text:BA5C3291                 push    2800h           ; SizeOfBitMap
.text:BA5C3296 位图空间数据的保存位置,就是上面分配的内存
.text:BA5C3296                 push    eax             ; BitMapBuffer
.text:BA5C3297                 mov     esi, offset BitMapHeader
.text:BA5C329C RTL_BITMAP结构体保存位置
.text:BA5C329C                 push    esi             ; BitMapHeader
.text:BA5C329D                 call    ds:RtlInitializeBitMap
.text:BA5C32A3                 push    esi             ; BitMapHeader
.text:BA5C32A4 将位图空间所有的位全部清零
.text:BA5C32A4                 call    ds:RtlClearAllBits
.text:BA5C32AA                 and     dword_BA5FC028, 0
.text:BA5C32B1                 xor     eax, eax
.text:BA5C32B3                 inc     eax
.text:BA5C32B4                 pop     esi
.text:BA5C32B5                 retn
.text:BA5C32B5 sub_BA5C326A    endp
.text:BA5C32B5
.text:BA5C32B5 ; ---------------------------------------------------------------------------

计算ImageBase

我们首先通过一个例子,来了解整个的计算过程,然后通过代码分析,看看具体的代码实现。

计算过程

计算最终的地址,需要3个参数:

0x7800: 起始内存地址的高16位,是个常量,写死在程序里的。换句话说,360将所有需要ASLR的模块加载到0x78000000以下的地址。
HintIndex: 位图空间的起始搜索位,是个8位的随机值,RtlFindClearBits中的参数之一。我们说过,位图空间的搜索不一定非得从第0位开始。
ModuleBlockSize: 模块所需内存区块的数量,每个区块的大小为0x10000。使用模块所占内存大小计算而来。

我们通过一个例子来说明整个地址的计算过程:

# 初始值,设
HintIndex = 0x7E
ModuleSize = 0x96000 # 模块所需内存大小

首先,由于内存的分配是以0x10000为单位分配的,那我们需要得到模块加载到内存需要多少个内存区块:

0x96000 >> 12 = 0x96
0x96 + 0xF = 0xA5
0xA5 >> 4 = 0xA

因此ModuleBlockSize = 0xA,模块至少需要0xA个内存区块。

然后,在10240个位的位图空间中,从HintIndex(也就是第0x7E位)开始,搜索具有0xA个连续空位(即置为0的位)的位置,这里假设搜索结果为BlockIndex = 0x1F0,表示在第0x1F0位处找到了0xA个连续的为0的位。换句话说,从第0x1F0个内存区块起有连续0xA个空着的内存区块。

最后,计算加载模块的起始内存地址:

0x7800 - BlockIndex = 0x7800 - 0x1F0 = 0x7610
0x7610 - ModuleBlockSize = 0x7610 - 0xA = 0x7606
0x7606 << 16 = 0x76060000

这样就计算出了模块的内存映射位置。也就是说,0x76060000~0x76100000为模块所占内存。

通过以上的计算过程,我们可以看到,如果HintIndex是随机的,那么模块加载的位置也是随机的。因为,在搜索位图空间的空位的时候,不同起始搜索位,计算得到的内存区块位置是不同的。具体例子可以看看上文“内存的位图管理方式”节中的例子。

代码分析

下面重点分析0xBA5C32DC函数的计算过程。刚开始,计算了模块所需要的内存区块:

.text:BA5C32DC
.text:BA5C32DC ; =============== S U B R O U T I N E =======================================
.text:BA5C32DC
.text:BA5C32DC ; 计算新地址的函数
.text:BA5C32DC ; Attributes: bp-based frame
.text:BA5C32DC
.text:BA5C32DC ; int __stdcall sub_BA5C32DC(KIRQL ModHandler, int, int ModSize, ULONG HintIndex)
.text:BA5C32DC sub_BA5C32DC    proc near               ; CODE XREF: sub_BA5BA3DA+3CE
.text:BA5C32DC                                         ; sub_BA5BA3DA+47A
.text:BA5C32DC
.text:BA5C32DC HashValue       = dword ptr -8
.text:BA5C32DC NumberToFind    = dword ptr -4
.text:BA5C32DC ModHandler      = byte ptr  8
.text:BA5C32DC arg_4           = dword ptr  0Ch
.text:BA5C32DC ModSize         = dword ptr  10h
.text:BA5C32DC HintIndex       = dword ptr  14h
.text:BA5C32DC
.text:BA5C32DC                 mov     edi, edi
.text:BA5C32DE                 push    ebp
.text:BA5C32DF                 mov     ebp, esp
.text:BA5C32E1                 push    ecx
.text:BA5C32E2                 push    ecx
.text:BA5C32E3                 push    ebx
.text:BA5C32E4                 mov     ebx, [ebp+ModSize]
.text:BA5C32E7                 push    esi
.text:BA5C32E8                 lea     eax, [ebp+HashValue]
.text:BA5C32EB                 push    eax             ; HashValue
.text:BA5C32EC                 mov     eax, dword ptr [ebp+ModHandler]
.text:BA5C32EF                 mov     esi, ebx
.text:BA5C32F1                 shr     esi, 0Ch
.text:BA5C32F4                 add     esi, 0Fh
.text:BA5C32F7                 add     eax, 30h ; \'0\'
.text:BA5C32FA                 shr     esi, 4          ; 以上是模块长度变换,计算出模块所需要的内存区块
.text:BA5C32FA                                         ; 例如:模块长度54000
.text:BA5C32FA                                         ; 54000 >> 12 = 54
.text:BA5C32FA                                         ; 54 + 0F = 63 ;对第五位进行天花板取整
.text:BA5C32FA                                         ; 63 >> 4 = 6
.text:BA5C32FA                                         ; 以上这些计算的目的就在于保证分配到的空间比模块所需要的空间大。
.text:BA5C32FA                                         ; 例如上面模块需要54000的空间,则会分配60000的空间。
.text:BA5C32FD                 push    eax             ; int
......

然后,搜索可以存放模块的连续内存区块并最终计算出地址:

......
.text:BA5C332F loc_BA5C332F:                           ; CODE XREF: sub_BA5C32DC+3E
.text:BA5C332F                 cmp     [ebp+HintIndex], 0
.text:BA5C3333                 push    edi
.text:BA5C3334                 jnz     loc_BA5C3401
.text:BA5C333A                 cmp     BitMapHeader.Buffer, 0
.text:BA5C3341                 mov     edi, offset BitMapHeader
.text:BA5C3346                 jz      short loc_BA5C335B
.text:BA5C3348                 push    HintIndex       ; HintIndex; 起始寻找位置
.text:BA5C334E                 push    esi             ; NumberToFind
.text:BA5C334F                 push    edi             ; BitMapHeader
.text:BA5C3350                 call    ds:RtlFindClearBits
.text:BA5C3356                 mov     [ebp+HintIndex], eax ; 寻找拥有指定大小连续区域的内存块。注意这里将返回值保存到了HintIndex变量中,从这时起HintIndex不再代表起始搜索位置,而是代表找到的空区块的起始位的索引,如果返回FFFFFFFF则为错误。
.text:BA5C3359                 jmp     short loc_BA5C335F
.text:BA5C335B ; ---------------------------------------------------------------------------
.text:BA5C335B
.text:BA5C335B loc_BA5C335B:                           ; CODE XREF: sub_BA5C32DC+6A
.text:BA5C335B                 or      [ebp+HintIndex], 0FFFFFFFFh
.text:BA5C335F
.text:BA5C335F loc_BA5C335F:                           ; CODE XREF: sub_BA5C32DC+7D
.text:BA5C335F                 cmp     [ebp+HintIndex], 0FFFFFFFFh
.text:BA5C3363                 jz      loc_BA5C3401
.text:BA5C3369                 mov     ebx, ds:KfAcquireSpinLock
.text:BA5C336F                 mov     esi, offset dword_BA5FC028
.text:BA5C3374                 mov     ecx, esi        ; SpinLock
.text:BA5C3376                 call    ebx ; KfAcquireSpinLock
.text:BA5C3378                 push    [ebp+HintIndex] ; HintIndex
.text:BA5C337B                 mov     [ebp+0Bh], al
.text:BA5C337E                 push    [ebp+NumberToFind] ; NumberToFind
.text:BA5C3381                 push    edi             ; BitMapHeader
.text:BA5C3382                 call    ds:RtlFindClearBitsAndSet ; 如果上面找到了连续的内存块,那么这里寻找并分配这块内存,用于模块加载。
.text:BA5C3388                 mov     dl, [ebp+0Bh]   ; NewIrql
.text:BA5C338B                 mov     ecx, esi        ; SpinLock
.text:BA5C338D                 mov     [ebp+HintIndex], eax
.text:BA5C3390                 call    ds:KfReleaseSpinLock
.text:BA5C3396                 cmp     [ebp+HintIndex], 0FFFFFFFFh
.text:BA5C339A                 jz      short loc_BA5C33FE
.text:BA5C339C                 mov     eax, 7800h      ; 基址计算因子,所有的新基址都是根据这个因子运算得出的。
.text:BA5C33A1                 sub     eax, [ebp+HintIndex]
.text:BA5C33A4                 sub     eax, [ebp+NumberToFind] ; 基址(7800) - 内存块起始索引([ebp+HintIndex]) - 内存块大小([ebp+NumberToFind])
.text:BA5C33A7                 shl     eax, 10h        ; 左移16位,得到真正的地址。假设HintIndex = 6E,内存块大小(NumberToFind) = 68,则地址为:
.text:BA5C33A7                                         ; (7800 - 68 - 6E) << 16 = 772A0000
.text:BA5C33AA                 cmp     eax, [ebp+arg_4]
.text:BA5C33AD                 jnz     loc_BA5C3450    ; 判断新基址与旧基址是否一样,不一样则跳转,一样则再计算一遍
.text:BA5C33B3                 mov     ecx, esi        ; SpinLock
.text:BA5C33B5                 call    ebx ; KfAcquireSpinLock
.text:BA5C33B7                 mov     ebx, [ebp+NumberToFind]
......

最后,将新计算出的ImageBase作为参数传递给原始的MmMapViewOfSection,实现基址的变化。

......
.text:BA5BA8A7 loc_BA5BA8A7:                           ; CODE XREF: sub_BA5BA3DA+4B9
.text:BA5BA8A7                 push    [ebp+arg_24]
.text:BA5BA8AA                 lea     eax, [ebp+ImageBase]
.text:BA5BA8AD                 push    [ebp+arg_20]
.text:BA5BA8B0                 push    [ebp+arg_1C]
.text:BA5BA8B3                 push    [ebp+arg_18]
.text:BA5BA8B6                 push    [ebp+arg_14]
.text:BA5BA8B9                 push    [ebp+arg_10]
.text:BA5BA8BC                 push    [ebp+arg_C]
.text:BA5BA8BF                 push    eax
.text:BA5BA8C0                 push    [ebp+arg_4]
.text:BA5BA8C3                 push    [ebp+arg_0]
.text:BA5BA8C6                 call    dword_BA5FC3E0  ; 调用原始MmMapViewOfSection函数的地方,ImageBase参数不为0而是新的基址,之前有计算基址的代码
......

内核加载是如何实现ASLR的?

通过以上分析,可以确定360是通过Inline Hook内核的MmMapViewOfSection函数,修改其ImageBase参数,实现的模块加载地址随机化。但是还有个问题,使用XP盾甲后,内核本身的加载地址也是随机的。而MmMapViewOfSection函数是内核提供的,也就是说,内核地址的随机,不可能是通过MmMapViewOfSection实现的。

内核是通过ntldr加载的,直觉告诉我,360肯定修改了ntldr,而且不可能是Hook方式,估计得直接修改ntldr。通过对比,确实发现有修改:

\"ntldr的修改\"

通过替换方式,发现使用原始ntldr后,内核ASLR失效了,内核加载到了默认地址0x804D8000处。替换为360修改后的ntldr,内核ASLR立即恢复。但是这里有个问题,ntldr仅仅被就改了几个字节,就实现了ASLR,这也太不可思议了,难不成是ntldr原本就有ASLR机制,只不过默认没有打开,而360打开了?

通过跟踪,最后发现,其实360用了个很巧妙的方法。原来,每次系统启动后,360都会重新打开ntldr修改一次,将相应的位置修改为另一个随机值。这样下一次启动的时候,内核加载地址就变了。另外,通过跟踪发现,对ntldr的改动,只有0x2674E处影响到了内核加载地址。另一些修改,是对SharedUserData内存地址的修改。这块区域也是黑客经常使用的地方,这里不做详述。以下是ntldr被修改的位置:

ntldr被修改的地方(地址含义:文件地址(内存加载地址)):
8788(4046C8) 01 改为 XX(随机,下同)
2674E(42268E) 40 改为 MM
2D01F(428F5F) FC 改为 YY
2D035(428F75) FC 改为 YY
2D041(428F81) FC 改为 YY
2D054(428F94) F0 改为 ZZ

以下是ntldr内核的加载流程以及上面说到的内核加载地址的保存位置。ntldr是从文件偏移0x50C0开始,映射到的内存的0x401000处,以下的地址都是内存映射地址。

以下代码是设置内核加载地址,包括360修改的位置(0x42268A处的地址是修改过的,原始的地址为0x400000):

......
seg001:00422678
seg001:00422678 loc_422678:                             ; CODE XREF: sub_421AE6+B76
seg001:00422678 mov     eax, dword ptr unk_43AF14       ; 处理内核加载地址的地方,从这可以修改内核加载地址
seg001:0042267D mov     edx, eax
seg001:0042267F neg     edx
seg001:00422681 sbb     edx, edx
seg001:00422683 mov     ecx, 800000h
seg001:00422688 and     edx, ecx
seg001:0042268A add     edx, offset unk_590000          ; 内核加载地址,修改这里会改变内核加载位置。这里是相对地址,在载入函数中还会为其加基址0x80000000
seg001:00422690 sar     edx, 0Ch
seg001:00422693 neg     eax
seg001:00422695 sbb     eax, eax
seg001:00422697 and     eax, ecx
seg001:00422699 add     eax, ecx
seg001:0042269B sar     eax, 0Ch
seg001:0042269E cmp     ds:byte_4369FC, 0
seg001:004226A5 mov     ds:dword_436A94, edx
seg001:004226AB mov     ds:off_4362A4, eax
seg001:004226B0 jz      short loc_4226C6                ; 读取内核路径字符串
seg001:004226B2 push    236Bh
seg001:004226B7 call    sub_417521
seg001:004226BC push    2B06h
seg001:004226C1 call    sub_417402
seg001:004226C6
seg001:004226C6 loc_4226C6:                             ; CODE XREF: sub_421AE6+BCA
seg001:004226C6 lea     eax, [ebp+var_384]              ; 读取内核路径字符串
seg001:004226CC push    eax
seg001:004226CD push    [ebp+var_CE4]
seg001:004226D3 call    sub_4238F2
seg001:004226D8 xor     edi, edi
......

上面代码设置内核加载地址后,会在以下位置调用加载代码:

......
seg001:00422718 lea     eax, [ebp+var_D14]
seg001:0042271E push    eax
seg001:0042271F push    edi
seg001:00422720 push    edi
seg001:00422721 push    ebx
seg001:00422722 lea     eax, [ebp+var_384]
seg001:00422728 push    eax
seg001:00422729 push    9
seg001:0042272B push    [ebp+var_CE4]
seg001:00422731 call    sub_41627B                      ; 这里调用载入函数
seg001:00422736 mov     esi, eax
seg001:00422738 cmp     esi, 10h
seg001:0042273B jz      short loc_4226F4
......

最后,在以下位置处,将会计算出内核加载的绝对地址(可以看出,加载基址在0x41649C是写死的)并最终调用一个通用的加载函数0x4161AB,调用完毕后,内核会加载到0x80590000(此地址已是修改过的)。

......
seg001:00416488 ; ---------------------------------------------------------------------------
seg001:00416488
seg001:00416488 loc_416488:                             ; CODE XREF: sub_41627B+1BE
seg001:00416488                                         ; sub_41627B+1FC
seg001:00416488 mov     esi, [ebp+var_580]              ; 一个通用的模块处理与加载的地方
seg001:0041648E push    edi
seg001:0041648F lea     eax, [ebp+var_57C]
seg001:00416495 push    eax
seg001:00416496 push    [ebp+var_558]
seg001:0041649C or      esi, 0FFF80000h                 ; 将相对地址加上绝对地址,此时地址都是右移12位的格式
seg001:004164A2 lea     eax, [ebp+var_5A8]
seg001:004164A8 shl     esi, 0Ch                        ; 左移12位得到最终的绝对地址
seg001:004164AB push    eax
seg001:004164AC mov     [ebp+var_55C], esi
seg001:004164B2 mov     [ebp+var_57C], edi
seg001:004164B8 mov     [ebp+var_578], edi
seg001:004164BE call    sub_416217
seg001:004164C3 cmp     eax, edi
seg001:004164C5 mov     [ebp+var_554], eax
seg001:004164CB jnz     loc_416815
seg001:004164D1 lea     eax, [ebp+var_564]
seg001:004164D7 push    eax
seg001:004164D8 push    dword ptr [ebx+54h]
seg001:004164DB lea     eax, [ebp+var_5A8]
seg001:004164E1 push    esi
seg001:004164E2 push    [ebp+var_558]
seg001:004164E8 push    eax
seg001:004164E9 call    sub_4161AB                      ; 调用通用的加载函数
seg001:004164EE cmp     eax, edi
seg001:004164F0 mov     [ebp+var_554], eax
seg001:004164F6 jnz     loc_416815
......

其它关键点

版本问题

在研究过程中,360更新了360rosdrv.sys,为了保证分析的连续性,我仍然用的旧版1.0.0.1011版。新版为1015,地址有所变化。

单字节随机值是如何产生的?

对于地址随机化,那个单字节的随机值是很关键的,那么它是如何生成的呢?我在“360内存分配的流程及地址随机化的实现”→“初始化工作,创建位图空间及单字节随机值”中仅仅在代码中有注释,这里简要说一下它的来历。

生成随机值的函数代码如下:

.text:BA5C27B8
.text:BA5C27B8 ; =============== S U B R O U T I N E =======================================
.text:BA5C27B8
.text:BA5C27B8 ; 生成随机值
.text:BA5C27B8
.text:BA5C27B8 sub_BA5C27B8    proc near               ; CODE XREF: sub_BA5B7576+21
.text:BA5C27B8                                         ; sub_BA5B7576+73
.text:BA5C27B8                 push    offset dword_BA5FC248 ; 保存生成HintIndex随机数的种子
.text:BA5C27BD                 call    ds:RtlRandomEx
.text:BA5C27C3                 retn
.text:BA5C27C3 sub_BA5C27B8    endp
.text:BA5C27C3
.text:BA5C27C3 ; ---------------------------------------------------------------------------

可见就是调用RtlRandomEx生成的。不过经过跟踪发现,随机数的种子一直在变化,那么这个随机数种子又是怎么生成的呢?代码在下面:

.text:BA5C2734
.text:BA5C2734 ; =============== S U B R O U T I N E =======================================
.text:BA5C2734
.text:BA5C2734 ; 计算生成随机数HintIndex的种子
.text:BA5C2734 ; Attributes: bp-based frame
.text:BA5C2734
.text:BA5C2734 sub_BA5C2734    proc near               ; CODE XREF: sub_BA5B68B4+132
.text:BA5C2734                                         ; sub_BA5BC8FA+4E
.text:BA5C2734
.text:BA5C2734 var_158         = dword ptr -158h
.text:BA5C2734 var_150         = dword ptr -150h
.text:BA5C2734 var_11C         = dword ptr -11Ch
.text:BA5C2734 var_D8          = dword ptr -0D8h
.text:BA5C2734 var_C4          = dword ptr -0C4h
.text:BA5C2734 var_30          = dword ptr -30h
.text:BA5C2734 var_24          = dword ptr -24h
.text:BA5C2734 SystemInformation= byte ptr -20h
.text:BA5C2734 var_1C          = dword ptr -1Ch
.text:BA5C2734 var_10          = byte ptr -10h
.text:BA5C2734 var_A           = word ptr -0Ah
.text:BA5C2734 var_8           = word ptr -8
.text:BA5C2734 var_6           = word ptr -6
.text:BA5C2734 var_4           = word ptr -4
.text:BA5C2734
.text:BA5C2734                 mov     edi, edi
.text:BA5C2736                 push    ebp
.text:BA5C2737                 mov     ebp, esp
.text:BA5C2739                 sub     esp, 158h
.text:BA5C273F                 push    esi
.text:BA5C2740                 lea     eax, [ebp+var_10]
.text:BA5C2743                 push    eax
.text:BA5C2744                 call    ds:HalQueryRealTimeClock
.text:BA5C274A                 mov     esi, ds:ZwQuerySystemInformation
.text:BA5C2750                 push    0               ; ReturnLength
.text:BA5C2752                 push    10h             ; SystemInformationLength
.text:BA5C2754                 lea     eax, [ebp+SystemInformation]
.text:BA5C2757                 push    eax             ; SystemInformation
.text:BA5C2758                 push    21h ; \'!\'       ; SystemInformationClass
.text:BA5C275A                 call    esi ; ZwQuerySystemInformation
.text:BA5C275C                 push    0               ; ReturnLength
.text:BA5C275E                 push    138h            ; SystemInformationLength
.text:BA5C2763                 lea     eax, [ebp+var_158]
.text:BA5C2769                 push    eax             ; SystemInformation
.text:BA5C276A                 push    2               ; SystemInformationClass
.text:BA5C276C                 call    esi ; ZwQuerySystemInformation
.text:BA5C276E                 movsx   ecx, [ebp+var_8]
.text:BA5C2772                 movsx   eax, [ebp+var_A]
.text:BA5C2776                 xor     eax, ecx
.text:BA5C2778                 movsx   ecx, [ebp+var_6]
.text:BA5C277C                 xor     eax, ecx
.text:BA5C277E                 movsx   ecx, [ebp+var_4]
.text:BA5C2782                 xor     eax, ecx
.text:BA5C2784                 xor     eax, [ebp+var_150]
.text:BA5C278A                 pop     esi
.text:BA5C278B                 xor     eax, [ebp+var_D8]
.text:BA5C2791                 xor     eax, [ebp+var_158]
.text:BA5C2797                 xor     eax, [ebp+var_C4]
.text:BA5C279D                 xor     eax, [ebp+var_24]
.text:BA5C27A0                 xor     eax, [ebp+var_30]
.text:BA5C27A3                 xor     eax, [ebp+var_11C]
.text:BA5C27A9                 xor     eax, [ebp+var_1C]
.text:BA5C27AC                 mov     dword_BA5FC248, eax ; 保存生成HintIndex随机数的种子
.text:BA5C27B1                 leave
.text:BA5C27B2                 retn
.text:BA5C27B2 sub_BA5C2734    endp
.text:BA5C27B2
.text:BA5C27B2 ; ---------------------------------------------------------------------------

可见其获取了很多系统的信息并做异或,得到了随机数种子。

地址无关代码的处理

经老大们提示,加载完模块后,360还做了一件事。由于模块的基址改变了,就涉及到了对模块进行地址无关的处理。换句话说需要手动处理模块PE文件的.reloc段。以下就是处理reloc区段的函数代码及分析:

.text:BA5C1E32
.text:BA5C1E32 ; =============== S U B R O U T I N E =======================================
.text:BA5C1E32
.text:BA5C1E32 ; 处理reloc区段,对地址进行重定位
.text:BA5C1E32 ; Attributes: bp-based frame
.text:BA5C1E32
.text:BA5C1E32 ; int __stdcall sub_BA5C1E32(PVOID ImageBase, int, int, int)
.text:BA5C1E32 sub_BA5C1E32    proc near               ; CODE XREF: sub_BA5BA3DA+6CC
.text:BA5C1E32                                         ; sub_BA5BA3DA+749
.text:BA5C1E32
.text:BA5C1E32 ms_exc          = CPPEH_RECORD ptr -18h
.text:BA5C1E32 ImageBase       = dword ptr  8
.text:BA5C1E32 arg_4           = dword ptr  0Ch
.text:BA5C1E32 arg_8           = dword ptr  10h
.text:BA5C1E32 arg_C           = dword ptr  14h
.text:BA5C1E32
.text:BA5C1E32                 push    8
.text:BA5C1E34                 push    offset off_BA5C6AF0
.text:BA5C1E39                 call    __SEH_prolog4
.text:BA5C1E3E                 and     [ebp+ms_exc.registration.TryLevel], 0
.text:BA5C1E42                 mov     edi, [ebp+ImageBase]
.text:BA5C1E45                 push    edi             ; ImageBase
.text:BA5C1E46                 call    ds:RtlImageNtHeader ; 返回的结果是在ImageBase基址的基础上又加了D0
.text:BA5C1E4C                 test    eax, eax
.text:BA5C1E4E                 jz      short loc_BA5C1EAE
.text:BA5C1E50                 mov     ebx, [eax+34h]  ; 获取旧的基址
.text:BA5C1E53                 lea     eax, [ebp+ImageBase] ; 获取新的基址
.text:BA5C1E56                 push    eax             ; Size
.text:BA5C1E57                 push    5               ; DirectoryEntry
.text:BA5C1E59                 push    1               ; MappedAsImage
.text:BA5C1E5B                 push    edi             ; ImageBase
.text:BA5C1E5C                 call    ds:RtlImageDirectoryEntryToData
.text:BA5C1E62                 test    eax, eax
.text:BA5C1E64                 jz      short loc_BA5C1EBA
.text:BA5C1E66                 cmp     [ebp+ImageBase], 0
.text:BA5C1E6A                 jz      short loc_BA5C1EBA
.text:BA5C1E6C
.text:BA5C1E6C loc_BA5C1E6C:                           ; CODE XREF: sub_BA5C1E32+7A
.text:BA5C1E6C                 cmp     [ebp+ImageBase], 0 ; 此处[ebp+ImageBase]实际保存reloc数据区剩余数据大小
.text:BA5C1E6C                                         ; reloc中保存很多Blocks,每个Block拥有多个Entry,每个Entry代表一个需要做reloc的地方。
.text:BA5C1E70                 jle     short loc_BA5C1ECD ; 检查reloc里的数据是否已经全部处理完毕,完毕则跳转
.text:BA5C1E72                 mov     ecx, [eax+4]    ; 获取当前Block大小
.text:BA5C1E75                 sub     [ebp+ImageBase], ecx ; 减少剩余的reloc数据
.text:BA5C1E78                 cmp     ecx, 8
.text:BA5C1E7B                 jb      short loc_BA5C1EAE ; 如果Block大小小于0x8,说明有问题,跳转到异常处理
.text:BA5C1E7D                 add     ecx, 0FFFFFFF8h ; 减去2字节的Block头
.text:BA5C1E80                 shr     ecx, 1          ; 因为每个Entry占用2字节,这里除以2,就可以得到Entry的数量
.text:BA5C1E82                 lea     esi, [eax+8]
.text:BA5C1E85                 mov     eax, [eax]
.text:BA5C1E87                 add     eax, edi        ; 计算当前Block所指向的基址,也就是说当前Block中的Entry所保存的偏移都是以这个基址进行偏移的
.text:BA5C1E89                 mov     edx, ds:MmUserProbeAddress
.text:BA5C1E8F                 cmp     eax, [edx]
.text:BA5C1E91                 ja      short loc_BA5C1EAE ; 处理极端情况,越界检查,地址指针超过0x7FFF0000被认为是错误的,跳转到异常处理
.text:BA5C1E93                 mov     edx, edi
.text:BA5C1E95                 sub     edx, ebx        ; 计算新基址与旧基址的距离
.text:BA5C1E97                 push    edx             ; 参数:新旧基址距离
.text:BA5C1E98                 push    esi             ; 参数:Entry首地址
.text:BA5C1E99                 push    ecx             ; 参数:Entry数量
.text:BA5C1E9A                 push    eax             ; 参数:Entry所需的基址
.text:BA5C1E9B                 call    sub_BA5C1C24    ; 处理所有的Entry,返回值eax保存下一个Block的首地址
.text:BA5C1EA0                 test    eax, eax
.text:BA5C1EA2                 jz      short loc_BA5C1EAE
.text:BA5C1EA4                 mov     ecx, ds:MmUserProbeAddress
.text:BA5C1EAA                 cmp     eax, [ecx]      ; 判断是否有极端越界情况发生(数据指针超过了地址0x7FFF0000)
.text:BA5C1EAC                 jbe     short loc_BA5C1E6C ; 此处[ebp+ImageBase]实际保存reloc数据区剩余数据大小
.text:BA5C1EAC                                         ; reloc中保存很多Blocks,每个Block拥有多个Entry,每个Entry代表一个需要做reloc的地方。
.text:BA5C1EAE
.text:BA5C1EAE loc_BA5C1EAE:                           ; CODE XREF: sub_BA5C1E32+1C
.text:BA5C1EAE                                         ; sub_BA5C1E32+49 ...
.text:BA5C1EAE                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:BA5C1EB5                 mov     eax, [ebp+arg_C]
.text:BA5C1EB8                 jmp     short loc_BA5C1ED7
.text:BA5C1EBA ; ---------------------------------------------------------------------------
.text:BA5C1EBA
.text:BA5C1EBA loc_BA5C1EBA:                           ; CODE XREF: sub_BA5C1E32+32
.text:BA5C1EBA                                         ; sub_BA5C1E32+38
.text:BA5C1EBA                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:BA5C1EC1                 mov     eax, [ebp+arg_8]
.text:BA5C1EC4                 jmp     short loc_BA5C1ED7
.text:BA5C1EC6 ; ---------------------------------------------------------------------------
.text:BA5C1EC6
.text:BA5C1EC6 loc_BA5C1EC6:                           ; DATA XREF: .rdata:off_BA5C6AF0
.text:BA5C1EC6                 xor     eax, eax        ; Exception filter 0 for function 1BE32
.text:BA5C1EC8                 inc     eax
.text:BA5C1EC9                 retn
.text:BA5C1ECA ; ---------------------------------------------------------------------------
.text:BA5C1ECA
.text:BA5C1ECA loc_BA5C1ECA:                           ; DATA XREF: .rdata:off_BA5C6AF0
.text:BA5C1ECA                 mov     esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 1BE32
.text:BA5C1ECD
.text:BA5C1ECD loc_BA5C1ECD:                           ; CODE XREF: sub_BA5C1E32+3E
.text:BA5C1ECD                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:BA5C1ED4                 mov     eax, [ebp+arg_4]
.text:BA5C1ED7
.text:BA5C1ED7 loc_BA5C1ED7:                           ; CODE XREF: sub_BA5C1E32+86
.text:BA5C1ED7                                         ; sub_BA5C1E32+92
.text:BA5C1ED7                 call    __SEH_epilog4
.text:BA5C1EDC                 retn    10h
.text:BA5C1EDC sub_BA5C1E32    endp
.text:BA5C1EDC
.text:BA5C1EDC ; ---------------------------------------------------------------------------

在0xBA5C1E9B处,调用了函数0xBA5C1C24,用于处理一个Block内的所有Entry:

.text:BA5C1C24
.text:BA5C1C24 ; =============== S U B R O U T I N E =======================================
.text:BA5C1C24
.text:BA5C1C24 ; 处理所有的Entry,返回值eax保存下一个Block的首地址
.text:BA5C1C24 ; Attributes: bp-based frame
.text:BA5C1C24
.text:BA5C1C24 sub_BA5C1C24    proc near               ; CODE XREF: sub_BA5C1E32+69
.text:BA5C1C24
.text:BA5C1C24 var_4           = dword ptr -4
.text:BA5C1C24 arg_0           = dword ptr  8
.text:BA5C1C24 arg_4           = dword ptr  0Ch
.text:BA5C1C24 arg_8           = dword ptr  10h
.text:BA5C1C24 arg_C           = dword ptr  14h
.text:BA5C1C24
.text:BA5C1C24                 mov     edi, edi
.text:BA5C1C26                 push    ebp
.text:BA5C1C27                 mov     ebp, esp
.text:BA5C1C29                 push    ecx
.text:BA5C1C2A                 cmp     [ebp+arg_4], 0  ; 判断Entry个数是否为0
.text:BA5C1C2E                 push    ebx
.text:BA5C1C2F                 push    esi
.text:BA5C1C30                 push    edi
.text:BA5C1C31                 mov     edi, [ebp+arg_8] ; 将Entry数据首地址保存到edi
.text:BA5C1C34                 jz      loc_BA5C1D4D    ; Entry个数为0则跳转
.text:BA5C1C3A                 mov     ebx, [ebp+arg_C] ; 将新旧基址之差保存到ebx
.text:BA5C1C3D
.text:BA5C1C3D loc_BA5C1C3D:                           ; CODE XREF: sub_BA5C1C24+123
.text:BA5C1C3D                 movzx   eax, word ptr [edi] ; 获取一个Entry数据
.text:BA5C1C40                 dec     [ebp+arg_4]     ; Entry总数减去1,显然这是个for循环
.text:BA5C1C43                 mov     esi, eax
.text:BA5C1C45                 and     esi, 0FFFh      ; 去除高位的标志位,只保留低12位
.text:BA5C1C4B                 add     esi, [ebp+arg_0] ; 计算出需要reloc操作的地址,保存在esi,下面reloc操作会用到
.text:BA5C1C4E                 shr     eax, 0Ch        ; 获取Entry的高4位,即标志位
.text:BA5C1C51                 cmp     eax, 4
.text:BA5C1C54                 jg      short loc_BA5C1CA6 ; 检查标志位是否超过了0x4,一般Entry的类型为HIGHLOW(0x3)
.text:BA5C1C56                 jz      short loc_BA5C1C8D ; 检查标志位是否为0
.text:BA5C1C58                 sub     eax, 0
.text:BA5C1C5B                 jz      loc_BA5C1D41    ; 如果标志位为0则跳转
.text:BA5C1C61                 dec     eax
.text:BA5C1C62                 jz      short loc_BA5C1C7D ; 如果标志位为0x1则跳转
.text:BA5C1C64                 dec     eax
.text:BA5C1C65                 jz      short loc_BA5C1C75 ; 如果标志位为0x2则跳转
.text:BA5C1C67                 dec     eax
.text:BA5C1C68                 jnz     loc_BA5C1D56    ; 如果标志位为0x4则跳转,这里用了组合判断,因为如果此时eax不为0,那么标志位必然大于0x3,而上面有判断标志位如果大于0x4则不会执行到这里,那么这里必然是在判断标志位是否等于0x4
.text:BA5C1C6E                 add     [esi], ebx      ; 将当前地址的数据进行reloc操作,ebx为新旧基址的差值,esi为计算出的需要reloc操作的地址。
.text:BA5C1C6E                                         ; 另外,如果这是第一次写入,会触发写时复制,也就是说,在第一次修改之前,[esi]的数据都是空(IDA里全是问号)。在上面的代码中经常碰到这种情况,稍微注意一下。
.text:BA5C1C70                 jmp     loc_BA5C1D41
.text:BA5C1C75 ; ---------------------------------------------------------------------------
.text:BA5C1C75
.text:BA5C1C75 loc_BA5C1C75:                           ; CODE XREF: sub_BA5C1C24+41
.text:BA5C1C75                 add     [esi], bx
.text:BA5C1C78                 jmp     loc_BA5C1D41
.text:BA5C1C7D ; ---------------------------------------------------------------------------
.text:BA5C1C7D
.text:BA5C1C7D loc_BA5C1C7D:                           ; CODE XREF: sub_BA5C1C24+3E
.text:BA5C1C7D                 movzx   eax, word ptr [esi]
.text:BA5C1C80                 shl     eax, 10h
.text:BA5C1C83                 add     eax, ebx
.text:BA5C1C85
.text:BA5C1C85 loc_BA5C1C85:                           ; CODE XREF: sub_BA5C1C24+80
.text:BA5C1C85                 sar     eax, 10h
.text:BA5C1C88                 jmp     loc_BA5C1D3E
.text:BA5C1C8D ; ---------------------------------------------------------------------------
.text:BA5C1C8D
.text:BA5C1C8D loc_BA5C1C8D:                           ; CODE XREF: sub_BA5C1C24+32
.text:BA5C1C8D                 movzx   eax, word ptr [esi]
.text:BA5C1C90                 inc     edi
.text:BA5C1C91                 inc     edi
.text:BA5C1C92                 movsx   ecx, word ptr [edi]
.text:BA5C1C95                 dec     [ebp+arg_4]
.text:BA5C1C98                 shl     eax, 10h
.text:BA5C1C9B                 add     eax, ebx
.text:BA5C1C9D                 lea     eax, [ecx+eax+8000h]
.text:BA5C1CA4                 jmp     short loc_BA5C1C85
.text:BA5C1CA6 ; ---------------------------------------------------------------------------
.text:BA5C1CA6
.text:BA5C1CA6 loc_BA5C1CA6:                           ; CODE XREF: sub_BA5C1C24+30
.text:BA5C1CA6                 sub     eax, 6
.text:BA5C1CA9                 jz      loc_BA5C1D41
.text:BA5C1CAF                 dec     eax
.text:BA5C1CB0                 jz      loc_BA5C1D41
.text:BA5C1CB6                 sub     eax, 4
.text:BA5C1CB9                 jnz     loc_BA5C1D56
.text:BA5C1CBF                 movzx   eax, word ptr [esi]
.text:BA5C1CC2                 shl     eax, 10h
.text:BA5C1CC5                 cdq
.text:BA5C1CC6                 mov     ebx, eax
.text:BA5C1CC8                 inc     edi
.text:BA5C1CC9                 mov     eax, edx
.text:BA5C1CCB                 inc     edi
.text:BA5C1CCC                 mov     [ebp+arg_8], eax
.text:BA5C1CCF                 lea     ecx, [edi+2]
.text:BA5C1CD2                 movsx   eax, word ptr [ecx]
.text:BA5C1CD5                 cdq
.text:BA5C1CD6                 add     ebx, eax
.text:BA5C1CD8                 mov     eax, [ebp+arg_8]
.text:BA5C1CDB                 push    0
.text:BA5C1CDD                 push    10000h
.text:BA5C1CE2                 adc     eax, edx
.text:BA5C1CE4                 push    eax
.text:BA5C1CE5                 push    ebx
.text:BA5C1CE6                 mov     [ebp+var_4], ecx
.text:BA5C1CE9                 call    _allmul
.text:BA5C1CEE                 mov     ebx, [ebp+arg_C]
.text:BA5C1CF1                 mov     ecx, eax
.text:BA5C1CF3                 mov     eax, edx
.text:BA5C1CF5                 mov     [ebp+arg_8], eax
.text:BA5C1CF8                 movzx   eax, word ptr [edi]
.text:BA5C1CFB                 cdq
.text:BA5C1CFC                 add     ecx, eax
.text:BA5C1CFE                 mov     eax, [ebp+arg_8]
.text:BA5C1D01                 adc     eax, edx
.text:BA5C1D03                 mov     [ebp+arg_8], eax
.text:BA5C1D06                 mov     eax, ebx
.text:BA5C1D08                 cdq
.text:BA5C1D09                 add     ecx, eax
.text:BA5C1D0B                 mov     eax, [ebp+arg_8]
.text:BA5C1D0E                 adc     eax, edx
.text:BA5C1D10                 mov     edi, 8000h
.text:BA5C1D15                 add     ecx, edi
.text:BA5C1D17                 adc     eax, 0
.text:BA5C1D1A                 mov     [ebp+arg_8], eax
.text:BA5C1D1D                 mov     eax, ecx
.text:BA5C1D1F                 mov     ecx, [ebp+arg_8]
.text:BA5C1D22                 mov     edx, ecx
.text:BA5C1D24                 mov     cl, 10h
.text:BA5C1D26                 call    _allshr
.text:BA5C1D2B                 add     eax, edi
.text:BA5C1D2D                 adc     edx, 0
.text:BA5C1D30                 mov     cl, 10h
.text:BA5C1D32                 call    _allshr
.text:BA5C1D37                 mov     edi, [ebp+var_4]
.text:BA5C1D3A                 sub     [ebp+arg_4], 2
.text:BA5C1D3E
.text:BA5C1D3E loc_BA5C1D3E:                           ; CODE XREF: sub_BA5C1C24+64
.text:BA5C1D3E                 mov     [esi], ax
.text:BA5C1D41
.text:BA5C1D41 loc_BA5C1D41:                           ; CODE XREF: sub_BA5C1C24+37
.text:BA5C1D41                                         ; sub_BA5C1C24+4C ...
.text:BA5C1D41                 inc     edi
.text:BA5C1D42                 inc     edi
.text:BA5C1D43                 cmp     [ebp+arg_4], 0  ; 检查是否所有的Entry都处理了
.text:BA5C1D47                 jnz     loc_BA5C1C3D    ; 获取一个Entry数据
.text:BA5C1D4D
.text:BA5C1D4D loc_BA5C1D4D:                           ; CODE XREF: sub_BA5C1C24+10
.text:BA5C1D4D                 mov     eax, edi
.text:BA5C1D4F
.text:BA5C1D4F loc_BA5C1D4F:                           ; CODE XREF: sub_BA5C1C24+134
.text:BA5C1D4F                 pop     edi
.text:BA5C1D50                 pop     esi
.text:BA5C1D51                 pop     ebx
.text:BA5C1D52                 leave
.text:BA5C1D53                 retn    10h
.text:BA5C1D56 ; ---------------------------------------------------------------------------
.text:BA5C1D56
.text:BA5C1D56 loc_BA5C1D56:                           ; CODE XREF: sub_BA5C1C24+44
.text:BA5C1D56                                         ; sub_BA5C1C24+95
.text:BA5C1D56                 xor     eax, eax
.text:BA5C1D58                 jmp     short loc_BA5C1D4F
.text:BA5C1D58 sub_BA5C1C24    endp
.text:BA5C1D58

处理的效果如下图:

\"地址无关机制\"

5 评论

发表评论

电子邮件地址不会被公开。