CTF流量分析实战:从加密SMTP中提取隐藏Excel文件 1. 项目概述从加密流量中“捞”出隐藏的Excel在CTF的Misc杂项或Forensics取证类题目里流量分析一直是个高频考点。它考察的不仅仅是Wireshark的基本操作更是对网络协议、数据封装乃至加密算法理解的综合检验。最近遇到一道挺有意思的题目表面看是一段加密的邮件流量SMTP over TLS/SSL但出题人却在其中藏了一个完整的Excel文件。直接打开流量包看到的全是加密的乱码常规的“Follow TCP Stream”在这里完全失效。这道题的核心挑战在于你需要识别出加密外壳下的数据本质并找到正确的“钥匙”将其剥离出来最终还原出那个隐藏的Excel文档。整个过程就像一场数字考古你需要先判断“地层”协议层再找到“文物”嵌入文件的轮廓最后用合适的“工具”解密或提取脚本将其完好无损地取出。本文将详细拆解这道题的完整解题思路并附上用于自动化提取和解密的Python脚本。无论你是CTF新手想了解流量分析中的文件提取技巧还是有一定经验的选手想深化对加密协议下数据隐藏的理解这篇实战记录都能提供清晰的路径。2. 核心思路与协议层分析面对一个加密的流量包通常是.pcap或.pcapng格式第一步永远是进行初步的协议统计和会话分析而不是一头扎进某个数据流里。2.1 初探流量识别加密协议与可疑会话用Wireshark打开流量包后我首先查看“统计”菜单下的“协议分级”。这里通常会显示绝大部分流量都被标记为TLS或SSL这证实了通信内容被加密。接下来需要找到具体的通信会话。因为题目提及“邮件流量”所以我会在过滤栏输入smtp或pop3或imap进行筛选。有时出题人可能使用非标准端口所以如果标准协议过滤无果我会查看“统计”-“会话”按照端口或IP地址排序寻找那些流量较大或交互模式类似邮件发送例如从客户端到服务器端口25、465或587的持续连接的TCP会话。关键点在加密流量中我们虽然看不到应用层内容如邮件正文、附件但元数据如IP地址、端口、协议类型、数据包长度、传输时间仍然是明文且极具价值的。一个突然增大的、在TLS应用数据记录中的TCP段很可能就是附件传输的标志。2.2 假设与验证文件如何隐藏在加密流中在SMTP over TLS的通信中邮件内容包括附件会在建立TLS安全通道后被加密传输。那么出题人可能用什么方式隐藏文件呢常见的有以下几种思路我们需要逐一验证隐写于加密载荷中这是最直接的方式。整个Excel文件可能被Base64编码后作为邮件附件MIME格式的一部分在TLS通道内传输。虽然我们无法直接解密TLS没有私钥但如果出题人“不小心”或故意在某个环节泄露了密钥或者使用了弱加密算法我们就有机可乘。首先需要检查Wireshark是否能够解析出TLS的密钥编辑-首选项-Protocols-TLS检查是否有SSL key log file。在CTF中有时会直接提供服务器私钥或会话密钥。文件拆分并嵌入多个数据包Excel文件可能被分割成多个小块作为TLS Application Data的有效载荷。即使不能解密我们也可以将所有加密应用层数据按顺序拼接起来形成一个大的二进制块。如果这个文件没有经过额外的加密只是被TLS包裹那么拼接后的二进制数据本身可能就是完整的Excel文件通常以PKZIP归档或特定的OLE2头开始。我们可以直接提取这些原始字节进行验证。利用协议元数据文件数据可能伪装成其他协议字段但结合“加密邮件”这个场景前两种可能性更高。我的分析路径是先尝试提取所有TLS应用层数据的原始字节查看文件头。如果失败再深入寻找解密的可能性。注意在真实CTF比赛中题目通常会给出一些提示比如“密码是邮箱号”、“密钥在流量中”等。要仔细阅读题目描述任何非流量文件如readme.txt、hint.txt都可能包含关键信息。3. 实操步骤从流量提取到文件还原假设我们通过会话分析确定了一个从客户端192.168.1.100到邮件服务器10.0.0.1在端口465SMTPS上的TLS会话。我们的目标是从这个会话中提取出隐藏的Excel文件。3.1 步骤一提取加密应用数据载荷由于无法直接解密我们选择提取原始传输数据。在Wireshark中选中这个TLS会话的任意一个包右键选择“追踪流” - “TCP流”。此时Wireshark会展示这个TCP连接上所有数据的原始字节包括TLS握手记录和应用数据记录并以ASCII和Hex形式显示。这里有一个至关重要的技巧在TCP流窗口的底部将“显示和保存数据为”的选项从“ASCII”改为“原始数据”。然后点击“另存为…”保存为一个二进制文件例如raw_tcp_stream.bin。这个文件包含了该TCP连接上所有的原始网络层数据包括TCP头、TLS记录头和应用层加密数据。我们的目标是从中剥离出纯应用层数据。更精确的方法是使用tsharkWireshark的命令行版本进行过滤提取。我们可以只提取tls.app_data的载荷。打开终端执行以下命令tshark -r encrypted_email.pcapng -Y tls and ip.src192.168.1.100 and tcp.dstport465 -T fields -e tls.app_data app_data_hex.txt这条命令做了以下几件事-r encrypted_email.pcapng: 指定输入的流量文件。-Y tls and ip.src192.168.1.100 and tcp.dstport465: 设置显示过滤器只抓取来自客户端且目的端口为465的TLS数据包通常是应用数据。-T fields -e tls.app_data: 指定输出格式为字段并提取tls.app_data这个字段的值即加密载荷的十六进制字符串。 app_data_hex.txt: 将输出重定向到文本文件。现在app_data_hex.txt文件里每一行就是一个TLS应用数据包的加密载荷十六进制字符串。我们需要将这些字符串连接起来并转换成二进制。3.2 步骤二十六进制字符串转二进制并识别文件接下来编写一个Python脚本读取app_data_hex.txt将所有十六进制字符串拼接转换为二进制数据并保存。同时检查这个二进制数据的开头判断其文件类型。import binascii def extract_raw_payload(hex_file_path, output_bin_path): 从包含十六进制字符串的文件中提取并合并所有载荷为二进制文件。 with open(hex_file_path, r) as f: # 读取所有行去除空白字符过滤空行 hex_strings [line.strip() for line in f if line.strip()] # 将所有十六进制字符串拼接成一个大的字符串 full_hex .join(hex_strings) # 将十六进制字符串转换为二进制数据 try: raw_data binascii.unhexlify(full_hex) except binascii.Error as e: print(f十六进制转换错误: {e}) # 尝试处理可能存在的非十六进制字符如冒号分隔 full_hex full_hex.replace(:, ).replace( , ) raw_data binascii.unhexlify(full_hex) # 将二进制数据写入文件 with open(output_bin_path, wb) as f: f.write(raw_data) print(f[] 原始载荷已保存至: {output_bin_path}) print(f[] 数据总大小: {len(raw_data)} 字节) # 检查文件头 print([*] 检查文件头...) file_header raw_data[:8] # 查看前8个字节 print(f 文件头 (Hex): {file_header.hex()}) print(f 文件头 (ASCII): {file_header}) # 常见文件头识别 if raw_data.startswith(bPK\x03\x04): print([!] 文件头为 PK\\x03\\x04 - 这是一个ZIP文件也可能是.docx, .xlsx, .jar等。) # .xlsx 本质上是ZIP压缩包 elif raw_data.startswith(b\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1): print([!] 文件头为 OLE2 Compound Document - 可能是旧的.xls文件。) elif raw_data.startswith(b\x50\x4B\x03\x04): print([!] 明确的ZIP文件头。) else: print([*] 未识别到常见归档或文档文件头可能数据仍需进一步处理如解密。) if __name__ __main__: extract_raw_payload(app_data_hex.txt, extracted_payload.bin)运行这个脚本后我们得到了extracted_payload.bin。如果运气好控制台输出识别到了ZIP或OLE2文件头那么恭喜隐藏的文件可能就在这里面。但更常见的情况是输出显示文件头是乱码这说明TLS加密仍然有效我们提取的只是密文。3.3 步骤三尝试解密TLS流量如果直接提取的二进制是密文我们就需要寻找解密的方法。在CTF中解密TLS通常有以下几种预设场景提供RSA私钥如果题目提供了服务器的私钥文件如server.key可以在Wireshark的TLS设置中导入Wireshark会自动解密所有使用该密钥的会话。提供SSL会话密钥有时会提供一个sslkeylogfile.txt文件其中包含了TLS会话的预主密钥。在Wireshark中配置此文件路径后也能直接解密。弱加密或自定义加密题目可能使用了已被破解或强度很弱的加密算法如RC4、NULL加密套件或者根本不是标准TLS而是自定义的简单XOR加密。这时就需要分析流量寻找加密算法和密钥的线索。实操在Wireshark中配置解密打开Wireshark进入编辑 - 首选项 - Protocols - TLS。在(Pre)-Master-Secret log filename栏浏览并选择提供的sslkeylogfile.txt。点击确定Wireshark会自动重新解析流量。此时原来的TLSv1.2 Application Data数据包可能会变成解密后的协议如SMTP、IMFInternet Message Format等。解密成功后你就可以像分析普通邮件流量一样追踪TCP流看到明文的SMTP对话和邮件的原始内容包括MIME部分。附件通常以Content-Disposition: attachment; filenamesecret.xlsx的形式出现其内容经过Base64编码。你可以直接从这个明文的TCP流窗口中复制Base64编码的部分进行解码还原。4. Python自动化解密与提取脚本在实战中尤其是当解密后的邮件流很大或者需要批量处理时手动从Wireshark复制Base64数据非常低效。下面提供一个功能更全面的Python脚本它假设我们已经通过某种方式如上述的keylog文件获得了明文的TCP流数据并保存为文本文件decrypted_smtp_stream.txt。这个脚本的目标是自动从这个明文流中识别并提取出Excel附件。import re import base64 import os from io import BytesIO import zipfile # 用于验证.xlsx文件 import olefile # 用于验证旧的.xls文件需要安装olefile包 def extract_excel_from_smtp_stream(stream_file_path, output_dirextracted): 从解密的SMTP流文本中自动寻找并提取Excel附件。 支持识别.xlsxBase64编码在MIME部分和.xls可能为二进制形式。 if not os.path.exists(output_dir): os.makedirs(output_dir) with open(stream_file_path, r, encodingutf-8, errorsignore) as f: stream_text f.read() print(f[*] 正在分析SMTP流文件大小: {len(stream_text)} 字符) # 情况1查找Base64编码的附件常见于.xlsx # MIME中附件的典型模式以Content-Disposition: attachment开始后面跟着Base64块 # Base64块通常由空行隔开且每行长度固定通常为76或64字符 print([*] 尝试查找Base64编码的附件...) # 改进的正则表达式更精确地匹配MIME附件部分的Base64数据 # 匹配 Content-Disposition: attachment 之后到下一个MIME边界之前的内容 attachment_section_pattern re.compile( rContent-Disposition:\s*attachment[^]*?(?:\r?\n)([A-Za-z0-9/\r\n]), re.IGNORECASE | re.DOTALL ) matches attachment_section_pattern.findall(stream_text) if matches: print(f[] 找到 {len(matches)} 个可能的Base64附件区块。) for idx, base64_block in enumerate(matches): # 清理Base64字符串中的空白字符 clean_base64 re.sub(r[\r\n\s], , base64_block) # 确保长度是4的倍数 padding 4 - (len(clean_base64) % 4) if padding ! 4: clean_base64 * padding try: file_data base64.b64decode(clean_base64, validateTrue) # 尝试识别文件类型 if file_data[:4] bPK\x03\x04: # 是ZIP格式很可能是.xlsx filename fattachment_{idx}.xlsx filepath os.path.join(output_dir, filename) with open(filepath, wb) as f_out: f_out.write(file_data) print(f [] 已提取文件: {filename} (大小: {len(file_data)} 字节)) # 可选验证是否为有效.xlsx即ZIP包 try: with zipfile.ZipFile(BytesIO(file_data)) as zf: if [Content_Types].xml in zf.namelist(): print(f - 验证通过: 是一个有效的.xlsx文件。) except zipfile.BadZipFile: print(f - 警告: 提取的数据具有ZIP头但不是一个有效的ZIP文件。) elif file_data[:8] b\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1: # 是OLE Compound Document很可能是旧的.xls filename fattachment_{idx}.xls filepath os.path.join(output_dir, filename) with open(filepath, wb) as f_out: f_out.write(file_data) print(f [] 已提取文件: {filename} (大小: {len(file_data)} 字节)) else: # 未知类型保存为.bin并打印头信息 filename fattachment_{idx}_unknown.bin filepath os.path.join(output_dir, filename) with open(filepath, wb) as f_out: f_out.write(file_data) print(f [-] 提取了未知类型数据已保存为: {filename} (头: {file_data[:8].hex()})) except (base64.binascii.Error, ValueError) as e: print(f [-] 区块 {idx} Base64解码失败: {e}) continue else: print([-] 未找到典型的Base64编码附件。) # 情况2查找直接以二进制形式嵌入的文件较少见但可能存在于原始数据中 # 我们可以直接在整个流中搜索Excel文件魔数 print([*] 尝试在原始二进制数据中搜索文件魔数...) # 将整个文本视为Latin-1编码以保留所有字节值这是一种技巧 binary_data stream_text.encode(latin-1) # 搜索.xlsx的ZIP头 (PK\x03\x04) zip_header bPK\x03\x04 zip_positions [] start 0 while True: pos binary_data.find(zip_header, start) if pos -1: break zip_positions.append(pos) start pos 1 # 继续搜索下一个 if zip_positions: print(f[] 找到 {len(zip_positions)} 个ZIP文件头(PK\\x03\\x04)签名。) for i, pos in enumerate(zip_positions): # 尝试从这个位置开始提取一个完整的ZIP文件 # 这是一个简单化的方法假设文件连续直到末尾或者我们通过查找下一个PK头或流结束来确定边界 # 更稳健的方法是使用zipfile模块来尝试解析 remaining_data binary_data[pos:] try: with zipfile.ZipFile(BytesIO(remaining_data)) as zf: # 如果能成功打开说明从这里开始是一个有效的ZIP文件 filename fembedded_zip_{i}.xlsx filepath os.path.join(output_dir, filename) with open(filepath, wb) as f_out: f_out.write(remaining_data) print(f [] 从偏移量 {pos} 提取出ZIP文件: {filename}) # 这里可以跳出循环因为我们可能只关心第一个有效的 break except zipfile.BadgeFile: # 不是有效的ZIP继续尝试下一个位置 continue else: print([-] 未找到ZIP文件头签名。) print(f[*] 提取完成。文件保存在目录: {output_dir}) if __name__ __main__: # 假设你已经将解密后的SMTP流保存为文本文件 extract_excel_from_smtp_stream(decrypted_smtp_stream.txt)脚本核心逻辑解析正则匹配MIME附件脚本首先使用正则表达式在解密的SMTP流文本中搜索符合MIME附件格式的Base64数据块。这是最高效、最准确的方法因为标准邮件附件就是这么传输的。Base64解码与验证对找到的Base64块进行清洗去除换行符和空格、补全长度对齐到4的倍数然后解码。解码后通过检查文件头Magic Number来判断是否是Excel文件PK\x03\x04对应.xlsx\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1对应旧的.xls。备用方案二进制扫描如果MIME匹配失败脚本会将整个流视为二进制数据直接搜索PK\x03\x04这个ZIP文件头签名。这是一种“暴力”但有时有效的方法特别是当文件被简单拼接在流末尾时。文件保存与验证提取出的文件会被保存到指定目录并尝试用zipfile或olefile库进行简单验证确保文件的完整性。实操心得这个脚本的成功率取决于SMTP流解密的完整性和规范性。有时解密后的流可能包含控制字符或格式不规范导致正则匹配失败。此时可以尝试将stream_text先输出到文件用文本编辑器人工检查附件的大致位置然后调整正则表达式或直接手动选取Base64字符串进行解码。另外安装olefile库pip install olefile可以更好地处理旧的.xls文件。5. 常见问题排查与技巧实录在实际操作中你可能会遇到各种意想不到的情况。下面是我在多次CTF实战和日常分析中总结的一些常见问题及解决方法。5.1 问题提取的二进制数据没有明显文件头下一步怎么办排查思路检查提取是否正确确认你提取的是tls.app_data字段而不是整个TCP负载。使用tshark命令时确保过滤条件准确只提取了客户端发送或服务器返回的应用数据。尝试交换方向你可能提取了错误方向的数据流。尝试提取相反方向例如ip.dst192.168.1.100的tls.app_data因为附件可能是在服务器回复中。查看数据包长度分布在Wireshark中添加一列“Length”然后对目标TLS会话的数据包按长度排序。一个较大的、孤立的“Application Data”包比如长度在几十KB到几MB非常可疑很可能就是附件。你可以单独提取这个包的载荷进行查看。考虑是否经过二次编码或加密出题人可能先对Excel文件进行了加密或编码如XOR Base85 二进制反转然后再通过TLS发送。你需要分析提取出的二进制数据的熵值或寻找规律。使用binwalk或foremost工具对提取的bin文件进行分析看是否能分离出已知文件类型。寻找密钥或密码提示仔细阅读题目描述、流量包中的任何明文部分如TLS握手阶段的Client Hello中的Server Name Indication扩展有时会藏有信息、或者伴随流量包的其他文件。密码可能是一个单词、一个日期、一个邮箱地址等。5.2 问题Wireshark无法解密TLS也没有提供密钥文件解决方案检查加密套件在Wireshark中查看TLS握手包Client Hello和Server Hello。在Server Hello包的详情里会显示协商使用的Cipher Suite。如果套件是TLS_RSA_WITH_NULL_SHA之类的NULL加密那么实际上传输并未加密你可以直接看到明文。虽然这很不安全但在CTF中作为题目是可能的。尝试已知的弱密钥或攻击对于非常老的协议如SSL 2.0/3.0或弱算法如EXPORT级RSA密钥可能存在已知的攻击。但这在CTF中较少见通常题目会提供解密途径。理解题目意图这道题的核心可能不是让你破解TLS加密。也许“加密”只是一个幌子真正的Excel文件就藏在TLS记录层的原始字节里只需要正确拼接即可即我们之前提到的第二种隐藏方式。所以当你无法解密时回头仔细检查直接提取的二进制数据用十六进制编辑器如010 EditorHxD打开滚动查看寻找PK头或可读的字符串如[Content_Types].xml这是.xlsx内部的必然文件。5.3 问题Python脚本找到了Base64数据但解码后文件损坏排查与解决Base64填充错误确保解码前字符串长度是4的倍数并用填充。我们的脚本已经处理了这一点。字符集问题SMTP流文本可能是多种编码如utf-8,latin-1。在Python中读取时如果使用utf-8编码并遇到错误可以尝试errorsignore参数或者先用latin-1编码读取所有字节再将其视为字符串处理。最稳妥的方法是将Wireshark TCP流窗口中的原始十六进制Hex Dump直接复制出来用Python的binascii.unhexlify处理完全绕过文本编码问题。MIME边界问题正则表达式可能没有精确匹配到完整的Base64数据块可能混入了其他文本。解决方法是先在Wireshark的明文TCP流中手动找到附件开始和结束的位置通常介于--boundary_string之间确认Base64数据的准确范围然后据此调整脚本的匹配逻辑或者直接手动复制解码。文件本身被加密或压缩解码出的二进制数据可能仍然是加密的或者被额外的压缩算法处理过。再次检查文件头。如果头是PK但用压缩软件打不开可能是伪加密或需要密码。可以尝试用zip2john和johnJohn the Ripper进行密码破解如果题目给出了提示。5.4 高级技巧使用NetworkMiner或Xplico进行快速文件提取除了手动分析和编写脚本还有一些强大的网络取证工具可以自动化完成文件提取工作即使流量是加密的。NetworkMiner这款工具可以被动地解析pcap文件并自动重组和提取传输的文件。对于TLS流量如果它无法解密它可能无法识别文件。但是如果你已经通过Wireshark配置SSL key log file解密了流量你可以将解密后的流量重新保存为一个新的pcap文件然后用NetworkMiner打开这个新文件它通常能非常智能地识别出SMTP、HTTP等协议中的文件并一键提取。这比手动找Base64块方便得多。Xplico这是一个开源的网络取证分析工具功能类似。它也可以从捕获的流量中提取应用层数据包括邮件和附件。使用建议在CTF比赛中时间紧张可以先用这些工具进行快速扫描。如果工具成功提取出文件那就直接进入下一步。如果失败再回头进行手动深度分析。工具和手动分析相结合效率最高。整个从加密邮件流量中提取隐藏Excel文件的过程是一次对网络协议栈、数据封装和脚本编写能力的综合锻炼。核心在于保持清晰的思路先宏观分析协议统计、会话梳理再微观聚焦定位可疑数据流然后大胆假设文件如何隐藏最后小心求证尝试提取、解密、解码。希望这份详细的实战记录和附带的Python脚本能成为你解决类似CTF题目时的有效参考。