ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SMTP伪造原理与C++ MFC邮件客户端源码解析:从发送到SPF/DKIM/DMARC防护

SMTP伪造原理与C++ MFC邮件客户端源码解析:从发送到SPF/DKIM/DMARC防护 简介这是一份围绕SMTP协议与邮件伪造原理的VC工程源码包面向网络安全学习者、邮件服务器运维人员及对SPF/DKIM/DMARC防护机制感兴趣的开发者。资源通过可编译的完整项目演示了邮件提交与伪造发件人的技术细节并结合ReadMe与源码注释说明SMTP命令流程、验证机制及防护对策便于研究者从攻击与防御双向理解邮件安全。压缩包共36个文件以h头文件、cpp源文件、rc资源脚本和vcproj工程文件为主包含obj、pdb、exe等编译产物整体约11.97MB目录结构清晰。已有1314人学习下载。该资源可直接用于本地编译调试借助类封装和示例代码快速掌握MAIL FROM、RCPT TO、DATA等SMTP核心交互并进一步分析SPF、DKIM与DMARC在实际服务器中的校验逻辑是学习邮件伪造原理与防御配置的实用参考资料。1. SMTP伪造实战一份C MFC源码包里的发件人控制细节先有一个反直觉的事实收发邮件这么多年其实SMTP协议本身从一开始就没想过要确认你是谁。网上流传的这组SMTP源码工程就是一套基于Visual Studio的MFC对话框程序里面用SMTP.cpp这个核心类实现了完整的客户端会话——连接服务器、EHLO、MAIL FROM、RCPT TO、DATA、QUIT界面上的发件人输入框会原样填进MAIL FROM命令。平时大家说的邮件伪造、钓鱼邮件里的伪造发件原理就是这么简单协议不校验身份发件地址只是个可修改的字符串。这套资源适合三类人想搞懂邮件投递全过程的协议学习者、需要在内网搭建邮件系统做联调测试的运维、以及要验证SPF/DKIM/DMARC防护策略的安全工程师。注意用它之前先给自己立个规矩——只测自己名下或已授权的服务器别拿去做任何骚扰性操作。2. 从工程文件清单到协议五步这份资源包里藏着什么2.1 文件结构SMTP类、对话框界面和base64三块分工把压缩包解压之后第一眼会看到一堆文件名字不规整但只要分清三类就很容易读懂。先看MFC对话框界面相关的一组SMTPDlg.cpp、SMTPDlg.h、SMTP.APS、SMTP.rc、resource.h其中SMTPDlg开头的文件负责所有窗口逻辑——界面上那排服务器地址、端口、发件人、收件人、主题、正文的编辑框以及发送按钮的响应函数都在这里。SMTP.APS是资源脚本编译后的缓存文件老版VS工程里很常见删了会自动重建。第二类是协议核心SMTP.cpp和SMTP.h。这两个文件把SMTP会话封装成了一个可复用的类一般会提供构造函数传入服务器地址和端口再暴露一个SendMail之类的方法传入发件人、收件人、主题、正文就能发出一封邮件。第三类是支撑模块base64.cpp和base64.h这个只在SMTP AUTH LOGIN认证时才用得上——用户名和密码要经过base64编码后发给服务器。文件归属职责SMTP.cpp / SMTP.h协议核心封装EHLO、MAIL FROM、RCPT TO、DATA、QUIT命令SMTPDlg.cpp / SMTPDlg.hMFC界面对话框布局、编辑框数据绑定、发送按钮逻辑base64.cpp / base64.h工具模块AUTH LOGIN认证时对用户名、密码做base64编码stdafx.h / targetver.h工程标配预编译头、版本目标宏SMTP.vcproj工程文件VS2005/2008格式的工程配置SMTPDlg.aps / resource.h资源文件对话框资源ID定义与缓存ClassDiagram1.cd辅助文件VS类图用于阅读类关系Debug/SMTP.exe编译产物可直接运行的可执行程序附PDB调试符号提示SMTP.ncb和SMTP.suo分别是VC6时代的智能感知缓存和VS解决方案用户选项文件对功能没影响二次开发时忽略它们即可。为什么会有base64因为SMTP服务器在587端口上普遍要求先认证再发信AUTH LOGIN的流程是客户端发AUTH LOGIN服务器返回334提示客户端把用户名做base64编码发上去服务器再返回334客户端把密码做base64编码发上去服务器返回235表示认证通过。base64.cpp里通常就是编码和解码两个函数实现方式是查表法三个字节映射成四个可见字符结尾不足三字节时补等号。这份资源不是那种命令行式的sendmail工具MFC对话框的交互方式决定了它的主要使用场景是人工填写、手动发送。你可以把它理解成一个带图形界面的SMTP调试器发起连接时能看到服务器返回值填写收件人时能看到信封命令怎么拼接这在学协议时比直接用命令行工具有直观优势。2.2 邮件投递五步会话MAIL FROM命令里的伪造点SMTP协议从连接建立到发送完成标准流程可以拆成五步。第一步TCP连接socket连到邮件服务器的25或587端口服务器返回220就绪状态码。第二步问候客户端发送EHLO加本机域名服务器返回250后进入扩展模式并在响应里列出支持的认证机制比如AUTH LOGIN、AUTH PLAIN以及是否支持STARTTLS。第三步定义信封MAIL FROM命令设置退信地址和发件人RCPT TO命令设置收件人这一步定义的是信封和邮件正文里写的From头字段是两回事。第四步发送数据DATA命令进入正文模式客户端依次发送From、To、Subject、空行、正文最后用单独一行的英文句号结束。第五步QUIT断开连接。S: 220 smtp.test.com ESMTP C: EHLO client.local S: 250-smtp.test.com S: 250-AUTH LOGIN PLAIN S: 250 STARTTLS C: MAIL FROM:attackerfake.com S: 250 OK C: RCPT TO:victimtarget.com S: 250 OK C: DATA S: 354 End data with CRLF.CRLF C: From: 伪装者 attackerfake.com C: To: victimtarget.com C: Subject: test C: C: hello C: . S: 250 OK: queued as 12345 C: QUIT S: 221 Bye找一下MAIL FROM这一行再对比DATA段里的From头。整个会话里最容易产生误区的就是这两个位置。MAIL FROM在信封层面定义发件人服务器转发邮件时把它当退信地址同时它也是SPF校验的对象而DATA阶段写的From头才是收件人在邮件客户端里看到的发件人。伪造邮件通常把两个位置都改掉信封MAIL FROM改成伪装域名正文From头再显示一层看起来更真的显示名。为什么能生效因为SMTP协议默认信任连接方服务器不会主动问你怎么证明你就是这个发件人。MFC界面上的字段和这套流程的对应关系很直接发件人编辑框的内容填进MAIL FROM收件人编辑框填进RCPT TO主题和正文在DATA阶段拼进邮件体。如果源代码里没做额外校验这三个字段在拼接时是什么样就什么样所以伪造的实际操作就变成了在界面上填一个不属于你的发件地址。SMTP命令典型响应MFC界面字段与伪造的关系EHLO250固定值无MAIL FROM250发件人编辑框伪造的核心位置RCPT TO250收件人编辑框无DATA354主题与正文编辑框From头可二次伪装QUIT221固定无理解到这一步后面不管做防护还是做测试脑子里都会有一条清晰的命令链路。接下来真正把这套工程编译出来跑通一次真实投递。3. 编译与运行把这份VS工程从vcproj升级到能跑通3.1 用新版Visual Studio打开旧工程的正确姿势SMTP.vcproj是Visual Studio 2005到2008年代的工程文件格式现在的VS2019、VS2022默认不认识它需要用导入功能做一次格式转换。常见的做法是直接双击vcproj文件VS会弹出一个安全警告对话框确认后进入转换向导把vcproj升级为vcxproj转换完成后自动生成Debug和Release配置。注意转换过程不会修改源代码文件只是重新生成工程配置源文件里的字符集、MFC用法等要等编译时才能看出问题。如果习惯命令行也可以用devenv做升级在VS开发人员命令提示符里执行一行命令效果和IDE向导相同# 把旧版工程升级为当前VS版本格式 devenv SMTP.vcproj /Upgrade升级完成后用MSBuild编译# 生成Release版可执行文件 msbuild SMTP.sln /p:ConfigurationRelease编译遇到的第一堵墙通常是MFC库缺失。SMTPDlg.cpp里大量使用CString、CDialog这些MFC类型如果安装VS时没勾选适用于最新v143生成工具的C MFC组件链接时会直接报fatal error C1083: 无法打开包括文件afxwin.h。解决办法是在Visual Studio Installer里点修改在单个组件里搜MFC装好对应版本的库再重开工程。第二堵墙是字符集。老工程经常用多字节字符集新版VS默认创建的项目是Unicode。如果SMTP.cpp里用char数组接socket接收缓冲区而界面用CString在Unicode配置下会提示无法从char*转换为LPCTSTR。我一般把项目属性设为使用Unicode字符集网络收发统一用char赋值给界面时用CA2T或A2T做一次编码转换比反向改到多字节省事得多。第三堵墙是运行库。老工程默认用/MD动态运行库新机器上缺对应版本的运行库Debug版会直接起不来。好在这份资源Debug目录里已经带了SMTP.exe和SMTP.pdb如果只是先看效果不二次开发直接跑exe就行一般在装了运行库的Windows 10/11上能直接运行。3.2 发信前必须填对的四项基本参数编译通过只是第一步要让程序真正发信下面四个参数一个都不能错。参数常见取值说明SMTP服务器地址smtp.qq.com、自建IP决定连接目标端口号25、465、58725明文587走STARTTLS465直接TLS发件人/收件人完整邮箱地址拼进MAIL FROM和RCPT TO认证信息用户名密码/授权码AUTH LOGIN时需Base64编码最容易翻车的点是端口和加密方式的组合。源工程如果是Windows XP时代写的大概率不支持STARTTLS那么即使把参数改成587端口程序只是把同样的明文命令发出去服务器会在EHLO后返回530 Must issue a STARTTLS command first。这种问题不是改参数能解决的得回到代码里给SMTP类补一段TLS握手逻辑第4章会展开。反过来如果用25端口连本地测试服务器没有加密要求协议裸奔没关系反而适合观察命令细节。还有一个高发坑邮箱开了客户端授权码比如QQ邮箱AUTH LOGIN时用邮箱密码会直接报535认证失败必须改成16位授权码。不同类型的邮箱服务商授权码获取入口都在账号安全设置里生成后复制粘贴进来就行。一套典型的连接流程跑完观察点在哪里看界面或调试输出里的服务器响应码220是连接就绪250是命令成功235是认证通过354是开始接收邮件数据DATA结束后250表示邮件已入队。收到5xx属于不可恢复错误4xx是可重试的临时错误。我拿到任何一个SMTP客户端程序都会先手工发一遍这几条命令摸清服务器脾性再让程序自动化。4. 改造发件与认证逻辑把SMTP类改成自己顺手的工具4.1 区分信封发件人与显示发件人界面加一个字段原始工程里通常只有发件人、收件人、主题、正文四个输入框。实际发信时想让伪造效果更接近真实钓鱼邮件靠的是信封发件人和DATA里的From头不一致。多数邮箱客户端在收件时如果信封发件人是a.com而正文From头显示的是b.com会直接触发DMARC的对齐检查。所以做合法功能扩展时第一步就是在界面上加一个显示发件人编辑框控制DATA阶段的From头显示名。改造步骤是这样的在SMTPDlg.h里加一个CString成员变量m_strDisplayFrom对应新编辑框的关联值界面用资源编辑器拖一个Edit Control出来ID设为IDC_EDIT_DISPLAYFROM发送按钮的处理函数里原本只有lpszFrom变量填进MAIL FROM现在把显示名拼进DATA头部// SMTPDlg.cpp 发送函数中的DATA构造部分示意 CString strData, strFrom, strTo, strBody; // 信封发件人来自界面“发件人”编辑框用于MAIL FROM strFrom m_strFrom; // DATA段头部加显示名RFC 5322格式 名字 邮箱 strData.Format( From: \%s\ %s\r\n To: %s\r\n Subject: ?UTF-8?B?%s?\r\n \r\n %s\r\n.\r\n, m_strDisplayFrom, // 显示发件人编辑框的值 m_strFrom, // 信封发件人 m_strTo, // 收件人 Base64Encode(m_strSubject), // 主题Base64编码 m_strBody); // 正文拼接逻辑里最关键的是From那一行引号包裹的显示名和尖括号包裹的邮箱地址是标准写法。收件人客户端展示时会优先把显示名作为昵称鼠标点开才看到真实邮箱。如果显示名和信封发件人域名不一致到对方服务器就要面对DMARC校验。自己做防护测试时故意制造这种不一致来观察防护策略是否生效这是很有价值的验证手段。Subject行用?UTF-8?B?...?的编码头中间内容是主题字符串做base64编码后的结果。为什么要这么做原始工程如果直接发中文主题部分客户端里会显示成乱码因为DATA段的默认charset可能是ASCII。对主题做Base64编码再把编码结果放进格式串兼容性是最好的。正文部分同样建议在DATA里加一行Content-Type: text/plain; charsetUTF-8否则中文正文会变问号。4.2 给SMTP类补STARTTLS现代服务器要求的加密握手前面提到587端口强制STARTTLS的问题如果原工程没实现就需要手动补。常见做法是EHLO收到250后先发一条STARTTLS命令等服务器返回220表示就绪再调用Windows SChannel或OpenSSL把socket升级为加密通道。Windows下最省事的是Winsock加SChannel但代码量大做个人工具的话我一般直接引入OpenSSL的libssl省事很多。// SMTP.cpp 中增加STARTTLS后的会话流程示意代码 // 1. EHLO获取服务器能力 send(sock, EHLO client.example.com\r\n, 24, 0); recv(sock, buf, sizeof(buf), 0); // 250响应能力列表含STARTTLS关键字 // 2. 发起STARTTLS协商 send(sock, STARTTLS\r\n, 11, 0); recv(sock, buf, sizeof(buf), 0); // 220可以开始TLS握手 // 3. TLS握手基于OpenSSL SSL_CTX* ctx SSL_CTX_new(TLS_client_method()); SSL* ssl SSL_new(ctx); SSL_set_fd(ssl, sock); int ret SSL_connect(ssl); // 握手失败返回0 if (ret ! 1) { // 常见原因证书校验失败或服务器不支持 // 自建测试环境可设SSL_VERIFY_NONE } // 4. 加密通道里重新EHLO再走AUTH LOGIN SSL_write(ssl, EHLO client.example.com\r\n, 24); SSL_read(ssl, buf, sizeof(buf)); SSL_write(ssl, AUTH LOGIN\r\n, 13);这四步的先后顺序错一个都会出问题。特别提醒STARTTLS协商完成之后必须重新发一次EHLO加密前后的服务器状态是独立的邮件服务器需要再次确认客户端能力直接接着发MAIL FROM会收到语法错误。SSL_connect返回值要检查返回1以外的值多半是证书校验失败自建测试环境通常把SSL_CTX的验证模式改成SSL_VERIFY_NONE因为本地邮件服务器的证书大多是自签的。加完这层逻辑参数就活了25端口走明文适合内网测试587端口走STARTTLS适合过运营商出口封锁465端口走SSL/TLS直接加密协商。三种模式的差别在代码里就是连接的入口函数不同465端口连接完就要直接SSL_connect不能等服务器先发言跟25、587的时序是反的。这个细节不写注释的话过三个月自己都容易忘。5. 邮件伪造防护避坑从SPF、DKIM到DMARC的验证链路5.1 SPF不对退信、DKIM验签失败、DMARC策略未对齐收到退信是判断邮件伪造防护是否生效的最直观信号。SPFSender Policy Framework的作用是收件服务器收到邮件后去DNS查询发件人域名下的TXT记录看邮件来源IP是否在授权范围内。如果你的测试发件IP不在SPF允许列表里对方服务器最常见的响应是550 5.7.1退信或者静默投递到垃圾箱。判断依据很简单退信内容里出现spffail字样说明问题出在发件IP的授权上。DKIMDomainKeys Identified Mail用另一套机制防篡改。它要求在发件域名里发布公钥发件服务器用私钥对邮件头和正文做签名收件端验签确认邮件在传输过程中被改过。如果你用SMTP客户端程序直发完全没有DKIM签名对方服务器看到的是dkimnone这个不算致命但配合DMARC策略就可能触发拒收。真正要命的是签名验证失败即dkimfail那几乎直接进垃圾箱。DMARCDomain-based Message Authentication Reporting and Conformance把SPF和DKIM的结果汇总后按域名所有者的策略执行。它有一个对齐检查信封发件人MAIL FROM的域名和正文From头显示的域名要一致至少一个域名匹配且对应的SPF/DKIM校验通过才算通过DMARC。如果你改造后的工具里信封发件人和显示发件人不一致就能看到dmarcfail。这类失败是可以人为构造的也是做防护方案验证时最有价值的一个测试点。5.2 五个必踩的坑端口封锁、base64换行、响应码误判、界面假死、中文乱码第一个坑25端口被运营商和云厂商默认封锁。现象是连接超时ping得通IP但TCP三次握手没有响应。原因是25端口是垃圾邮件重灾区IDC机房默认就封出站方向。解决方法是改用587端口配合STARTTLS或者干脆建本地邮件测试服务器不要在公网上纠结25端口。第二个坑base64编码结果里混入换行导致认证失败。现象是AUTH LOGIN发完用户名后服务器返回535或501。原因是base64规范要求编码结果每76个字符换行SMTP认证却要求单行。解决方法是去掉编码结果里的换行符或者在上层做一次替换处理我通常在编码函数末尾加一行把\r\n全部过滤掉。第三个坑误把250当发送成功。现象是程序提示发送完成收件人却迟迟没收到。原因是MAIL FROM、RCPT TO、DATA每一条命令成功后服务器都返回250四次250含义完全不同。看起来最接近成功的是RCPT TO阶段的250它只代表服务器同意接收这个收件人不代表邮件已经投递。只有DATA结束后返回的250才代表邮件入队。我习惯在每个响应处理分支里写清楚当前状态避免状态机串位。第四个坑界面假死。现象是一按发送按钮窗口拖不动点哪都没反应。原因是MFC的按钮响应函数里直接执行了同步网络收发Winsock的recv阻塞阻塞了UI线程的消息循环。解决方法是把发送逻辑放进工作线程发送完用PostMessage通知界面更新状态改造量不大但直接影响使用体验。第五个坑中文乱码。现象是主题和正文在对方客户端里显示成问号。原因是DATA段没声明charset主题没有编码。解决方法是主题用?UTF-8?B?...?格式正文加Content-Type: text/plain; charsetUTF-8。这个坑在测试中文邮件时几乎百分百遇到提前处理比事后补救省事。5.3 用这份资源做防护验证的正确姿势既然工具本身能改发件人地址反过来它就是验证防护策略的趁手工具。我最常做的一件事在自己管理的域名里配置SPF记录故意用一个不在SPF范围内的IP发信观察邮件服务器是否按预期拒收再故意构造信封发件人和正文From头域名的错配然后读DMARC报告。这套流程配合域名服务商的邮件日志能实打实验证SPF、DKIM、DMARC三条策略是否都在生效。有条件的话把第3章编译好的程序接一个本地邮件服务器用本地域名跑验证别拿公共邮箱做测试。公共邮箱往往有多层反垃圾过滤你以为防护没生效其实是被反垃圾策略拦截了现象和结论都会被污染。本地服务器能看到完整的Received头每一跳的服务器标识、时间戳、SPF、DKIM结果都写在邮件头里排查起来一目了然。注意无论测试还是学习搞清楚邮件系统的授权边界。没有明确的授权关系不要对别人的邮件服务器发起测试。伪造发件人的功能只用于自己环境下的防御验证和协议理解这个边界在动手前必须想清楚。6. 进阶技巧用本地SMTP测试服务器把判断链条收口做邮件安全测试最快收口的方法是在本地起一个轻量SMTP服务器把第3章编译好的客户端指向它发信。选型上常见的是MailHog它不转发邮件、不要求认证、把所有收下的邮件放到UI里展示自带Web界面能查看邮件头也能模拟收件人视角查看伪造邮件。用Docker起一个几秒就能搞定。# 启动MailHog1025是SMTP端口8025是Web界面 docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog启动后客户端程序的SMTP服务器填localhost端口填1025发件人和收件人都用虚构地址比如testtest.com不用认证直接点发送。然后打开http://localhost:8025能看到收下的邮件点开可以完整看到Received链、From头、Subject的编码细节。这一步就验证了客户端本身的发送链路是通的、MFC界面的字段拼接是对的、base64编码的结果能正确解析。接着在这套环境里验证防护策略。如果想让结果更接近生产环境可以换成一台完整邮件服务器比如Postfix配合OpenDKIM和Dovecot配置SPF、DKIM、DMARC再用客户端发伪造邮件从Postfix的日志和退信队列里看防护结果。Postfix日志里会明明白白写spffail、dkimfail、dmarcfail比在公网上猜原因高效太多。整个链路从客户端到服务器都在自己掌控里任何一个环节出问题都能直接切到对应日志排查。这套流程是我做邮件系统运维时固定用的方法。从那以后每测一个SMTP客户端或者改一次反垃圾策略我都强制走一遍本地环境先看客户端能不能通、再看服务器防护策略有没有拦截。市面上类似的邮件伪造工具用Java、Python写的也有很多协议流程和这套C MFC实现完全一致但它能让你看清Winsock层每一个字节的收发这是脚本类工具给不了的直观感。配合本地验证链路你对邮件协议信任边界的理解会和只看文档完全不同。希望帮到你。本文还有配套的精品资源点击获取
返回列表