脚步声在走廊尽头停住了。
陈默的手指悬在 ThinkPad 键盘上方,没有动。程思语的呼吸压在嗓子眼里,应急灯的绿色指示灯是机房里唯一的光源。
十秒。
二十秒。
然后——一声猫叫。
在空荡荡的走廊里拖了个长音,接着是爪子踩碎水泥地上落叶的碎响。
程思语整个人松下来,手里的电源线掉在桌上。
「废弃八年的科学岛…」她低声说,「现在有猫了。」
陈默没有笑。他重新点亮屏幕,光标停在 `patch_pass_block` 函数的最后一行。
「如果进来的不只是猫。」他说。
程思语从包里掏出一个手掌大的设备——USB 隔离卡,军工级,物理断开除指定通信线路外的一切连接。她把卡插进 ThinkPad 唯一的 USB 口。
「从现在开始,这台机器只走壳公司的专线。」她说,「物理层隔离。就算我爸的人摸到这儿,也只能看到一台关了机的路由器。」
陈默点了点头。他们还有不到两天。
他重新打开 `build_raise_wrapper` 的端到端测试结果。
「一个函数。」他说。
「是。」程思语站到他身后。
「还有两万七千个。」
陈默把编辑器的窗口拆分。左边是 `patch_pass_block`,右边是新文件——`raise_importer.py`。
「一个一个改,时间不够。但如果我们从源头下手——」
他敲下第一行:
```python
import sys
import importlib.abc
import importlib.util
```
「Python 每次 `import` 一个模块,都经过 `sys.meta_path`。」陈默边说边敲,「里面默认有三个 Finder:`BuiltinImporter`、`FrozenImporter`、`PathFinder`。它们决定去哪里找模块、怎么加载。」
「我们要加第四个。」
```python
class RaiseImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
"""自定义导入器:拦截每条 import,检查异常处理完整性。"""
```
程思语看着屏幕:「它会在 `import` 的时候自动应用 RaiseGuard?」
「对。任何模块一被加载——不管是神码 AI 生成的还是人写的——经过 `RaiseImporter` 手里走一圈,所有 `except: pass` 都会被替换。用户什么都不用改。」
他继续敲:
```python
def find_spec(self, fullname, path, target=None):
"""拦截所有非标准库的导入。"""
# 跳过标准库 —— 它们不会从神码 AI 来
if fullname.startswith(('_', 'importlib', 'sys', 'os')):
return None
# 尝试用默认的 PathFinder 找到这个模块
try:
spec = importlib.util.find_spec(fullname)
except (ModuleNotFoundError, ValueError):
return None
if spec is None:
return None
# 劫持 Loader,插入我们的验证逻辑
spec.loader = self
return spec
```
程思语皱眉:「你替换了 `spec.loader`,但 `create_module` 和 `exec_module` 的默认行为呢?」
「所以要重写。」
陈默另起一段:
```python
def create_module(self, spec):
"""让 Python 使用默认的模块创建方式。"""
return None # None 表示让 importlib 自己创建模块对象
def exec_module(self, module):
"""执行模块前,先扫描并修复其所有函数。"""
# 1. 遍历模块的所有属性
for name in dir(module):
obj = getattr(module, name)
# 2. 找到函数/方法
if isinstance(obj, types.FunctionType):
# 3. 应用 RaiseGuard
repaired = guard.repair(obj)
if repaired is not obj:
setattr(module, name, repaired)
# 对于模块内部的嵌套导入,需要递归处理
self._recurse_module(module)
```
程思语指了指 `guard.repair`:「`types.CodeType` 构造函数的参数——你有把握在任意函数上都能正常工作?」
陈默没有回答。他打开目录 `/lib/shenma/core`——一份从壳公司内网拖下来的神码 AI 金融核心模块。
他随手挑了一个文件:`settlement_engine.py`。
十六个函数,七千行代码。
「试试就知道了。」
他启动了一个干净的 Python 子进程:
```python
import sys
sys.meta_path.insert(0, RaiseImporter())
# 导入一个神码 AI 模块
from lib.shenma.core import settlement_engine
# 触发一个已知的静默吞异常场景
result = settlement_engine.process_transfer(
sender='TEST001',
receiver='TEST002',
amount=Decimal('0.00')
)
```
程思语看着屏幕:「零元转账——边界条件,AI 最喜欢在这里用 `except: pass`。」
陈默按回车。
终端输出:
```
╭─ 导入报告 ─────────────────────────╮
│ settlement_engine.py │
│ 函数检查: 16/16 │
│ 修复: 4 │
│ 未修复: 0 │
│ 异常传播: 已验证 │
╰─────────────────────────────────────╯
```
四行修复。零异常被吞。
陈默和程思语同时盯着那行「未修复: 0」,谁都没说话。
然后程思语靠回椅背:「两万七千个函数,变成了一个 `sys.meta_path.insert`。」
「理论上。」陈默说。
「实践上呢?」
陈默没有回答。他写了一段更彻底的测试——批量加载神码 AI 核心模块下的全部 127 个 `.py` 文件:
```python
import os
import importlib
modules = []
root = '/lib/shenma/core'
for f in os.listdir(root):
if f.endswith('.py') and not f.startswith('_'):
name = f.replace('.py', '')
mod = importlib.import_module(f'lib.shenma.core.{name}')
modules.append(mod)
# 汇总验证
total_fixed = sum(len(getattr(m, '_raiseguard_fixed', [])) for m in modules)
print(f"模块数: {len(modules)}")
print(f"共计修复函数: {total_fixed}")
```
他按下回车。这次等了四秒——127 个模块的加载和扫描需要时间。
输出:
```
模块数: 127
共计修复函数: 412
```
陈默盯着数字。
「四百一十二个空 `except`。在这个目录里。」
程思语的声音很轻:「全行业都说神码 AI 写的代码是行业标准。」
「行业标准的标准差是零。」陈默说,「因为没有人测过——或者测了,但异常被吞了,所以看不到。」
他正要继续写下一步的测试,终端弹出一条新的输出:
```
⚠ 警告: settlement_engine.verify_report — 尝试修复失败
原因: 代码对象包含不受支持的 flags (CO_VARARGS+CO_KWONLY)
```
程思语的头探过来:「没有全部成功?」
陈默打开 `verify_report` 的反汇编:
```python
import dis
dis.dis(settlement_engine.verify_report)
```
输出让他的手指停在键盘上:
```
0 LOAD_GLOBAL check_consensus
2 CALL_FUNCTION 0
4 POP_JUMP_IF_TRUE 20
6 LOAD_FAST timeout
8 LOAD_CONST None
10 COMPARE_OP ==
12 POP_JUMP_IF_FALSE 20
14 LOAD_CONST True
16 RETURN_VALUE
18 ...
20 LOAD_FAST data
22 SETUP_FINALLY 34 ← 这条指令有问题
24 LOAD_GLOBAL process
26 CALL_FUNCTION 0
28 POP_TOP
30 POP_BLOCK
32 LOAD_CONST None
34 RETURN_VALUE
...
```
陈默把反汇编逐行看过,然后笑了。
「不是修不了。」他说,「是 `verify_report` 根本就没有 `except` 块——它是一个用了 `SETUP_FINALLY` 做早期返回的优化写法,不是异常处理。」
「所以?」程思语问。
「所以误报。」陈默在 `RaiseImporter` 里加了一行过滤:
```python
# 如果函数体里没有任何 except handler,跳过
if not _has_except_handler(code_obj):
continue
```
重新运行。误报那行消失。修复总数还是 412。
「干。」陈默说,「四百一十二个货真价实的 `except: pass`。」
程思语的手机在这时候震了。
她低头看了一眼。
「找到地址了。」她抬起头,「我爸的人查到了这家壳公司的注册地。」
「还有多远?」
「壳公司在深圳前海。他们坐最早的航班飞深圳,再从深圳转过去——大概明天下午到。」
陈默看了一眼时间:凌晨 3:47。
「十四小时。」
「不到十四小时。」程思语说,「他们可能在飞机上也在远程操作。」
陈默没有慌。他打开一个新终端。
「那我们就用这十四小时做一件事——把 `RaiseImporter` 做成一个可以分发的包。一个 `pip install` 就能让全世界的 Python 应用自动免疫 `except: pass`。」
他敲下 `setup.py` 的骨架:
```python
from setuptools import setup
setup(
name='raiseguard',
version='0.1.0',
py_modules=['raise_importer'],
python_requires='>=3.8',
)
```
然后是 `raise_importer.py` 的完整版——包含激活接口:
```python
def activate():
"""激活全局导入拦截。只需要在一处调用。"""
import sys
importer = RaiseImporter()
sys.meta_path.insert(0, importer)
return importer
```
陈默把这三行写完,正要打包,程思语忽然按住他的手。
「等一下。」
「怎么?」
「你把它做成 `pip` 包,分发出去——神码的人也会下载。他们一看就知道怎么绕过。」
陈默的手指停在键盘上。
她说得对。开源是双刃剑——你曝光漏洞的同时,也让漏洞的作者看到了你的反制手段。
「那就只发编译后的版本。」他说。
「编译?`.pyc`?」
「不。」陈默打开 `distutils`,「用 Cython 编译成`.so`。」
```python
# setup.py
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize(['raise_importer.py']),
)
```
程思语盯着 Cython 那行:「编译成二进制——反编译难度指数级上升。」
「而且标准 Cython 编译会抹掉调试符号。」陈默补充,「三周内逆向不出来。三周后——」
「三周后我们不需要它了。」
他们之间没有多余的对话。陈默敲下 `python setup.py build_ext --inplace`——
编译只用了七秒。
目录下多了一个 `.so` 文件:`raise_importer.cpython-311-x86_64-linux-gnu.so`。
程思语把 `.so` 文件拷贝到一个 U 盘里。
「一个 U 盘,四百一十二个修复。」她说。
「还不够。」
陈默把 U 盘插回电脑,打开了一个更深的目录。
「神码 AI 的核心不全是 Python。你用 `dis` 看过的那个 `settlement_engine`——」
他用 `file` 命令检查:
```bash
file /lib/shenma/core/_settle_core.cpython-311-x86_64-linux-gnu.so
```
输出:
```
ELF 64-bit LSB shared object, x86-64
```
程思语的脸色变了。
「C 扩展。」她说。
「C 扩展。」陈默重复,「`.py` 文件我们能用 `sys.meta_path` 拦截。但 `.so` 文件——Python 的 import hook 只能看到 .py 的模块。C 扩展的代码是直接通过 `ctypes` 或 `PyInit` 函数加载的,不走 Python 的 import 流程。」
程思语沉默了几秒。
「那 `RaiseGuard` 碰不到 C 扩展的代码?」
「碰不到。」陈默说,「而神码 AI 最核心的高频交易结算、医疗影像处理——都是 C 扩展。`except: pass` 在那里面是硬编译的 C 代码。」
程思语盯着屏幕上的 `.so` 文件,视线一寸一寸地扫过 ELF 文件头的十六进制。
「所以我爸——」她缓缓说,「他知道你早晚会发现 Python 层面的毒。所以他提前把最关键的代码编译成 C。」
「防的就是我这个层面。」
两个人同时沉默。
机房的通风管道传来风声,像什么东西在地下深处叹气。
然后陈默开口了:
「但 C 扩展也是 `dlopen` 加载的。」
程思语抬起头:「你想劫持 `dlopen`?」
「不。」陈默把编辑器拉到最大,「Python 加载 C 扩展的入口点是一个叫 `_bootstrap_external` 的内部模块。你在 Python 源码里能找到它——」
他打开 CPython 的源码目录:
```python
# Lib/importlib/_bootstrap_external.py
class ExtensionFileLoader(importlib.abc.Loader):
def create_module(self, spec):
"""从共享库创建模块。"""
# ...
module = _call_with_frames_cleaned_up(
_exec, spec.name, self.path)
```
「要改 CPython 源码?」程思语说,「重新编译解释器?」
「不用。」陈默说,「你注意到 `_exec` 前面没有下划线前缀保护了吗?」
程思语凑近屏幕。
「这个函数——`importlib._bootstrap._exec`——它在 `sys.modules` 里。我们可以把整个 `ExtensionFileLoader.create_module` 替换掉。」
陈默敲下一段代码,手指比之前快了一倍:
```python
import importlib._bootstrap
import importlib._bootstrap_external
# 保存原始加载器
_original_loader = importlib._bootstrap_external.ExtensionFileLoader
# 创建一个包装加载器
class MonitoredExtensionLoader(_original_loader):
def create_module(self, spec):
"""创建模块前进行安全检查。"""
module = super().create_module(spec)
# 如果模块里有 Python 可调用的部分,检查它
if hasattr(module, '__init__'):
# 递归检查所有可调用属性
self._check_module(module)
return module
def _check_module(self, obj, path=''):
"""遍历 C 扩展模块的所有 Python 可见属性。"""
for name in dir(obj):
try:
attr = getattr(obj, name)
except Exception:
continue
if isinstance(attr, types.FunctionType):
# 即使是 C 扩展暴露的 Python 函数包装,也可以检查
code = attr.__code__
# 检查字节码中是否缺少 RAISE_VARARGS
if b'\x82\x01' not in code.co_code:
# 这是一个潜在风险 —— C 扩展内部无法触及
self._warn(f"{path}.{name}: C 扩展内部异常处理未知")
```
程思语看着那段代码说:「你在做的是——检测边界,但真正的问题在边界里面。」
「对。」陈默放下手,「Python 层面的 import hook 能劫持 `.py` 文件的加载,能包装 C 扩展暴露的接口。但 C 扩展内部的函数——那些直接用 C 写的 `try-except` 逻辑——我已经碰不到了。」
程思语沉默了很久。
然后她说:「所以 `except: pass` 的最底层,我们改不了。」
「用 Python 改不了。」
「你要用 C?」
「不。」陈默把屏幕关掉,转向程思语,「我要用你。」
程思语怔了一下:「我?」
「你爸在 C 扩展里下毒,是因为他以为只有编译器层面的攻防。」陈默说,「他没想到他的女儿会站在另一边。」
程思语的手机又震了。她没看。
「神码 AI 的 C 扩展签名证书——」陈默顿了顿,「你拿得到吗?」
程思语的表情从困惑变成了理解的寒光。
「你想重新编译 C 扩展。」
「不重新编译。」陈默说,「是重新签名。你用你爸的证书签一份补丁过的 `.so`,替换原文件——系统会以为它是官方更新。」
程思语拿着手机的手垂了下去。手机屏幕亮着,那条未读消息显示着航班信息——深圳湾,明天 14:30 落地。
「签证书需要从他的个人保险柜里拿。」她说,「那个保险柜在我妈生前的书房里。」
「你进得去吗?」
程思语没有回答。她按灭了手机屏幕。
「我六岁那年,他在书房里写第一版交易引擎。」她说,「我在他脚边拼乐高。保险柜的密码——用的就是我生日。」
陈默没有接话。
程思语站起来,从包里拿出一把老式 U 盘钥匙扣——磨得发白的金属环上挂着三把钥匙。
「我回一趟程家老宅。」
「安全吗?」
「不安全。但你还剩十三个半小时。」
她走到机房门口,回头看了一眼。
「别等我回来才跑测试。你一边打包 `raiseguard`,一边等我消息。」
「如果你没回来呢?」
程思语在门口站了两秒。应急灯光在她脸上切出明暗分明的界线。
「那你就在 `except` 块里——替我 `raise`。」
门在身后关上。脚步声顺着走廊远去,越来越轻。然后是铁门合拢的闷响。
机房里只剩下机柜的嗡鸣和 ThinkPad 风扇的轻响。
陈默一个人坐在三面空机架中间。
他转回屏幕。
光标在 `MonitoredExtensionLoader` 的最后一行闪烁。
他删掉了那行代码。然后重新敲下三行:
```python
def activate():
import sys
sys.meta_path.insert(0, RaiseImporter())
sys.meta_path.append(MonitoredExtensionLoader)
print("RaiseGuard active. 0% silent failures.")
```
他盯着最后那行 `0% silent failures`。
然后按下了回车。
终端输出一个干净的提示符,没有任何报错。
但程思语的脚步声已经消失在走廊尽头,而她的手机——她说她爸的人查到壳公司地址的时候——那架飞往深圳的航班是明天下午。
从深圳到合肥,高铁只要三小时。
陈默看了一眼电量:73%。
他又看了一眼手机:没有新消息。
他把 `raiseguard` 的最后一个验证跑完——加载整个神码 AI 核心运行时,激活 `RaiseImporter`,执行全部边界测试用例。
屏幕输出:
```
╭─────────────────────────────────────╮
│ RaiseGuard 全局验证报告 │
├─────────────────────────────────────┤
│ Python 模块扫描: 127 │
│ 修复函数: 412 │
│ C 扩展模块检查: 23 │
│ 可监控接口: 89 │
│ C 内部不可达: 13 (已标记) │
│ 遗漏率: 0.3% │
│ 状态: ████████████▉ 99.7% │
╰─────────────────────────────────────╯
```
99.7%。
四百一十二个 `except: pass` 被修复。
还有十三个无法触及的 C 扩展内部路径。
陈默关掉终端。把 `raiseguard` 的 `.so` 文件复制到三块不同型号的 U 盘里,又从不同的端口上传到三个海外网盘。分散存储。
然后他靠在椅背上,闭上眼。
风扇声。
呼吸声。
走廊——没有脚步声。
没有消息。
他拿起手机,给程思语发了一行:
```
.activate()
已就绪。
```
消息发出去。没有已读回执。
他合上屏幕,把 ThinkPad 抱在胸前,在应急灯的微光里,听着走廊的寂静。
不——不完全是寂静。
远处,某扇铁门轻轻合拢,一声极轻的金属碰撞。
像是有人进了科学岛的主楼。
又像是风。
也可能是猫。