xfocus logo xfocus title
首页 焦点原创 安全文摘 安全工具 安全漏洞 焦点项目 焦点论坛 关于我们
Xcon English Version

用Snort从原理上检测MS05-051攻击


创建时间:2005-12-27
文章属性:原创
文章提交:stardust (stardust_at_xfocus.org)

MS05-051漏洞及相关的攻击代码和蠕虫已经出现一些日子了,从IDS的角度来看,如何检测利用MS05-051漏洞的攻击呢?

Snort虽然提供了一些规则来检测攻击相关的请求,但并远不是攻击本身:

alert udp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT-UDP IXnRemote BuildContextW little endian attempt"; flowbits:isset,dce.bind.IXnRemote; content:"|05|"; byte_test:1,&,16,3,relative; content:"|00|"; within:1; distance:1; content:"|07 00|"; within:2; distance:19;)
alert tcp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT v4 IXnRemote BuildContextW attempt"; flow:established,to_server; content:"|04 00|"; byte_test:1,!&,16,2,relative; content:"|E0 0C|k|90 0B C7|g|10 B3 17 00 DD 01 06|b|DA|"; within:16; distance:22; content:"|00 07|"; within:2; distance:28; pcre:"/^.{10}/sR";)
alert udp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT-UDP v4 IXnRemote BuildContextW attempt"; content:"|04 00|"; byte_test:1,!&,16,2,relative; content:"|E0 0C|k|90 0B C7|g|10 B3 17 00 DD 01 06|b|DA|"; within:16; distance:22; content:"|00 07|"; within:2; distance:28; pcre:"/^.{10}/sR";)
alert tcp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT IXnRemote BuildContextW attempt"; flow:established,to_server; flowbits:isset,dce.bind.IXnRemote; content:"|05|"; byte_test:1,!&,16,3,relative; content:"|00|"; within:1; distance:1; content:"|00 07|"; within:2; distance:19;)
alert tcp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT IXnRemote BuildContextW little endian attempt"; flow:established,to_server; flowbits:isset,dce.bind.IXnRemote; content:"|05|"; byte_test:1,&,16,3,relative; content:"|00|"; within:1; distance:1; content:"|07 00|"; within:2; distance:19;)
alert udp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT-UDP IXnRemote BuildContextW attempt"; flowbits:isset,dce.bind.IXnRemote; content:"|05|"; byte_test:1,!&,16,3,relative; content:"|00|"; within:1; distance:1; content:"|00 07|"; within:2; distance:19;)
alert tcp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT v4 IXnRemote BuildContextW little endian attempt"; flow:established,to_server; content:"|04 00|"; byte_test:1,&,16,2,relative; content:"|E0 0C|k|90 0B C7|g|10 B3 17 00 DD 01 06|b|DA|"; within:16; distance:22; content:"|07 00|"; within:2; distance:28; pcre:"/^.{10}/sR";)
alert udp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"NETBIOS DCERPC DIRECT-UDP v4 IXnRemote BuildContextW little endian attempt"; content:"|04 00|"; byte_test:1,&,16,2,relative; content:"|E0 0C|k|90 0B C7|g|10 B3 17 00 DD 01 06|b|DA|"; within:16; distance:22; content:"|07 00|"; within:2; distance:28; pcre:"/^.{10}/sR";)

对于正常的请求,这些规则也可能触发告警,显然这是不另人满意的。


漏洞分析
--------

要检测攻击当然需要先对MS05-051漏洞作一下比较深入的成因分析,以下的分析完全整理自小四(scz at nsfocus dot com)的工作。

漏洞的成因在于远程调用msdtcprx!BuildContextW()时存在内存破坏问题,msdtcprx.dll!BuildContextW()对应DCE-RPC 7号调用,相应的最简请求报文参数手工解码如下:

--------------------------------------------------------------------------
0x00, 0x00,                                     // +0x000 param0开始,2字节长
0x00, 0x00,                                     // 填充字节,4字节对齐
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // +0x004 param1开始,24字节长
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,                         // +0x018 param2开始,参数最大可能长度,4字节长
0x00, 0x00, 0x00, 0x00,                         // 参数最小可能长度,4字节长
0x01, 0x00, 0x00, 0x00,                         // 参数实际长度,4字节长
0x00, 0x00,                                     // 参数串,Unicode格式
0x00, 0x00,                                     // 填充字节,4字节对齐
0x01, 0x00, 0x00, 0x00,                         // +0x028 pwszHostName,param3开始,参数最大可能长度,4字节长
0x00, 0x00, 0x00, 0x00,                         // 参数最小可能长度,4字节长
0x01, 0x00, 0x00, 0x00,                         // 参数实际长度,4字节长
0x00, 0x00,                                     // 参数串,Unicode格式
0x00, 0x00,                                     // 填充字节,4字节对齐
0x01, 0x00, 0x00, 0x00,                         // +0x038 pwszUuidString param4开始,参数最大可能长度,4字节长,正常情况下应该是0x00000025,如果大于此值则是畸形的
0x00, 0x00, 0x00, 0x00,                         // 参数最小可能长度,4字节长
0x01, 0x00, 0x00, 0x00,                         // 参数实际长度,4字节长
0x00, 0x00,                                     // 参数串,Unicode格式
0x00, 0x00,                                     // 填充字节,4字节对齐
0x01, 0x00, 0x00, 0x00,                         // +0x048 param5开始,参数最大可能长度,4字节长,正常情况下应该是0x00000025,如果大于此值则是畸形的
0x00, 0x00, 0x00, 0x00,                         // 参数最小可能长度,4字节长
0x01, 0x00, 0x00, 0x00,                         // 参数实际长度,4字节长
0x00, 0x00,                                     // 参数串,Unicode格式
0x00, 0x00,                                     // 填充字节,4字节对齐
0x01, 0x00, 0x00, 0x00,                         // +0x058 pwszGuidOut param6开始,参数最大可能长度,4字节长
0x00, 0x00, 0x00, 0x00,                         // 参数最小可能长度,4字节长
0x01, 0x00, 0x00, 0x00,                         // 参数实际长度,4字节长
0x00, 0x00,                                     // 参数串,Unicode格式
0x00, 0x00,                                     // 填充字节,4字节对齐
0x00, 0x00, 0x00, 0x00,                         // +0x068 param7
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,                         // +0x074 Count
0x00, 0x00, 0x00, 0x00                          // +0x078 Array[*]
                                                // +0x07C
--------------------------------------------------------------------------

该函数的第5、6形参使用了一种少见的特殊类型,当参数最大可能长度大于0x25时,可能触发内存破坏漏洞,事实上为了触发漏洞,并不要求第5、6形参本身超长。利用这个漏洞,远程攻击者可能以Distributed Transaction Coordinator服务进程所拥有的权限在目标系统上执行任意指令。

此漏洞攻击的目标端口是可变的,可以通过查询3372端口的MSDTC服务来得到目标端口,一般大于等于1024,可暴力猜测扫描。


现有攻击代码的分析
------------------

Swan[at]0x557[dot]org 发布了一个攻击代码,导致攻击的畸形BuildContextW()请求如下:

Transmission Control Protocol, Src Port: 4738 (4738), Dst Port: 1030 (1030), Seq: 73, Ack: 61, Len: 1024
DCE RPC Request, Fragment: Single, FragLen: 1324, Call: 1 Ctx: 0, [Resp: #9]
    Version: 5
    Version (minor): 0
    Packet type: Request (0)
    Packet Flags: 0x83
        1... .... = Object: Set
        .0.. .... = Maybe: Not set
        ..0. .... = Did Not Execute: Not set
        ...0 .... = Multiplex: Not set
        .... 0... = Reserved: Not set
        .... .0.. = Cancel Pending: Not set
        .... ..1. = Last Frag: Set
        .... ...1 = First Frag: Set
    Data Representation: 10000000
    Frag Length: 1324
    Auth Length: 0
    Call ID: 1
    Alloc hint: 1284
    Context ID: 0
    Opnum: 7
    Object UUID: 906b0ce0-c70b-1067-b317-00dd010662da
    Response in frame: 9
[Packet size limited during capture: DCERPC truncated]

0000  00 0c 29 95 cc 65 00 12 3f 99 da 9f 08 00 45 00   ..)..e..?.....E.
0010  04 28 46 96 40 00 80 06 20 65 c0 a8 07 0a c0 a8   .(F.@... e......
0020  07 7a 12 82 04 06 80 f4 f5 18 90 a5 58 90 50 18   .z..........X.P.
0030  ff c3 5f 8f 00 00 05 00 00 83 10 00 00 00 2c 05   .._...........,.
0040  00 00 01 00 00 00 04 05 00 00 00 00 07 00 e0 0c   ................
0050  6b 90 0b c7 67 10 b3 17 00 dd 01 06 62 da 00 00   k...g.......b...
0060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0070  00 00 00 00 00 00 00 00 00 00 06 00 00 00 00 00   ................
0080  00 00 06 00 00 00 31 00 31 00 31 00 31 00 31 00   ......1.1.1.1.1.
0090  00 00 07 00 00 00 00 00 00 00 07 00 00 00 31 00   ..............1.
00a0  31 00 31 00 31 00 31 00 31 00 00 00 00 00 58 02   1.1.1.1.1.....X.
00b0  00 00 00 00 00 00 2b 02 00 00 cc cc cc cc cc 00   ......+.........
00c0  cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 cc 00   ................
                   ....
0420  cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 cc 00   ................
0430  cc 00 cc 00 cc 00                                 ......

从TCP数据区开始我们手工解码如下:

05                                              // DCE RPC请求头开始,总24字节长,Version: 5
00                                              // Version (minor): 0
00                                              // Packet type: Request (0)
83                                              // Packet Flags: 0x83,带Object,也就是说参数开始前有16字节长的UUID
10 00 00 00                                     // Data Representation: 10000000
2c 05                                           // Frag Length: 1324
00 00                                           // Auth Length: 0
01 00 00 00                                     // Call ID: 1
04 05 00 00                                     // Alloc hint: 1284
00 00                                           // Context ID: 0
07 00                                           // Opnum: 7

e0 0c 6b 90 0b c7 67 10 b3 17 00 dd 01 06 62 da // Object UUID,16字节长,这里并不应该是当前填的这个,但不影响攻击
00 00                                           // param0开始,2字节
00 00                                           // 填充字节,4字节对齐
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 // param1开始,24字节
00 00 00 00 00 00 00 00
06 00 00 00                                     // param2开始,参数最大可能长度,当前值为0x06
00 00 00 00                                     // 参数最小可能长度,当前值为0x00
06 00 00 00                                     // 参数实际长度,当前值为0x06
31 00 31 00 31 00 31 00 31 00 00 00             // 参数串,Unicode格式的串,长度为6*2=12字节
07 00 00 00                                     // param3开始,参数最大可能长度,当前值为0x07
00 00 00 00                                     // 参数最小可能长度,当前值为0x00
07 00 00 00                                     // 参数实际长度,当前值为0x07
31 00 31 00 31 00 31 00 31 00 31 00 00 00       // 参数串,Unicode格式的串,长度为7*2=14字节
00 00                                           // 填充字节,4字节对齐
58 02 00 00                                     // param4开始,参数最大可能长度,当前值为0x258=600>37,畸形!导致攻击
00 00 00 00                                     // 参数最小可能长度,当前值为0x00
2b 02 00 00                                     // 参数实际长度,当前值为550
cc cc cc cc cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 // 以下为参数串,不重要


攻击请求的检测
--------------

近期出现的Dasher蠕虫使用了改写后的Swan代码作为攻击传播的手段之一,针对这个特定的攻击代码,可以写出如下一个针对检测畸形BuildContextW()请求的Snort规则来方便地检测攻击:

alert tcp $EXTERNAL_NET any -> $HOME_NET 1024: (msg:"EXPLOIT MSDTC Overflow Attempt"; flow:to_server,established; content:"|05 00 00 83|"; offset:0; depth:4; content:"|2c 05|"; offset:8; depth:2; content:"|e0 0c 6b 90 0b c7 67 10 b3 17 00 dd 01 06 62 da|"; offset:24; depth:16; content:"|cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 cc 00|"; offset:208; dsize:1024; reference:url,www.microsoft.com/technet/security/bulletin/MS05-051.mspx; rev:5;)

规则的匹配要点如下:

1. TCP协议
2. 目标端口大于等于1024
3. DCE RPC头的主次版本号,“\x05\x00”
4. DCE RPC头的Packet type: Request (0),“\x00”
5. DCE RPC头的Packet Flags: 0x83,Object位置位,带Object,“\x83”
6. DCE RPC头的Frag Length: 1324,“\x2c\x05”
7. Object UUID,“\xe0\x0c\x6b\x90\x0b\xc7\x67\x10\xb3\x17\x00\xdd\x01\x06\x62\xda”
8. 一些攻击代码带的特征串,“\xcc\x00\xcc\x00\xcc\x00\xcc\x00\xcc\x00\xcc\x00\xcc\x00\xcc\x00”
9. 数据区长度等于1024

上述的规则无疑缺陷多多,因为是针对特定攻击代码的而不是基于漏洞成因的,误报的可能性虽然不大,但漏报的可能非常大,对攻击代码稍加修改即可躲过此规则的检测:攻击代码可以改成不带Object,包长完全可以不等于1024,Object的UUID可以也可以改为任意值,攻击包里的“\xcc\x00\xcc\x00\xcc\x00”填充串也可以改成别的。

新版Snort提供了新的匹配选项和功能,具体细节可以参看Snort的手册或我写的贴子:

Snort 2.x数据区搜索规则选项的改进
http://www.xfocus.net/releases/200509/a824.html

利用这些新增的匹配能力可以写出如下基于原理的检测规则:

alert tcp any any -> any 1024:1050 (flow:to_server,established; content: "|05 00 00|"; offset: 0;depth: 3; byte_test: 1, >, 128, 3, little; byte_test: 2, >, 100, 8, little; content: "|07 00|"; offset: 22; depth: 2; byte_jump: 4, 76, multiplier 2, align, little; pcre: "/......../R"; byte_jump: 4, 0, relative, align, multiplier 2, little; byte_test: 4, >, 37, 0, relative, little; msg: "DCE-RPC MSDTC Malform Opnum 7 Request (MS05-051 with object)"; rev:3;)

匹配要点结合上面的攻击报文解码分析如下:

05                                              // content: "|05 00 00|"; offset: 0;depth: 3;,匹配DCE RPC主次版本号和包类型
00                                              //
00                                              //
83                                              // byte_test: 1, >, 128, 3, little;,值大于128,表示最高位的Object位置位
10 00 00 00                                     //
2c 05                                           // byte_test: 2, >, 100, 8, little;,值大于100,要使攻击成功,请求不能太小
00 00                                           //
01 00 00 00                                     //
04 05 00 00                                     //
00 00                                           //
07 00                                           // content: "|07 00|"; offset: 22; depth: 2;,Opnum 7,BuildContextW()调用

e0 0c 6b 90 0b c7 67 10 b3 17 00 dd 01 06 62 da //
00 00                                           // param0
00 00                                           //
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 // param1
00 00 00 00 00 00 00 00
06 00 00 00                                     // param2
00 00 00 00                                     //
06 00 00 00                                     // byte_jump: 4, 76, multiplier 2, align, little;,当前匹配指针跳到下一个参数开始处
31 00 31 00 31 00 31 00 31 00 00 00             //
07 00 00 00                                     // param3,pcre: "/......../R"; ,跳过总8个字节长的两个长度字段
00 00 00 00                                     //
07 00 00 00                                     // byte_jump: 4, 0, relative, align, multiplier 2, little; ,跳到下一个参数开始处
31 00 31 00 31 00 31 00 31 00 31 00 00 00       //
00 00                                           //
58 02 00 00                                     // param4,byte_test: 4, >, 37, 0, relative, little;,检查param4的最大可能长度是否大于37,大于的话则认为是攻击
00 00 00 00                                     //
2b 02 00 00                                     //
cc cc cc cc cc 00 cc 00 cc 00 cc 00 cc 00 cc 00 //


告警关联
--------

如上改进后的规则可以从原理上检测到畸形的Opnum为7的畸形BuildContextW()请求,但是即使如此还是有很大的问题:其一,由于目标端口的不确定性,规则可能匹配到其他运行在各种不同端口上的各种不同的协议;其二,即使匹配的对象就是DCE RPC协议,还是有可能匹配到其他类型的操作,所以上述规则虽然在大多数情况下不会有漏报,但很有可能导致误报。如果能够关联此畸形的BuildContextW()请求和之前的DCE-RPC BIND MSDTC操作,那么基本上就不会有什么误报了。

匹配DCE-RPC BIND MSDTC的操作是很简单的,可以用如下规则:

alert tcp any any -> any 1024:1050 (flow:to_server,established; content: "|05 00 0b|"; offset: 0; depth: 3; byte_test: 2, =, 72, 8, little; content: "|e0 0c 6b 90 0b c7 67 10 b3 17 00 dd 01 06 62 da|"; offset: 32; depth: 16; msg: "DCE-RPC MSDTC BIND Request"; rev:1;)

匹配要点结合下面的报文解码分析如下:

Transmission Control Protocol, Src Port: 4738 (4738), Dst Port: 1030 (1030), Seq: 1, Ack: 1, Len: 72
DCE RPC Bind, Fragment: Single, FragLen: 72, Call: 1
    Version: 5                                  // content: "|05 00 0b|"; offset: 0; depth: 3;,匹配DCE RPC的主次版本号及报文类型
    Version (minor): 0
    Packet type: Bind (11)
    Packet Flags: 0x03
        0... .... = Object: Not set
        .0.. .... = Maybe: Not set
        ..0. .... = Did Not Execute: Not set
        ...0 .... = Multiplex: Not set
        .... 0... = Reserved: Not set
        .... .0.. = Cancel Pending: Not set
        .... ..1. = Last Frag: Set
        .... ...1 = First Frag: Set
    Data Representation: 10000000
    Frag Length: 72                             // byte_test: 2, =, 72, 8, little;,匹配请求长度,此BIND请求差不多此固定长度
    Auth Length: 0
    Call ID: 1
    Max Xmit Frag: 5840
    Max Recv Frag: 5840
    Assoc Group: 0x00000000
    Num Ctx Items: 1
    Context ID: 0
        Num Trans Items: 1
        Interface UUID: 906b0ce0-c70b-1067-b317-00dd010662da  // content: "|e0 0c 6b 90 0b c7 67 10 b3 17 00 dd 01 06 62 da|"; offset: 32; depth: 16;,匹配UUID串
            Interface Ver: 1
            Interface Ver Minor: 0
            Transfer Syntax: 8a885d04-1ceb-11c9-9fe8-08002b104860
            Syntax ver: 2

0000  00 0c 29 95 cc 65 00 12 3f 99 da 9f 08 00 45 00   ..)..e..?.....E.
0010  00 70 46 95 40 00 80 06 24 1e c0 a8 07 0a c0 a8   .pF.@...$.......
0020  07 7a 12 82 04 06 80 f4 f4 d0 90 a5 58 54 50 18   .z..........XTP.
0030  ff ff 69 7e 00 00 05 00 0b 03 10 00 00 00 48 00   ..i~..........H.
0040  00 00 01 00 00 00 d0 16 d0 16 00 00 00 00 01 00   ................
0050  00 00 00 00 01 00 e0 0c 6b 90 0b c7 67 10 b3 17   ........k...g...
0060  00 dd 01 06 62 da 01 00 00 00 04 5d 88 8a eb 1c   ....b......]....
0070  c9 11 9f e8 08 00 2b 10 48 60 02 00 00 00         ......+.H`....

接下来的事就是如何关联此BIND请求和畸形的BuildContextW()请求,Snort本身不具有事件关联能力,但可以使用SEC(Simple Event Correlator)达到这个目的。SEC是一个轻量级的基于规则的事件关联引擎,这个工具完全用Perl实现,可以为多类事务提供强大的事件关联能力。如何使用工具及编写规则可以参看链接:http://kodu.neti.ee/~risto/sec/

SEC可以读取Snort产生的告警日志的条目,结合上述的Snort规则,事件关联规则可以是如下这样:

ms05051.sec
---------------------------------- 8< -------------------------------------------
type=Single   # SEC规则类型
ptype=RegExp  # 匹配模式类型为正则表达式
pattern=^(.+) .+ DCE-RPC MSDTC Malform Opnum 7 Request .+ ([\d\.]+):?(\d*) -> ([\d\.]+):?(\d*)  # 匹配规则告警条目,匹配到畸形的BuildContextW()请求告警,并提取出告警时间,源目的IP和源目的端口
context=!MS05051$2$4 && MSDTC_BIND$2$3$4$5  # 分析上下文标记,MS05051$2$4(即“MS05051源目的IP”)是否不存在,此标记标志着已经在一段之前有相应源目的IP相关的MS05-051告警;MSDTC_BIND$2$3$4$5(即“MSDTC_BIND源IP端口目的IP端口”),是否存在,此标记标志着已经有MSDTC BIND操作发生。
desc=$1 [MSDTC MS05-051 Attack!] $2 -> $4  # 在一段时间内没有报过相应源目的IP MS05-051告警且有BIND操作的情况下,生成事件名
action=write - %s; delete MSDTC_BIND$2$3$4$5; create MS05051$2$4 60  # 输出告警,删除之前的BIND标记,创建已告警标记,超时为60秒,也就是说在60秒内不对相同源目的IP MSDTC攻击告警。

type=Single
ptype=RegExp
pattern=DCE-RPC MSDTC BIND Request .+ ([\d\.]+):?(\d*) -> ([\d\.]+):?(\d*)  # 匹配规则告警条目,匹配到MSDTC BIND操作事件
desc=$0
action=create MSDTC_BIND$1$2$3$4 2 # 创建BIND标记,以备之后可能匹配到畸形BuildContextW()请求时参考,超时2秒,也就是2秒内没有如果发现畸形BuildContextW()请求就作废而不作为参考。
---------------------------------- 8< -------------------------------------------


配置Snort与SEC协同工作
----------------------

主要的任务是配置SEC能够读取Snort告警信息的输出,这可以通过先进先出的PIPE进行。

对Snort的配置:

1. 创建先进先出的管道文件

   [root@ /usr/snort/logs]> mkfifo alert_pipe

2. 编辑snort.conf文件,插入输出告警信息到管道的配置行

   output alert_fast: alert_pipe
  
3. 重启Snort

安装并运行SEC

[root@ /usr/snort]> sec.pl -conf=ms05051.sec -input=logs/alert_pipe -debug=4
Simple Event Correlator version 2.3.2
Reading configuration from ms05051.sec
12/21-13:52:10.365657  [**] [MSDTC MS05-051 Attack!] 10.0.1.1 -> 10.0.1.113