百度一下 藏锋者 就能快速找到本站! 每日资讯归档 下载藏锋者到桌面一键访问

当前位置:主页 > 网络安全 > VC编程分析套接字发送带附件的电子邮件过程

VC编程分析套接字发送带附件的电子邮件过程

所在栏目:网络安全 时间:04-13 00:25 分享:

本文要分析的是带附件电子邮件的发送。发送邮件有几种方法,可以使用控件,使用MAPI,这些似乎都是基于Outlook客户端的了。但在没有安装Outlook的电脑是上就要使用套接字实现了。简单邮件的发送以前已经有人给出了,对于搞安全的人来说,如果邮件没有附件,其实是没有什么攻击力的,用纯文本来实现攻击的可能性其实非常小(至少也是需要一个网页吧)。所以这篇文章将从协议开始出发,使用套接字编写一个可以发送附件的邮件客户端。有关SMTP(简单邮件协议)的详细相关内容,请参考协议标准。下面来大概看一下这个协议。

SMTP通信模型

在互联网上使用的SMTP通信示意图如图1所示(SMTP所对应的RFC文档为RFC821)。

SMTP通信示意图

图1

SMTP是基于C/S模型建立在TCP/IP协议之上的通信协议,其处理的文件是基于ASCII码的,所以命令也是ASCII码。用户发送一封电子邮件的基本过程是:
1) 客户端格式化邮件,以备发送;
2) 客户端使用TCP连接至SMTP服务器;
3) SMTP服务器返回是否就绪状态,如果连接失败,邮件发送终止;
4) 现在许多服务器到要有验证登录了,所以接下来就是验证用户身份,说通俗一点就是验证用户名和密码(就是网页上登录邮箱用的那个了);
5) 服务器返回验证信息,如果验证失败,邮件发送终止;
6) 客户端发出发送请求,并将格式好的邮件传给服务器;
7) 发送完毕退出。

由上面的发送过程可以知道,客户端和服务器的连接其实是使用一问一答的联系方式的。相互之间可以识别的语言就是SMTP邮件命令了。SMTP命令相对比较少,常用的命令有以下几个(实际使用时首先连接服务端会收到欢迎信息):

HELO <domain> <CRLF>:向服务器标识用户身份,注意后面的domain 是必不可少的。

MAIL FROM:<reverse-path> <CRLF>:<reverse-path>为发送者地址,此命令用来初始化邮件传输,即用来对所有的状态和缓冲区进行初始化。

RCPT TO:<forward-path> <CRLF>:<forward-path>用来标志邮件接收者的地址,常用在MAIL FROM后,可以有多个RCPT TO。
DATA <CRLF>:将之后的数据作为数据发送,以<CRLF>.<CRLF>标志数据的结尾。
REST <CRLF>:重置会话,当前传输被取消。
NOOP <CRLF>:要求服务器返回OK应答,一般用作测试。
QUIT <CRLF>:结束会话。

以上命令均不区分大小写,返回2开头的就是命令成功得到响应,5开头表示失败,具体内容请参看有关文档。由于简单邮件协议只处理7位的ASCII文本,对身份认证也不支持,为了支持身份验证和传输二进制文件,标准化组织对简单邮件传输协议进行了扩展,扩展后的协议(MIME标准来支持)已经支持附件发送了。使用的登录命令为:AUTH LOGIN返回334表示成功。验证使用的加密方式不定,不过通常是BSASE64加密,BASE64明显不太安全(其实也没有什么安全可言了,只要截获就可以还原出密码来),所以也有采用MD5加密验证的了。随文已经提供一个用于MD5加密的动态链接库,如果有需要,可以扩展,在本程序里使用base64加密。这个扩展了的协议在使用时需要把附件用Base64进行编码。

下面说说常见电子邮件的格式,为了方便说明,首先使用Windows自带的Outlook编写一封邮件,简单写个内容,添加一个附件(我这里添加一个可执行程序),为便于分析,附件不要太大。另存在某处,用记事本打开这个文件。如果不出意外,得到的应该是类似于如下的内容:
From: "Test" 6252656757@qq.com //这个是发件人的名字,我猜可以伪造,不过估计服务端可以检测出来
To: 37510834727@QQ.COM //这个是收信人的邮箱了
Subject: TEST //邮件主题
Date: Wed, 16 Sep 2009 12:48:20 +0800 //邮件日期
MIME-Version: 1.0 //这个就是扩展协议的内容之一了,要发送带附件的邮件,这是必须的
Content-Type: multipart/mixed;

上面这个表示内容类型是MIME标准,“/”前面的是主类型,后面是子类型。具体含义如表1所示。

内容类型

子类型

 

t e x t

p l a i n

r i c h t e x t

e n r i c h e d

无格式文本

简单格式文本,如粗体、斜体或下划线等

r i c h t e x t的简化和改进

m u l t i p a r t

m i x e d

p a r a l l e l

d i g e s t

a l t e n a t i v e

多个正文部分,串行处理

p a r a l l e l 多个正文部分,可并行处理

d i g e s t 一个电子邮件的摘要

a l t e n a t i v e 多个正文部分,具有相同的语义内容

m e s s a g e

r f c 8 2 2

p a r t i a l

e x t e r n a l - b o d y

内容是另一个RFC 822邮件报文

内容是一个邮件报文的片断

内容是指向实际报文的指针

a p p l i c a t i o n

o c t e t - s t r e a m

p o s t s c r i p t

任意二进制数据

一个P o s t S c r i p t程序

v i d e o

m p e g

ISO 111 7 2格式

a u d i o

b a s i c

8 bit ISDN律格式编码

i m a g e

j p e g

g i f

ISO 10918格式

C o m p u S e r v e的图形交换格式

表1

下面是定义一个分隔符,用于隔开多个部分,对于有多个段的邮件是必须的。

boundary="----=_NextPart_000_0008_01CA36CB.FD2609F0"
下面这个段是Outlook自己加上去的,我们自己发送时可以忽略。X-开头的在标准里属于用户自定义内容,服务器不做检查。
X-Priority: 3
X-MSMail-Priority: Normal
X-Unsent: 1
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.5579
This is a multi-part message in MIME format.
------=_NextPart_000_0008_01CA36CB.FD2609F0
Content-Type: multipart/alternative;
boundary="----=_NextPart_001_0009_01CA36CB.FD2609F0"
------=_NextPart_001_0009_01CA36CB.FD2609F0 //注意和下面一行不要有空格
Content-Type: text/plain;
charset="gb2312"
Content-Transfer-Encoding: base64 //这一句指定Content的加密方式,这里可以不加密了,文本为了方便不加密,把加密方式设置成7bit
VGhpcyBpcyBhIHRlc3QgZmlsZSE= // This is a test file!的base64的加密内容,也就是发送的内容了

------=_NextPart_001_0009_01CA36CB.FD2609F0
Content-Type: text/html;
charset="gb2312"
Content-Transfer-Encoding: base64
……这里省略了一大部分内容,其实是base64的加密内容,这个地方标准里没有规定,估计是Outlook为了方便以网页形式阅读邮件而加上去的吧?

------=_NextPart_001_0009_01CA36CB.FD2609F0--
//下面这个就是附件的邮件体了
------=_NextPart_000_0008_01CA36CB.FD2609F0
Content-Type: application/x-msdownload;
name="FontWindow.exe"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="FontWindow.exe"

TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
……省略,附件base64加密后的内容
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
//结尾了,注意比前面的分隔符在后面多了“—”,这点很重要,否则不能被正确解析
------=_NextPart_000_0008_01CA36CB.FD2609F0--

通过对邮件内容进行分析,可见其格式的主要内容如表2所示。

主邮件头

这里的几个域不能省略,可以作假,但估计服务端可以检测出来

邮件分隔符

 

第一个个内容头

这个要紧挨着上面的分隔符,我试过了,只要有一个空行就有问题

第一个邮件内容

 

邮件分隔符

 

第二个邮件头

注意点同上

第二个邮件体

 

………………

可以有多个邮件体

邮件结尾符

格式为“--分隔符

表2

从以上分析来看,发送环节相对简单,复杂的是对文件的格式化,这里为了降低编程的复杂度,分成几个类来写,一个类用于格式文本,一个类用于格式附件,第三个类将以上两个格式结果格式化为符合标准的邮件文件,最后一个类用于发送命令等套接字处理。对于Base64的加解密专门写成一个类,这个编码算法就不细说了,感兴趣的可以在网上搜索看看,网上也可以找到开源的代码。

打开VC6.0,新建一个基于对话框的程序,在里面添加所需要的几个类。为了简化发送,文本内容通过读取TXT文件获取,制作的界面如图2所示。下面那个编辑框显示格式化好的邮件内容,为了验证邮件的正确性,可以将里面的内容复制出来另存为XXX.eml,双击后操作系统将会默认调用Outlook来打开,如果显示的内容正确,就说明邮件格式正确了。

VC编程分析套接字发送带附件的电子邮件过程

图2

理论就是以上这些了,如果还是不清楚,就把协议标准找来结合一封实际的邮件看看。下面来简单的看一下实现代码,知道原理代码就简单了。

格式化邮件文本

主要有两个函数,一个格式化文本邮件头,另一个格式邮件内容。这里为什么要格式文本内容呢?细心的读者估计在看实际邮件时就发现了,Outlook生成的邮件每行只有七十六个字符,所以要提供这个函数,免得超过可以显示的内容。

CString CFormatMainText::Format_Head(CString Conext, CString szParam)
{CString Result;
Result.Format("Content-Type:%s%s\r\n",GetConnectType(),szParam);
Result+="Connect-Transfer-Encoding:7Bit\r\n\r\n";
return Result;
}
CString CFormatMainText::FormatString(CString szInput)
{//这个函数把内容整理为每行不超过70个字符的文本,为了兼容英文单词,主要使用空格来判断
CString szAll=szInput;
CString Result="";
CString Temp=szAll.GetAt(0);
int nlPos=1;
int Seek;
if(szAll.IsEmpty())
return "";
while(nlPos<szAll.GetLength())
{
Temp+=szAll.GetAt(nlPos);
if((nlPos%70)==0)
{  
Seek=Temp.GetLength();
while(Temp.GetAt(--Seek)!=' ');
Result+=Temp.Left(Seek);
Result+="\r\n";
Temp=Temp.Right(Temp.GetLength()-Seek);
}
nlPos++;
}
if(nlPos<70)
Result=szInput;
Result+="\r\n";
return Result;
}

简单说一下后面这个函数的算法,首先获得一行70个字符长的文本,如果最后一个不是空格,回溯回去直到找到空格,后面部分放下一行。格式化附件的代码如下,没有什么难度,也就是字符串操作,这个地方似乎还不太完善,如果中英文混合,好像处理不太好,这个问题就留给读者自行解决了。

格式化附件

以下函数用于读取文件并用Base64进行编码。
bool CFormatAttachFile::Attach_File(CString FilePath, CString &Result)
{
CStdioFile  hFile;
Result="";
char szBuffer[BYTES_TO_READ+1]={0};
int nByteRead=0;
if(!hFile.Open(FilePath,CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary))
return false;
CBase64* Encode=new CBase64;
if(Encode==NULL)
return false;
do{
try
{nByteRead=hFile.Read(szBuffer,BYTES_TO_READ);
}
catch(CFileException e)
{OutputDebugString("读文件发生异常\n");
return false;
}
Result+=Encode->Encode(szBuffer,nByteRead);
memset(szBuffer,0,BYTES_TO_READ+1);
Result+="\r\n";
}while(nByteRead==BYTES_TO_READ);
Result+="\r\n";delete Encode;hFile.Close();
return true;
}
}

格式附件头的方法和上面类似,就不贴代码了,可以结合前面的理论看看附件里完整的代码。最后看看发送电子邮件的实现,这个就更简单了,连接服务器,发送命令,验证成功后发送数据就可以了。在获取到套接字后,写个专门发送数据的函数SendCommand(CString Cmd),这个只是简单调用send函数,再写个接收数据函数即可。

bool CSendMessage::RevcData(CString &Result, char* Need)
{
char * Buffer=(char*)malloc(MAX_BUFFER);
memset(Buffer,0,MAX_BUFFER);
recv(m_hSocket,Buffer,MAX_BUFFER,0);
if(strncmp(Buffer,Need,1)==0)
{free(Buffer);
return true;
}
free(Buffer);
return false;
}

后一个参数传进来的是命令正确执行的返回值。前面已经说了,除登录命令以外,返回2开头就正确,5就失败,所以只比较第一字节。

总的来说代码编写起来没有多大难度,主要是写格式化文本函数时要细心,注意换行等,如有不清楚的请参看完整代码。为了调试方便,在下面的文本框里输出格式化好的邮件,复制出去另存为eml文件,打开就可以看到是否正确。代码在VC6.0下编译通过,并且用QQ邮箱测试成功,如图3所示。

编程测试

图3

附件里的程序是一幅图片,可见发送过程中附件没有遭到损坏,完全正确。工程只是作为一个简单示例,要是用来使用或者用作攻击,可以做成一个邮件群发器,调用搜索引擎来获取邮箱地址。剩下的任务已经很简单了,有兴趣的读者可以改进。最后说一点,其实ftp、http都是建立在TCP/UDP之上的,也可是用类似的方法实现功能。

VC编程分析套接字发送带附件的电子邮件过程 免费邮件订阅: 邮件订阅

图片推荐

热点排行榜

CopyRight? 2013 www.cangfengzhe.com All rights reserved