注意:由于原文是用markdown写的,发在论坛里格式比较乱而且没图片,可以去我博客查看格式正常的版本,地址:http://blog.virtao.org/articles/213.html
这篇文章是去年为某公司的面试准备的,虽然最终没有被录用,但是在这过程中收获很多,总结出这篇文章与大家分享。对于逆向分析我纯属于爱好者水平,而且之前主要以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:\WINDOWS\system32\drivers\hookport.sys
C:\WINDOWS\system32\drivers\360rosdrv.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
处理的效果如下图:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: