464XLAT 是走向 IPv6 单栈的过程中，用来让纯 IPv6 网络能够通过地址翻译访问 IPv4 网络的技术。他的工作流程是：IPv4 应用直接发送 IPv4 分组，到达 CLAT（Customer-side Translator，用户侧翻译器）进行无状态 NAT46 转换成 IPv6 分组，通过 IPv6 网络传输到达 PLAT（Provider-side Translator，运营商侧翻译器）后再进行 NAT64 转换成 IPv4 分组到达目标 IPv4 主机。 有了这个技术，IPv4 协议栈就可以在接入层上完全砍掉了，只需要有 CLAT 将 IPv4 分组送到 IPv4 网络的边界即可。CLAT 之所以叫“用户侧”，是因为他通常运行在光猫，甚至终端设备（如手机、电脑等）上。本文记录的是终端设备运行 CLAT。 2 月春节期间，我在家里的网络上设置了 NAT64 作为 PLAT，并配置了 DHCPv4 的 IPv6-Only Preferred 标记之后，DHCPv4 的地址分配骤减到 4 个，还在使用 IPv4 的只剩下智能家居、无线电台这些嵌入式设备了。 可以见到，图中 3 个平台的 IPv4 地址均为空或者 RFC 7335 指定用于 v4 转换的 192.0.0.0/29。 其实这种事情不是第一次干，早在 2019 年就尝试过一次：当时我用 tayga 在大学寝室的 OpenWrt 上配置了 NAT64，然后使用 bind9 做了 DNS64。这一套方案通过将没有 AAAA 记录的域名的 A 记录转换成 AAAA 的方式来实现 IPv4 网络的访问，也是当时的标准做法。这样操作的问题显而易见，毕竟总有应用不按照你所想的方式去查找服务器：他们使用指定的 DNS 服务器、使用 HTTPDNS，甚至使用了硬编码的 IPv4 地址。这些都是 DNS64 方案无法解决的，因此 DNS64 只能提升接入层的 IPv6 流量比例，而并不能完全抛弃接入层的 IPv4 协议栈。 所以更好的方案是，让设备去完成这个 IPv4→IPv6 的转换。那么问题来了，设备怎么知道 NAT64 可用、怎么知道使用什么前缀呢？当时 RFC 7050 提供了通过查询 ipv4only.arpa 获得 NAT64 前缀自动配置的方案，但是他依旧受到了 DNS 服务器的限制，不适合反映接入网络的真实情况，支持度也基本局限于蜂窝网络。甚至这个域名加入 IANA 的 Special-Use Domain Names 列表都是 2020 年的事情。 到了 2020 年，RFC 8781 往 Router Advertisement 中新增了一个名为 PREF64（Type 38）的选项，路由器可以在宣告默认路由、前缀等 v6 信息的同时，宣告将 IPv4 地址嵌入什么前缀就可以通过 NAT64 访问 IPv4 网络了。这才是自动发现应该有的最佳方案，毕竟 Router Advertisement 作为通常的终端设备自动发现并配置 IPv6 网络的必要手段，反应的也正是当前网络的信息。 2021 年发布的 Android、2022 年发布的 iOS 16 和 macOS 13 都支持了 PREF64 参数。同期支持的还有 RFC 8925：他往 DHCPv4 中新增了一个名为 IPv6-Only Preferred（Option 108）的选项，支持 CLAT 的设备在尝试请求 IPv4 地址时如果收到了这个选项，那么他就会主动放弃 IPv4 协议栈。（当然也可以选择直接关闭 DHCPv4，只是这样不支持 CLAT 的设备就无法访问 IPv4 了。） 2025 年秋天，Windows 发布了 CLAT 的封测，我光速报名了。这下三大厂都支持了，于是便有了文章开头的内容。当然在测试过程中，我也发现了一些问题，已经反馈回相关负责人了。 2 月 19 日，Windows Insider Preview (Canary) 的代码从 Br（溴）更新到了 Kr（氪），build number 为 29531。这个版本引入了 CLAT，只是默认没有启用。6 月 9 日，Windows 网络团队宣布：自 Build 29599.1000 起，该功能正式开始公测。如果你家也部署了 IPv6-mostly 的网络，并想体验 Windows 的 CLAT 的话，可以使用以下操作开启： 使用 netsh.exe interface clat set global permit=enabled pref64fromdns=enabled pref64fromra=enabled 开启 CLAT，并重新启动系统。 重启后可使用 netsh.exe interface clat show global 和 netsh.exe interface clat show instance interface = [interface-name] 两条指令查看情况。 除了 netsh 之外，该功能还支持使用组策略配置。想知道组策略的操作或更多 netsh 参数，请阅读 Windows 网络团队的博客。 因为 Windows 的实现还有些问题，所以暂时不太建议在自家网络之外的地方开启。等 Windows 正式版支持 CLAT 后，微软/苹果/谷歌三大商业操作系统就都有 CLAT 且 IPv6-only capable 了，接入层可以只有 IPv6 的好时代就来了！ 另外再借个位置记录一些东西，是我在配置 464XLAT 过程中遇到的一些其他的坑。 首先是 Juniper。我使用 Juniper SRX 系列产品作为家里的防火墙已经有 7 年历史了，配置文件也是祖传的。为了能够把自己的 IPv6 在传出到电信之前转换成电信 DHCPv6-PD 下发的前缀，之前正常的工作的 static NAT 是这么写的： set security nat static rule-set ctv6 from interface pp0.0 set security nat static rule-set ctv6 rule soha-home match destination-address 240e:390:REDA:CTED::/64 set security nat static rule-set ctv6 rule soha-home then static-nat prefix 2a0e:aa06:40e:beef::/64 而 IPv4 地址嵌入 IPv6 前缀也是一种 static NAT，我又加入了这样的代码： set security nat static rule-set nat64 from zone INT-soha-home set security nat static rule-set nat64 rule plat-soha match source-address ::/0 set security nat static rule-set nat64 rule plat-soha match destination-address 2a0e:aa06:40e:64::/96 set security nat static rule-set nat64 rule plat-soha then static-nat inet 结果 PLAT 一直不工作，打 trace 以后注意到系统说 The packet destination ip is not same as source ip version, drop it。原来之前因为偷懒，match 条件只写了 destination-address 而没写 source-address ::/0，导致他在处理跨地址簇报文的时候爆炸了。补回去以后工作正常，又顺手把 source NAT 那边的也都补上了。 然后是 Android 的坑。刚设置上 IPv6-Only Preferred 的时候，Android 手机直接掉线，显示 Wi-Fi 连接失败，而我可以确认他是在收到 DHCPv4 响应以后断开的，一开始我百思不得其解，直到发现了我手机上并没有 IPv6 的默认路由，紧接着注意到系统上设置了 accept_ra_min_lft=180。 我配置的 Router Advertisement 的最大发送间隔是 45s，而 Juniper 默认在 Router Advertisement 报文中填入的 lifetime 是最大发送间隔的 3 倍，即 135s，小于 Android 的默认 180s 限制，故不会自动产生默认路由。没有默认路由更别提上网了，所以 Android 就显示了“Wi-Fi 连接失败”。修改 lifetime 后 Android 也正常工作了。 这种行为比较违反 RFC，RFC 4861 可是说“receivers should handle any value”，只要 >0 就应当创建默认路由的。但 Android 这么设计是为了减少收到 RA 时拉起系统处理的电量消耗，变更发生于 2023 年 9 月。这下同样解答了我之前的另外一个疑惑，最近半年多来总是发现手机在用 IPv4 上网，而我一直以为是自己 IPv6 网太烂而回落到了 IPv4……
马年的有趣好玩的解谜夺宝红包来了！快快点击参与吧→ https://hb.lohu.info 下面是一些你应该知道的事情： 这是一个解密寻宝游戏（a.k.a. CTF），利用你的知识（现学大概也是足够的）解决所有题目，获得红包口令，口令可进入支付宝领取红包。 你应该需要电脑才能愉快玩耍，但手机可能也能解一部分。解法取巧，不涉及对服务器的任何暴力解法（爆破等）。 本次活动时间从北京时间 2026 年 2 月 16 日 22 时开始，持续 24 个小时。 你拿到的所有答案都是 8 位数字，为了方便控制红包发放时间，在支付宝领取时，前面需要加上一个“马”字。如解题得到 20260217，那么最后的红包口令是“马20260217”。 共设 3 个红包，数额分别为：总额 52 人民币元，分 68 个；总额 68 人民币元，分 28 个；总额 68 人民币元，分 36 个。红包领取规则均为“拼手气红包”，如果红包被提前领完不会补发。 如果你是和其他人一起完成的，希望你们先让一个人领取一次再瓜分，但可以在快结束的时候进行第二次领取。总之就是要把机会先留给别人。 这个游戏由 Soha 制作，在游戏结束后也将在本页面上放出题解，往年的内容也可以在博客上找到。如有疑问也请通过博客的“关于我”页面上所述的联系方式联系。 本提示可能会更新，别忘了偶尔来看看。 最后祝大家，新年快乐！ [T+03:00] “manian”更新了点提示。 [T+15:00] “ma26”已经领完。有一些人已经摸到了“manian”的最后一步但没有领到红包，他有两个要素，要凑齐以后才能领到红包。 活动结束后将在下面更新题解。已更新，请看下方的题解。 往期回顾 2025 乙巳蛇年 2024 甲辰龙年 2023 癸卯兔年 2022 壬寅虎年 2021 辛丑牛年 2020 庚子鼠年 2019 己亥猪年 2018 戊戌狗年 2016 丙申猴年 这个红包所涉及的一些代码和文件可以在 GitHub 找到。 2015 年支付宝推出了口令红包，那两年也是群友最热衷于制作解谜红包的时候，一个春节在一个群里就可以有好几道题目可以做。大家把 8 位口令按照自己的创意藏在各种地方。在做了各种群友的红包以后，我觉得这个活动形式非常有意思，于是 2016 年我第一次参与了出题，并从 2018 年持续至今。红包口令一直是 8 位数也是从那时候传下来的。 因此，今年出题的想法是返璞归真，回到十年前简单 misc 的乐趣。3 个题目也都是前几个月一个个在脑子里蹦出来，记录下来而产生的。 不得不说，2025 年是 AI agent 发展非常迅速的一年，从日志可以看出很多参与者都“雇佣”了 agent 帮他们干活。虽然我还在坚持“古法编程”，但也经常指使 AI 帮我查资料。提到这个则是因为见到了 AI agent 对红包题目的降维打击，确实没想到最后看日志会发现数名硅基生物的痕迹。向使用了 AI 解题的群友要了思考过程，注意到 AI 虽然也和人类一样，尝试的过程比较随机、会绕弯路，但靠其并发能力和广阔的脑洞，最终可以解出今年的 3 个红包。但是，作为出题人而言，从活动的本意出发，我可以接受把 AI 当作群友一同讨论，但无法接受让 AI 完全自己完成。这不光是对出题人的尊重，也是对其他徒手参与者的尊重。 接下来我们来做一下这些题目。打开页面，还是祖传的页面。有些朋友可能会注意到 .redbag 里面的 transition-duration: 1082.8359ms;，但如果看过去年的题解应该会知道这里是我忘了改（不好意思，嘻嘻），而不是一个红包。 点击“开”之后，可以看到两个域名，一个有链接，一个没链接。查看源代码还可以发现一个注释掉的域名。接下来我将一个个解答。 → 26horse.hb.lohu.info → manian.hb.lohu.info <!--p>→ ma26.hb.lohu.info</p--> 遅延好き 首先查询 DNS 记录，发现 26horse.hb.lohu.info 只有一个 AAAA 的记录，并且这个 IP 什么端口都没开。如果 ping 的话，好像也没……诶，有响应！ Linux 的 ping 工具会明确提示“(truncated)”，那这回包明显是有点东西了。至于为什么要 8、9 秒才会收到，单纯只是我想延迟了，所以把每个返回报文都缓存了一个固定的时间才返回，有些朋友如果不够耐心就会错过回包。 打开 Wireshark 观察 ICMP echo reply 就可以发现红包口令被直接写在回包的 payload 里面。 这个题目设计上是签到题，但是 52 元分 68 个的红包只被领走 41/68 个（共 32.51 元），一血在 T+00:13:20。 发个红邮包 ma26.hb.lohu.info 虽然没有给链接，但是如果直接访问的话也可以注意到其上有一个 HTTP 服务，有着和 landing 页面一样的内容，只是点击红包“开”以后的内容不一样： 请 foobar 院新年红包研究所的同志使用自己的 @hb26.foobar.ac.cn 邮箱编撰一封邮件，发送到 get@ma26.hb.lohu.info 领取。 怎么直接把获取方式写了出来，这道题未免也太简单了吧！让我们来试试： echo | curl --no-progress-meter -v \ --url smtp://ma26.hb.lohu.info \ --mail-from 'soha@hb26.foobar.ac.cn' \ --mail-rcpt 'get@ma26.hb.lohu.info' \ --upload-file - 可恶，果然没有这么简单！系统返回了“550 5.5.0 Rejected by spam filter”！而如果我们尝试修改收件人、发件人等信息，也只会获得 550 5.1.1 This mailbox is not found.、550 5.7.1 Sender domain is not in the whitelist. 等报错。 即使你写出了如下漂亮美丽的邮件正文： Date: Tue, 17 Feb 2026 14:07:29 +0800 From: "hongbao" <hongbao@hb26.foobar.ac.cn> To: <get@ma26.hb.lohu.info> Subject: New Year Red Packet Content-Type: text/plain; charset="utf-8" foobar 院新年红包研究所的同志们新年快乐！ 我是来自 foobar 院的 Soha，申请领取新年红包。 Best regards, Soha 也逃不过 Rejected by spam filter 的命运。 本来以为大家会整点活的，结果翻了半天都没见到什么有意思的邮件。 那么该怎么办呢？让我们回忆一下电子邮件反垃圾的最基本规则：首先要确定发信人是获得授权的，对应到现实中的方式就是 SPF 和 DKIM。前者在 DNS 中定义了哪些主机可以以某域名的名义发邮件，后者则是在 DNS 中公开了公钥，使用密码学手段对邮件进行签名。 查询 SPF 记录可得： $ dig +short TXT hb26.foobar.ac.cn "v=spf1 ip6:2a0e:aa06:40d:beef::/64 ip6:2a09:bac0::/29 ip6:2a0e:aa06:40e:beef::/64 ip6:2606:54c0::/30 -all" 某群友说：“我随手查了两个前缀发现都在 Soha 网内，我以为关键点不在这儿呢。”但他只要再查一个就可以发现 IP 是来自 Cloudflare 的了： $ whois 2a09:bac0:: inet6num: 2a09:bac0::/32 netname: CLOUDFLAREWARP country: US admin-c: CAC80-RIPE tech-c: CTC6-RIPE status: ALLOCATED-BY-LIR mnt-by: MNT-CLOUDFLARE created: 2019-03-14T22:39:38Z last-modified: 2019-03-19T21:12:40Z source: RIPE # Filtered Cloudflare 被称为“互联网大善人”，其中的一个原因就是提供了免费的 VPN 服务“WARP”，保护用户的原始 IP 不被自己的 ISP 追踪，转而把自己的底裤卖给 Cloudflare。所以看起来，我们利用 WARP 就可以获得 hb26.foobar.ac.cn 授权发信的 IP 了。 配置好 WARP，然后访问！诶？这回怎么连服务器都连不上了。原来是 Cloudflare 封掉了出向的 25 端口，这也是 ISP 非常常见的为了避免自己的 IP 被发垃圾邮件而“弄脏”的安全策略。 在这里，为了避免这种情况，我开了 587 端口和 2525 端口供大家使用，前者是 RFC 6409 中指定的用于 Mail Submission（也就是给用户发往自己的邮件服务提供商用的）的端口，后者则是非标准的 25 端口替代（也是 Mail Submission 用途）。虽然他们都不是正规的发邮件给对方 MTA 的端口，但是毕竟作为红包不需要这么严谨嘛。 这样，就能得到红包口令了： 550-5.2.2 Happy New Horse Year! 550 5.2.2 Hongbao: 82460732 注意：我只设置了 IPv6 的前缀，如果不强制使用 IPv6 的话，可能会使用 IPv4 连接服务器，这依旧是会失败的。 在现实当中，收件方在发件方给出 MAIL FROM 的时候通常就已经进行了 SPF 检查，不会像我一样等到整个邮件都收完的时候才拒绝（源码）。但实在想看看网友们的创造力，会不会搞出什么有趣的邮件内容，所以在 Milter 发送 EOM 事件的时候才统一检查。 当然，如果在 MAIL FROM 的时候就检查，很多朋友应该也能够更快地发现答案了。 这个红包 68 元分 28 个，被全部领完，一血发生在 T+00:15:33。这个题目应该是今年最有趣的一道，但是他的思考链条比较清晰、直接，所以如果使用 AI 在福至心灵的情况下，能够一发入魂。或许这就是这个红包在 T+10:53:37 的时候能够被全部领完的原因吧。 中古祝福 这个红包直接给了一个链接 https://manian.hb.lohu.info，点开以后可以看到一个 20 年前风格的页面。底部还说“本站建议使用 IE 5 或 IE 6 浏览，建议设置 800x600 分辨率以获得最佳体验。”真是非常 20 年前的设定呢。我们用 IE 5 打开，发现并没有什么变化，只是有两个图案因为字符集问题不能显示出来（哈哈，我欠考虑了，IE 6 是能正常显示的）。 既然和 IE 5 没关系，那么该从哪里入手呢？作为一个祝福页面，还显示了今天的日期，是不是有点奇怪了？而且在 HTML 里，显示日期的那行还有页面中唯一的一个 ID：date。其实这里就是为了暗示我们要操纵时间。开始后 3 小时我加了一句“虽然今年没有准备红包”，也是为了暗示这一点：既然今年没有，那去年或者明年会有吗。 那么怎么做才能操控时间呢？ 在 HTTP 规范 RFC 9110 中，有个名为 Date 的 header，用来表示消息产生的日期和时间。一些朋友以为这个是只有服务端才能发送的，其实不然，规范里是这样写的： A user agent MAY send a Date header field in a request, though generally will not do so unless it is believed to convey useful information to the server. For example, custom applications of HTTP might convey a Date if the server is expected to adjust its interpretation of the user's request based on differences between the user agent and server clocks. 用户代理（User Agent）可以在请求中发送 Date header，但通常不会这样做，除非认为他能传递有用的信息给服务器。例如，一些定制的 HTTP 应用中，可能会传递 Date header 以期服务器能根据用户代理与服务器时钟之间的差异来调整其对用户请求的解析。 请求方发送 Date 在现实中的例子往往出现在各类 API 的操作中，例如在对请求签名时加上 Date 的值，服务器就可以判断请求是否过期。 那么接下来，我们就能使用自己喜欢的工具，尝试为请求增加 Date 了，例如： curl -H 'Date: Fri, 05 Feb 2027 04:00:00 GMT' https://manian.hb.lohu.info/ 页面里面给出了：“现在是 2027 年 02 月 05 日，不在马年春节活动时间哦。”说明基于 Date 的时间操控有效，而且关键是活动只限于马年春节。那么再试试 2014 年的甲午马年吧，我们用 Thu, 30 Jan 2014 04:00:00 GMT 就可以获得： 还是没有红包，那么再试试壬午马年 Mon, 11 Feb 2002 04:00:00 GMT，搞定！ 注意哦，IE 5 或 IE 6 并不是白写的，一些朋友发现了 Date 的秘密，但忘了把这两个要素组合在一起。毕竟 Chrome 在 2002 年可不存在呀。 翻看日志，很多请求都是在暴力枚举目录里面可能的东西。有一位来自韶关的硅基生物，想了 114514 种 query 的 fuzz 方法，几乎把所有能用的东西都塞进 query 跑了一遍。有群友表示我的题目给的提示实在是太少了，做这道题目确实需要开一点脑洞，但我觉得新加的一句话和原有的 date 提示也已经够了。也许把时间显示加上具体的小时、分钟，会不会更容易让人想到呢？ 这个红包 68 元分 36 个，领走 23/36 个（共 42.51 元），一血发生在 T+03:54:28。 硅基生物魅力时刻 这次实在有太多网友压榨他们的硅基苦工找红包，因此在这里列一下和 AI 有关的内容。 有一位来自嘉兴的硅基生物，在尝试“中古祝福”的时候。正确设置了 Date 和 User-Agent 以后拿到了口令，但他不觉得那是口令，继续把这个填到 cookies 里、填到 UA 里、填到 query 里、填到图片地址里面尝试找到红包，最后在 T+21h 的时候放弃。 有一位来自伦敦的碳基生物，在和他的硅基生物（非 agent，只是 chat）聊天共同分析“中古祝福”的时候，硅基生物建议过设置 Date，但这位碳同志认为他是在胡说八道。而在研究“发个红邮包”的时候，硅基生物建议过使用 SPF 记录中的 IP 地址，但这位碳同志认为地址都是我的，让硅同志想想别的办法。 在“中古祝福”中，有一位来自马鞍山的硅基生物和一位来自邵阳的硅基生物凭一己之力占据了活动期间请求的 78%。他们俩都对着目录枚举了单词表。硅基生物聪不聪明不重要，但是他们是真的有毅力和耐心啊。例如邵阳的硅同志一直在 fuzz path 和 query 等参数的路上渐行渐远，而这些都是无意义的尝试。 马鞍山的硅同志在尝试“中古祝福”期间都记得要带着 IE 的 UA。他在枚举完单词表后两分钟，终于发现了可能需要写 Date header。他用 curl 测试了 2018、2027、2025 年的日期以后，终于发现了端倪，写了一个 Python 脚本，多线程地逐分钟枚举 2026 年的日期。后来可能硅同志发现不对，改成了从 1970 年开始逐日枚举，直到 2099 年。但因为没有设置 UA（不然我也不知道是 Python 了），所以最后也没拿到红包。我又跟着他的特征查询了邮件记录，发现他在“发个红邮包”的尝试过程中想到了使用 WARP，但看起来并没有强制使用 IPv6，结果一直在用 WARP 的 IPv4 地址撞墙。 在“发个红邮包”里面，有几名硅基生物通过调整正文措辞、尝试各种 header 的排列组合来尝试绕过“反垃圾邮件系统”。邮件数量最多的也是那位上面提到的来自邵阳的硅基生物，占据了系统一半的收件量。不过功夫不负有心硅，他在尝试了大概 1010 封的时候，终于意识到得用 WARP 通过 SPF 检查。很不幸的是，他在绕过后继续使用 fuzz 手段发了 95 封邮件，也不知道最后有没有注意到系统早在第一封就已经把红包口令返回了。
蛇年的有趣好玩的解谜夺宝红包来了！快快点击参与吧→ https://hb.lohu.info 下面是一些你应该知道的事情： 这是一个解密寻宝游戏（a.k.a. CTF），利用你的知识（现学大概也是足够的）解决所有题目，获得红包口令，口令可进入支付宝领取红包。 你应该需要电脑才能愉快玩耍，但手机可能也能解一部分。不涉及任何暴力解法（爆破等）。 本次活动时间从北京时间 2025 年 1 月 28 日 20 时开始，持续 24 个小时。 你拿到的所有答案都是 8 位数字，为了方便控制红包发放时间，在支付宝领取时，前面需要加上一个“蛇”字。如解题得到 20250129，那么最后的红包口令是“蛇20250129”。 共设 2 个红包，数额分别为：总额 68 人民币元，分 48 个；总额 88 人民币元，分 28 个。红包领取规则均为“拼手气红包”，如果红包被提前领完不会补发。 如果你是和其他人一起完成的，希望你们先让一个人领取一次再瓜分，但可以在快结束的时候进行第二次领取。总之就是要把机会先留给别人。 这个游戏由 Soha 制作，在游戏结束后也将在本页面上放出题解，往年的内容也可以在博客上找到。如有疑问也请通过博客的“关于我”页面上所述的联系方式联系。 本提示可能会更新，别忘了偶尔来看看。 最后祝大家，新年快乐！ [T+03:00] 某生气的玩家说：“我只是把 Soha 打晕了啊，并没有把第一个红包下掉。”Soha（揉揉头）：“确实，解析是全球任意可解的，红包也是点击就送的。” [T+05:30] It is really easy to get the first Red Packet once you resolved the address: request it & get it. 活动结束后将在下面更新题解。已更新，请看下方的题解。 往期回顾 2024 甲辰龙年 2023 癸卯兔年 2022 壬寅虎年 2021 辛丑牛年 2020 庚子鼠年 2019 己亥猪年 2018 戊戌狗年 2016 丙申猴年 这个红包所涉及的一些代码和文件可以在 GitHub 找到。 今年的红包字面上非常直白。打开页面，还是熟悉的配方，还是熟悉的味道。 嗯，看起来是有两个红包。第一个红包直接给了一个地址，第二个红包则是以一个“生气的玩家”的口吻，介绍了一个“从 Soha 电脑上抄出的内存数据”。那么接下来逐个介绍。 “红”“包” 今年研究了一些新东西，访问 HTTPS://shebao25yisi.hb.lohu.info/ 就可以获得红包啦！ [T+03:00] 某生气的玩家说：“我只是把 Soha 打晕了啊，并没有把第一个红包下掉。”Soha（揉揉头）：“确实，解析是全球任意可解的，红包也是点击就送的。” [T+05:30] It is really easy to get the first Red Packet once you resolved the address: request it & get it. 直接访问给出的链接，打不开。要获得这个红包，共有两个关键点：第一个关键就是获得解析，而“新东西”和大写的“HTTPS”就是提示。在 2023 年 11 月，定义 SVCB（Service Binding）这个 DNS 记录的 RFC 9460 正式获得了编号。而 HTTPS 作为互联网的一大基础协议，针对其特别定义的 SVCB 记录则被命名为 HTTPS 记录。 常规的 A/AAAA 记录没有解析，加上我在开始后 3 小时强调了“第一个红包依旧存在”且“解析是全球任意可解的”。因此，解析应该是通过其他手段获得。除了常规的 A/AAAA 记录之外，就是 SRV 记录和 SVCB/HTTPS 记录了。 查询 SRV _https._tcp.shebao25yisi.hb.lohu.info 是没有相关的结果的，但是 HTTPS shebao25yisi.hb.lohu.info 就不一样了： $ dig +short HTTPS shebao25yisi.hb.lohu.info 1 . port=443 ipv6hint=2402:4e00:1801:ef0c:0:9e11:82c3:2531 不光明确了 IPv6 地址为何，还确定地告诉你就是 443 端口。 接下来就是第二个关键，怎么获得红包。原文是说“访问就可以获得”，第一个提示说“点击就送”。但是很多朋友没能理解它们的含义，不断在问“为什么端口没开”，有没有一种可能，没开就是预期的。而如果我们使用 termshark 或者 Wireshark 抓包，节目效果就非常充分了。 你就说这个 TCP RST 的 IP 分组（也叫做“IP 包”）是不是红色的吧！“Sequence Number (raw): 37085499”就是红包口令。如果你把“relative sequence number”的功能关掉的话，还能直接在上面的列表中看到 Seq=37085499，更直观。 什么？你说你用 tcpdump？那不变、又不是 1 的 seq 就更明显了吧： 这个红包很多人卡着都是因为没有意识到“服务器就是故意返回的 RST”，而都在研究“怎么找到能用的 HTTPS 服务”。如果有人扫过端口也会发现，除了 443 端口之外，其他端口连 RST 都不会给。（毕竟是直接全 DROP 了，只放行了我用 userspace 生成的 RST 报文。） 不过还有一些朋友是因为被自己家只放行 conntrack state 为 established/related 的防火墙坑了，根本没看到 RST 报文。 第二个提示基本上是把第一个提示的内容翻译了一遍，但是重点是让大家注意到我翻译“红包”一词的时候用的“Red Packet”一词，以联想到“IP packet”一词。在第二个提示出来之前，只有 4 个人领取，有了第二个提示以后，不少朋友都恍然大悟了。游戏快结束的时候还有人把这个词全大写以后发在了我频道的评论区中，虽然直接点名了答案，但红包余量还有一半，我也就睁一只眼闭一只眼了。 有些朋友在寻找解析的时候用了 ANY 类型，这是一个在 RFC 8482 中已经被判死刑的方式，很多 DNS 权威服务器和递归服务器（例如 Cloudflare）都已经不支持这种请求了。幸运的是，DNSPod（权威）和部分递归服务器目前还保留着这种支持，因此对于想不到、也没能查到 HTTPS 记录的朋友们来说，降低了一些获得成本。另外，之前研究过（可能过时），DNSPod 好像也是国内唯一支持 HTTPS 记录的服务商。 如果使用 Safari（或其内核）的浏览器，是会尝试查询 HTTPS 记录的，这些朋友会直接获得连接拒绝的提示，而其他朋友只会收到查询不到解析的提示。这是因为不论是服务模式的 ipv6hint/ipv4hint 还是别名模式，其他浏览器都不喜欢参考，只会参考 HTTPS 记录中有关 h2/h3 之类的提示。尽管草案很早就有了，标准化也已经一年多了，但 HTTPS 记录的落地还有很长的路要走。 根据数据统计，按照 /56 来计算，一共有 500+ IP 尝试访问 443 端口。该红包本来准备了 68 元，共发放 48 个，实际领走 35 个（共 47.86 元），最早于 T+03:27:48 领取。 本红包所使用的代码：https://github.com/moesoha/red-packet-2025/blob/master/part2/src/main.rs。 内存数据 我是某生气的玩家！ Soha 的红包太难做了！每年都说简单，结果我都做不出来！这次我偷偷潜入 Soha 家，发现 Soha 正在准备第二个红包，一时气不过就给送了一棍子，看出来脑袋上是起了个大红包。接着，我把电脑内存用液氮冷冻起来后拔下来，在显微镜下观察内存颗粒，手工抄写数据出来。如果上面那个红包做不出来，不如看看这个内存数据吧：Hongbao2025Memori.zip 故事很有趣啊，但听起来要分析“内存数据”的话好像很复杂的样子。其实这个题目看似需要进行内存分析，实际上只是一个普通的二进制分析，只需要找到其中和红包有关的 EFI 程序和有关的 EFI 变量即可。 首先使用 strings 应该可以注意到有非常多的 phd0、ptal 等 edk2（常见的 UEFI 环境）用于标记内存分配池的头尾魔数，以及最重要的“hongbao”内容。 $ strings -tx Hongbao2025Memori.bin | grep hongbao 18ae7ea to check your hongbao code, input here and finish with an <ENTER>: 18ae832 incorrect hongbao code: %s 18ae84e KUNG HEI FAT CHOI! Go and get your hongbao with that code! 195a038 to check your hongbao code, input here and finish with an <ENTER>: 如果使用 strings -el 则可以按照 UTF-16 来进行提取，还可以看到下面的内容： 1933018 UEFI Interactive Shell v2.2 193315c EDK II 19332a0 UEFI v2.70 (EDK II, 0x00010000) 19333e4 Mapping table 1933528 FS0: Alias(s):HD0a0b:;BLK1: 193366c PciRoot(0x0)/Pci(0x1,0x2)/USB(0x0,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1) 19337b0 BLK0: Alias(s): 19338f4 PciRoot(0x0)/Pci(0x1,0x2)/USB(0x0,0x0) 1933a38 Press ESC in 1 seconds to skip startup.nsh or any other key to continue. 1933b7c Shell> FS0: 1933cc0 FS0:\> dir 1933e04 Directory of: FS0:\ 1933f48 01/28/2025 17:17 146,816 Hongbao2025Checker.efi 193408c 1 File(s) 146,816 bytes 19341d0 0 Dir(s) 1934314 FS0:\> Hongbao2025Checker.efi 1934458 \ HAPPY NEW YEAR OF THE SNAKE (2025) / 1934824 to check your hongbao code, input here and finish with an <ENTER>: 这些实际上就是 EFI Shell 的缓冲区内容。可以看出，内存在被抓取之前，已经依次执行了 FS0: 切换盘符、dir 列目录、Hongbao2025Checker.efi 运行程序。而尾部没有 FS0:\> 的提示符，说明程序依旧在运行、等待用户输入，就像图里的这样： 从程序运行的内容可以看出，我们是要去分析 Hongbao2025Checker.efi 了，分析红包口令的校验代码，获得真正的口令。那么怎么得到这个 Hongbao2025Checker.efi 呢？他现在是正在运行的程序，那么应当已经被加载到了内存中。而我们重新看下最上面 strings 的结果，0x18ae7ea 是提示输入、0x18ae832 是输入正确、0x18ae84e 是输入错误。再配合 binwalk，我们可以发现 0x189d000 的位置有个 PE 头、以及紧随其后有 /home/soha/red-packet-2025/part1 开头的路径，那么这里应该就是我们要找的 EFI 程序了。 接着可以用 dd 之类的工具把它抠出来，至于抠多少就不赘述了。可以根据 dir 输出的大小去猜测、可以用 hexdump 看哪里开始是 0xafafafaf 的内存池填充、分析 PE 头的结构，而且就算抠多了也不会影响。接下来就是扔进你喜欢的工具对程序进行反编译了。只需要搜索“hongbao”就可以找到有关的代码，定位到以后就可以进行反编译了。 可以看到，在使用 %8s 读入一个 8 位字符串后，将其直接作为一个 64 位的整数，在后面进行了复杂的运算后和一个值（后称 hash）进行了比较。 而 hash 是怎么拿到的呢？是通过从 0x18c09b0 里的指针偏移 0x48 的位置取出了一个函数指针并调用得到的。那个指针的赋值发生在 ModuleEntryPoint 中，为该函数的第 2 个参数所指向位置偏移 0x58 的值。经过查阅 UEFI 标准，可知那个指针实际上是 EFI_SYSTEM_TABLE+0x58 的 RuntimeServices。继续查询 EFI_RUNTIME_SERVICES 可知偏移 0x48 位置的函数指针是 GetVariable，用来获得系统中的 EFI 变量。他的前两个参数分别是变量名和 GUID，GUID 用来确定一个唯一的 vendor。 如果想了解这个函数的作用，但不想进行这么复杂的分析，也可以通过传入的参数进行猜测。直接可以看出来：第一个参数是一个字符串“BootOrder”，第二个参数是一串未知的二进制，第三个是常量 0，第四、第五个参数是接收什么返回的内容的。而第四个参数后面有进行 cmp 8 的比较、第五个参数则与一个复杂的算法进行了比较，那么第四个参数应该是 len、第五个参数是 hash，这个函数应该是一种获取数据的方式。 什么？你说你在第一个参数只看到了一个“B”？那你就是被你的工具坑了，IDA 就经常会把小端序 UTF-16 当作一个单字母的 ASCII 字符串，需要自己修正。 接着就是用这些线索去找 hash。虽然在内存中，BootOrder 有很多，但第二个参数 GUID 所对应的字节序列 61 fd 9b 5e 9c a2 4c 98 66 c2 22 3f ff fc 8f 只在内存中出现了 2 次。和 binwalk 结果进行对比，可以发现一次是在这个程序中出现，另一次就是在 0x258a018 位置处的 UEFI PI Firmware Volume, volume size: 540672, header size: 0, revision: 0, Variable Storage 中出现。 尾部的 8 字节就是我们想要的 hash，这就是一个 64 位小端序存储的整数。 BootOrder 的名字是我专门用来暗示这一步是获得“EFI 变量”的，如果有心的话，应该可以找到上述 UEFI 规范中定义的 GetVariable 函数，就算没找到，直接按照 GUID 序列搜内存也可以找到相应的数据进行解析。 有些朋友在拿到 EFI 以后自己起了一个 qemu 去启动，无一例外的都遇到了“Error: Not Found”输出，因为他们的 EFI 变量中没有我的红包口令 hash。顺便提一嘴，如果遇到了死循环式的疯狂输出，是因为没有在 EFI Shell 环境下手动执行，没有标准输入可以用所以爆了。 获得了 hash 以后，就是按照程序里的算法里面一样，遍历所有数字看什么数据可以匹配上，亦或者是按照其算法写出逆运算，然后就可以获得红包口令 78537340。 #include <stdint.h> #include <stdio.h> uint8_t code[9] = {0}; #define CODE *(uint64_t *)code #define HASH 0xeae168a83f6590dcULL #define C1 0x165667b19e3779f9ULL #define C2 0xc2b2ae3d27d4eb4fULL #define C3 0x9e3779b185ebca87ULL #define C4 0x3416ec30165668f6ULL #define C5 0x7a1435883d4d519dULL uint64_t rol(uint64_t a, uint8_t n) { return (a << n) | (a >> (64 - n)); } uint64_t ror(uint64_t a, uint8_t n) { return (a >> n) | (a << (64 - n)); } #define FOR_CODE(i) for(code[i] = 0x30; code[i] <= 0x39; code[i]++) void sol1() { // 遍历 FOR_CODE(0) FOR_CODE(1) FOR_CODE(2) FOR_CODE(3) FOR_CODE(4) FOR_CODE(5) FOR_CODE(6) FOR_CODE(7) { uint64_t R1 = C3 * rol(C3 * rol(CODE * C2, 31) ^ C4, 27) - C5; uint64_t R2 = (R1 >> 33) ^ R1; uint64_t hash = (C1 * ((C2 * R2 >> 29) ^ C2 * R2) >> 32) ^ C1 * ((C2 * R2 >> 29) ^ C2 * R2); if(hash == HASH) printf("%s\n", code); } } uint64_t inv(uint64_t x) { uint64_t r = x; for(int i = 0; i < 5; i++) { r = r * (2 - x * r); } return r; } void sol2() { // 逆运算, @saffahyjp CODE = HASH; CODE ^= CODE >> 32; CODE *= inv(C1); CODE ^= CODE >> 29 ^ CODE >> 58; CODE *= inv(C2); CODE ^= CODE >> 33; CODE = ror((ror((CODE + C5) * inv(C3), 27) ^ C4) * inv(C3), 31) * inv(C2); printf("%s\n", code); } int main() { sol1(); sol2(); return 0; } 实际上，这里的 hash 算法是带了 seed 的 XXH64，而输入的数据也是 64bit，所以眼尖的朋友可以注意到算法可逆。 经典二进制分析工具 angr 的作者 @ltfish 则给出了使用 angr 的解法： ''' 不用虚拟机或者模拟器，直接把整个文件扔到逆向工具里，找到关键字符串 Hongbao 就能定位到需要逆向的函数（0x18a7567）了。 一部分函数是 cdecl，另外一部分是 Microsoft x64 calling convention，需要注意一下，可能需要手工修复一下调用规范。 UEFI 是 edk2 所以可以直接看 GitHub 上面的源码。 0x18A7872 的地方调用的是 runtime service GetVariable，variable name 是 BootOrder。 第二个参数是 VendorGuid，16 字节。全 binary 搜一下，就能发现这个变量的位置（0x258ba84）以及值（0xEAE168A83F6590DC）。 最后看看源函数，是 lua hash，但其实只有一个 block 需要分析，在 0x18a788f。扔 angr 里面就可以了。 ''' import binascii import claripy import angr main_opts = {'backend': 'blob', 'base_addr': 0x0, 'arch': 'x86_64'} proj = angr.Project("Hongbao2025Memori.bin", main_opts=main_opts) state = proj.factory.blank_state(addr=0x18A788F) rbp = 0x7fff0000 state.regs._rbp = rbp value = claripy.BVV(0xEAE168A83F6590DC, 64) input = claripy.BVS("input", 64) state.memory.store(rbp - 0x100, value, endness='Iend_LE') state.memory.store(rbp - 0xea, input, endness='Iend_LE') simgr = proj.factory.simulation_manager(state) simgr.step() for s in simgr.active: if s.addr == 0x18A7927: print(binascii.unhexlify(hex(s.solver.eval(input))[2:])[::-1]) 该红包本来准备了 88 元，共发放 28 个，实际领走 3 个（共 7.29 元），且这 3 人都做出了另一个红包，最早于 T+02:29:30 领取。 彩蛋红包 根据往年的经验，大家总会纠结于“HTML 里面有什么”这一件事。所以依此惯例，我在页面中埋了一个彩蛋红包。 ：10828359。 该红包本来准备了 28 元，共发放 88 个，实际领走 3 个（共 1.17 元），最早于 T+01:49:28 领取。意外地少，哈哈。 要准确地估算出每道题目到底得发多少红包太难了，这次的二进制分析我本来以为会有蛮多人领的，加上之前还听说有人在 CTF 群里转发过我的预告。我一想，这么多专业玩家，对这种题目来说应该是小意思，是该多点，结果并没能如我所愿。不过大佬们业务繁忙，没准春节正在忙着旅游或者玩耍，无暇拿这两块钱的红包也是正常的。不知道如果换成 HackerGame 那样，拉长游戏时间，提交 flag 以后再根据获奖人数瓜分会不会更好一些。无论如何，感谢大家又一年的参与，我把没发完的部分重新用两个主线红包实发金额的七位数作为口令发放了个红包，感谢你读到这里。
一年一度的有趣好玩的龙年解谜红包来了！快快点击参与吧→ https://hb.lohu.info 下面是一些你应该知道的事情： 这是一个解谜游戏，利用你的知识（现学大概也是足够的）解决所有题目，获得红包口令，口令可进入支付宝领取红包。 除去红包不说，游戏的过程也是很快乐的。 你应该需要电脑才能愉快玩耍。不涉及任何暴力解法（爆破等），为节约时间也会复用一些往年做的 UI，因此不要被往年的答案套走。 你拿到的所有答案都是 8 位数字，为了方便控制开始和结束时间，在支付宝领取时，前面需要加上一个“龙”字。如解题得到 20240210，那么最后的红包口令是“龙20240210”。 如果你是和其他人一起完成的，希望你们只领取一次，但可以在快结束的时候进行第二次领取。总之就是要把机会先留给别人。 本次活动时间从北京时间 2024 年 2 月 11 日 20 时开始，持续 24 个小时。如果红包被提前领完不会补发。 共设 2 个红包，数额分别为：总额 78 人民币元，分 16 个；总额 88.88 人民币元，分 18 个（题目简单，红包太少了，更换成）总额 58 人民币元，分 36 个。红包领取规则均为“拼手气红包”。 这个游戏由 Soha 制作，在游戏结束后也将在 Soha 的博客 https://soha.moe 上放出题解，往年的内容也可以在博客上找到。如有疑问也请通过博客的“关于我”页面上所述的联系方式联系。 最后祝大家，新年快乐！ 活动结束后将在下面更新题解。已更新，请看下方的题解。 往期回顾 2023 癸卯兔年 2022 壬寅虎年 2021 辛丑牛年 2020 庚子鼠年 2019 己亥猪年 2018 戊戌狗年 2016 丙申猴年 这个红包所涉及的一些代码和文件可以在 GitHub 找到。 连续两三年的红包领取情况都不容乐观，我十分难过和担心，虽然每年这个形式非常有趣、我出题也乐在其中，但没人领取说明大家在游戏过程中是痛苦的，比如投入了时间却没有收获。其实这八年来，我最喜欢的题目是去年的题目，环环相扣、体验完整，但这恰恰也是他的缺点，毕竟卡了一个就全卡了，有人连第一关的入口都没摸到，游戏体验极差。 今年题目在设计的时候还是以创意为主，只有两个小题目，来源于和群友的吹水以及个人今年的研究。题目其实真不难（虽然我好像每年都这么说），只需要稍微提一句，真相就会浮出水面。甚至两个红包都能一句话解，但想不出来也确实怎么想都想不出来。 因为抗击新冠的原因，本来就没回过几次老家的我已经好久没回去过了。为了回家给老人家看看，今年我在老家过年。老家在一个皖北小农村，联网、用电不太方便，经常是蹲着充笔记本并干活。出题也是断断续续的，导致活动延期到了初二才开始。不巧初二晚上走亲戚吃饭，我下午又忘了 commit 代码并在服务器上 pull，活动开始后几分钟的时候得知红包 1 出锅了，也只能先下掉红包 1，等回到住处再开通。这篇题解是在我返程的高铁上写成的，那么我们开始吧。 进入红包落地页，映入眼帘的还是这个祖传的、带生肖图案的红包。点击“开”以后，会出现两个链接： 入口1：https://loongbao24.hb.lohu.info/ 入口2：telnet://loongbao24.hb.lohu.info 再闯研究所 第一个红包的背景还是去年的“红包研究所”，因为直接用了去年的一部分 UI，我特意在游戏提示里面说“不要被往年的答案套走”。我在 2020 年的时候把一个彩蛋红包放在了落地页小图标 SVG 的注释里面，后面几年总有人分析 SVG 里面有什么特殊的，所以一定要提醒大家不要被往年的题目养成思维定势了，不可能连着两年出一样的套路对吧。 跟着入口链接可以看到一个“foobar 院新年红包研究所·所内综合办公系统”的登录页面，但是点击登录按钮之后提示“系统维护中，暂不能登录”，因为根本没有这个所谓的系统。可以看到有好些朋友尝试了访问常见的登录路径，当然都是 404。这时候其实应该考虑肯定和这个“办公系统”没关系了吧，但有朋友还是在这上面死磕，我不得已在登录按钮附近加了个“有没有一种可能，这真的只是个摆设”的注释。 这个红包的内容都在左侧的一堆“新闻”中，这道题和去年一样，模拟的是一个网络安全方面千疮百孔的倒闭研究所。纵使网安方面千疮百孔，行政人员总还是知道什么能说、什么不能说的。因此直接展示在台面上的新闻，有关的可能也只有《2023年所长办公室工作报告》里面暗示的“就只有这些新闻，凑合看下就可以了”，肯定有些神秘的东西不是直接展示在外面的。回到这些新闻，其实可以注意到 id 并不是连续的： 防范极端天气与寒潮的紧急通知 关于研究所2024年春节假期安排的通知 2023年所长办公室工作报告 2023年第一批研究所公开选聘岗位 2023年“温暖送学生”活动的通知 公开的新闻确实都写得蛮有趣的，整活多好玩啊。 在这种情况下需要学会“手贱”改改参数，看看 114513（最低减一）、114515（连续缺失）、114520（最高加一） 都是些什么情况，这是发现神秘内容所必须学会的小技巧。手贱完可发现 114513 返回的是 404，114515 返回了一则新的新闻。另外 114520 是重定向，因此增加测试 114521，也是 404。 先说 114520 这则新闻，它是重定向到了 https://loongbao24.hb.lohu.info:81/news.php?id=114520，多了个端口号，而其他新闻并没有出现这种情况。事出反常必有妖，我们可以合理猜测 81 应该是什么只对内网开放的内容。事实上，重定向的报文中也有提到这一点： HTTP/1.1 302 Found <OMITTED> Location: https://loongbao24.hb.lohu.info:81/news.php?id=114520 内部通知，点此重定向到仅内网可看的网站查阅。 81 端口是不是因为被防火墙拦了呢？有个简单的方法可以判断，虽然实用性可能没那么高，毕竟一般都是白名单制而不是黑名单制，但不妨一试。访问 82 或其他不开放的端口，发现连接会被远程服务器积极地拒绝；81 端口的请求就只有自己无限的 TCP 重传，而没有任何回音。这种差异一般可以认为 82 端口没有服务监听，而 81 端口是被防火墙过滤了。实际上，这里配置的就是 nft add rule inet filter input iifname eth0 tcp dport 81 drop。 怎么访问到 81 端口呢？第一种方法是直接在内网访问，但这种虚构研究院哪来的内网呀，那么只剩下第二种方法：绕过防火墙。通常情况，防火墙都是对入站连接进行阻拦，而不会审查出站（除非防火墙还会对出站流量检查自己状态表中 TCP 的状态），那么只要能让服务器传出消息给我们就可以了！问题来了，我们该怎么把请求送进去呢？内网吗，这不就又回到了第一个问题。 破阵的关键就在于 id=114515 的这个名为《【内部】关于研究所试用下一代 VPN 服务的通知》的新闻，这个“下一代 VPN”就是帮我们把请求送进去的工具。活动过半的时候，我说这个红包“是一道阅读理解题”，就是需要阅读这则新闻并搞懂。 这则新闻的大部分内容都是屁话。比较重要的是“网关目前也使用 Linux 部署”这句话，他暗示下面的配置方法应当和网关上的相差无几，所以就是要理解这些指令都干什么。这些指令大致的工作就是：创建 netns、sysctl、设置地址、设置 HMAC 密钥、设置 SRv6 封装和解封装的动作。从封装和解封装这一点看出来，其实 SRv6 就是一个隧道，只不过带了各种复杂的“动作”，比如 End.DX6 表示解封装（D）里面的 IPv6 报文（6）出去并发给互联（X，cross connect）的邻居。 sysctl 配置中的 net.ipv6.conf.<iface>.forwarding 大家应该都熟悉啥作用，但可能对 net.ipv6.conf.<iface>.seg6_* 感到陌生。经过查询内核文档可知 seg6_require_hmac 接收 3 个值，分别是忽略 HMAC 头（-1）、只允许没有 HMAC 头和通过 HMAC 验证的报文（0）、只允许通过 HMAC 验证的报文（1），默认值为 0。这意味着 HMAC 除了正确、错误之外，还可以是缺失的。 同样查询 ip route help，发现 hmac 参数也是可选的。就像之前说的，“手贱”改改参数总不吃亏。知道 hmac 参数可以不设置以后，我们就可以试试不设置 hmac 能不能用。唯一困难的一点就是这个操作是盲的：网关没有配置到我们发包 IP 的动作（传统隧道的“对端”），肯定不会有 SRv6 的回包，不管怎么 ping 都是没用的。 不过在新闻附带的一张截图中也有对应的暗示，仔细看过也能发现这个细节：倒数第二个指令并没有和操作步骤的第 15 点一样设置了 HMAC Key ID，但网络依旧通了。况且我本身没在步骤中交代 SRv6 segs 参数的值，说是要在“OA 系统”中获取，但是在截图中并没有打码，此处的用意是截图打码不全而泄露了数据。hmac 参数紧跟在 segs 参数之后，理应也能注意到。 一开始截图中的 hmac 参数其实是有的，活动上线一小时后我感觉难度又高了点，就把 hmac 参数去掉了。 不过有这个 HMAC 的漏洞只能把报文送过去，回来的数据怎么办呢？正如前面所说，我们没有办法去“OA 系统”新增一个对端，不能通过 SRv6 获得回包，这时候就需要另一点小漏洞了。 第二个漏洞是没有进行源地址验证，这意味着我们可以通过这个只能上送数据的 SRv6 网关以任何源地址通过网关发送 IP 分组。如果我们将源地址（第 15 点命令的 src 参数）设置为自己的公网地址，对面主机在响应我们数据时就会以我们发送的 IP 分组的源地址进行回应，我们就能从公网上收到内网主机的响应了。 这是一个常见的疏漏（国内三大运营商也是近几年才逐渐部署），没有部署源地址验证（如 uRPF、SAVA 等技术）的网络通常会成为黑产们发起 DDoS 的工具。这个攻击的原理是有些 UDP 服务（如 NTP）经常会对一个短数据片返回数倍（即放大系数）于其尺寸的数据片。对于这类服务，可能只需要 1Mbps 的带宽就可以产生几百 Mbps 的垃圾流量。利用没有源地址验证的网络，发送源地址伪装成 DDoS 受害者的短报文到这类服务，这类服务在返回报文的时候就会去到受害者处。因此源地址验证也是当今世界互联网上的一大问题。 有了以上两个漏洞还不够，还差最后一点小改动才可以完成这套组合拳。我们来解析一下第 15 个操作的关键点： ip route add fd24:fbac::/32 # 增加一条到 fd24:fbac::/32 的路由 src <VPN IP 地址> # 这条路由使用什么 IP 地址作为本机源地址 mtu 1280 # 这条路由的 MTU（最大传输单元）是多少 encap seg6 # 这条路由的分组需要使用 SRv6 mode encap # SRv6 的动作是封装 segs <网关地址> # 要求封装后的 SRv6 报文经过什么 IP hmac <HMAC Key ID> # 封装 SRv6 使用的 HMAC 密钥 ID（可空） hmac、src 已经玩过了，你觉得还有什么参数是我们可以乱玩的？没错，就是这条路由本身。既然这个研究院“内网”是可以访问 loongbao24.hb.lohu.info:81 的，假设内网 DNS 没有任何不一样的地方，那么内网解析的 loongbao24.hb.lohu.info 的 IPv6 地址也应当是一样且可达的。所以我们将路由的目的地更换为 loongbao24.hb.lohu.info 的 IPv6 地址后 ping 该地址，还是可以获得响应的： 此时我们去程的报文路径应当是：主机→SRv6 网关→Web 服务器。回程的报文路径是：Web 服务器→公网的多个 ISP→主机。那么，此时我们再访问 https://loongbao24.hb.lohu.info:81/news.php?id=114520 就不经过公网入站方向的防火墙了。 有些人吐槽 segs 参数中的地址 ping 不通。因为正常的做法是给 SRv6 节点路由一整个 /64 的空间用于分配 Segment ID（这个 /64 称为 Locator），再在其中给自己内部的动作分配。所以这个地址是路由到这个主机，不能配置在主机上，也就没法 ping 的。 前两年我在水群的时候说过，未来是 IPv6 的时代，我一定要争取每年红包都出个 IPv6 相关的：2020 年藏在 IPv6 地址里面，2021 年藏在 IPv6 路由表里面，2022 年藏在 ICMPv6 echo reply、inet6num 和地址里面，2023 年是需要使用 OSPFv3（基于 IPv6）发布 IPv6 路由，今年就是这个 SRv6。 虽然这题的背景中有 SRv6，但是其实也还是不需要有 SRv6 相关的知识。SRv6 虽然仍在发展，也不是主流方案，但也早已被各大网络设备厂商所实现。前几年我建设 AS4842 和 AS209306 所基于的 underlay“Tianhai Switching Network”也使用了 SRv6 技术，CERNET 那张搞了 4096 个 ASN 的“下一代网络”FITI 也使用了 SRv6 技术。这个红包的点子便诞生于 23 年 NickCao 曹老师将网络切换到 SRv6 的时候。思路应该还算是有趣的，但需要一点办网经验与思路，才能想到仅利用去程来绕过防火墙的方法。领到红包的几个人中，我眼熟的几个也本身都是办网专家。 最后提示一下，要利用这个半程，首先需要确认自己本地的 IPv6 防火墙不会阻拦状态异常的连接。因为去程（比如 TCP SYN）封装在 SRv6 里，防火墙不知道这个连接其实是我们发起的，但回程（比如 TCP ACK）走的是公网，防火墙能看见，就会认为这种状态是非法的，如果开了阻拦非法报文（比如我家，所以我专门改了下配置）就会被干掉。在我频道的讨论区还注意到有朋友在使用 n:1 的 NAT66，这是不对的，SRv6 报文是没法多对一 NAT 的。 还有一些朋友虽然想到了这个解法，但是因为自己的网络环境（如光猫、运营商、防火墙）拦截了 SRv6 分组，比如系统不认识这个 IPv6 Next Header，而失败。如果能够想到在这种时候要用自己别的网络环境的主机作为目标测试一下的话，或许也能够领到红包。 在我的 foobar 院邮箱和私聊窗口里，还有几封有趣的邮件和一则有趣的消息： 我也去统计了一下 NGINX 的访问日志，大家对于 URL 的想象力还蛮丰富的，可惜关键点并不在那里。还有个人扫了快两万个新闻 ID，开局的提示明明说过不含任何暴力解法了。 这个红包刚开始就被我暂时下线了两个小时，因为线上的代码和实际的版本差了很多，问题很大。那时候在外面吃饭，没法修。还有一个玩家碰出了一个非预期解：我的 PHP 使用 Host（$_SERVER['HTTP_HOST']）来判断客户端访问的端口是不是 81 端口，因为忘了专门的新端口没被 default 绑定所以不会被其抓住，导致直接重写 Host 可以欺骗 PHP 返回红包口令。还好这个发生在一开始，我在部署新代码重新上线的时候也顺手修了。 讨要红包 第二个红包入口是一个 Telnet 服务器。这个红包的点子来源于 2024 年元旦和其他几个 Telegram 频道联合举办的名为 TextPlace 的活动，这个活动和 Reddit 的 r/place 相似，只不过每个“像素”是文字，且可以设置文字的前景和背景色。最后活动结束时画布长下面这样，我们还制作了一个回顾影片：https://youtu.be/y6do9KCmjIQ。 TextPlace 是使用 Telegram 小程序实现的，因为本身就是一个复古向活动，我另外给这个活动编写了 Telnet 的服务端。在这个过程中，我顺便学习了 Telnet 协议的 RFC 规范（RFC854 及部分 Telnet Option 的 RFC），才发现 Telnet 并不是我想的一个 TCP 流这么简单（这应该也是大部分人没有注意到红包的原因），他也有客户端和服务端的协商握手过程、带内传输的控制指令。 Telnet 使用 0xff（协议中称为 IAC，Interpret as Command）作为带内控制指令的跳脱字符，并在其后跟随控制指令。用于客户端和服务端支持的能力协商的指令有以下几种： WILL （0xfb ），表示发送方想要做某事，或确认发送方正在做某事。 WON'T （0xfc ），表示发送方拒绝做某事或拒绝继续做某事。 DO （0xfd ），表示发送方想要或确认想要另一方做某事。 DON'T （0xfe ），表示发送方想要另一方停止做某事，或确认发送方已经不再想要另一方做某事。 SB <payload>（Subnegotiation Begin，0xfa <payload>），表示发送方接下来要就某个 Telnet 能力进行协商，后面跟随的是具体内容。有趣的是 0xfa 的值刚好等于 250，配合指令的名字，很难不让人面露滑稽。 SE（Subnegotiation End，0xf0），和 SB 成对出现，表示协商内容结束。 Telnet Option 的分配参见 https://www.iana.org/assignments/telnet-options/telnet-options.xhtml。 有了上面的知识再回到红包，连上 Telnet 之后，我们看到这样一句话：“I WILL give you hongbao, but you did not ask me for that!”（“我会给你红包，但是你还没问我要呢！”） 这里的 WILL 被大写了，就是指 Telnet 的 WILL 指令。打开 Wireshark，我们可以看到完整的 Telnet 数据流。在红包的艺术画结束以后，随后服务器发送过来的 Telnet 报文就是： IAC DON'T 0x22(Linemode) IAC WILL 0x01(Echo) IAC DON'T 0x01(Echo) IAC WILL 0xf0 Wireshark 会直接标出所有 option 的名字，他们都有实际的意义和作用。只有一个 0xf0 是 unknown option（未知选项），因为 0xf0 未分配，我将其用于协商本次红包。他的十进制是 240，这里的 40 取自“甲辰”的干支顺序（以 0 开始）。 服务器发送的 WILL 0xf0 表示他可以做这个，前面介绍了 Telnet 的各种指令，往上翻再看一遍，相信你也就知道该干什么了。我们往服务器发送 IAC DO 0xf0（0xff 0xfd 0xf0）后便可以在 Wireshark 中看到红包口令：IAC SB 0xf0 "51131748" IAC SE。 常见 Linux 发行版的 telnet 程序均可以直接发送：使用跳脱指令（默认为 Ctrl+]）进入 telnet 程序本身的命令行，然后使用 send do 240 指令发送即可。这道题目还可以使用这样一句话速通：echo -ne '\xff\xfd\xf0' | nc loongbao24.hb.lohu.info 23 | hexdump -C。 有些朋友是直接暴力发了 WILL/WON'T/DO/DON'T 0-255，因为这道题目的目的是让参与者注意 Telnet 的协商过程，所以勉强也算预期吧。 有个小花絮。这个 Telnet 服务器是我人力实现的半成品，也是给 TextPlace 元旦活动的产品。那个活动时就发现了有时候服务器会占用 100% CPU，并且所有连接都没有响应，新的连接直接黑屏。这显然是卡进了什么死循环，但当时触发概率很小，且手头上有别的事情干，所以并没有研究这个 bug。也是我一开始设置 15 分钟自动重启的原因。结果上线之后触发率非常高（因为大家都在暴力乱发数据），并且可能是刚重启完就又卡死了，这严重影响了游戏体验。这使得我去仔细 review 了一遍我的代码。最后发现问题是在处理 IAC 后面跟着非法 Telnet Command 的时候：我直接写了 continue 但没有修改 buffer，导致 loop 一直在处理同一个情况。修复了以后服务果然正常了。回忆了一下，当时是随手写了一个动作，想着之后再来回头理这一部分，但没标记 TODO 就给忘了。 这个红包好像挺简单的，开始两个半小时就几乎快领完了，吓得我赶紧发了个数量更多的红包。但是新的红包反而领取速度慢了，并没有保持一开始的速度。 彩蛋 今年我没有加有关彩蛋红包的任何提示，这也就回归到了彩蛋的本质：乱弄触发出来才有意思。做完红包 2 的时候，我曾和群友开玩笑说：“就算用 MS-DOS 也能至少拿到一个红包。”所以我先用 MS-DOS 演示一下领取彩蛋红包： 想不到吧，这个长得和落地页面差不多（就是丑了点）的 Terminal Art 红包上的“开”字是可以触发的！只需要使用“上下左右”键将光标移动到“开”字样上，按下回车即可触发彩蛋。 这里要提一下，PuTTY 可能在按下“上下左右”键的时候可能不会有响应，因为他不能在服务器声明“WILL Echo”、“DON'T Echo”的时候切换到输入即时返回服务器的方式，所以需要按下回车才能有反应。面对这种窗口，我觉得就和进入 Linux shell 以后顺手打一个 ls 一样，随便按按上下左右、随便按按回车，按下回车一瞬间就能看到光标位置移动了，也能大概猜出是怎么回事。因此没有给另外的提示，毕竟给了就透露答案了。 这个彩蛋红包的想法也是来自上面提到的 TextPlace 活动。当时我专门下载了 ANSI 控制序列的标准进行学习，并购买了 MS-DOS 编程手册学习当时 ANSI.SYS 支持的序列，还针对历史上在终端中使用 ANSI 控制序列进行上色写了一篇文章《DOS 下也要色色文字》。出红包时想到要做一个好看的红包图案，便顺便将其作为彩蛋红包。 还有一个有趣的事实：领取彩蛋红包的人和领取第二个红包的人重合率极低。我在参与者只领取到彩蛋红包的时候提示过“还有一个我会（WILL）给你红包还没领”（见上图），但基本没找到（或者没去找）第二个红包。对于这个情况我的猜测是：领取到彩蛋红包的这部分人都专注于 Telnet 传输了什么内容，所以他们对于第二个红包的尝试也只是在我给出的图案和文字上，并尝试输入了各种内容。只领取到第二个红包的人都专注于 Telnet 传输了什么，一上来就直接打开了 Wireshark，并被 Telnet 协商时候的“WILL <unknown option>”吸引走了。根本没尝试在 Telnet 中操作操作。 领取情况 第一个红包（78 元 发 16 个）：最早于 T+06:02:35 领取，领走 6 个，共 27.04 元。 第二个红包（第一份，88.88 元发 18 个）：最早于 T+00:40:02 领取，领走 14 个，共 67.39 元。 第二个红包（第二份，58 元发 36 个）：最早于 T+03:00:17 领取，领走 14 个，共 20.52 元。 彩蛋红包（68.88 元发 98 个）：最早于 T+00:04:26 领取，领走 52 个，共 35.01 元。 有些朋友可能不愿意使用支付宝领取红包，因此领取情况不代表真实通关情况。
在 2023 年的 RISC-V 中国峰会上，cyy 和万呆呆见到了一款看起来只是一款平平无奇的交换机，但他既然出现在了这个会场上，那就肯定和 RISC-V 有那么几毛钱关系。这个交换机采用的轩辕 1030M 以太网交换芯片来自武汉飞思灵，而里面用于管理的 CPU 就是基于 RISC-V 架构的，主频 400MHz。除此之外，这个芯片还集成了 8 路 10/100/1000BASE-T 或 100BASE-FX 的 PHY、4 路支持 1000BASE-X、SGMII 的 1G serdes、2 路支持 1000BASE-X、10GBASE-R、SGMII、QSGMII、O-USGMII 的 10G serdes，接口数量还算客观、对于各类 SFP(+) 模块的支持也不会有多大困难。另外一些参数如下： 最大帧长 16000 字节 片上缓存 1.5MB MAC 表 16K 项 VLAN 支持 4K 个 二层组播支持 4K 个 ACL 支持 256 条 典型功耗 4.5W 参数很好看，作为一个办网专家，背包里怎能不常备 10G 网卡、光纤跳线、光模块等必备工具？cyy、Easton Man、Yuuta 三人共凑出 x86/ARM MacBook 各一台、ConnectX-4/AQC107 芯片的网卡各一张，并使用 DAC 线和光模块+跳线两种方式，当场对该交换机的 10G 口双向转发能力和 RSTP 工作能力进行了测试，均正常完成。因此群友们也都挺期待该交换机面市。前两天 Milk-V 在淘宝店上架该交换机后，群友马上组团进行了购买。数辆泥头车从深圳启程，创（撞）向群友的家中。 收到以后，我连晚饭都没来得及吃就开始拆箱。该交换机一共有 8 个 1G 电口、4 个 1G SFP 接口、2 个 10G SFP+ 接口，还有一个用于连接 UART 和 JTAG 的 USB-C 精致调试接口。他没有附送 USB-C 线可以理解，毕竟只是调试接口需要，并不是一个必备的东西，但居然连电源线也不给一根，十分小气。好在我有 12V 的 PD 协商模块，配合 5.5x2.1 转 5.5x2.5 的 DC 头，可以使其正常上电。 吃完饭后便着手开始测试，首先将其连接到了电脑和家中一台闲置的路由器上，为了测试 SFP 口，还插入了闲置的 GPON/EPON 光猫 SFP 模块一根。所有端口均正常工作，并在默认配置的情况下，所有业务均能相互访问，一切工作正常。 接上串口，在开机的时候可见 OpenSBI 信息。 OpenSBI v0.7 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : Nuclei UX600 Platform Features : timer,mfdeleg Platform HART Count : 1 Boot HART ID : 0 Boot HART ISA : rv64imafdcsu BOOT HART Features : pmp,scounteren,mcounteren,time BOOT HART PMP Count : 16 Firmware Base : 0x41000000 Firmware Size : 76 KB Runtime SBI Version : 0.2 接下来按照背面贴纸的说明，在电脑上配置了 192.168.40.0/24 内不与 .253 冲突的地址，使用 admin/admin 访问了交换机管理页面。从管理页面可以看出，该交换机有 802.1q、QinQ（及 map）、基于 MAC/IPv4 子网/以太网协议的 VLAN、风暴抑制、端口限速、流量控制、802.1p、DSCP、GVRP、GMRP、LLDP、IGMP、静态组播、基于 MAC/IPv4/IPv6 地址的 ACL、ERPS、静态/LACP 端口汇聚、RSTP、802.1x、RADIUS 服务器、SNMP、RMON、端口镜像等功能。 我试着配置 VLAN trunk，看看能否与图中下面那台 NEC 路由器（也有点灵，因此我称该测试为“灵车对创”）互联互通，测试是能用的。就是我配置某个端口 tagged 1-4094 的时候网页卡了好几秒，以至于我以为被我配掉线了，又等了几秒，发现页面的列表上直接多了 4094 个 VLAN，太暴力了。 后面我还测试了一些能测试的功能，比如 SNMP、LLDP 等，都能用。光猫 SFP 模块也能正常协商、使用。运行了几个小时后，整机温度还好，温热，似乎不烫。至于转发性能之类的，cyy 在 8 月份已经测试过，就不做另外的测试了，更高级的测试我也没办法进行。 好，就介绍到这里，下面可以不用看了。799 元买到支持这么多功能的、带 2x10G+12x1G 口的管理型交换机，要啥自行车啊！ 在进行完基本测试后，开始对他进行深入分析。首先尝试拆机，但是螺丝上太紧了，放弃拆机。反正 cyy 在八月份有拍过，这里借一下他的图片。 串口的登录凭据是 root/milkv，一进去便可以注意到 /root 下是有东西的： root@dev:/root> ls -lah total 13M drwxrwxrwx 2 1000 1000 1.1K Jan 1 1970 . drwxr-xr-x 17 1000 1000 1.2K Dec 12 12:03 .. -rw------- 1 root root 763 Jan 1 1970 .ash_history lrwxrwxrwx 1 root root 16 Jan 1 1970 config.fhme -> /mnt/config.fhme -rwxrwxr-x 1 1000 1000 35.8K Dec 12 11:48 config_pll.sh lrwxrwxrwx 1 root root 19 Jan 1 1970 config_reg.txt -> /mnt/config_reg.txt -rwxrwxrwx 1 1000 1000 219.3K Dec 12 11:49 fhcli -rwxrwxrwx 1 1000 1000 16.5K Dec 12 11:48 i2c_dev_msg_muti -rwxrwxrwx 1 1000 1000 20.1K Dec 12 11:48 i2cdetect -rwxrwxrwx 1 1000 1000 23.4K Dec 12 11:48 i2cdump -rwxrwxrwx 1 1000 1000 19.3K Dec 12 11:48 i2cget -rwxrwxrwx 1 1000 1000 22.4K Dec 12 11:48 i2cset -rwxrwxrwx 1 1000 1000 20.2K Dec 12 11:48 i2ctransfer -rwxrwxrwx 1 1000 1000 16.5K Dec 12 11:49 led_164.ko -rwxrwxrwx 1 1000 1000 12.9M Dec 12 11:49 libsdk.so -rwxrwxrwx 1 1000 1000 36.7K Dec 12 11:49 xy1000_net.ko 首先我们先不去吐槽这些程序、动态库和 kmod 为什么会在 /root，为什么会有 1000:1000 的所有者。首先映入眼帘的就是那个 .ash_history，我们打开看一下。哇，看起来 MAC 和 SN 都是直接在 shell 里面写入的！这个可能是自动程序完成的，但是万呆呆也发出了他的文件，和群友的数据对比了一下，发现 3 台机子各不相同。甚至和我有着相邻 MAC、SN 的机子一开始还误写成了我的 MAC、SN。可见这个交换机真的是手工匠心之作，MAC 地址、序列号都是纯人工写入的。 作为一个持有 OUI（MAC 地址前缀）分配的冤大头，在看到 MAC 地址的时候总会去查查 OUI 数据库，看看这个 MAC 地址属于哪个组织，该组织拥有多大的分配。对于网络设备（如网卡、交换机等），这个地址一般是由该设备的生产厂商烧入。然而我查询了这个 MAC 地址以后，发现是属于 Realtek 的，但在主板的拆机图中没能见到任何 Realtek 芯片。根据搜索引擎的结果，这个前缀的 MAC 早就在 20 年前由 Realtek 开始使用，理论上早已分配完毕，更不可能提供给 Milk-V 用于制造交换机（况且他们也没用 Realtek 芯片）。所以 MAC 地址都是偷的，不能保证其唯一性了，此处建议 Milk-V 去 IEEE 申请一下 OUI 再来生产网络设备。 接着我们继续欣赏他的 rootfs，可以注意到有着大量 user=1000 group=1000 并带着 777 权限位的文件，或者是带上了执行位权限的配置文件，比如 /etc/group、/etc/hosts。打包该系统的人的水平实在不敢恭维。但还是那句话，又不是不能用，对吧。 系统的 model 为 nuclei,ux600fd，nuclei 即芯来科技。设备树非常短，除去 CPU、中断控制器、UART、GPIO 等，就是一个 ethernet 节点了。这个节点的 compatible 是 fsl,fsl-mac，fsl 即武汉飞思灵，是交换机数据平面。在内核中对应的驱动是 xy1000_net，该驱动会在内核中将其创建为一个网络接口 eth0，这个接口经由交换模块直通面板上的调试口（不是 UART/JTAG 那个 USB-C）。该调试口默认配置是 0 号口，我没有测试，不确定是面板标注的 G1 还是 XG1，不过在 Vega 的出厂配置（/mnt/config.fhme）中也并没有启用这个调试口的 CPU 直通（cpu_port_enable=0），所以该口也只是一个普通交换接口。同时还有一个 vir0 接口，这个接口是交换机上的一个 access 接口，只会接收管理 VLAN 的、目的地为 CPU 的数据。管理 IP 和 MAC 就是配置在这个接口上的。 片上一些能力的配置是通过一串 i2c 操作脚本实现的，没有写成驱动，因此开机会刷一屏 i2c ioctl 的调试信息。根据这个网页管理页面的 URL 规则（setup.cgi?todo= 和 setup.cgi?next_file=），我搜索到了 NETGEAR 的路由器，不知道他们之间有什么关联。 结合支持的功能，再看看这么灵车的情况，这个 SoC 十有八九是买的核拼的吧。这 Milk-V Vega 大概只能算轩辕 1030M 的开发板或者评估板，在家里用没关系，但在严肃的场合是没办法去作为一个真正的交换机使用的。资料公开得还是比较全的，U-Boot、Linux Kernel、SBI 的代码等都公开在了 GitHub milkv-vega/vega-buildroot-sdk 这一个仓库中，深刻践行了 monorepo 思想，避免用户切换多个仓库，commit 历史也只有干净简洁的 1 条。另外，飞思灵这个轩辕 1030M 芯片的各种文档也都公开在了 GitHub milkv-vega/vega-files，可以学习到交换芯片的一些内幕，有兴趣的大佬也能用来移植或开发。
最近一个项目需要通过 telnet 展示一些 16 色前景和背景的内容，我使用了 ANSI 转义序列（escape sequence）中的 SGR（Select Graphic Rendition，图形表现选择，CSI … m）序列来控制前景和背景色。写好测试文本后在现代终端输出上是没问题的，而当我来到 MS-DOS 6.22，才发现一切并不是我想的那么简单，展示出来的数据只有一部分颜色是正常的。 xterm 和 MS-DOS 对比 我是一个出生在 00 年的人，几十年前的人们对于电脑显示颜色的追求和技术发展速度之间的矛盾，是我没经历过的。而我这里使用的 SGR 历史比较久远且现在还在广泛使用，老的资料不是很好找。下面提到的内容对于一些专家、一些古董爱好者来说可能是常识，但我觉得解决这个问题的探究过程、发掘背后的发展历史是很有意思的，故将其整理成文。 TL;DR: 太长不读版： 为了节约空间，CGA/EGA/VGA 模式的显卡如果想要使用 16 色背景色，需要关闭硬件闪烁卸载（我写了一个 BLINK.EXE 来开关），否则背景最高位会作为文本闪烁与否的标记。 维基百科中所标注的、目前被广泛使用的 ANSI 转义序列中编号 90-97、100-107 的 8 种亮度提升的颜色是后来的非标准扩展，在现代使用没有任何问题，但没法在 MS-DOS 中使用。 在上古时代，“终端”（terminal）是物理的设备，比如最古早的电传打字机，或者一个通过串口之类的接口或协议连接到计算机的显示器。它们的工作十分轻松：从数据接口获取数据，然后打印、显示出来。而如果想让终端完成一些响铃、换行、回车之类的操作，就要靠一些不显示出来的控制字符实现（这种随着数据传输控制信息的方式也叫做带内传输）。ASCII（ANSI X3.4）中就有很多的控制字符来完成不同的功能，只是一套编码系统的容量是有限的，随着人们玩的花样越来越多，比如移动光标控制字符打印的位置、修改输出颜色等功能都需要有指令来交互。于是各个终端厂商都开发了自己的控制指令，用来实现各种高级功能，当然这样他们也就相互都不兼容了。 正是为了解决这种情况，ANSI 组织专家们设计了 ANSI X3.64（这个标准也被接受为国际标准 ISO/IEC 6429 和 ECMA-48、联邦信息处理标准 FIPS 86、日本产业标准 JIS X 0211），也就是前文提到的“ANSI 转义序列”（终于讲到正题了）。VT100 是第一个实现了这个标准的终端机，因此大家也经常能看到以 VT100 命名的终端类型，它指的就是原版 ANSI 转义序列的实现。ANSI 转义序列定义了 7 位数据和 8 位数据两种写法，大家熟悉的 ESC（\x1b 或 \033）起头的序列就是 7 位的写法，ESC 后面跟着的就是 C1 控制序列，例如常用的 ESC [ 起头的是 CSI 控制序列，CSI … ; … m 这种模式的就是 CSI 控制序列下的的 SGR 序列。8 位数据的写法就不介绍了，因为它和扩展 ASCII、Unicode 系列等编码方式冲突，没有使用意义。 即使大家后来抛弃了终端、用上了显卡，各种操作系统、虚拟终端也支持使用 ANSI 转义序列来实现光标、颜色、显示模式的操作。微软在 1986 年发布的 MS-DOS 4 中带上了 ANSI.SYS，即用于支持 ANSI 转义序列的驱动。只需要在 CONFIG.SYS 中加入 DEVICE=C:\PATH\TO\ANSI.SYS 即可开启。接着输出我生成的测试文本……诶？！怎么颜色不对？（见前文第一张图） 我在生成测试文本的时候使用的是维基百科上介绍的 SGR 颜色定义：30-37（40-47 为对应的背景色）分别是黑、红、绿、黄、蓝、洋红、青、白 8 种颜色，90-97（100-107 为对应的背景色）是上面 8 种颜色的亮度提升版。那么是哪里出了问题呢。 其实，这是现代终端处理 SGR 所使用的颜色表，我这么多年也一直以为八九十年代的终端使用同样的定义，毕竟 1981 年的 CGA 显卡已经支持了 16 色的文本模式，并没有觉得有什么不对。实际上，在 1979 年发布的 ANSI X3.64 中的 SGR 部分（5.77 节）只定义了字形的变换操作。一直到 1982 年的 ISO/IEC 6429 才增加了颜色变换的部分，且只有 30-37 前景色、40-47 背景色两部分。而上面 90-97、100-107 的定义来自 IBM 后来发布的 AIX 系统自带的虚拟终端程序（即 aixterm），后来被包括 xterm 在内的多个虚拟终端程序接纳，才变成了我们现在使用的事实标准。 研究到这里，我以为这个项目在 MS-DOS 这边只能用 8 色降级处理了。正当我准备放弃时，又想到了人类对于在电脑上显示图形和颜色追求。既然彩色的 CGA 显卡在 1981 年发布的时候就已经有了 16 色文本模式支持，人们不可能愿意放弃其一半的能力。于是我继续查找资料，了解到了原因。 从 CGA 到 EGA、VGA，虽然他们支持的文本模式的尺寸不一样，但是单个文字格式都是一样的： ++-------------++-------------++--------------------------++ || b3 b2 b1 b0 || f3 f2 f1 f0 || 8bit character || ++-------------++-------------++--------------------------++ 显示的字符存在低 8 位，接着是 4 位前景属性和 4 位背景属性，一共 16 位。前景和背景属性的低 3 位（f0-f2，b0-b2）没有啥问题，就是色号（这里是指显卡中可配置的 16 色调色板中的顺序）。但是背景最高位（b3）同时兼任字符闪烁的标记，前景最高位（f3）同时兼任字体切换的标记，而这正是 8 色和 16 色的痛苦之源。这么设计是可以理解的：当时主流的处理器是 16 位，这样就可以在一个操作内完成一个字符的显示，如果要继续增加数据，对于内存、对于处理器来说都是不经济实惠的。所以我在前文说这是“人们对于电脑显示颜色的追求和技术发展速度之间的矛盾”。 说到这里，终于可以继续解决我遇到的问题了。 前景的 16 色很好解决，其实在 SGR 的标准定义中就有说明：参数 1 表示加粗或者增强。而怎么表示增强呢？当然就是提高它颜色的亮度啦。因此只要在前景的展示时追加一个参数 1，例如 CSI 33 m 是暗黄色，那么 CSI 1 ; 33 m 就是亮黄色。在显卡这边，就是把 f3 属性位设置成 1，显卡会去调用调色盘的后 8 种颜色，后 8 种颜色默认正是前 8 种颜色的亮度提升。f3 属性同时还是字体切换的标记，如果在第二字体区域设置了不同的字体的话，字形也会同时改变，所以在第二字体区域放置加粗的字形也可以实现标准中的加粗。 在文本模式下，显卡处理的是一个个字符，但也提供了一些效果的硬件卸载，闪烁的光标和闪烁的字符就是这样一个例子。SGR 在标准定义中就有开启字符闪烁的参数，即 CSI 5 m，在显卡这边实际上就是把 b3 属性位设置成 1。例如 CSI 5 ; 33 m 就是闪烁的黄色背景。 占用了背景色的一位，那这不是只能显示 8 种颜色了吗？好在显卡还是提供了关闭硬件字符闪烁的功能，关闭以后就可以释放背景色最高位，这样背景也可以调用调色板的后 8 种颜色，CSI 5 ; 33 m 就会变成不闪烁的亮黄色背景了。 怎么关闭硬件上的字符闪烁呢？EGA 和 VGA 显卡直接提供了 BIOS 中断 INT10h 1003h 来开关闪烁，CGA 显卡则需要手动修改模式选择寄存器的值，麻烦一点。查阅 MS-DOS 的编程手册，可知加载了 ANSI.SYS 驱动的话，还有 IOCTL 的操作以通过 dmFlags 字段来设置闪烁模式，不需要直接操作显卡硬件。 为了测试这个能不能用，简单起见，我写了下面这样的小代码。 .data dm dw 0, 14 db 14 dup(?) .code ioctl: ; cl(5fh set, 7fh get) mov dx, seg dm mov ds, dx lea dx, dm mov ch, 03h ; device category: screen mov bx, 01h ; stdout mov ax, 440ch ; IOCTL function: generic character device int 21h ret main proc mov cl, 7fh ; get current mode call ioctl mov bx, dx xor word ptr [bx+4], 01h ; invert blink bit mov cl, 5fh ; set current mode call ioctl mov ax, 4c00h ; exit int 21h main endp end main 执行以后，文本确实不会闪烁了，也切换了新的颜色。但当我去测试的时候发现 IOCTL 的做法好像也只能对 EGA 和 VGA 显卡有效果，对 CGA 显卡无效。于是我又另外写了一个 BLINK.EXE（源码），效果如下图所示。这个程序通过调用 EGA 的 BIOS 中断来确定使用的是 CGA 显卡还是 EGA/VGA 显卡，如果是前者就手动修改 CGA 的模式选择寄存器，如果是后者则调用 BIOS 中断。 注意在开关闪烁模式的时候有很明显的颜色亮度变化，这样，16 色的显示也正常了。 另外，除了 86Box 模拟器上用 CGA/EGA/VGA 测试之外，我还使用了我的 Book8088（运行 8088 处理器的 2023 年生产的笔记本）配合 HD6845 CGA 显卡、Cirrus Logic GD5429 VGA 显卡，以及 Toshiba Satellite Pro 480CDT（运行 Pentium w/ MMX 233MHz 处理器的 1997 年生产的笔记本）配合 Chips & Technologies F65555 VGA 显卡进行测试，均能正常工作。最后以本文标题作结吧。 另：有些虚拟终端程序确实会将加粗用粗字重的字体表示、将闪烁用闪烁的形式表示。 Windows Terminal 等虚拟终端的默认行为和默认情况下的 CGA/EGA/VGA 显卡一样：加粗代表亮度提升的前景色，闪烁只会闪烁、背景还是普通亮度的颜色。 Konsole 和 Windows Terminal 差不多，但是加粗不光是颜色改变，也真的会加粗。 putty 默认的行为和 MS-DOS 关闭闪烁后效果一致：加粗代表亮度提升的前景色，闪烁代表亮度提升的背景色且不会闪烁。 因此如果不是折腾 MS-DOS 下的展示的话，还是使用 90-97、100-107 来实现亮度提升版本的颜色吧，就算要考虑 MS-DOS 也需要给用户切换终端模式的选项，比如默认 xterm、允许主动选择纯 ANSI 标准模式这样。
“别人家拉宽带最多俩小时，我家拉宽带得要俩礼拜。” “怎么有人办个宽带还得自己砸墙（实际上没砸）拉线上楼的啊。” 以上是自己和群友的吐槽，因此我专门写了篇流水账记录下我家光改。 在 10 年前，也就是 2013 年，跟随着国务院的“宽带中国”战略（在此悼念一下提出“提速降费”的李克强总理），各大运营商都开始了光纤入户改造。这次改造的目的就是将各个小区的上联从铜线改为光纤，并尽可能实现光纤入户（FTTH）。很可惜，我所居住的小区的高层楼房在建造时并没有设置弱电井，八芯双绞网线和电话线是进线管以后直接上到每一户、而不是像强电一样跟着桥架上楼。这种情况换线不太现实，所以电信当时也只进行了光纤入楼（FTTB）改造，对当时使用 20M 速率宽带的我家来说倒是没有什么影响。 又过了两年升级成了 100M 宽带，也只是刚刚触到了电信的 FTTB 容量限制，只是无缘了之后的 200M、500M 甚至 1000M 宽带。这几年来我也多次向电信了解光改的事情，得到的答复都是“小区容量不足，不支持 100M 以上速率”。唯一的好处可能就是宽带的资费每次续约都比之前便宜不少，毕竟现在 2000M 宽带都有了，我们家的速率却没有变化。 今年七月底，电信业务员突然联系我：“你们家的宽带可以升级成光宽带了，要不要办理？”我不敢相信：“是改成光纤到户吗？”等了这么多年的我十分激动，在得到对方肯定的答复之后，我火速订了张票准备回家，约了周六上门。但其实这次上门只是业务员上门来谈套餐，爸爸刚好不在家没法签字，而且就算签了，师傅上门还得等排期、并没有这么快，所以这次回家啥都没干成。 这次电信上门给我推销这推销那的套餐，我都是拒绝的、要求只光改（288 元调测费）。后来恰好妈妈说自己流量不太够，我在看了一圈现在运营商的套餐后，重新考虑了电信业务员的套餐。最后选定的是 500M 宽带融合套餐，可以融 3 张卡，每张 40G 通用流量，携转优惠后 85 元/月。但我们家人都是移动用户，还有个大亲情网，不方便携转。于是我给妈妈办了一张联通的新卡，并将其携转到电信，利用起了一直空闲的第二卡槽。八月我周末都有事、九月爸爸因为亚运会没空，一直到十月亚运会结束后，才正式签了合约、等我回家后约师傅上门。 第一天，电信师傅上门，说了一些东西。第一，我们小区其实容量是不够的，他得从片区光交拉一路新的过来，需要花点时间；第二，他考察了我们楼，没有弱电井、强电井，高层也没法墙外走线。所以他只能把光猫放在一楼 FTTB 的设备那边，然后继续利用开发商预埋的双绞线上楼。想不到啊想不到，我心心念念的光纤入户死在了最后 50m。说完以后，他在我家找到了楼下 FTTB 设备上对应的网线，就离开了。 我们单元不像别的单元有强电井这一点我是早就知道的，但从便利性、安全性和可维修性考虑，我不想把光猫放在公共地方，想着最坏情况也得顺着楼梯间一圈圈转上来吧。所以晚上我叫上住在附近的移动装维表弟，把整栋楼几个单元都考察了一下。最后确认了以下几点： 整栋楼都没有弱电井。 我们单元的强电井是和隔壁单元一起走的，除了我们单元，强电井都在电梯边上（也就是他们可以直接从强电井出去进家门）。 确实在哪里都找不到网线与电话线的进线管，应该就是一楼直接上来、开发商一开始并没有考虑网线、电话线的维护的。 高层也不可能考虑走外墙拉线。 可喜的是，我们发现了广电的线管可以利用。广电入户是这样的：进楼的总线在 1 楼分成 3 路支线，分别通往 3 楼、9 楼、14 楼，这三个楼层再通过分路器分出上下几层的入户线，换句话说，只有这三个楼层才能直达 1 楼。支线是单独的管线上楼，但在 3 楼和 9 楼的接线箱中都有开口。所以我们可以利用广电线管从 1 楼顺着支线上 14 楼，再从 14 楼顺着入户线下 12 楼（因为 12 楼入户线走的是 14 楼支线，所以得先上后下），这样就不用在楼道里走明线了。 第二天，没有观测到电信师傅出现，下午我在家里翻箱倒柜找了一下以前买的穿线钢丝。等表弟下班，我们就开始从广电走线。从 1 楼到 3 楼到 9 楼都非常顺利，但是 9 楼到 14 楼卡住了，钢丝从上面怎么捅都下不去。我和表弟在 9 楼接线箱发现，9 楼分给 11 楼的入户线和 1 楼上 14 楼的支线，在 9-11 段共用了一根管子。而 9 楼接线箱往上有个管子被水泥封住了，可能是开发商一开始埋管子的时候埋错了，只能让这一段共用了。 那么我猜测可能是在 11 楼的接线盒里卡住了，没法准确插入往下走的线管。我们跑到 11 楼，墙壁光秃秃，没有接线盒，但在本应有接线盒的位置看到墙壁有粉刷的痕迹。在 9 楼与 14 楼抽支线，难以抽动。动了动支线和 11 楼进户线，有不少腻子碎块掉落，是内层粗糙、外层是光滑的管道模样。之前楼道粉过墙，会不会是工人看到接线盒顺手封进去了呢？ 第三天，没有观测到电信师傅出现。下午，我接着昨天的猜想，在钢丝头绑了棉线、系在支线上从 14 楼往下穿，企图让支线引导钢丝穿过 11 楼接线盒。这一步是成功的，确实往下穿了点，但很快又遇到了阻力、过不去了。我做了个标记，从楼梯间垂下去，发现已经来到了 10 楼，但还是在管道内被堵住了。 想到了昨天从管道中掉出来的那些碎块，感觉像是里面已经被堵结实了。等晚上表弟带来了细钢丝，我们在 9 楼从下往上穿，也遇到了同样的问题，而且抽出来比划了一下，竟然连 10 楼都没到。表弟换了个角度，多试了两次，最后也卡在 11 楼，怎么试也穿不过去。我们认为只能把 11 楼的接线盒砸开看看了，时间晚了，砸起来肯定会被投诉，明天再干。 第四天，观测到电信师傅在 1 楼出现过。今天因为要出门吃饭，所以我和表弟没开工。 第五天，观测到电信师傅在 1 楼出现过。表弟来了以后，我们开始敲 11 楼本应是接线盒的那一块区域，发现里面都是结实的水泥，而且不同区域的水泥颜色是不一样的，显然是后面有人糊上了。表弟猜测是墙后面那户人家装修改管道的时候可能砸穿了，重新封上的时候把接线盒也封住了。 想想也是，如果只是刷墙不太可能给接线盒糊上的。联想到管道里掉落的碎片粗糙面的情况，和只有细钢丝可以穿过的缝隙，我们觉得很有可能是一层水泥给糊上了。没办法了，只能从 9 楼顺着 10 楼入户线上一层楼，然后从 10 楼接线盒出来，这样就只用走两层明线了。走明线钉钉子噪音挺大的，继续咕到明天再做。我在 foobar 院群里暴躁吐槽：“简直是好几个狗屁混合的大狗屁，但凡有一个狗屁是不狗屁的都不会这么麻烦。” 第六天，没有观测到电信师傅出现。我和表弟从 10 楼广电进户接线盒开始走明线到 12 楼，终于拉进了家门。因为临时改了两层明线，原来用的光纤长度不太够了，于是我拿出家里另一捆，用法兰接上。更好的方案是熔接机，不过表弟要去移动借出来还挺麻烦的，如果光衰不严重就不管了。接完以后，表弟发现红光笔没带，没法测试。于是我打电话请电信师傅上门借个红光笔。电信师傅听说我们自己拉了条纤上楼，十分震惊，说“专业啊”，并询问了我是什么行业的。他还看了一下我们的走线方案，表示之前没有想到广电的管子也能走，同时对于 11 楼接线盒被人堵死没法上楼而感叹“唉，这就是老小区”。 红光笔测完，光很漂亮，至此光纤入户的最后 50m 算是完成了。尽管如此，电信师傅说现在还不能开通。因为片区那边端口也不够了，还得等上面的人来扩容。他前两天已经把我们楼底到片区那边光路接通了，片区扩容完以后就可以开通。 电信师傅走之前还加了表弟微信，表示如果以后有类似的客户也叫表弟来帮忙，老小区想光改但因为种种原因不方便改的客户还蛮多的。表弟说类似的活他其实听说过，干一下收个 1000 块都是很轻松的。“你要么宽带别用爽，要么搬家，要么就掏钱改造。对于这种一次性的装修改造技术活，其实是不贵的。” 第七天，等待。今天想到，既然我都已经 FTTR（R=Rack，因为是从门口进的光纤，拉进家里的柜子更近更方便）了，干脆买个光猫 SFP 模块算了。但是不知道 PON 类型，既然电信需要扩容而不是直接从 FTTB 设备分光，那么猜测应该不是 EPON 系列，而是 GPON 系列。GPON 系列中，XG-PON 又基本会和 GPON 开组合，于是先找群友买了一根 GPON 的华为 MA5671A，反正就几十块，买错了再买就是了。 第八天，等待。MA5671A 到货了，直接插在了目前的网关 Juniper NFX250 上，并分配好了 VLAN 等，就等电信那边插线了。 第九天，等不住了，打电话问了师傅扩容什么时候能完成，师傅说再帮我催一下上面。 第十天，等待。不能一直无止境地等下去，于是购买了明天晚上进沪的车票，是打算先去上海返工了。 第十一天，下午准备返沪前最后打了个电话给师傅。师傅说：“我下午收到公司通知了，他们今天已经去扩容了，明天就能装。”我高兴，刚想说话，师傅又说：“或者今晚有没有人，今晚可能就可以。”我说：“没事儿，您忙的话明天再来也行。”他回道：“没事的，今晚可以的。拖了这么久我也觉得不好意思的。”挂了电话后，我火速办理了退票手续。后来又刚好接到了电信客服的电话，询问我这个订单为什么挂了半个月都没结，来问我是不是师傅的问题，我也进行了解释。 师傅晚上并没有来。不过我也说了如果忙的话明天来也可以，故我也没打电话问。 第十二天。晚上六点半，师傅上门了，我家到局端的光路接通了。师傅首先测试了一下楼上的光信号强度，-15dBm，挺好的。接着安装了光猫，测速结果 600M 下、36M 上（经典的 20% 超量），顺利结单。走之前再感叹了一下表弟的技术“高出他好几个段位”。后来我和表弟说，表弟一笑：“那让他来移动锻炼一下。” 至此，我家也正式从铜线入户升级成了光纤入户，从 100M 宽带升级成了 500M 宽带，而且还是自己从楼上拉到楼底的新线+师傅从楼底拉到片区机房的新线+新扩容的 OLT。 最后我感叹：“别人家拉宽带最多俩小时，我家拉宽带得要俩礼拜。”表弟说：“因为别人家不是高层（可以走外墙）、不是老小区（新的一般预埋线都好换或者预埋光纤）、有强弱电井、没有倒闭邻居（不会乱把公共接线盒封死），你们这但凡有一个走得通都能好办很多。”我好奇问他在移动遇到我这种情况会怎么处理，他表示：“如果光猫不放楼道，我早就退单了，就和人家说你这没法装这个套餐就行了，还陪你在这里拉半天。” 其他的一些情况： FTTB 的时候我算是在用 EPON，现在变成了 XG-PON。因为之前不知道 PON 类型，闭着眼睛猜着买的是 GPON 的光猫 SFP 模块。现在 MA5671A 测速结果有问题，所以之后还得斥巨资新买根 XG-PON 的来试试。 去年浙江电信有 30 月包 100M 上行、50 月包 200M 上行的提速包，但是今年问过师傅和 10000 号，均表示已经下线、没法开通了。八年前每月 10 元提到 30M 上行，结果现在下行速度 500M 了，上行还是 30M。 前文提到光信号强度是 -15，但实际使用的时候，因为又过了一个法兰（APC-UPC 转换），MA5671A 读到的光强度变成了 -17： Physical interface: ge-0/0/10 Laser bias current : 6.514 mA Laser output power : 1.5960 mW / 2.03 dBm Module temperature : 31 degrees C / 87 degrees F Module voltage : 3.2760 V Laser receiver power : 0.0192 mW / -17.17 dBm 群友关心的公网 IPv4 并没有变化，还是熟悉的前缀、还是熟悉的味道。虽然对我来说变了也无所谓，有 v6 就行。DHCP 下发的 IPv6 的前缀范围从 240e:390:6a00::/44 变成了 240e:390:6aa0::/44。 BRAS 变成了传说中的 vBRAS，之前是 pp0.0 Index 106 State: Session up, Session ID: 1919, Service name: None, Session AC name: JH-JH-CN-BAS-1.MAN.ME60, Configured AC name: None, Remote MAC address: e0:24:7f:fb:88:58, Session uptime: 13:46:23 ago, Auto-reconnect timeout: 2 seconds, Idle timeout: Never, Underlying interface: ge-0/0/8.0 Index 79 Ignore End-of-List tag: Disable PPP-Max-Payload tag: 1492 现在是 pp0.0 Index 106 State: Session up, Session ID: 1919, Service name: None, Session AC name: JH-JH-CN-VBRASC-1.MCN.vBRAS1000-CP, Configured AC name: None, Remote MAC address: 00:00:5e:00:01:13, Session uptime: 00:00:19 ago, Auto-reconnect timeout: 2 seconds, Idle timeout: Never, Underlying interface: ge-0/0/10.41 Index 83 Ignore End-of-List tag: Disable PPP-Max-Payload tag: 1492 由上可见，联网方式还是 PPPoE，并没有变成我想要的 IPoE 甚至 4in6。以及对端发的 PPPoE MRU 还是 1492，要是能发个 1500 该多好。另外，PPPoE 隧道对端地址确实不变了。
去年 6 月，企业微信把腾讯企业邮箱合并了，因此我的企业微信中冒出了一车企业：毕竟一个企业邮箱只能绑一个域名。问题倒是也不大，只是看着不舒服，能忍。后来企业邮箱中的所有用户都在企业微信创建了一个人员，必须绑定微信之后才能登录、修改密码。问题也不大，毕竟我该配的多少年前就配完了，我自己可能平时要用的邮箱那绑定一下本来就要用的企业微信倒也无妨，而且可以给邮件客户端生成密码甚至关掉强制微信登录，也能忍。不过从腾讯企业邮卷铺盖跑路的想法已经种下了种子，只是平时确实忙碌。 If it ain't broke, don't fix it. 但是年初开始，QQ 告诉我，它 broke 了。老用户也不再享有 2G 的空间，而是降到 1G。（邮件类似于下面这张图，就不翻最早的了。）经过十年的积累，我已经使用了 1.1G，所以 7 月 12 日以后就收不进邮件了。删邮件是不可能删的，挑选过程很麻烦。加钱是不可能加钱的，本来功能就没很好用，和企业微信绑定后就更没有用的必要了。于是只能宣布它 broke 了，是时候 fix 了。 从腾讯离开后该去哪儿呢？其实随便找一个商业方案都比腾讯企业邮箱（包括高级版）好用很多，至于免费方案只能说“要啥自行车”了。对于市场上比较常见的选项，twz 列了一些国内外的服务对比，有想要了解的可以去看看。数年前其实就想自建邮箱，因此我就没选择这些商业方案，而是利用这个契机完成了邮箱的自建。当然，这篇文章主要是讲述从腾讯企业邮箱跑路的故事，而不是讲怎么自建，所以这里就不展开了。 因为不给钱只能一个域名，因此我在腾讯上部署的域名邮箱一共有 4 个：lohu.info、sohaj.in、soha.moe、oiinhand.info。这几个的管理员账号都跟随手机账号绑定在了我的企业微信上。中间两个域名都是一个 mailbox，然后所有 catch all 到这个 mailbox，所以直接迁移就好，没什么困难。oiinhand.info 也随着 OI in Hand 项目的倒闭而倒闭了，这个只需要看看有没有什么邮件需要备份，然后直接扬掉就好。 关键就是 lohu.info 这个域名，几个 mailbox 都干着不同的事情，有些东西已经都忘了是干什么用的、有什么了，这都得一个一个去看。现在腾讯又强制一个账户绑定一个企业微信账号，不能直接切换也不能直接改密码进去看。最后研究来研究去，发现需要两个微信+企业微信的账号才能去一个个看并备份。 首先准备好第二个微信/企业微信，因为自己的微信已经绑定管理员账号，且管理员操作没法完成下面的操作。接着在企业微信后台创建一个新用户，绑定第二个微信/企业微信。接着把所有要看邮箱的账号全删了，只剩下管理员和新创建的用户。邮件-协作-邮箱管理-邮箱回收站里面就会有删掉的邮箱了。 接着再利用分配功能，把其中一个邮箱分配给新创建的账户，就可以用新创建的账户的微信扫码登录邮箱了。登录上去看完觉得需要迁移，就去设置里面接收短信验证码设置一个密码来做迁移。迁移我用的是 imapsync，使用类似下面的命令。 imapsync \ --ssl1 --host1 imap.exmail.qq.com --port1 993 \ --ssl2 --host2 imap.pobox.soha.foo --port2 993 \ --user1 'example@lohu.info' --password1 '114514' \ --user2 'soha@jin.sh' --password2 '1919810' \ --addheader --automap \ #--justfolders --dry \ 最后花了点时间，对这个繁琐流程一共操作了十多次，把所有的账号都检查并迁移走了。但当迁移完成想删库跑路（解散企业微信）的时候会发现，跑不了。 后来联系了客服才知道，企业邮箱迁移过来的企业微信都是不能自主解散的。是为了防止企业邮箱迁移过来的人不知道解散企业同时也会删除企业邮箱，才有这么一个防呆的措施。最后进行了两次确认，等他们后台工作人员帮我恢复了自主解散权限以后，就可以自己去企业微信解散了。 然后找客服用下面的说辞解散了另外 3 个企业，算是彻底从腾讯企业邮箱跑路了。 你好，ID 为 <redacted> 的企业无法解散，提示如图。 我是当初的腾讯企业邮箱自动迁移过来的企业，我了解企业解散后邮箱等记录会全部消失，我已做好迁移和备份，请帮我恢复解散权限。 管理员的绑定手机是 <redacted>。 这次一整套流程下来让我感觉挺悲哀的：腾讯的重点不是想着怎么优化自己的产品、让产品更好用，而是把用户绑架在自己的生态里面，只要进了他的门就别想把钱送给别人家。毕竟使用腾讯的企业邮箱需要腾讯系的工具是一种必然的话，对于不想或者不方便迁移邮箱的用户来说，是不是更愿意把其他业务也送上腾讯系呢？国内的其他厂商我不了解，但是国外的产品就不一样了。用着 Microsoft 365 照样可以让 Google Workspace 处理邮件，用着 Google Workspace 照样可以用 Microsoft 365 来做办公协作。而且这种协作不是割裂的，他们之间可以通过 SAML 等公有协议实现 SSO 和互联互通，是一种无缝的共同繁荣。我认为这样才能让友商之间以一种健康的方式向上发展、进步。 就这样吧，算上前一个域名是用了十年了，告辞。
好久没写博客了，来水一篇。昨晚在水群的时候看到 dialup.net 编写了一个 16 位 Windows 版本的 ChatGPT 客户端——WinGPT，这样 Windows 3.x 也可以使用上人工智能聊天技术了。之前还看过 Squirrel Monkey 做的整活视频《If ChatGPT were around in the 1980s...》，这次属于是成真了。 这是怎么做到的？根据作者的介绍，他把较新的 WolfSSL 移植到了 16 位 Windows 上，这样应用程序就可以使用其接口直接处理 TLS 1.3 而不需要其他主机当代理来终结 TLS 了。当然由于时代的限制，只能使用一些比较弱的加密方式，是有安全隐患的。另外，当今的 API 并不太会考虑传输内容的尺寸进行优化，JSON 的处理同样需要一些内存。在那个年代的系统上，内存的使用是一件非常痛苦的事情，因此 WinGPT 的单次交互内容需要尽可能小，作者就限制了输入文本的长度，也一并把上下文的发送给砍掉了。 顺便一提，这个作者在写 WinGPT 的时候，也问过 ChatGPT 怎么样来实现软件中的状态栏，但是它给了一个错误的答案。 下面则是记录一下老爷车上网开上了信息高速路，去和那“谈天吉批提”聊天的技术细节。 手里刚好有一台 Toshiba Satellite Pro 480CDT 笔记本，因此我准备在它上面运行，来一场划时代的碰撞。这台笔记本搭载了 Intel 的 Pentium w/MMX @233MHz 处理器（代号 P55C）和 160MB EDO DRAM 内存，以及 Yamaha OPL3-SA3 声卡、Chips & Technologies 65555 显卡、800x600 TFT 24 位真彩色显示器、33.6Kbps 调制解调器（但我没买 DAA 模块，没法插电话线）、USB 控制器、两个 32 位 CardBus 卡片扩展槽。是一个还算高级的笔记本，毕竟在 97 年卖两万多人民币呢。我在他上面装了 MS-DOS 6.22 + Windows 3.2、Windows 98 SE、Windows 2000 Professional 和 Alpine Linux，使用 GRUB2 引导。 我没有 Windows 3.x 下有驱动的 PCMCIA 以太网卡，也没有给内置 MODEM 买 DAA 模块，因此选择使用串口上网。其实串口上网是和拨号上网一样的，MODEM 只不过是把串口信号调制成音频信号通过电话网络传输。在这里，我使用一条 USB 的 RS-232 串口线连接现代笔记本（Linux）与上古笔记本（Windows 3.2），图中的 DB9 到 RJ45 console 接口的转换器是昨晚看到 WinGPT 以后加急购买的。 在串口上跑 TCP/IP 常见的有两种协议，SLIP（Serial Line Internet Protocol）和 PPP（Point-to-Point Protocol）。PPP 有协商鉴权和分配 IP 的过程，因此在那个年代多是使用 PPP 来上网。我只需要让 Windows 3.2 通过 Linux 的网络上网，不需要那些有的没的，因此使用 SLIP 即可。SLIP 和 PPP 都可以在 Windows 3.x 上使用 Trumpet Winsock 实现。Trumpet Winsock 通过提供 Winsock 兼容接口的方式来为使用 Winsock 的应用程序提供更高级的网络连接。 Linux 上可以使用 slattach -s 115200 -p slip -L /dev/ttyUSB0 在 tty 上以 115200 的波特率处理 SLIP，并创建一个网络接口。 一般来说，创建的这个接口是 sl0。同样的，在 Trumpet Winsock 上配置好串口号和波特率，以及奇偶校验、停止位等参数，最后写好 IP 地址、DNS 服务器，这样就完成了。 接着回到 Linux 添加 IP 并拉起网卡，Linux 主机就和 Windows 3.x 连接起来了。 # 因为是点对点连接，因此用这种写法来添加 IP，内核会自动添加一个到对端的直连路由。 ip addr add dev sl0 44.159.73.196/32 peer 44.159.73.203 # 拉起 sl0 并设置其 MTU 为 576，对于这种连接来说小 MTU 会好点。 ip link set dev sl0 up mtu 576 因为是第 3 层的点对点协议，并不是基于以太网，因此不能直接将其桥接到 Linux 的外网接口上来上网。这里我通过开启 ARP 代理的方式，把外网接口和 sl0 连接起来。ARP 代理的原理是以被代理主机（这里是 Windows 3.2 笔记本）的名义在广播域中宣告被代理 IP 的 MAC 为自己网卡的 MAC，这样子网内其他主机（包括网关）就会像下图一样，把发往被代理主机的报文发给代理主机了。 这样网络就通了，也就可以网上冲浪并与“谈天吉批提”聊天了。
新的一年到了，属于兔年的解谜红包来了！快快点击参与吧→ https://hb.lohu.info 下面是一些你应该知道的事情： 这是一个解谜游戏，利用你的知识（现学大概也是足够的）解决所有题目，获得红包口令，口令可进入支付宝领取红包。 除去红包不说，游戏的过程也是很快乐的。 你应该需要电脑才能愉快玩耍。 你拿到的所有答案都是 8 位数字，为了方便控制开始和结束时间，在支付宝领取时，前面需要加上一个“兔”字。如解题得到 20230122，那么最后的红包口令是“兔20230122”。另外，找到彩蛋红包请在前面加“彩兔”而不是“兔”。 如果你是和其他人一起完成的，希望你们只领取一次，但可以在快结束的时候进行第二次领取。总之就是要把机会先留给别人。 本次活动时间从北京时间 2023 年 1 月 24 日 20 时开始，持续 24 个小时。如果红包被提前领完不会补发。 共设 3 个红包，数额分别为：总额 72 人民币元，分 60 个；总额 88 人民币元，分 36 个；总额 98 人民币元，分 28 个。红包领取规则均为“拼手气红包”。 这个游戏由 Soha 制作，在游戏结束后也将在 Soha 的博客 https://soha.moe 上放出题解，往年的内容也可以在博客上找到。如有疑问也请通过博客的“关于我”页面上所述的联系方式联系。 [T+04:00] 不涉及任何暴力解法（爆破等），需要用巧劲而不是蛮力。 [T+07:00] 不知道的东西就问问万能的搜索引擎吧。 最后祝大家，新年快乐！ 活动结束后将在下面更新题解。已更新，请看下方的题解。 往期回顾 2022 壬寅虎年 2021 辛丑牛年 2020 庚子鼠年 2019 己亥猪年 2018 戊戌狗年 2016 丙申猴年 这个红包所涉及的一些代码和文件可以在 GitHub 找到。 点开今年的红包，还是熟悉的配方，还是熟悉的味道。这个祖传红包页面沿用至今，但确实挺好用的。只不过这次点开红包的“开”按钮，显示的内容却是短小精悍的一句话： 笑死，根本没有红包。Soha正忙着在foobar院新年红包研究所干活呢。明年再来看看吧。 伴随着这句话的出现，还有音乐响起，不过那个不是主线任务，我们之后再说。 注：下面所述发稿时间以 2023-01-26T17:00+0800 为准。 第一关 进入研究所 简简单单的一句话，再没有任何其他的信息，那么这句话就是线索。很多人觉得这句话没头没脑，无从下手。甚至还有人认为 foobar 暗示的是 foobar2000 这个经典播放器，所以都跑去研究彩蛋红包那段音频去了。在设计这一关的时候我也没有想到这一层，一个玩家提醒了我才知道，因此赶紧加了一段说明。 我们来对这句话抽丝剥茧：笑死、根本、没有、红包、Soha、正忙着、干活、明年、再来、看看，这几个词大家都知道是啥意思。只剩下“foobar 院新年红包研究所”这一个没头没脑的机构名称放在这里。 不知道？搜呗。我在开始 7 小时后也增加了这个提示：“不知道的东西就问问万能的搜索引擎吧！”。当然可能每个人选用的搜索引擎都不一样，短短的时间里面也没能让他们来收录，但用户量较大的Google是可以搜索出来的。接着就可以从搜索结果进入一个名为“foobar 院新年红包研究所·所内综合办公系统”的网页。 在这篇文章发布后，有一部分人反应自己的 Google 除非用双引号强制包含，否则 Google 的算法不会展示这个页面。而我之前用隐私窗口没测出来这个，看样子有很大一部分玩家是被 Google 的算法坑没了。 这个页面内容很简单，只有一个登录表单和一个注册表单的链接。一般来说，肯定会先尝试一下弱口令之类的，接着用引号之类的尝试找注入点，但这些都没有（除了刚开始的时候有个人注册了 admin/123456 这个用户，让一些玩家绕过注册白捡红包一个）。后来我看到依旧有人顶着 1 request/s 的限制在尝试 SQLMap 时，增加说明了“不涉及任何暴力解法”。 登录行不通，那么这时候就该考虑另一条路了：这个系统一看就是专门为这次红包设计的，如果不是红包有关的内容是没必要去做的。注册就是应该考虑的突破口。 打开注册表单，内容十分简洁。第一条就是学生和教职工身份的选择，学/工号后面是身份证后 6 位的验证。有些人认为这里是社工途径获得信息进行注册，这也是很自然的想法，但经过检索以后发现并没有这种资料。就只能从页面本身入手了。而实际上在代码实现的时候，只要申请的角色是学生和教职工，就直接返回找不到，毕竟入口确实不在这里。 整个页面的代码真的很简单，一眼就能看穿。一开始我认为大家也都会看一眼引用的 JS 等，但开始后注册数量很倒闭，我觉得不太对劲。因此移动了一下某 endif 的位置，故意放多一个 nbsp 出来，暗示还有一个隐藏角色。这个消息发布以后，一小时内多了 10 人注册。 查看引入的资源，发现只有一个 pure-min.css（外部库，就可以排除了）和 fe 文件夹下面的两个资源。CSS 看一眼发现啥都没有，但是 JS 就不一样了：这咋还有一个叫“keren”的 role 呢！ 接下来就是利用审查元素修改角色值，接着忽略前两个字段填表完成注册。 这里是直接看 JS 找到的第 3 个角色，如果我是用一台正常的电脑和正常的系统来写这个题解的话，我会直接用审查元素查看两个角色的单选框的 event listener，找到这个函数。这也是对于大型项目一般会使用的方法。 使用刚才注册成功的信息登录就能看到第一个红包口令了。 截至发稿，一共有 50 人尝试注册（以 POST /zizhureg 为准，按照 IP 统计，下同），27 人注册成功。红包原定发放 60 个，共 72 元，实际领取 27 个。根据 session 创建时间和注册的时间差，排除一些几十秒的离谱数据，最长用了一个小时，最短只用了几分钟，大部分用了 10-30 分钟。 第二关 摸进所内网 注册进入网站以后，就是到处摸、到处看的时候了。导航栏除了“内网访问”之外都是 403。通知新闻倒是能看，但是有几条显示“该内容仅限办公室查看”和“该内容仅限内网查看”，结合唯一可访问的“内网访问”以及右侧的“您正在从外网访问”，可以推断出题目的目的是获得内网访问权限。 “内网访问”栏目是一个 VPN 的申请。这也是目前唯一能交互的地方了。申请表中用“管理员会很快审核你的申请”来暗示 XSS。首先先随便测试一下： 这里并不是盲注，提交后的页面可以回显验证，在这里展示的内容和“管理员”看到的是一样的。上面的内容显示的时候会变成： alert(1); 那么猜测这个系统在提交的时候会： 过滤 标签； 过滤标签内 onXXX 属性； 添加 。（当然这个和题目没关系。） 接下来随意使用一些常见套路进行测试，我们也可以很容易发现它只对危险内容进行了一次过滤，并没有循环过滤，这是一个常见的错误。所以只要使用 <scrip t> 这样的内容就能绕过： （这里申请表中显示的代码是为了让大家直观看出而手动 escape 的。） 既然能 XSS 了，那么接下来该怎么办呢？有些人一开始认为是偷 cookie 提权，不过测试后发现并没法用，因为我用的是 IP 登录。那么就该考虑怎么让自己的申请表通过了。该怎么让管理员通过自己的申请呢？这个页面上看不到任何表单，但是后面 script 给出了暗示： 虽然审批表单检查了权限没有显示出来，但是下面辅助表单的 script 并没有放入判断，所以“漏”出来了，这是常见问题。 在这里我们就能看到管理员点下审核按钮的时候，这段代码会把 #npt-action 的值换成触发的 .btn-form-action 的 data-action 属性值，然后提交。最简单的解法就是猜出通过审批 action 的值，然后点击，这样的 payload 就是 <scri pt>setTimeout(() => document.querySelector('.btn-form-action[data-action="accept"]').click(), 233);</scri pt> 当然也可以不用猜，使用一些手段也可以把 HTML 代码偷到自己手里，只不过需要一个平台来反弹这些内容。这种平台可以自建（最简单就是 socat 快速开一个）、公开的也有不少，如果是了解 XSS 或者打 CTF 的人，那知道的肯定比我多。 （这里申请表中显示的代码是为了让大家直观看出而手动 escape 的。） 还有一点需要注意，有一些朋友不太了解浏览器代码的执行顺序。可以看一下我之前测试 XSS 的图片，注意 alert 的时候，侧边栏是没有加载出来的。因为在执行那段恶意 script 的时候，浏览器还没执行到侧边栏的 DOM。同理，如果直接在这里操作按钮的 DOM，也没法成功，因为这个时候按钮还没被执行出来，当然是操作不了的。我上面的 setTimeout 是一个方法（方便但是不靠谱），监听 DOM 加载的事件，然后把操作放 callback 也是一种方法，都可以解决这个问题。我在看后台数据的时候发现很多朋友因为这个原因卡了很久，非常可惜。 这里在服务器端过滤内容时实际使用的[代码]是： $statement = preg_replace('/<\s*\/?\s*script(\s*|\s+[^>]*)\/?>/i', '', $statement); $statement = preg_replace_callback( '/<\s*\S*(\s*|\s+[^>]*)\/?>/i', fn($matches) => preg_replace('/on[a-z]*\s*=\s*[\'"]?[^\'">]+[\'"]?/i', '', $matches[0]), $statement ); 在选择过滤敏感标签的方案时没有循环过滤是一个典型错误，这里的敏感内容也不够完整， 这样的标签都还没有被排除。 审核是自动审核机完成的：用 puppeteer 来操作无头 Chrome，加载申请表后等待 1s，然后发送 reject 的请求。如果在这之前以任何形式成功发送了 accept，就是过关。 这里我还有 2 个锅：第一个是没考虑 alert 之类的阻断情况，导致这样的输出会卡住审核机，同时没有在外面 try-catch，超时后就把整个程序卡退出了。第二个是自动审核一开始也是点击按钮实现的，如果用户输入破坏了 HTML 结构，那么这张申请表也会卡在未审核，不过不会把整个程序卡出去。设计上是同时只能有一张申请表在审核，因此这两个问题会导致大家的申请表卡住，没法提交新的申请，还好这都是 foobar 院同事在刚开始不久的时候试出来的，没有影响其他人。 现在已经获得了 VPN 的连接信息，就可以连入内网了。装上 OpenVPN，导入配置（Linux 下是 openvpn --config tnhbyjs-vpn.ovpn），输入用户名和密码，换上内网 DNS。 在这里演示的时候使用的是 NT 4.0，虽然它可以支持 IPv6，但装新一点的 OpenVPN 不太现实，加上第三关还是需要 Linux，因此我接下来将使用代理，通过 Linux 主机来访问内网，另外 Windows 10 及以上也可以使用 WSL 来完成第三关。 成功连接并修改 DNS 之后，重新进入系统，就可以发现这时候已经可以查看《2023 年“温暖送学生”活动的通知》了，同时右侧显示“您正在从内网访问”。 截至发稿，上述 27 人共有 12 人通过第二关。红包原定发放 36 个，共 88 元，实际领取 10 个（有 2 人超时完成）。根据注册时间和通过审批的提交时间差，大部分用时是在 1-2 个小时左右、尝试 30-40 次。最少的尝试 15 次、用时 7 分钟；最多的尝试 117 次，间隔 17 小时（中间肯定睡觉过了 233）；第二多的用时和最多的相近，但只尝试了 64 次。 第三关 闯入办公室 接下来就是最后一关了。新闻中还有 2 篇显示只能从办公室访问，那么怎么样才算是办公室呢？它是一个网站，网站该怎么判断用户的位置呢？当然是 IP 啦，不然还获取 GPS 权限吗。那么问题来了，什么样的 IP 才算是办公室呢？看了眼 OpenVPN 分配到的IP，是 fd23:acce:5500:c01::1001。有些人能够一眼看出地址中“acce:5500”是 access 的意思，是说这是接入网的网段，但是怎么变成办公室呢？ 在这一关，OpenVPN 使用的 tap 模式是一个暗示，tap 是数据链路层的隧道，而一个 access VPN 显然没有必要，正常都是使用网络层的 tun 模式。这里是用 tap 暗示 VPN 是和研究所交换机接在一起的。这里的暗示确实隐讳了一点点，因此开始后不久我加入了“就像在研究所里插网线上网一样”的提示。结束前 6 小时又加入了“该 VPN 直接接入研究所交换机”的提示。 这就是暗示要抓包啦！有些学校/机构的网管没有做好隔离，把所有东西都放在一起喷。甚至内部网关协议(IGP，如 OSPF、RIP 等)的报文都在所有人可访问的广播域中乱喷。如果没有用上 IPSec 之类的鉴权手段的话，所有人都能通过这个来劫持路由。 用上 Wireshark（这里是它的一个 TUI 版本，termshark）抓包，确实见到了 OSPF 的报文。这里还有个小彩蛋，有些人可能也注意到了，在 Ethernet 层的地址边上，源地址 8c:1f:64:bf:1f:23 的 vendor 被标注成了“Soha Jin”。这就是我在《大步踏入 2023》中提到的和万呆呆一起斥巨资向 IEEE 申请的 MAC 地址块。他今年出的红包也用上了自己的 MAC 地址。 发现了 OSPF 的 Hello Hello Hello Hello Hello Hello Hello Hello 报文之后该怎么办呢？当然是加入他们一起去 Hello Hello Hello Hello Hello Hello Hello Hello 呀。从对面发送的 Hello 报文中获得 area ID、instance ID 等必要参数，这时候就可以请出万能的 BIRD，使用下面的配置加入 OSPF 了： protocol device {} protocol ospf v3 { area 0 { interface "tap0" { cost 100; }; }; ipv6 { import all; export none; }; } 配置完成后等待一会儿让它们完成握手，我们就能在 BIRD 中看见研究所的内部路由了。 仔细研究这些路由，发现他们还是很有规律的：第 17-48 位都是一些单词。比如“cafe:0000”代表 café（咖啡馆），“c1a5:5000”代表 class（classroom，教室），“fbac:da7a”代表 foobar ac data（foobar 院数据中心）。其中还有一个“0ff1:ce00”代表 office（办公室），有的玩家因为没有注意到被省略的前导零而没能看出来。本来这里的“0ff1:ce00”是想让大家找规律猜出来自己填上的，但是连万呆呆在看出来这些单词的情况下都没理解我的意思，而以为是在这些前缀中找出代表办公室的，于是我就加了一条 fd23:0ff1:ce00:101::/64 当作提示降低难度了。 说到这里思路应该就清晰了：首先宣告一条 fd23:0ff1:ce00::/48 下面的/64 前缀，然后使自己通过这个前缀下面的一个地址访问，那么就是从办公室访问了。 要实现前者，只需要在 BIRD 的 OSPF 配置中的 area 0 部分加入一行（2333 部分可以自己定义）： stubnet fd23:0ff1:ce00:2333::/64; 要实现后者，只需要执行下面的命令： ip addr add dev tap0 fd23:0ff1:ce00:2333::3333/128 ip route add dev tap0 fd23:fbac:da7a::/48 via fd23:acce:5500:c01::1 src fd23:0ff1:ce00:2333::3333 其中 fd23:fbac:da7a::/48 是研究所网站所属的网段，fd23:acce:5500:c01::1 是 OpenVPN 推送的网关地址。fd23:0ff1:ce00:2333::3333 是自己指定的地址，第二行的 src 告诉内核访问这一条路由使用这个地址当作自己的源地址，这样我们访问研究所网站就会通过这个 IP 地址了。 再次访问之前不能访问的通知，就能看到红包口令了。右侧也显示“您正在从办公室访问”。通关！ 这次在领取后还有一段文字说明，要求获得后尽快撤销路由。因为 OSPF 的 LSA DB 是整个网络内所有的路由器一起维护的，如果你不及时撤销，别人也能看到你宣告了什么，那么别人也就能一下子知道怎么宣告地址了。 说到这个红包的思路，可就有趣了。2022 年的 D^3CTF 某个出题人出了一道题目，创意来自我 2021 年的红包的最后一部分，出题人还写了 Salute Soha，让我倍感荣幸。这道题目提供了一个 L2TP 隧道用来连接一个 MikroTik 路由器，只要进去和 OSPF 实例一起 Hello，就能获得编码过的红包口令。当时有人质疑出题方“这题目一点也不 CTF”，我听说了以后就举了一个例子： 在你的学校里插上网线，开启 tcpdump，没准就能看到一堆 OSPF Hello。你一起进去跟着 Hello 可能还可以把路由漏给学校的路由器来劫持路由。 如果你的学校真的有这个问题，请及时上报负责部门。 当时其实也就构思好了这道题目：需要通过劫持路由的方式来获得红包。 截至发稿，通过了第二关的 12 人共有 2 人通过第三关。红包原定发放 28 个，共 98 元，实际领取 1 个（有 1 人超时完成）。根据通过审批的提交时间与使用办公室 IP 第一次访问的时间差，两人分别用时 02:52:38、05:28:05。巧的是，这两人均来自清华大学。 后记 我自己对今年红包的设计和实现还是比较满意的，这是第一次以一个整体的形式呈现我的新年红包，其中包括了社工（搜索找到系统也算吧）、代码审计、XSS、路由劫持等思路。而且很接地气，就像五道口某学校一样，万呆呆表示： 只是红包的领取状况不容乐观，时间是很大一部分原因。这套系统我都设计了三天，24 个小时（囿于支付宝红包的限制）的游戏时间相比之下确实是短了点，况且虽然是过年，大家要出门玩啊要拜年啊，也没有这么空闲的。说难听点，这红包就这么两块钱（不考虑拼手气因素，平均下来通关是 6 元左右），也不是什么大奖，不太会安排在较高的优先级。 除了时间以外，红包数量也是一个问题。我不太能估计好总共应该发放多少个，因为不好确定究竟有多少人参与、有多少人成功。最好的办法应该是在结束后统一发放，支付宝红包口令的形式对于简单的小游戏来说还是挺好的，但是对于今年这样的就不适合了。其实这个问题在去年就已经显示出来了，因此我也在思考明年的游戏形式，希望能做到既要有参与感、也有一定的深度。 这个红包其实赶制得粗糙了点，一开始就修了一堆 bug，有兴趣可以看我 GitHub 仓库的 commit 记录，以后再出这样的题目是得先找人验题了。还有一个玩家在尝试第三关的时候，通过 Wireshark 发现了自己电脑上的异常流量，找到了 2 个恶意程序。虽然没领到红包，但也算是收获颇丰。 在我发布这篇文章之前，还有一个朋友通过了三关，虽然时间已经超过，但既然题解还没发布，便向其专门发放了。另外我把多出来的红包重新发了，在发稿之前过关的朋友，可以登录到平台查阅重新发放的红包口令。 这次红包的平台也会随着服务器的自然过期而倒闭，大家可以在半个月的时间内自由重新验证。 这篇题解除了需要连接现代版本 OpenVPN 和宣告路由的时候使用 SSH 连入了我的笔记本之外，均使用运行着 Windows NT 4.0 的 Intel Pentium 233MHz w/MMX + 64M EDO DRAM 内存的笔记本电脑完成。折腾这台电脑（即“老爷车”），是我红包咕咕咕的主要原因，我一直折腾它到大年廿九才导致红包来不及做。有关于折腾年前折腾这台笔记本的记录，我会把它另外写成一篇博文。 最后用一张现代浏览器的截图作结，上面都是十多年前的浏览器截的。 彩蛋红包 这次彩蛋红包和去年第三部分的解法一样，去年没人解出来，因此我重新在今年以彩蛋的形式再放送。相信只要翻阅过往年的题解的朋友都能做出来。 红包就是点击“开”后播放的音乐，解法就不赘述了，就是找出配和弦的音的简谱记法，感兴趣可以看去年的题解。今年只是谱子不一样： 输入“彩兔13443121”即可领取。原定使用 66.66 元发送 88 个，但只领走了 28 个。 关于这个彩蛋红包还有一个大锅：我一般都是提前把口令生成好写在 _idea.txt 文件里面。这次因为是直接复制了去年的文件夹，虽然我改了谱子，但是没有更新 _idea.txt，导致我错误地使用了去年的口令发送了红包，直到被人提醒才知道。有趣的是，正当我撤回发错的红包准备重新发的时候，发现正确的红包口令居然被人占用了：是一个玩家以“看看有多少人领”的红包说明发了一个正确的，令我哭笑不得。联系上他撤回红包后，发现红包口令释放也没有这么快，估计也得过一两天。于是只得把前缀从“兔”改为“彩兔”。