📄 762.html
字号:
.text:0041CAAB mov ebp, esp<br />
.text:0041CAAD mov ecx, _KeTickCount.High1Time<br />
.text:0041CAB3 mov eax, [ebp+arg_4]<br />
.text:0041CAB6 mov [eax+4], ecx<br />
.text:0041CAB9 mov edx, _KeTickCount.LowPart<br />
.text:0041CABF mov [eax], edx<br />
<br />
<br />
经过上面的分析我们知道,如果如果eax随机出来是1,2,那么最后分配的PEB的地址都是7FFDE000,这是为了避免以前的<br />
7FFDF000地址的出现,使得以前的堆利用代码都失效。:)<br />
1,2 7FFDE000<br />
3 7FFDD000<br />
4 7FFDC000<br />
5 7FFDB000<br />
6 7FFDA000<br />
7 7FFD9000<br />
8 7FFD8000<br />
9 7FFD7000<br />
A 7FFD6000<br />
B 7FFD5000<br />
C 7FFD4000<br />
D 7FFD3000<br />
E 7FFD2000<br />
F 7FFD1000<br />
0 7FFDE000<br />
<br />
上面列出了可以看到PEB的所有可能值,可以看到7FFDE000的概率最高,1/8,其他都是1/16。:),但即使这样,也没法稳定利用了。<br />
<br />
2、对TOP SEH的保护<br />
<br />
微软对函数SetUnhandledExceptionFilter的代码进行了重大的调整。SetUnhandledExceptionFilter是kernel32.dll中导出的一个函数,用来设置一个筛选器异常处理回掉函数,这个回掉函数不替换系统默认的异常处理程序,而只是在它前面进行了一些预处理,操作的结果还是会送到系统默认的异常处理程序中去,这个过程就相当于对异常进行了一次筛选。<br />
函数的SetUnhandledExceptionFilter调用方式为:<br />
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(<br />
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter<br />
);<br />
这个函数唯一的一个参数就是需要设置的回调函数的地址,返回值为上一次设置的回掉函数的地址。该函数不是在原来的回掉函数前再挂一个回掉函数,而是用这个新的回掉函数替换原来的那个回掉函数。如果地址参数被指定为NULL,那么系统将去掉这个“筛子”而直接将异常送往默认的异常处理程序。winxp sp2对这个函数做了重大的改变,在替换原来的回掉函数之前,首先会先对新的回掉函数的地址进行加密,而后再替换原来的回掉函数。在返回原回掉函数地址之前,会对其进行解密。该函数比较简单:<br />
.text:7C810386 SetUnhandledExceptionFilter proc near<br />
.text:7C810386 lpTopLevelExceptionFilter = dword ptr 8<br />
.text:7C810386 <br />
.text:7C810386 mov edi, edi<br />
.text:7C810388 push ebp<br />
.text:7C810389 mov ebp, esp<br />
;这里先对地址lpTopLevelExceptionFilter进行加密<br />
.text:7C81038B push [ebp+ lpTopLevelExceptionFilter]<br />
.text:7C81038E call RtlEncodePointer<br />
;而后将加密之后的地址和原回掉函数地址进行交换,也就是将加密之后的地址写入到<br />
;一个全局变量中,同时将该全局变量中的原回掉函数地址返回<br />
.text:7C810393 push eax ; Value<br />
.text:7C810394 push offset Target ; Target<br />
.text:7C810399 call InterlockedExchange<br />
;在返回原回掉函数地址之前先进行解密,因为原回掉函数地址也进行了加密<br />
.text:7C81039E push eax<br />
.text:7C81039F call RtlDecodePointer<br />
.text:7C8103A4 pop ebp<br />
.text:7C8103A5 retn 4<br />
.text:7C8103A5 SetUnhandledExceptionFilter endp ; sp = -8<br />
.text:7C8103A5<br />
<br />
而以前都是直接将回掉函数的地址写入到全局变量中,没有经过任何的处理。可见,我们再也无法像以前一样通过覆盖该函数指针来利用堆溢出了。而且经过分析发现,winxp sp2对所有的全局指针都进行了这样的加密处理。接着往下看它是怎么对地址进行加密的。<br />
RtlEncodePointer和RtlDecodePointer都是ntdll.dll导出的函数,RtlEncodePointer用来对一个指针进行加密,RtlDecodePointer用来对一个指针进行解密。其实整个个加密解密过程都很简单,加密时直接将指针和一个的随机数进行异或,解密时再和该随机数进行异或。<br />
加密:point = point ^ rand<br />
解密:point = point ^ rand<br />
rand是一个跟进程相关的随机数,通过调用函数ZwQueryInformationProcess得到,每个进程该随机数都不一样。<br />
为了避免你再次进行反汇编,这里贴出这两个函数的代码。<br />
RtlEncodePointer函数的代码如下:<br />
.text:7C933917 RtlEncodePointer proc near <br />
.text:7C933917 var_4 = dword ptr -4<br />
.text:7C933917 arg_4 = dword ptr 8<br />
.text:7C933917 <br />
.text:7C933917 mov edi, edi<br />
.text:7C933919 push ebp<br />
.text:7C93391A mov ebp, esp<br />
;调用函数ZwQueryInformationProcess得到一个跟进程相关的随机数<br />
.text:7C93391C push ecx<br />
.text:7C93391D push 0<br />
.text:7C93391F push 4<br />
;这里得到堆栈中的一个临时变量的地址,最后得到的随机数将保存在这个临时变量中。<br />
.text:7C933921 lea eax, [ebp+var_4]<br />
.text:7C933924 push eax<br />
.text:7C933925 push 24h ;子功能代码为0x24<br />
.text:7C933927 push 0FFFFFFFFh<br />
.text:7C933929 call ZwQueryInformationProcess<br />
;将得到的随机数和指针进行异或,这样就完成了加密。<br />
;解密的过程和加密的过程相同<br />
.text:7C93392E mov eax, [ebp+var_4]<br />
.text:7C933931 xor eax, [ebp+arg_4]<br />
.text:7C933934 leave<br />
.text:7C933935 retn 4<br />
.text:7C933935 RtlEncodePointer endp ; sp = 4<br />
<br />
函数RtlDecodePointer更简单,只是直接转到RtlEncodePointer执行,因为解密的过程和加密的过程完全相同。<br />
<br />
.text:7C93393D RtlDecodePointer proc near <br />
;下面这四跳语句没有任何的作用<br />
.text:7C93393D mov edi, edi<br />
.text:7C93393F push ebp<br />
.text:7C933940 mov ebp, esp<br />
.text:7C933942 pop ebp<br />
;下面这条语句转到RtlEncodePointer执行,其实就相当于直接调用了函数<br />
;RtlEncodePointer<br />
.text:7C933943 jmp short RtlEncodePointer<br />
.text:7C933943 RtlDecodePointer endp<br />
<br />
ZwQueryInformationProcess最后会调用一个系统调用,转到内核运行,最后会调用内核中的函数NtQueryInformationProcess,并且调用该函数的子功能代码为0x24。该子功能直接取出保存在进程中的一个随机数,并将其拷贝到用户堆栈中的一个临时变量中。如果该随机数为0,则还要根据系统时间重新生成该随机数,一般在进程刚开始创建的时候,这个随机数为0,从而会重新生成该随机数。由于该随机数跟进程创建的时间有关,所以这个随机数是无法猜测的。该函数在ntoskrnl.exe中导出,跟这个功能相关的函数代码为:<br />
PAGE:004970CC loc_4970CC: <br />
;下面的代码得到一个进程唯一的随机数,子功能代码为0x24<br />
PAGE:004970CC cmp edi, edx ; case 0x24<br />
PAGE:004970CE jnz loc_497349<br />
PAGE:004970D4 cmp dword ptr [ebp+8], 0FFFFFFFFh<br />
PAGE:004970D8 jnz loc_4977B8<br />
;下面的代码得到保存随机数的地址<br />
PAGE:004970DE mov eax, large fs:124h<br />
PAGE:004970E4 mov eax, [eax+44h]<br />
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -