文本版|topic 高级搜索
   名人堂 帮助 论坛制度 意见反馈 | 首页 博客 周新贴 专题 求职 读书
RSS 底部
 
社区导航: 专家门诊   网络技术   操作系统   数据库   程序设计   系统应用   考试认证   CIO及信息化   站长交流   综合交流   下载基地  51CTO产品服务 设为首页 | 收藏本站
51CTO技术论坛» 网络安全 » Widows Xp Sp2溢出保护       [ 打印]  [ 订阅]  [ 收藏]  [ 推荐给朋友]   [ 本帖文本页]

论坛跳转:
     
标题: [入侵分析] Widows Xp Sp2溢出保护  ( 查看:312  回复:1 )   
 
zhirumuma
新新人类  点击可查看详细



帖子 20
精华 0
无忧币 -2
积分 100
阅读权限 20
注册日期 2008-1-26
最后登录 2008-5-27 离线

[查看资料]  [发短消息]  [Blog
       
发表于:2008-1-26 20:02   标题:Widows Xp Sp2溢出保护
上一帖 |
文章作者:funnywei_at_163.com

我们知道在我们对溢出漏洞进行exp的时候,经常要利用全局性的指针,利用异常处理。那么XP的sp2对此作了处理。使得我们无法运用以前的技巧来完成我们的工作。例如,对全局性的指针都作了编码处理。

那么具体来讲,本文主要谈到以下
1、映射给PEB管理结构的起始地址做了随机处理。后面我们会看到这种随机是很弱的,但已经足够让exp无法完成或者说是稳定的工作。
2、对TOP SEH的保护
3、VEH链表指针_RtlpCalloutEntryList的保护
4、堆块结构的cookie保护

不涉及内容:
1、如何绕过保护机制
2、堆管理的细节,其实没有太大的变化

主题开始:

1、PEB的地址的随机

xp系统下,创建进程使用的是_NtCreateProcessEx函数,而不是_NtCreateProcess函数。_NtCreateProcess主要调用_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,因此加了几个字段。这些细节就不在这里罗嗦了。



网络虽虚拟,技术无边界,来看看大家“真面目”!
2008-1-26 20:021楼
[ 顶部 ]
 
opzx
新新人类  点击可查看详细



帖子 145
精华 0
无忧币 4
积分 144
阅读权限 20
注册日期 2007-10-21
最后登录 2008-9-6 离线

[查看资料]  [发短消息]  [Blog
       
发表于:2008-1-26 22:36 
下载学习,谢谢楼主



网络虽虚拟,技术无边界,来看看大家“真面目”!
2008-1-26 22:362楼
[ 顶部 ]
     
论坛跳转:  

| | |

| | |

标记已读 · 删除论坛Cookies · 文本版 · WAP
 
| 诚征版主 | 版主堂 | 意见建议 | 大史记 | 论坛地图
Copyright©2005-2008 51CTO.COM  Powered by Discuz!
本论坛言论纯属发布者个人意见,不代表51CTO网站立场!如有疑义,请与管理员联系。
京ICP备05051492号