好靶场-致给李华的一封信-WP
关注泷羽Sec和泷羽Sec-静安公众号,这里会定期更新与 OSCP、渗透测试等相关的最新文章,帮助你理解网络安全领域的最新动态。
学安全,别只看书上手练,就来好靶场,本WP靶场已开放,欢迎体验:
🔗 入口:http://www.loveli.com.cn/see_bug_one?id=737
✅ 邀请码:48ffd1d7eba24bf4
🎁 填写即领 7 天高级会员,解锁更多漏洞实战环境!快来一起实战吧!👇
致给李华的一封信
题目描述
附件中有一封呈递给李华的信,而当打开它时,呈现在眼前的竟是满篇的英文促销垃圾邮件。 同时题目中另附有一封”李华的回信”,提示:
“假如你是李华……”
即使高中毕业了,这句话依然是我的噩梦。三年了,李华,你总是让我替你写信。想邀请外国朋友参加画展?找我。想感谢老师?找我。还有你的笔友,我什么要找我写呢?现在的网络这么发达,网上不可以翻译或者AI来写吗?就连丢了东西写个失物招领也要找我!你是不是连一句完整的英语都不会说?
反正我已经找到了你的邮箱地址,2026年2月23日凌晨1点44,我给你写了一封信,我知道你都不懂,你记得签收,我知道你的水平,你应该可以找到真正的一封信,反正我就是要报复你 亲爱的:LiHua
Dear Contestant,
Hello! I am Li Hua. You must be very familiar with me, after all, over the past three years, we have spent countless afternoons together preparing for exams. A user named San Jiu sent me a messy email. You know me, my English isn’t very good. Even though I’ve been studying it for three years, my English is still poor, just like you said, I’ve been pretending to study diligently. So could you help me figure out what this letter is really trying to say?
Fluent in English,
Li Hua
翻译:
亲爱的选手:
你好!我是李华。你应该对我非常熟悉了,毕竟在过去的三年里,我们一起度过了无数个考试的下午。有一个叫叁玖的用户给我发了一封乱七八糟的邮件,你知道我的,我的英语不是很好,虽然我学了三年,但英文还是很差,就像你们说的那样,一直在假装努力学习。所以你能帮我看看,这封信真正想表达的是什么吗?
英语非常棒的,
李华解题思路
第一步:识别 Spam Mimic 隐写
看到大量格式统一的英文促销邮件,例如:
Dear Salaryman; Especially for you - this cutting-edge information...
Why work for somebody else when you can become rich as few as 42 MONTHS...这是经典的 Spam Mimic(垃圾邮件隐写) 特征。SpamMimic是一种隐写术,它能够把任意的信息编码到标准化的垃圾邮件模板当中,从视觉方面来看,呈现出的是看似正常却实际上无意义的促销邮件的形态。
解码工具:http://www.spammimic.com/decode.cgi
根据题目提示”密码是 LiHua”,在解码页面:
- 粘贴全部垃圾邮件文本
- 密码填入
LiHua - 点击 Decode
经解码操作之后,获取到一串起始部分为789c的十六进制数据。

第二步:识别 zlib 压缩并解压
789c 是属于zlib压缩格式的魔数,该魔数的存在意味数据已经经历了zlib压缩的处理过程,表明数据是经过zlib进行压缩的。 将十六进制数据转为二进制文件后,用 Python 解压:
import binascii
import zlib
# 粘贴 Spam Mimic 解码出的十六进制字符串
decoded_hex = "789c......"
raw_bytes = binascii.unhexlify(decoded_hex.replace(' ', ''))
decompressed = zlib.decompress(raw_bytes)
with open('output.bin', 'wb') as f:
f.write(decompressed)
print(decompressed.decode('utf-8')[:100])解压后得到一长串只包含 0 和 1 的字符串:
0110000110110000011010100101011111001110010011100101011111001011001100000100111001011000101100000110若直接按每8位转换为ASCII,得到的是乱码,这表明并非是普通的二进制编码形式。

第三步:识别格雷码并转换
在进行了多种不同的编码尝试之后,最终确定这是格雷码,我们常说的GrayCode。 格雷码具有这样的特性,即相邻的两个数值之间仅仅在一位二进制位上存在差异,它常常被应用于通信领域以及编码转换的相关场景里。
格雷码 → 普通二进制的转换规则:
- 最高位不变
- 后续每一位 = 前一位二进制结果 XOR 当前格雷码位
完整解码脚本:
def gray_to_binary(gray):
"""格雷码转标准二进制"""
binary = gray[0]
for i in range(1, len(gray)):
binary += str(int(binary[i - 1]) ^ int(gray[i]))
return binary
def binary_to_text(binary_str):
"""二进制转 ASCII 文本(每8位一组)"""
text = ""
for i in range(0, len(binary_str) - 7, 8):
byte = binary_str[i:i + 8]
ascii_val = int(byte, 2)
if 32 <= ascii_val <= 126:
text += chr(ascii_val)
return text
# 读取解压后的数据
with open('output.bin', 'r') as f:
gray_data = f.read().strip()
binary = gray_to_binary(gray_data)
result = binary_to_text(binary)
print(result)
自动化一键脚本
以下脚本自动完成 Spam Mimic 解码 → zlib 解压 → 格雷码转换的全流程:
import binascii
import zlib
# 把网站给你的十六进制字符串粘贴在这里
decoded_hex = "789c......" # 粘贴完整的十六进制
raw_bytes = binascii.unhexlify(decoded_hex.replace(' ', ''))
decompressed = zlib.decompress(raw_bytes)
gray_str = decompressed.decode('utf-8').strip()
def gray_to_binary(gray):
binary = gray[0]
for i in range(1, len(gray)):
binary += str(int(binary[i-1]) ^ int(gray[i]))
return binary
def binary_to_text(binary_str):
text = ""
for i in range(0, len(binary_str) - 7, 8):
byte = binary_str[i:i+8]
ascii_val = int(byte, 2)
if 32 <= ascii_val <= 126:
text += chr(ascii_val)
return text
binary = gray_to_binary(gray_str)
print(binary_to_text(binary))最终结果
解码后得到一封完整的中文信——一个被”李华”折磨了三年的高中考生的控诉书:
A Letter to Li HuaDear (and absolutely insufferable) Li Hua,As I sit down to write this, I don't even need to use my brain to come up with an opening. After all, over my three years of high school, the letters I've written to you, the ones I've ghostwritten for you, and the lies I've covered up for you probably outnumber the English vocabulary words I've memorized. Seriously, Li Hua, did you enroll in high school to actually study, or just to treat me as your full-time, unpaid secretary?I genuinely can't wrap my head around it: how can someone who made it into high school be so ridiculously dense that they can't even write a basic invitation or thank-you note? Every time an English exam gets handed out and I see "Suppose you are Li Hua" or "Write a letter to Li Hua," I literally feel physically ill. You want to invite a foreign friend to a school event? You come to me. You need to write a thank-you letter to a teacher? You come to me. You want to whine to your pen pal about your study stress? Me again. Hell, even when you lose your own stuff and need to put up a lost-and-found notice, I'm the one who has to write it. Did you literally have nothing better to do for three solid years than to be an absolute parasite?I often suspect you're doing this on purpose. Pretending you don't know a word of English, deliberately dumping all your messes onto us test-takers, and watching us tear our hair out racking our brains to fabricate those fake, insincere pleasantries and patch up your ridiculous excuses. You get to sit back and reap the benefits every single time. And what about me? Just to write a passing letter for you, I have to memorize templates, drill sentence structures, and desperately pad the word count, terrified that one misspelled word or a single grammar slip will tank my own grade.What's even more laughable is how excessively "busy" you are. One minute you're joining some activity, the next you're making a new friend, and then suddenly you have all these bizarre personal dilemmas. It's like you have to stick your nose into every single event on earth and touch every bit of trouble, leaving us to deal with the inevitable fallout. I've wondered more than once: if Li Hua didn't exist, would my English exams be a little less like torture? Would my high school life actually have a bit of peace and quiet?Honestly, I despise you. I hate that you show up in every single English composition. I hate that you are eternally incapable of writing your own damn letters. I hate that you've wasted my time and energy on this utterly meaningless "ghostwriting" gig. I am so sick and tired of speaking for you, expressing things for you, and finishing the tasks that you should be doing yourself.Today, I am writing you this letter for the very last time. Not to fix another one of your stupid messes, but just to tell you this: for the rest of your life, stay out of my essays, and stop bothering me to write for you. I never want to have anything to do with you ever again, and I refuse to suffer another headache on your account.Goodbye forever. May we never cross paths again.A test-taker you tormented for three yearsflag{09063956b0c14f709439b19892b474d2ee32e335}
致李华的一封信亲爱的(简直令人无法忍受的)李华:当我坐下来写这封信时,我甚至都不用动脑筋就能想到开头。毕竟,在我高中三年里,我给你写的信、替你代笔的信以及帮你掩盖的谎言,加起来的数量恐怕都超过了我背诵的英语单词。说真的,李华,你上高中是为了好好学习,还是就为了把我当你的全职、无偿秘书?我实在想不通:一个能考上高中的学生,怎么会蠢到连一封基本的邀请函或感谢信都写不出来?每次英语考试发下来,看到“假设你是李华”或者“给李华写一封信”,我简直要吐了。你想邀请外国朋友参加学校活动?找我。你需要给老师写封感谢信?找我。你想向笔友抱怨学习压力?还是我。天哪,甚至你丢了东西要贴寻物启事,还得我来写。你真的就整整三年没点正经事可做,就只会当个彻头彻尾的寄生虫吗?我常常怀疑你是故意这么干的。装作一句英语都不懂,故意把你的烂摊子都甩给我们这些考生,然后看着我们抓耳挠腮绞尽脑汁去编造那些假惺惺、不真诚的客套话,帮你圆那些荒唐的借口。每次都是你坐享其成,而我呢?就为了给你写封过得去的推荐信,还得死记硬背模板,苦练句型,拼命凑字数,就怕一个错别字或者一处语法错误就毁了我自己的成绩。更可笑的是你那过分的“忙碌”。一会儿参加这个活动,一会儿交个新朋友,然后突然冒出一堆稀奇古怪的个人难题。好像地球上的每一件事你都得插一手,每一点麻烦都得沾上边,最后把烂摊子留给我们收拾。我不止一次想过:要是没有李华,我的英语考试会不会好受点?我的高中生活真的能有点安宁吗?说实话,我讨厌你。我讨厌你出现在我每一篇英语作文里。我讨厌你永远都不会自己写信。我讨厌你浪费我的时间和精力在你这毫无意义的“代笔”活儿上。我受够了替你说话,替你表达,替你完成本该你自己做的事。今天,我最后一次给你写信。不是为了再帮你收拾烂摊子,只是想告诉你:余生,别再出现在我的作文里,别再烦我替你写东西。我再也不想和你有任何瓜葛,我绝不会再为你头疼。永别了。愿我们再无交集。一个被你折磨了三年的考生flag{09063956b0c14f709439b19892b474d2ee32e335}
Flag 即藏于此信之中。
flag{09063956b0c14f709439b19892b474d2ee32e335}解题链总结
垃圾邮件文本(spam.txt)
│
▼ Spam Mimic 解码(密码: LiHua)
│ http://www.spammimic.com/decode.cgi
│
十六进制数据(789c 开头)
│
▼ zlib 解压(Python: zlib.decompress)
│
一串 0/1 字符串(格雷码)
│
▼ 格雷码 → 标准二进制(本位 XOR 前一位)
│
▼ 二进制 → ASCII(每8位转一字符)
│
FLAG知识点总结
| 知识点 | 说明 |
|---|---|
| Spam Mimic 隐写 | 将信息编码进垃圾邮件模板,解码网站:spammimic.com |
| zlib 魔数 | 789c 是 zlib 压缩数据的标准文件头 |
| 格雷码 | 相邻值仅一位不同;转二进制:首位不变,后续位 = 前位结果 XOR 当前位 |
本题颇具匠心地将隐写,压缩,编码这三个知识点连贯起来,并且把“李华”这一高考经典人物当作彩蛋融入其中,饶有趣味。
Spam Mimic 隐写详解
它是什么?
在20世纪90年代末期,JackKilian创造出一种名为SpamMimic的语言学隐写术。
核心要义颇为简洁,即将秘密讯息隐匿于一篇乍看全然正常的垃圾邮件当中,使得审查者无论是人还是机器,错认为它仅仅是一封普通的促销邮件,以此达成绕过过滤的目的。
原理:用文法来编码
关键概念:上下文无关文法(CFG)
SpamMimic从根本来说,是一种具有可逆特性的文法编码体系。 它预先定义了一套垃圾邮件的模板文法,类似这样:
邮件 = 开头 + 正文段落×N + 结尾
开头 = 称呼 + 开场白
称呼 = "Dear Salaryman" | "Dear Friend" | "Dear Colleague" | ...(8种选项)
开场白 = "Especially for you -" | "Thank-you for your interest" | ...(4种选项)每一处所在都存在一定数量的固定备选项目,而选定哪一个备选项目,实际上就意味对信息进行了隐匿。
编码过程(藏信息)
若要对信息Hi实施隐藏操作,其对应的二进制形式就是 01001000 01101001。
编码器逐位读取这些二进制,用每个文法选择点来消耗比特:
称呼位置有 8 种选项 → 可以编码 3 bit(2³ = 8)
开场白有 4 种选项 → 可以编码 2 bit(2² = 4)
时间单位有 4 种 → 可以编码 2 bit
数字有多种范围 → 可以编码若干 bit
...每实施一次选择,就会损耗与之对应的位数的比特,直至所有信息都被编码完毕。
举个具体例子:
称呼有 8 种候选 → 读取 3 bit "010" = 2 → 选第2个 "Dear Colleague"
开场白有 4 种 → 读取 2 bit "01" = 1 → 选第1个 "Especially for you"
...最终生成的邮件里,每一个词语的选用都精准地与秘密信息的若干位比特一一对应起来。
解码过程(取信息)
反过来,解码器拿到这封垃圾邮件,逐个分析每个词语在文法中是第几个选项:
"Dear Colleague" 是称呼的第2个 → 输出 3 bit "010"
"Especially for you" 是第1个 → 输出 2 bit "01"
...将所有输出进行组合,便可还原出最初的二进制信息,随后把它转换成文字即可。 整个进程完全具备可逆性,不存在任何信息缺失情况。
密码的作用
你可能注意到解码时要输入密码 LiHua,这个密码的作用是:
打乱候选项的顺序(Permutation)
没有密码时,候选项按默认顺序排列:
称呼[0] = "Dear Friend"
称呼[1] = "Dear Salaryman"
称呼[2] = "Dear Colleague"
...有密码时,用密码生成一个伪随机排列:
称呼[0] = "Dear Colleague" ← 顺序被打乱了
称呼[1] = "Dear Friend"
称呼[2] = "Dear Salaryman"
...这样即使知道 Spam Mimic 技术,不知道密码也无法正确解码——因为每个选择点对应的比特值完全不同。
容量有多大?
容量是SpamMimic所存在的最为重大的不足之处所在。
在文法范畴内,每封邮件所具备的选择点数量是有限的,并且每个选择点所编码的比特数量也不多,一般处于1到3bit的范围,由此,一封邮件能够隐匿起来的信息总量极为有限,大约仅仅是几十字节到几百字节的程度。
这便是这道题需先经zlib实施压缩而后再隐匿其中的原因所在,因为原始信息体量过于庞大,唯有先行压缩才能够将其容纳进去。
安全性分析
| 攻击方式 | 效果 |
|---|---|
| 人工审查 | 很难发现,邮件看起来完全正常 |
| 关键词过滤 | 无效,没有可疑词汇 |
| 统计分析 | 理论上可行——真实垃圾邮件的词频分布和 Spam Mimic 生成的会有细微差异 |
| 知道算法但不知道密码 | 无法解码 |
| 知道算法和密码 | 完全破解 |
所以 Spam Mimic 的安全性依赖于:
- 攻击者不知道这封邮件藏了信息
- 攻击者不知道密码
倘若了解到SpamMimic被加以运用,从理论层面而言,破解密码的暴力方式是具有实施可能性的。
类似技术横向对比
| 技术 | 载体 | 原理 |
|---|---|---|
| Spam Mimic | 垃圾邮件文本 | 文法选择编码 |
| Twitter 隐写 | 推文 | 词语/标点选择 |
| 图片 LSB 隐写 | PNG/BMP | 修改最低位像素 |
| 音频隐写 | WAV | 修改不可闻频段 |
| 零宽字符隐写 | 任意文本 | 插入不可见 Unicode 字符 |
SpamMimic被归类于语言学隐写范畴,其特性在于载体自身能够完全被阅读,语义是合理的,并且是极难被人类察觉的类型。
什么是格雷码
格雷码的诞生确实源于转盘设计方面的需求,而该需求恰好是理解格雷码的理想切入点。
从转盘说起
想象一个旋转编码器,用来测量轴转了多少度。为了让传感器能读出位置,把转盘分成若干扇区,每个扇区用二进制编码:
位置0 → 000
位置1 → 001
位置2 → 010
位置3 → 011
...问题出现了。
当转盘从位置 1(001)转到位置 2(010)时,三位全都变了:
001
010
↑↑↑ 三位同时翻转传感器读取这三位不是瞬间完成的,而是有先后顺序。假设读取顺序是从高到低:
第1步读到:0 (高位已变)
第2步读到:0 (中位未变,还是旧值)
第3步读到:1 (低位未变,还是旧值)
→ 传感器瞬间读出了 001 = 1(错的!)
又比如读到 011 = 3(也是错的!)这便是毛刺问题,当转盘在两个合法位置之间运转时,有可能被误读成任意非法的中间数值,进而致使机器失去控制。
格雷码的解决方案
FrankGray在1947年提出这样一种设想,构造出一种编码,该编码具有相邻位置仅有一位存在差异的特性。 如此一来,无论传感器读取的速度多么迟缓,从一个所处位置过渡到下一个所处位置时,至多只会遇到“旧值”或者“新值”,不会出现第三种错误的状态。 他设计出来的就是格雷码:
位置 普通二进制 格雷码
0 000 000
1 001 001 ← 变了最低位
2 010 011 ← 变了中间位
3 011 010 ← 变了最低位
4 100 110 ← 变了最高位
5 101 111 ← 变了最低位
6 110 101 ← 变了中间位
7 111 100 ← 变了最低位每一行和上一行相比,永远只有一位不同,解决了毛刺问题。
那个转盘就长这样(4位格雷码的例子):

相邻的扇区之间始终仅有一条分界线的变化,所读取到的结果自始至终都处于某个合法的位置。
除了4位的格雷码盘,还有更高级的格雷码盘,主要用于一些电子机械的光电计数的部分,如讲格雷码盘刻印在电子机械的光路上,0的镂空1的地方遮挡,在起始位置射出4道光线分布在不同的半径位置上,轮盘转动的时候接收端能感应到光线的通断,进而知道轮盘旋转的角度和其代表的份数,进而还能计算出来轮盘旋转的快慢,突破早期机械弹簧式计数的速度上限。

推荐视频: https://www.bilibili.com/video/BV18W4y147Z2/ https://www.bilibili.com/video/BV18m4y1E7Nr/
为什么转换公式是 XOR?
这便是问题的关键所在,我们通过推导去探寻,而非仅仅是单纯的死记硬背。
格雷码是怎么从二进制构造出来的?
设普通二进制为 ,格雷码为 。
构造规则是:
便意味着格雷码的每一个数位是这样获得的,当前的二进制数位与高一位的二进制数位进行异或运算(最高位保持不变)。
为什么这样设计就能保证相邻只差一位?
从 到 ,普通二进制从右往左有一段连续的 1 会翻转(进位传播)。比如:
0111 → 1000 (最低4位全翻)但在格雷码里:
g_i = b_i XOR b_{i+1}由于 变化呈现出连续的进位翻转这一状态,在对XOR的相邻位进行操作之后,所有中间位置的翻转都会相互抵消,最终仅剩下最高翻转位的一个位置发生变化。这一现象是因为XOR的对消特性在发挥作用。
那反过来怎么解码?
知道格雷码 ,如何还原二进制 ?
从构造公式 出发,两边同 XOR :
所以反推公式是:当前二进制位 = 当前格雷码位 XOR 前一位二进制结果。
用代码写出来:
def gray_to_binary(gray):
binary = gray[0] # 最高位直接抄
for i in range(1, len(gray)):
# 本位二进制 = 本位格雷码 XOR 前一位二进制
binary += str(int(binary[i-1]) ^ int(gray[i]))
return binary一个具体例子:
格雷码: 1 0 1 1
↓
最高位: 1 → binary[0] = 1
第2位: 1 XOR 0 = 1 → binary[1] = 1
第3位: 1 XOR 1 = 0 → binary[2] = 0
第4位: 0 XOR 1 = 1 → binary[3] = 1
还原二进制:1101 = 十进制 13验证:查上面的对照表,格雷码 1101 对应的确实是位置 13(普通二进制 1101)。✓
总结
| 说明 | |
|---|---|
| 发明目的 | 解决旋转编码器读取时的毛刺问题 |
| 核心性质 | 相邻数值只有一位不同 |
| 构造方式 | (相邻二进制位 XOR) |
| 解码方式 | (从高位往低位逐位 XOR) |
| 现代应用 | 编码器、卡诺图化简、纠错码、CTF 隐写 |
XOR 之所以能用,本质上是因为格雷码的构造本身就是 XOR 定义的,解码只是把这个操作反向——而 XOR 的美妙之处在于它是自己的逆运算:。
🔔 想要获取更多网络安全与编程技术干货?
关注 泷羽Sec-静安 公众号,与你一起探索前沿技术,分享实用的学习资源与工具。我们专注于深入分析,拒绝浮躁,只做最实用的技术分享!💻
马上加入我们,共同成长!🌟
👉 长按或扫描二维码关注公众号
直接回复文章中的关键词,获取更多技术资料与书单推荐!📚
推荐阅读