米米的博客

做了一点微小的工作

最近,笔者家中的一辆 2015 款奔驰 CLA200 汽车出现了「辅助蓄电池故障」的警告。咨询 4S 店后得知,这一故障是因为辅助蓄电池电压不足导致,通常是损坏了,需要更换。CLA200 的辅助蓄电池是一个单独的小电瓶,更换过程较为复杂。由于 4S 店报价较高,且电瓶没有现货需要等待预定,因此笔者决定自己动手更换。

辅助蓄电池故障警告

购买辅助蓄电池

通过咨询多家淘宝上售卖相关电瓶配件的店铺,得知需要更换的电瓶外观如下所示。这款电瓶售价普遍在 200 多元。笔者挑选了一家看上去比较靠谱的店铺进行购买,但是也没有很好的办法验证真伪,毕竟这个电瓶看上去就很容易用旧的翻新。只能希望不要出现安全隐患。

辅助蓄电池外观

更换过程

与其它一些奔驰汽车不同,CLA200 的辅助蓄电池位于副驾驶的脚底,而不是后备箱中。在更换时,需要先将副驾驶侧的地垫揭开。

揭开地垫

这时,地垫下面的情况就很清楚了,左上角部分的塑料保护盖下方就是辅助蓄电池,还可以看到两侧的接线端子。

辅助蓄电池位置

阅读全文 »

AdGuard Home 是一个自托管的广告拦截器,旨在 DNS 层面通过拦截跟踪域名和广告域名来提升用户隐私和网络体验。它可以运行在本地网络的网关或旁路由上,无需在每个终端设备上单独安装插件。AdGuard Home 提供了多个平台的二进制文件和安装指引,方便用户部署。

OpenWrt 默认的软件源提供了 AdGuard Home 的软件包,用户可以通过 opkg 安装。但是,要提升使用体验,还需要配合防火墙等设置。为了让 AdGuard Home 更好地融入 OpenWrt 的生态,社区开发了 LuCI 插件来提供图形化界面。

插件的发展历史

最早由 rufengsuixing 开发的 luci-app-adguardhome,在 LuCI 界面中实现了安装 / 更新核心、端口与重定向策略、日志查看、YAML 配置编辑、计划任务等选项,可一键进行部署。这一版本在 2020 年后停止了更新。
随后,社区分支由 kongfl888 延续维护,功能框架基本一致,持续发版至 2022 年,方便了众多固件集成打包。

我的 fork

我在 kongfl888 的基础上继续了 luci-app-adguardhome 的开发,主要聚焦以下改进:

  1. 兼容性与可视化
    将「重定向 53 端口」的功能直接通过系统防火墙进行配置,对 fw4 / nftables 友好,并可以支持 IPv6 重定向;更新界面布局、提示,让运行状态与问题原因更直观。

  2. 配置与运维体验
    将诸多的配置选项按照功能分页展示,默认配置更加精简和清晰;把「系统升级保留文件」的表单重构为更优雅的动态列表并兼容旧配置格式;清理历史遗留代码并进行重构。

  3. 账号与日志
    完善 Web 口令变更流程与提示,避免误操作;统一临时文件命名与读取游标;在 YAML 配置校验失败时给出更清晰的错误信息,便于排错。

目前在我的仓库 README 中,对上述功能与组合使用方式(例如与 OpenClash/代理类插件并用时的上游/重定向关系)也做了集中说明,并提供可直接安装的发布包。欢迎使用:stevenjoezhang/luci-app-adguardhome

笔者近期在 OpenWrt 路由器上配置了 Cloudflare IP 优选工具 luci-app-cloudflarespeedtest,但是在使用过程中发现还是有一些影响体验的小问题没有解决。由于原仓库已经被作者归档并只读,因此我 fork 了该项目,并在此基础上做了一些改进。
本文将介绍该工具的背景、功能以及我在 fork 版本上做的修改。我的仓库地址是:stevenjoezhang/luci-app-cloudflarespeedtest

CloudflareSpeedTest 是什么

CloudflareSpeedTest 的目标很直接:在 Cloudflare 公布的大量 IP 里,自动测试延迟与下载速度,筛出最适合当前网络的 IP(支持 IPv4/IPv6)。项目 README 也强调了「自选优选 IP」的定位:先测延迟/丢包,再做实际下载测速,最终输出可二次处理的结果列表,方便用脚本或其它工具继续接管。一种典型的应用场景是,配合 DNS 服务器或代理出站工具(如 SSR+、Passwall 等),检测到要访问的网站是托管在 Cloudflare 上的,就自动切换到最快的出口 IP。这样,就可以实现对相关网站访问的加速。

原版 LuCI 插件

mingxiaoyu/luci-app-cloudflarespeedtest 的功能是把上述测速流程放到 OpenWrt 路由器的 Web 界面(LuCI)里,支持可视化配置、Crontab 定时运行,并自动把优选 IP 回写到常见代理出站插件中等,以在 OpenWrt 上实现「按计划自动换最快出口」。该仓库已在 2024 年归档并只读,意味着作者不再维护。

我的 fork

我在原版停止维护后 fork 了一版,并围绕可视化、可用性、可维护性做了多处调整。下面对关键改动进行汇总说明。

体验与易用性

  • 展示图表 & 历史曲线:加入历史数据图表,将延迟 / 速度曲线以子图形式展现,便于对比多次测速的趋势与稳定性。
  • 状态与日志更清晰:优化状态展示与日志格式,告警 / 进度更直观,便于排错。
  • 界面细节优化:统一风格、更新按钮模板,减少表单噪音。
  • 核心下载支持:加入自动下载核心二进制的功能,减少依赖,方便直接以 ipk 软件包形式安装,降低首次部署门槛,让「开箱即用」更顺滑。

配置与参数

  • 参数名称规范化:对 speedhour 等参数进行重命名,使其更加贴近实际含义,避免混淆。
  • 选项梳理与文案更新:优化选项组织结构与默认值;改进多语言翻译与 README。

工程与维护

  • 依赖与结构:更新依赖、理顺目录与引用路径,方便二次开发与打包发布;同时优化基于 GitHub Actions 的 CI workflow,保证可重复构建。

结语

如果你已经在用 OpenWrt 做出站路由,不妨把 fork 版的 luci-app-cloudflarespeedtest 装起来跑一跑 —— 历史曲线、速度阈值与更清晰的日志,相信能帮你更快地找到更快更稳定的那一个 Cloudflare IP。

PatchGuard 是 Windows 内核中的防御机制,用于检查内核中关键的数据结构和代码是否被恶意软件或驱动程序篡改。如果发现异常,PatchGuard 会直接以错误码CRITICAL_STRUCTURE_CORRUPTION或者KERNEL_SECURITY_CHECK_FAILURE触发蓝屏。在对 Windows 内核进行安全研究时,如果需要对内核中的关键函数进行挂钩,就必须先关闭 PatchGuard,否则测试过程会受到影响。

目前,最为成熟的关闭 PatchGuard 的方法是使用EfiGuard。EfiGuard 是一个 EFI 固件,可以在系统启动时提前劫持 Windows Boot Manager,进而实现对 Windows 内核的动态修补,阻止 PatchGuard 初始化的相关代码执行。EfiGuard 有多种使用方法,以下进行详细说明。

下载 EfiGuard

EfiGuard 的二进制 EFI 文件可以直接从 GitHub 的 Release 页面上下载。解压后,你会得到Loader.efiEfiGuardDxe.efi两个文件。它们就是我们需要的 EFI 固件。接下来,将Loader.efi重命名为bootx64.efi,后续步骤我们也直接使用bootx64.efi文件。

准备环境

接下来,需要确保当前机器的 BIOS 中设置为通过 UEFI 启动,并且识别到了 Windows Boot Manager 启动项。通常情况下,全新安装的 Windows 系统都满足这一要求。然而,笔者在实验中也发现,一些笔记本预装的 Windows 系统的启动项中并不包含 Windows Boot Manager,而是只有 NVMe0 这样的磁盘。Windows 系统虽然可以启动,但是这时直接加载 EfiGuard 可能会导致启动失败。

不存在Windows Boot Manager

这时,需要手动修复启动项。比较简单的解决方案是使用 Dism++ 等工具修复引导。打开 Dism++,点击「恢复功能」中的「引导修复」,即可重新添加 Windows Boot Manager 启动项。完成后,可以重新启动,判断 BIOS 是否识别到了 Windows Boot Manager。

部署 EfiGuard

通过普通 U 盘启动

最简单的方法就是参考 EfiGuard 的文档,在 U 盘上创建/EFI/Boot/目录,然后将bootx64.efiEfiGuardDxe.efi两个文件复制到该目录中。完成后,将 U 盘插入待测试的电脑,然后选择通过 U 盘启动即可。如果一切正常,EfiGuard 会在启动时自动加载,并输出大量的日志(特征为绿色文字)。

通过 Ventoy 启动

Ventoy 也可以用于加载 EfiGuard。将bootx64.efiEfiGuardDxe.efi两个文件复制到安装了 Ventoy 的 U 盘根目录下。然后,重启电脑,选择通过 Ventoy 启动。进入 Ventoy 页面后,首先点击EfiGuardDxe.efi,它会自动加载驱动程序,并退回到 Ventoy 菜单。之后,再次点击bootx64.efi,EfiGuard 就会开始工作,完成系统启动并关闭 PatchGuard。

背景

Enigma 机是二战时德军使用的加密装置。它拥有三个可以转动的转子,一个固定的反射器,插线板,键盘,灯板等组件。插线板能够交换两个字母,转子可以将一个字母映射为另一个字母。这都是通过电路实现的。使用者点击键盘上的按键输入明文字母后,机器中电池产生的电流将通过插线板、三个转子、反射器,然后折返,再次通过三个转子和插线板,将灯板上的密文字母对应的灯泡点亮。每次按下按键时,转子会产生旋转,并且在到达特定位置时会产生进位。因此,每次加密时导通的电路会发生变化,使得 Enigma 机的加密过程难以被破解。Enigma 的实现和软件模拟器可以参考前面的文章自己打造一台恩尼格玛密码机

在二战后期,德军使用的 Enigma 机允许操作者从多个转子中选择 3 个,每个转子有 26 种初始位置,并且插线板上可以交换 10 组字母。这产生了一个巨大的密钥空间,看上去使 Enigma 机坚不可摧。然而,波兰数学家 Rejewski 在 30 年代就发现了 Enigma 机操作流程上存在的弱点,并针对其设计了破解方法。在二战爆发后,相关的技术被共享给了盟军的情报机构,但德军也加强了 Enigma 密码的安全性。英国数学家和计算机科学家 Turing 进一步地设计了已知明文攻击的方法,成功破解了 Enigma 机,为世界反法西斯战争的胜利做出了重要的贡献。笔者复现了 Rejewski 和 Turing 破解 Enigma 机使用的方法,并将在本文中详细介绍。

算法原理

Rejewski 的破解方法

在上世纪三十年代,Enigma 机的操作流程是:每天所有人都会使用相同的日密钥,但日密钥不用于加密信息;在发送一条信息时,操作者需要先随机生成三个字母的信息密钥。操作者会首先使用日密钥,将信息密钥输入两次,产生的 6 个字母的密文作为开头。随后,操作者将机器的转子调整为信息密钥所对应的转子位置,并开始加密信息。信息密钥输入两次的原因是为了避免信息传输中因为干扰出现错误,收信方如果发现通过日密钥解码出的前 6 个字母明文不是重复两次的格式,则可以发现问题。但是,重复是加密的敌人,Rejewski 敏锐地观察到了这一点。第一个字母和第四个字母是同一个明文加密出来的,并且输入第一个字母时,转子的设置都是日密钥,是完全相同的。于是,在某一天中收到的所有由 Enigma 机加密的密文,都会满足类似这样的规则:

  • 如果第 1 个字母为 A,那么第 4 个字母为 E;
  • 如果第 1 个字母为 B,那么第 4 个字母为 L;
  • 如果第 1 个字母为 C,那么第 4 个字母为 C;
ABCDEFGHIJKLMN
ELCONWDIAPKSZH

表:密文的第一个字母和第四个字母的对应表格(部分)

将结果列出,如上表所示。Rejewski 发现,表中的字母存在一些 “链”,例如,上一行的字母 A 对应下一行的字母 E,上一行的字母 E 对应下一行的字母 N,以此类推,最后又回到字母 A。这样就产生了字母链 AENHI。同时,也可以找到其它的字母链:BLSJP,C,DOFWVG,K,MZURTY,Q,X。如果机器的初始状态相同,那么得到的字母链自然也是相同的。更重要的是,Rejewski 发现,插线板的存在只会改变字母链中的字母,但不会影响各个字母链的长度。
例如,在上面的场景中,额外将 A 和 C 之间加入一条接线,那么,原先的 AENHI 链会变为 CENHI,C 链会变为 A 链;但两条链的长度是不变的。因此,字母链的长度特征是插线板作用下的不变量,它只和转子的初始位置有关,由此可以将插线板的作用和转子的初始位置解耦合。波兰的破译团队用了一年的时间进行预计算,得到了所有转子初始位置与字母链的长度的对应关系。此后,在收到新的密文后,破译者可以查表,将字母链的长度与预计算的结果进行比较,得到可能的转子的初始位置。在成功恢复出转子初始位置后,破译者可以对密文进行解密,之后进一步地分析插线板的状态。具体的算法原理如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
输入:前6个密文字母的对应表格 T
输出:解集 Solutions

预计算阶段:
----------------------------------------
# 由波兰 Bomba 机模拟的 Enigma 机
E ← Enigma()

# 存储的数据库,用于之后查表
Database ← Map()

for each S in PossibleSettings:
E.setPosition(S)
C ← FindChainsByEnigma(E)
Database.store(S, C)

破解过程:
----------------------------------------
Solutions ← Set()

for each S in PossibleSettings:
Matched ← true

for i ← 1 to 3:
Chain ← FindChainsInTableRow(T[i], T[i+3])
Expected ← Database.get(S + i)

if Chain ≠ Expected:
# 字母链没有通过检验
Matched ← false

if Matched = true:
# 当前初始状态对应的字母链通过了检验
# 将可能的解加入解集
Solutions.add(S)
阅读全文 »
0%