从流量包到Flag:一次CTF Misc挑战中的Python加密流量逆向实战
1. 初探流量包:从海量数据中定位关键线索
那天下午,我像往常一样点开了一道CTF Misc题目,附件是一个名为xxx.pcap的文件。对于Misc方向的老手来说,.pcap后缀几乎等同于“流量分析”的入场券。我熟练地打开Wireshark,映入眼帘的是密密麻麻的TCP和HTTP数据包,数量之多让人有点头皮发麻。我的第一反应是尝试过滤HTTP的POST请求,毕竟很多CTF题目喜欢把关键信息藏在表单提交里。然而,在过滤栏输入http.request.method==POST后,结果一片空白——看来出题人没走这条寻常路。
面对上千条TCP流,盲目翻看显然不现实。我静下心来思考:既然题目最终目标是获取Flag,那么流量里很可能直接包含“flag”这个关键词。我尝试在Wireshark的显示过滤器中输入tcp contains "flag"。果然,一条编号为60的TCP流被高亮显示了出来。这个编号“60”当时看起来只是个普通的序号,我完全没想到它后来会成为解题的关键一环。双击这条数据包,在下方数据详情窗口的文本显示部分,我瞥见了一段不寻常的文本,它看起来像是Python代码的片段。
我立刻右键点击这条数据包,选择“追踪流” -> “TCP流”。一个新的窗口弹出,一段完整的Python脚本赫然出现在眼前。这不再是零碎的片段,而是一个定义了加密函数、并最终打印出加密后结果的完整程序。脚本的末尾,还跟着一串长得离谱、由数字和字母组成的字符串,这显然就是被加密后的Flag密文。至此,我从一个看似毫无头绪的流量包,成功定位到了最核心的加密逻辑。这一步的关键在于利用Wireshark的过滤和追踪功能,从海量噪音中精准地找到信息“宝石”。
2. 逆向加密逻辑:拆解三重随机嵌套的“俄罗斯套娃”
把流量里找到的Python代码复制到编辑器中,我开始仔细分析这段加密逻辑。代码开头导入了string、random和base64模块,并定义了一个初始的FLAG变量(当然是占位符)。核心在于一个名为enc_ciphers的列表,它包含了三种加密方式:rot13、b64e(base64编码)和caesar(凯撒密码)。
加密的主函数是encode(pt, cnt=50)。它接收明文pt和加密次数cnt(默认50次)。我注意到它的第一步就很特别:tmp = '2{}'.format(b64encode(pt))。这意味着在进入随机循环加密之前,明文Flag先被进行了一次base64编码,并在结果前面硬拼接了一个数字字符'2'。这个'2'非常重要,它对应着enc_ciphers列表中b64e的索引(1)加1。
接下来是一个for循环,循环次数为cnt。在每一次循环中:
c = random.choice(enc_ciphers):从三种加密方法中随机选择一个。i = enc_ciphers.index(c) + 1:获取所选方法在列表中的索引,并加1,得到一个标识数字(1,2,或3)。_tmp = globals()[c](tmp):通过globals()动态调用选中的加密函数,对当前的tmp进行处理。tmp = '{}{}'.format(i, _tmp):将步骤2的标识数字和步骤3的加密结果拼接起来,作为下一轮加密的输入。
这就是一个经典的“俄罗斯套娃”式加密。最终输出的密文,是一个长字符串,其结构是:[标识数字][加密结果][标识数字][加密结果]...,从外到内层层嵌套。解密的过程,就是把这个套娃一层层剥开。需要从最外层开始,读取第一个字符(1,2,或3),根据它判断这一层使用了哪种加密,然后对后面的字符串执行对应的解密操作,得到内一层的结果,如此反复。
然而,这里存在一个关键障碍:加密循环的次数cnt是未知的。题目中encode(FLAG, cnt=?)里的问号暗示了这一点。我最初尝试了默认值50,也试过一些常见数字,但解密都失败了。后来结合官方Writeup的提示才恍然大悟:加密次数cnt就是发现这段代码的TCP流编号60。再加上最初那次单独的base64编码(它符合后续的“标识数字+密文”格式),总解密层数应该是 60 + 1 = 61 层。这个“彩蛋”设计得非常巧妙,将流量分析(数据包编号)和代码逆向(加密次数)紧密结合了起来。
3. 编写解密脚本:从Python 2到Python 3的“暗坑”与调试
理清逻辑后,我开始动手编写解密脚本。最直接的方法是在原加密脚本的基础上进行修改,补全三种解密函数(rot13、b64d、caesard),并编写一个反向操作的decode函数。最初的脚本看起来很简单,我信心满满地运行,结果却令人沮丧——脚本没有任何报错,但输出就是那一长串原始的密文,仿佛解密函数根本没起作用。
我一度怀疑自己的逻辑写错了,反复检查。后来找到官方Writeup提供的脚本(基于Python 2)运行,竟然也得到同样的结果。这让我意识到问题可能不在算法逻辑,而在运行环境。我使用的是Python 3,而题目中的加密脚本和很多网上搜到的解法是基于Python 2的。这两个版本在一些细节上的不兼容,就是导致解密失败的“暗坑”。
我开始了排查。首先,base64库的b64decode()函数在Python 2中返回字符串,但在Python 3中返回的是字节流(bytes)。如果直接将其作为下一轮解密的输入,会导致类型错误。解决方法是在每次b64decode()后,需要加上.decode('utf-8')将其转换为字符串。
其次,string.maketrans()和string.translate()函数在Python 3中已移至str类下。因此,原代码中的string.maketrans()需要改为str.maketrans(),string.translate(s, ...)需要改为s.translate(...)。
最后,print语句在Python 3中是一个函数,需要加括号。这些语法差异不会总是抛出清晰的错误,有时只会导致程序静默失败或行为异常,调试起来非常头疼。
修正了这些版本差异后,我的解密脚本核心部分如下:
def decode(encrypted_text, cnt=61):
for _ in range(cnt):
# 取出当前层的加密标识
cipher_index = encrypted_text[0]
# 取出当前层的密文主体
cipher_body = encrypted_text[1:]
if cipher_index == '1':
# rot13解密
encrypted_text = rot13(cipher_body)
elif cipher_index == '2':
# base64解密,并注意将bytes转为str
encrypted_text = b64decode(cipher_body).decode('utf-8')
elif cipher_index == '3':
# 凯撒密码解密(默认位移-3)
encrypted_text = caesard(cipher_body)
else:
print(f"未知的加密标识: {cipher_index}")
break
return encrypted_text
将流量中提取的那串超长密文赋值给变量,调用这个decode函数,设置循环次数为61。点击运行,屏幕上终于没有再次输出那串令人绝望的长字符,而是打印出了清晰可读的Flag:flag{li0ns_and_tig3rs_4nd_b34rs_0h_mi}。
4. 实战复盘与经验沉淀:不止于解题
回顾整个解题过程,从流量分析到代码逆向,再到脚本编写与调试,每一步都充满了CTF Misc挑战的典型特征。这次经历让我沉淀下几点重要的实战经验,远不止于解出这一道题。
第一,流量分析中的“关键词思维”与“元数据意识”。使用tcp contains "flag"进行过滤是一个高效技巧。更重要的是,要留意一切可能成为“提示”的元数据,比如数据包编号。在这道题里,编号60不是随机数字,而是解密密钥的一部分。在真实的安全分析或CTF中,时间戳、协议特定字段、长度异常等都可能是突破口。
第二,逆向工程中的“输入输出观察法”与“流程画像”。面对未知加密算法,不要急于一行行读代码。先整体观察:有哪些函数?核心逻辑(encode)的输入输出是什么?画出大致的流程图。这道题的核心流程就是“预处理 -> N次随机加密 -> 拼接输出”。理解了这个“套娃”模型,逆向解密的方向就非常明确了。
第三,编程环境兼容性是必须跨越的“隐形门槛”。尤其是在处理老旧CTF题目或开源工具时,Python 2/3的差异、库版本的变化常常是最大的“坑”。b64decode的返回值、string模块函数的迁移、print语法,这些都是高频雷区。一个良好的习惯是:在编写涉及加解密、编码转换的脚本时,明确指定编码(如utf-8),并对可能返回bytes类型的方法做好.decode()处理。运行脚本时,先确认其目标Python版本。
第四,调试中的“二分法”与“最小化测试”。当脚本不报错却不出结果时,最有效的方法是进行“二分法”隔离测试。例如,可以单独写一个测试,只解密一层(甚至手动模拟一层),验证b64decode后的类型是否正确,解密函数是否按预期工作。将大问题分解为小问题,逐一验证每个环节,往往能快速定位到像“字节串未解码”这类隐蔽的错误。
最后,保持耐心与记录。就像我在这道题上花费了一个下午一样,CTF挑战常常需要反复试错和深入研究。把遇到的错误、排查的思路、最终找到的解决方案记录下来,无论是写在博客、笔记还是代码注释里。这份记录不仅是个人知识库的宝贵财富,下次再遇到类似问题时,也能帮你快速唤醒记忆,节省大量时间。真正的成长,就藏在这些看似折腾的调试过程和事后的复盘总结之中。
更多推荐



所有评论(0)