个人产品
企业产品
安全信息
病毒专栏 | 漏洞专栏 | 木马专栏 | 防护专栏 | 安全业界 | 毒霸活动 | 热点专题 | 漫画安全 | 期刊订阅
302 Found

302 Found


nginx/1.0.5
您的位置:首页 > 资讯中心 > 防护技巧 > 正文 免费在线查毒
系统防护技巧:Windows Xp Sp2溢出保护(二)
2004年12月22日 16:15 中国网络信息安全 

  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,因此加了几个字段。这些细节就不在这里罗嗦了。

    【责任编辑:grace
【我要发表评论】【内容指正】【论坛】【推荐给好友】·【 】【打印】·【顶部】【关闭窗口
【相关文章】 【去论坛】
· VERITAS Backup Exec 存远程缓冲区溢出漏洞 2004-12-21 14:37
· 系统应用:突破Windows 2003系统的种种限制 2004-12-21 11:29
· Samba smbd存在安全描述符远程整数溢出漏洞 2004-12-20 14:52
· MPlayer MMST电影播放程序流栈远程溢出漏洞 2004-12-20 14:33
· 微软已公布SP2补丁程序 称并非软件安全缺陷 2004-12-20 11:57
· Konqueror存在windows注入内存伪造欺骗漏洞 2004-12-17 16:49
· Win XP SP2又出漏洞 微软偷偷发"严重"补丁 2004-12-17 16:37
· 经验交流:Windows操作系统之异常处理流程 2004-12-15 13:51
· 系统应用:Windows的故障恢复控制台应用详解 2004-12-15 12:50
· MS04-044:Windows 内核和 LSASS中存在漏洞 2004-12-15 11:51
·金山毒霸单机版讨论区
·金山网镖讨论区
·金山毒霸网络版讨论区
·毒霸在线业务专题讨论区
·安全软件讨论区
·病毒救援
【相关专题】
· Windows“JPEG图片漏洞”专题专题 2004-09-28 01:21
· 再谈“CIH病毒”专题 2004-04-24 14:44
· “蠕虫王”蠕虫专题 2003-01-26 07:33
· 微软IE漏洞专题 2002-02-25 15:59
· WindowsXP漏洞专题 2001-12-24 23:31
· 微软Office专题 2001-10-22 09:59
302 Found

302 Found


nginx/1.0.5
302 Found

302 Found


nginx/1.0.5
·全民围剿 新春无毒 
·
毒霸、网镖新功能体验版 
·
毒霸网络版2.0 sp1发布 
·
毒霸6新禧礼包惊喜登场
·
毒霸网络版 人人有礼
·毒霸网络版“租用服务”
·
金山毒霸网络版大事件
·第三次缉毒万里行专题
·JPEG图片病毒专题
·金山引爆“双响炮”计划
·金山毒霸6增强版介绍
·
十面埋伏围剿木马发布会
·
6.12北京用户产品座谈会
·
金山"网游防火墙"产品
·
反电子垃圾新功略专题
·“金山毒霸再获桂冠”
·金山“安全体验风暴”
·“冲击波”病毒之罪?
·全球黑客攻击专题
·金山毒霸V金山网镖V介绍
·金山V攻略详解专题
·金山毒霸&智冠捆绑销售
·金山软件爱心服务活动
·2003年病毒疫情调查专题
更多...
·病毒短信 ·在线杀毒
·病毒上报 ·专杀工具
·产品答疑 ·
媒体合作
金山简介 | 业务合作 | 广告服务 | 招聘信息 | 客服中心 | 网页报错 | 添加毒霸到QQ上
© 2007 Kingsoft Corp. 增值电信业务经营许可证B2-20040288号