Windows Xp Sp2对于溢出保护 funnywei & jerry
我们知道在我们对溢出漏洞进行exp的时候,经常要利用全局性的指针,利用异常处理。那么XP的sp2对此作了处理。使得我们无法运用以前的技巧来完成我们的工作。例如,对全局性的指针都作了编码处理。
那么具体来讲,本文主要谈到以下 1、映射给PEB管理结构的起始地址做了随机处理。后面我们会看到这种随机是很弱的,但已经足够让exp无法完成或者说是稳定的工作。 2、对TOP SEH的保护 3、VEH链表指针_RtlpCalloutEntryList的保护 4、堆块结构的cookie保护
不涉及内容: 1、如何绕过保护机制 2、堆管理的细节,其实没有太大的变化
主题开始:
1、PEB的地址的随机
xp系统下,创建进程使用的是_NtCreateProcessEx函数,而不是_NtCreateProcess函数。_NtCreateProcessEx主要调用_PspCreateProcess@36函数来完成进程的创建工作
PAGE:004B4649 call _PspCreateProcess@36 ; PspCreateProcess(x,x,x,x,x,x,x,x,x)
进程的创建主要包括设置EPROCESS,创建初始进程地址空间等。这里就不罗嗦了。PEB的设置通过调用_MmCreatePeb.
PAGE:004B428E push eax PAGE:004B428F push ebx PAGE:004B4290 push dword ptr [ebp-60h] PAGE:004B4293 call _MmCreateProcessAddressSpace@12 ; MmCreateProcessAddressSpace(x,x,x)
PAGE:004B43E5 lea eax, [ebx+1B0h] PAGE:004B43EB push eax PAGE:004B43EC lea eax, [ebp-40h] PAGE:004B43EF push eax PAGE:004B43F0 push ebx PAGE:004B43F1 call _MmCreatePeb@12 ; MmCreatePeb(x,x,x)
而MmCreatePeb又主要通过调用_MiCreatePebOrTeb
PAGE:004B4A61 ; __stdcall MmCreatePeb(x,x,x) PAGE:004B4A61 _MmCreatePeb@12 proc near ; CODE XREF: PspCreateProcess(x,x,x,x,x,x,x,x,x)+303p PAGE:004B4A61 PAGE:004B4A61 ; FUNCTION CHUNK AT PAGE:005267FF SIZE 000000DC BYTES PAGE:004B4A61 PAGE:004B4A61 push 3Ch PAGE:004B4A63 push offset dword_42DAA8 PAGE:004B4A68 call __SEH_prolog PAGE:004B4A6D xor ebx, ebx PAGE:004B4A6F mov [ebp-20h], ebx PAGE:004B4A72 mov [ebp-4Ch], ebx PAGE:004B4A75 mov [ebp-48h], ebx PAGE:004B4A78 mov [ebp-2Ch], ebx PAGE:004B4A7B mov esi, [ebp+8] PAGE:004B4A7E push esi PAGE:004B4A7F call _KeAttachProcess@4 ; KeAttachProcess(x) PAGE:004B4A84 push 2 PAGE:004B4A86 pop edi PAGE:004B4A87 push edi PAGE:004B4A88 push (offset loc_4FFFFE+2) PAGE:004B4A8D push 1 PAGE:004B4A8F lea eax, [ebp-2Ch] PAGE:004B4A92 push eax PAGE:004B4A93 lea eax, [ebp-4Ch] PAGE:004B4A96 push eax PAGE:004B4A97 push ebx PAGE:004B4A98 push ebx PAGE:004B4A99 lea eax, [ebp-20h] PAGE:004B4A9C push eax PAGE:004B4A9D push esi PAGE:004B4A9E push ds:_InitNlsSectionPointer PAGE:004B4AA4 call _MmMapViewOfSection@40 ; MmMapViewOfSection(x,x,x,x,x,x,x,x,x,x) PAGE:004B4AA9 mov [ebp-24h], eax PAGE:004B4AAC cmp eax, ebx PAGE:004B4AAE jl loc_5267FF PAGE:004B4AB4 lea eax, [ebp-1Ch]
注意下面这个210参数,类似一个Flag。在后面你会发现,如果该参数不等于210,那么映射的PEB地址将不会产生随机值,而是会跟以前的一样,始终在7FFDF000位置。
PAGE:004B4AB7 push eax PAGE:004B4AB8 push 210h ;注意这个参数! PAGE:004B4ABD push esi PAGE:004B4ABE call _MiCreatePebOrTeb@12 ; MiCreatePebOrTeb(x,x,x)
真正完成工作 _MiCreatePebOrTeb@12 函数
PAGE:004B01AE call _ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x) PAGE:004B01B3 mov esi, eax
PAGE:004B01B5 test esi, esi PAGE:004B01B7 jz loc_52678E PAGE:004B01BD mov eax, [ebp+arg_8] PAGE:004B01C0 mov ecx, [ebp+arg_8] PAGE:004B01C3 and eax, 0FFFh PAGE:004B01C8 neg eax PAGE:004B01CA sbb eax, eax PAGE:004B01CC neg eax PAGE:004B01CE shr ecx, 0Ch
PAGE:004B01FB cmp [ebp+arg_8], 210h PAGE:004B0202 jz loc_4B4A0A ;这里将210与压栈的参数比较,如果压入栈的不是210呢
PAGE:004B0208 loc_4B0208: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+48ADj PAGE:004B0208 mov edi, [ebp+arg_C] PAGE:004B020B mov eax, _MmHighestUserAddress PAGE:004B0210 push edi PAGE:004B0211 push dword ptr [ebx+11Ch] PAGE:004B0217 add eax, 0FFFF0001h PAGE:004B021C push 1000h PAGE:004B0221 push eax PAGE:004B0222 mov eax, [ebp+arg_8] PAGE:004B0225 add eax, 0FFFh PAGE:004B022A and eax, 0FFFFF000h PAGE:004B022F push eax PAGE:004B0230 call _MiFindEmptyAddressRangeDownTree@20 ; MiFindEmptyAddressRangeDownTree(x,x,x,x,x) PAGE:004B0235 test eax, eax PAGE:004B0237 mov [ebp+arg_C], eax PAGE:004B023A jl loc_5267A5
关键是这里 PAGE:004B4A0A loc_4B4A0A: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+66j PAGE:004B4A0A mov edi, _MmHighestUserAddress ;总是7FFEFFFF PAGE:004B4A10 lea eax, [ebp+var_C] PAGE:004B4A13 push eax PAGE:004B4A14 add edi, 0FFFF0001h ;此时edi为7FFE0000 PAGE:004B4A1A call _KeQueryTickCount@4 ; KeQueryTickCount(x) PAGE:004B4A1F mov eax, [ebp+var_C] PAGE:004B4A22 and eax, 0Fh ;只取最后一个字节的值,比如此时为0C PAGE:004B4A25 cmp eax, 1 ;看eax此时是不是为01 PAGE:004B4A28 mov [ebp+var_C], eax PAGE:004B4A2B jbe loc_4B4928 ;如果是就跳到去处理
PAGE:004B4A31 loc_4B4A31: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+4792j PAGE:004B4A31 shl eax, 0Ch PAGE:004B4A34 sub edi, eax PAGE:004B4A36 lea eax, [edi+0FFFh] PAGE:004B4A3C push eax PAGE:004B4A3D push edi PAGE:004B4A3E push ebx PAGE:004B4A3F mov [ebp+var_4], edi
PAGE:004B4928 loc_4B4928: ; CODE XREF: MiCreatePebOrTeb(x,x,x)+488Fj 如果eax为1,那么就更改为2.这样避免最后计算出来为7FFDF000.而是为7FFDE000 PAGE:004B4928 push 2 PAGE:004B492A pop eax PAGE:004B492B mov [ebp+var_C], eax PAGE:004B492E jmp loc_4B4A31
因为KeTickCount是进程的一个时间计数,所以无法预测。
.text:0041CAA8 mov edi, edi .text:0041CAAA push ebp .text:0041CAAB mov ebp, esp .text:0041CAAD mov ecx, _KeTickCount.High1Time .text:0041CAB3 mov eax, [ebp+arg_4] .text:0041CAB6 mov [eax+4], ecx .text:0041CAB9 mov edx, _KeTickCount.LowPart .text:0041CABF mov [eax], edx
经过上面的分析我们知道,如果如果eax随机出来是1,2,那么最后分配的PEB的地址都是7FFDE000,这是为了避免以前的 7FFDF000地址的出现,使得以前的堆利用代码都失效。:) 1,2 7FFDE000 3 7FFDD000 4 7FFDC000 5 7FFDB000 6 7FFDA000 7 7FFD9000 8 7FFD8000 9 7FFD7000 A 7FFD6000 B 7FFD5000 C 7FFD4000 D 7FFD3000 E 7FFD2000 F 7FFD1000 0 7FFDE000
上面列出了可以看到PEB的所有可能值,可以看到7FFDE000的概率最高,1/8,其他都是1/16。:),但即使这样,也没法稳定利用了。
2、对TOP SEH的保护
微软对函数SetUnhandledExceptionFilter的代码进行了重大的调整。SetUnhandledExceptionFilter是kernel32.dll中导出的一个函数,用来设置一个筛选器异常处理回掉函数,这个回掉函数不替换系统默认的异常处理程序,而只是在它前面进行了一些预处理,操作的结果还是会送到系统默认的异常处理程序中去,这个过程就相当于对异常进行了一次筛选。 函数的SetUnhandledExceptionFilter调用方式为: LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ); 这个函数唯一的一个参数就是需要设置的回调函数的地址,返回值为上一次设置的回掉函数的地址。该函数不是在原来的回掉函数前再挂一个回掉函数,而是用这个新的回掉函数替换原来的那个回掉函数。如果地址参数被指定为NULL,那么系统将去掉这个“筛子”而直接将异常送往默认的异常处理程序。winxp sp2对这个函数做了重大的改变,在替换原来的回掉函数之前,首先会先对新的回掉函数的地址进行加密,而后再替换原来的回掉函数。在返回原回掉函数地址之前,会对其进行解密。该函数比较简单: .text:7C810386 SetUnhandledExceptionFilter proc near .text:7C810386 lpTopLevelExceptionFilter = dword ptr 8 .text:7C810386 .text:7C810386 mov edi, edi .text:7C810388 push ebp .text:7C810389 mov ebp, esp ;这里先对地址lpTopLevelExceptionFilter进行加密 .text:7C81038B push [ebp+ lpTopLevelExceptionFilter] .text:7C81038E call RtlEncodePointer ;而后将加密之后的地址和原回掉函数地址进行交换,也就是将加密之后的地址写入到 ;一个全局变量中,同时将该全局变量中的原回掉函数地址返回 .text:7C810393 push eax ; Value .text:7C810394 push offset Target ; Target .text:7C810399 call InterlockedExchange ;在返回原回掉函数地址之前先进行解密,因为原回掉函数地址也进行了加密 .text:7C81039E push eax .text:7C81039F call RtlDecodePointer .text:7C8103A4 pop ebp .text:7C8103A5 retn 4 .text:7C8103A5 SetUnhandledExceptionFilter endp ; sp = -8 .text:7C8103A5
而以前都是直接将回掉函数的地址写入到全局变量中,没有经过任何的处理。可见,我们再也无法像以前一样通过覆盖该函数指针来利用堆溢出了。而且经过分析发现,winxp sp2对所有的全局指针都进行了这样的加密处理。接着往下看它是怎么对地址进行加密的。 RtlEncodePointer和RtlDecodePointer都是ntdll.dll导出的函数,RtlEncodePointer用来对一个指针进行加密,RtlDecodePointer用来对一个指针进行解密。其实整个个加密解密过程都很简单,加密时直接将指针和一个的随机数进行异或,解密时再和该随机数进行异或。 加密:point = point ^ rand 解密:point = point ^ rand rand是一个跟进程相关的随机数,通过调用函数ZwQueryInformationProcess得到,每个进程该随机数都不一样。 为了避免你再次进行反汇编,这里贴出这两个函数的代码。 RtlEncodePointer函数的代码如下: .text:7C933917 RtlEncodePointer proc near .text:7C933917 var_4 = dword ptr -4 .text:7C933917 arg_4 = dword ptr 8 .text:7C933917 .text:7C933917 mov edi, edi .text:7C933919 push ebp .text:7C93391A mov ebp, esp ;调用函数ZwQueryInformationProcess得到一个跟进程相关的随机数 .text:7C93391C push ecx .text:7C93391D push 0 .text:7C93391F push 4 ;这里得到堆栈中的一个临时变量的地址,最后得到的随机数将保存在这个临时变量中。 .text:7C933921 lea eax, [ebp+var_4] .text:7C933924 push eax .text:7C933925 push 24h ;子功能代码为0x24 .text:7C933927 push 0FFFFFFFFh .text:7C933929 call ZwQueryInformationProcess ;将得到的随机数和指针进行异或,这样就完成了加密。 ;解密的过程和加密的过程相同 .text:7C93392E mov eax, [ebp+var_4] .text:7C933931 xor eax, [ebp+arg_4] .text:7C933934 leave .text:7C933935 retn 4 .text:7C933935 RtlEncodePointer endp ; sp = 4
函数RtlDecodePointer更简单,只是直接转到RtlEncodePointer执行,因为解密的过程和加密的过程完全相同。
.text:7C93393D RtlDecodePointer proc near ;下面这四跳语句没有任何的作用 .text:7C93393D mov edi, edi .text:7C93393F push ebp .text:7C933940 mov ebp, esp .text:7C933942 pop ebp ;下面这条语句转到RtlEncodePointer执行,其实就相当于直接调用了函数 ;RtlEncodePointer .text:7C933943 jmp short RtlEncodePointer .text:7C933943 RtlDecodePointer endp
ZwQueryInformationProcess最后会调用一个系统调用,转到内核运行,最后会调用内核中的函数NtQueryInformationProcess,并且调用该函数的子功能代码为0x24。该子功能直接取出保存在进程中的一个随机数,并将其拷贝到用户堆栈中的一个临时变量中。如果该随机数为0,则还要根据系统时间重新生成该随机数,一般在进程刚开始创建的时候,这个随机数为0,从而会重新生成该随机数。由于该随机数跟进程创建的时间有关,所以这个随机数是无法猜测的。该函数在ntoskrnl.exe中导出,跟这个功能相关的函数代码为: PAGE:004970CC loc_4970CC: ;下面的代码得到一个进程唯一的随机数,子功能代码为0x24 PAGE:004970CC cmp edi, edx ; case 0x24 PAGE:004970CE jnz loc_497349 PAGE:004970D4 cmp dword ptr [ebp+8], 0FFFFFFFFh PAGE:004970D8 jnz loc_4977B8 ;下面的代码得到保存随机数的地址 PAGE:004970DE mov eax, large fs:124h PAGE:004970E4 mov eax, [eax+44h] PAGE:004970E7 mov [ebp-34h], eax PAGE:004970EA PAGE:004970EA loc_4970EA: PAGE:004970EA mov edi, [ebp-34h] PAGE:004970ED add edi, 258h ;edi地址中保存的是一个跟进程相关的随机数,这里取出这个随机数 PAGE:004970F3 mov eax, [edi] PAGE:004970F5 test eax, eax PAGE:004970F7 jz loc_4B2379 { ;如果得到的随机数为0,则重新得到随机数,得到随机数的过程如下: ;1、先得到系统的时间, ;2、而后将这个时间和系统内核中的一个值进行不断的异或操作, ;就产生了一个随机数 PAGE:004B2379 PAGE:004B2379 loc_4B2379: ;得到系统时间 PAGE:004B2379 lea eax, [ebp-3Ch] PAGE:004B237C push eax PAGE:004B237D call KeQuerySystemTime PAGE:004B2382 db 3Eh ;得到系统内核中的一个全局变量,该全局变量估计也是一个随机数 PAGE:004B2382 mov eax, ds:0FFDFF020h PAGE:004B2388 mov ecx, [eax+518h] PAGE:004B238E xor ecx, [eax+4B8h] ;将得到的随机数和得到系统时间进行异或 PAGE:004B2394 xor ecx, [ebp-38h] PAGE:004B2397 xor ecx, [ebp-3Ch] ;将计算得到的随机数保存在上面的跟进程相关的全局变量中,edi中保存的就是 ;这个地址。 PAGE:004B239A mov [ebp-0CCh], ecx PAGE:004B23A0 mov [ebp-0D4h], edi PAGE:004B23A6 mov eax, 0 PAGE:004B23AB mov ecx, [ebp-0D4h] PAGE:004B23B1 mov edx, [ebp-0CCh] PAGE:004B23B7 cmpxchg [ecx], edx PAGE:004B23BA push 4 PAGE:004B23BC pop edx ;重新转到loc_4970EA,再一次得到刚才生成的随机数,如果该生成的随机数为 ;0,则还会重新生成。 PAGE:004B23BD jmp loc_4970EA } ;得到随机数之后,将其拷贝到用户栈中的一个临时变量中,esi保存的就是这个临时 ;变量的地址。至此,就得到了一个跟进程相关的随机数,该随机数跟进程的创建时间 ;相关。 PAGE:004970FD mov dword ptr [ebp-4], 15h PAGE:00497104 mov [esi], eax PAGE:00497106 test ebx, ebx PAGE:00497108 jnz loc_497AA5 PAGE:0049710E jmp loc_4955F5
到这里我们已经完全清楚了整个随机数的获取过程。该随机数跟进程的创建时间相关,可见我们是无法猜得该随机数的。不过这个随机数只是再进程创建的时候产生,并且直到进程结束,该随机数都不会改变。所以,如果我们可以得到该随机数,在进程结束之前还是可以利用的。比如我们可以将其和我们的跳转地址进行异或,通过溢出将其写入到最高溢出处理地址,就可以像以前一样利用了。 不过这种方法对于远程溢出是无法利用的。但是如果能够覆盖程序的导入表或者静态数据段,那就是最理想的情况了。不过系统DLL的导入表不能够修改,但是一般程序的导入表还都是可以改的,所以还是有利用的可能性的。如果在静态数据段中存在某些函数的指针,则可以进行覆盖,从而加以利用,如果存在这种情况的话,要做到利用的通用还是有可能的。
3、VEH链表指针_RtlpCalloutEntryList的保护
我们知道堆溢出经常用的一个技巧就是修改VEH的链表指针。这在xp sp0和sp1的环境下都好使。但是sp2同样堵住了这条路。
xp_sp2下
异常处理过程 KiUserExceptionDispatcher | ________RtlDispatchException | ___________RtlCallVectoredExceptionHandlers
sp2中,该指针位于 .data:7C99C320 _RtlpCalloutEntryList dd 0 ; DATA XREF: LdrpInitializeProcess(x,x,x,x,x)+2EFo .data:7C99C320 ; LdrpInitializeProcess(x,x,x,x,x)+2F9w ...
我们就直接看看RtlCallVectoredExceptionHandlers函数
.text:7C95779C ; __stdcall RtlCallVectoredExceptionHandlers(x,x) .text:7C95779C _RtlCallVectoredExceptionHandlers@8 proc near .text:7C95779C ; CODE XREF: RtlDispatchException(x,x)+14p .text:7C95779C mov edi, edi .text:7C95779E push ebp .text:7C95779F mov ebp, esp .text:7C9577A1 push ecx .text:7C9577A2 push ecx .text:7C9577A3 push edi 这里就比较VEH的链表是不是空的,也就是看自己是否指向自己。如果是空的就不用说了,非空就转向该指针的调用 .text:7C9577A4 mov edi, offset _RtlpCalloutEntryList .text:7C9577A9 cmp _RtlpCalloutEntryList, edi .text:7C9577AF jnz loc_7C962DA0
.text:7C962DA0 loc_7C962DA0: ; CODE XREF: RtlCallVectoredExceptionHandlers(x,x)+13j .text:7C962DA0 mov eax, [ebp+arg_4] .text:7C962DA3 push ebx .text:7C962DA4 push esi .text:7C962DA5 mov [ebp+var_8], eax .text:7C962DA8 mov eax, [ebp+arg_8] .text:7C962DAB mov ebx, offset _RtlpCalloutEntryLock .text:7C962DB0 push ebx .text:7C962DB1 mov [ebp+var_4], eax .text:7C962DB4 call _RtlEnterCriticalSection@4 ; RtlEnterCriticalSection(x) .text:7C962DB9 mov esi, _RtlpCalloutEntryList .text:7C962DBF jmp short loc_7C962DD6
.text:7C962DC1 loc_7C962DC1: ; CODE XREF: RtlInitializeResource(x)+21C3Dj .text:7C962DC1 push dword ptr [esi+8]
代码就不解释那么多了,可以看到指针在使用前必须先解码,这个函数前面已经讲解过了。 .text:7C962DC4 call _RtlDecodePointer@4 ; RtlDecodePointer(x) .text:7C962DC9 lea ecx, [ebp+var_8] .text:7C962DCC push ecx .text:7C962DCD call eax .text:7C962DCF cmp eax, 0FFFFFFFFh .text:7C962DD2 jz short loc_7C962DEE .text:7C962DD4 mov esi, [esi]
所以可以看到在sp2下无法利用这个覆盖VEH链表指针的技巧了。
给出xp sp1下通用的指针
xp sp1下 .text:77F60C26 ; __stdcall RtlCallVectoredExceptionHandlers(x,x) .text:77F60C26 _RtlCallVectoredExceptionHandlers@8 proc near .text:77F60C26 ; CODE XREF: RtlDispatchException(x,x)+Ep .text:77F60C26 push ebp .text:77F60C27 mov ebp, esp .text:77F60C29 push ecx .text:77F60C2A push ecx .text:77F60C2B push edi .text:77F60C2C mov edi, offset _RtlpCalloutEntryList .text:77F60C31 cmp _RtlpCalloutEntryList, edi ;这里我们可以看到将77FC3210的值放入edi,然后和该地址的内容相比较,如果没有安装VEH,那么该地址 ;的内容也是77FC3210,就不会跳转到77F7F485。如果用户安装了VEH,那么就会跳到77F7F485 .text:77F60C37 jnz loc_77F7F485 .text:77F60C3D xor al, al .text:77F60C3F .text:77F60C3F loc_77F60C3F: ; CODE XREF: RtlInitializeResource(x)+1B6CDj .text:77F60C3F pop edi .text:77F60C40 leave .text:77F60C41 retn 8
.text:77F7F485 loc_77F7F485: ; CODE XREF: RtlCallVectoredExceptionHandlers(x,x)+11j .text:77F7F485 mov eax, [ebp+8] .text:77F7F488 push ebx .text:77F7F489 push esi .text:77F7F48A mov [ebp-8], eax .text:77F7F48D mov eax, [ebp+0Ch] .text:77F7F490 mov ebx, offset _RtlpCalloutEntryLock .text:77F7F495 push ebx .text:77F7F496 mov [ebp-4], eax .text:77F7F499 call _RtlEnterCriticalSection@4 ; RtlEnterCriticalSection(x)
关键的下面这个部分,从77FC3210里面取出安装的处理函数地址
.text:77F7F49E mov esi, _RtlpCalloutEntryList .text:77F7F4A4 jmp short loc_77F7F4B4 .text:77F7F4A6 loc_77F7F4A6: ; CODE XREF: RtlInitializeResource(x)+1B6BCj .text:77F7F4A6 lea eax, [ebp-8] .text:77F7F4A9 push eax .text:77F7F4AA call dword ptr [esi+8] ;这里esi指向struct _VECTORED_EXCEPTION_NODE结构,其0x08处为m_pfnVectoredHandler ;看到这里我们也就明白了,如果我们可以控制该指针,那么我们就可以控制程序的流程了! .text:77F7F4AD cmp eax, 0FFFFFFFFh
.text:77F7F4B4 loc_77F7F4B4: ; CODE XREF: RtlInitializeResource(x)+1B6AAj .text:77F7F4B4 cmp esi, edi .text:77F7F4B6 jnz short loc_77F7F4A6
xp_sp0下
_RtlpCalloutEntryList 位于77FC5BD0
.data:77FC5BD0 _RtlpCalloutEntryList dd 0 ; DATA XREF: RtlCallVectoredExceptionHandlers(x,x)+6o .data:77FC5BD0 ; RtlCallVectoredExceptionHandlers(x,x)+Br ...
4、堆块的cookie保护
现在堆块的结构 HEAP_ENTRY struc ; (sizeof=0X8) Size dw ? PrevSize dw ? Cookie db ? Flags db ? UnusedBytes db ? Index db ? HEAP_ENTRY ends
空闲块管理结构 _RTL_HEAP_FREE_BLOCK struc ; (sizeof=0X10) Entry _RTL_HEAP_ENTRY ? List LIST_ENTRY ? _RTL_HEAP_FREE_BLOCK ends
对比一下以前的堆块结构 typedef struct _HEAP_ENTRY { /*0x00*/ USHORT Size; /*0x02*/ USHORT PreviousSize; /*0x04*/ UCHAR SegmentIndex; /*0x05*/ UCHAR Flags; /*0x06*/ UCHAR UnusedBytes; /*0x07*/ UCHAR SmallTagIndex; } HEAP_ENTRY, *PHEAP_ENTRY;
可以看到SmallTagIndex被舍弃了,SegmentIndex挪动到后面,而第5个字节更改为cookie。 Cookie的计算公式:
堆块头部地址除以8,然后跟Heap的总体管理结构中的cookie来异或就得到了cookie的值。 代码如下 .text:7C931487 mov edx, esi ;ESI指向HEAP_ENTRY .text:7C931489 shr edx, 3 .text:7C93148C xor eax, eax .text:7C93148E mov al, [edi+4] ; 进行Cookie处理,此时edi指向堆管理结构分配头部 .text:7C931491 xor eax, edx .text:7C931493 mov [esi+4], al
那么我们可以看到cookie有256个可能的值,所以你也就不用费尽心思来想怎么覆盖cookie而不出错了。当然有很多办法绕过cookie的检测。 xp sp2对于堆的管理并没有太大的变化,但是堆的管理结构,堆块,还有Lookaside表的某些字段发生了变化,比如说有的字段从dd变成了dw,因此加了几个字段。这些细节就不在这里罗嗦了。 |