Noroboto: Lying fonts and mitigation in Rust

Noroboto: Lying fonts and mitigation in Rust

Noroboto:欺骗性字体与 Rust 中的缓解方案

What if your font is lying to your AI? LegalTech’s Mythos Moment Modern legal tech stacks in 2026 are Rube Goldberg machines of open-source and proprietary products from Word to LibreOffice, to python-docx and PDFium, to tesseract, node.js and dozens of UI libraries like SuperDoc, PDF.js and Office.js. 如果你的字体在对你的人工智能撒谎会怎样?法律科技的神话时刻:2026 年的现代法律技术栈就像是鲁布·戈德堡机械(Rube Goldberg machines),由各种开源和专有产品拼凑而成,从 Word 到 LibreOffice,从 python-docx 和 PDFium,到 Tesseract、Node.js 以及数十种 UI 库(如 SuperDoc、PDF.js 和 Office.js)。

Through those pipelines are pushed artifacts of decades-old written specifications which span tens of thousands of pages. In addition to the venerated OSS parts of these stacks exist partial, proprietary implementations of these specs. Many of these have been spun up in the last year with the assistance of coding agents. Meanwhile even the oldest, grayest-beard OSS maintainers in the ecosystem complain of specification complexity. 在这些流水线中,传输着跨越数万页、拥有数十年历史的书面规范产物。除了这些技术栈中备受推崇的开源(OSS)部分外,还存在着对这些规范的部分专有实现。其中许多实现是在过去一年中借助编程智能体(coding agents)构建的。与此同时,生态系统中即使是最资深、最老练的开源维护者也在抱怨规范的复杂性。

What if an adversary were to try to take advantage of this complexity and the imperfections in these implementations? Could these imperfections be leveraged for a tactical legal advantage? I reached out to my friends at the LegalQuants and recruited a team to answer this question, and you can read the analysis of the “lexploit” discussed below and about our new “Red Team” mission here: link. 如果攻击者试图利用这种复杂性和实现中的缺陷会怎样?这些缺陷能否被利用来获取战术上的法律优势?我联系了 LegalQuants 的朋友们并组建了一个团队来回答这个问题。你可以在此链接阅读下文讨论的“法律漏洞”(lexploit)分析以及我们新的“红队”任务:[链接]。

Noroboto.ttf

Noroboto.ttf

The “noroboto.ttf” “lexploit” is straightforward: create a new malicious font definition which is embedded in a document according to the specification and lies about the Unicode representation of its glyphs. “noroboto.ttf”这种“法律漏洞”非常直观:创建一个新的恶意字体定义,根据规范将其嵌入文档中,并对其字形的 Unicode 表示进行伪造。

TrueType

TrueType

Among many other things, TrueType fonts like those distributed with Windows and macOS contain outlines and a cmap (or character map) which maps Unicode code points to these outlines. The Unicode specification which is huge. In addition to code points for scripts such as Latin and CJK, among many others, it also reserves ranges of code points for “private use”. 除其他功能外,像 Windows 和 macOS 分发的 TrueType 字体包含轮廓和 cmap(字符映射表),用于将 Unicode 码位映射到这些轮廓。Unicode 规范非常庞大。除了拉丁文、中日韩(CJK)等脚本的码位外,它还为“私有使用”保留了一系列码位。

The simplest “full obfuscation” noroboto attack works by swapping valid Unicode-encoded scripts in the subject document with Unicode code points occupying these so-called “Private Use Areas” of Unicode. These glyphs typically render as “tofu” or some other unknown glyph in most graphical applications, or as a glyph from a fallback font definition as determined by such applications. 最简单的“完全混淆” Noroboto 攻击方式是:将目标文档中有效的 Unicode 编码脚本替换为占用 Unicode 所谓“私有使用区”(PUA)的码位。这些字形在大多数图形应用程序中通常显示为“豆腐块”(tofu)或其他未知字形,或者显示为应用程序所确定的回退字体定义中的字形。

For “PUA” code points LibreOffice, for example, seems to fallback to Wingdings. But noroboto provides a glyph for these PUA code points. And those glyphs are metric compatible with the replaced font. Their underlying Unicode mapping, however, is incomprehensible garbage. 例如,对于“PUA”码位,LibreOffice 似乎会回退到 Wingdings 字体。但 Noroboto 为这些 PUA 码位提供了字形,且这些字形与被替换的字体在度量上是兼容的。然而,它们底层的 Unicode 映射却是无法理解的乱码。

This only works because the Word and PDF specifications allow for font definitions to be embedded in their containing documents. Embedding fonts is critical to maintain compatibility and pixel-tight rendering across platforms. And consistent rendering is especially important in legal documents where font metrics determine page layout and pagination, and page numbers can have legal meaning. 这之所以可行,是因为 Word 和 PDF 规范允许将字体定义嵌入到文档中。嵌入字体对于保持跨平台的兼容性和像素级精确渲染至关重要。而一致的渲染在法律文档中尤为重要,因为字体度量决定了页面布局和分页,而页码可能具有法律意义。

Noroboto.py

Noroboto.py

With the help of ChatGPT 5.4 we had a proof-of-concept for full obfuscation within a few hours. On the left of the above GIF is what the user sees. When the text is copied and pasted, however, you get the Unicode representation in an arbitrary non-Noroboto font. It’s garbage. 在 ChatGPT 5.4 的帮助下,我们在几小时内就完成了完全混淆的概念验证。上述 GIF 左侧是用户看到的内容。然而,当文本被复制粘贴时,你会得到一个任意非 Noroboto 字体下的 Unicode 表示。那是一堆乱码。

We opted for Python to maximize legibility, but that somewhat backfired given the “vibes heavy” implementation. 我们选择 Python 是为了最大化代码的可读性,但考虑到这种“全凭感觉”的实现方式,这反而适得其反。

Testing

测试

An early testing against a version which leveraged a 1-to-1 mapping was defeated by ChatGPT 5.5 in Codex using “high effort”. ChatGPT 5.5 deobfuscated in two ways. First, given the simple PUA-to-glyph deterministic mapping, ChatGPT 5.5 treated deobfuscation as a basic cryptoanalysis exercise. It sussed out our “monoalphabetic” cipher and broke our “simple substitution cipher with side channels left intact”. 针对一个利用 1 对 1 映射的版本进行的早期测试,被 ChatGPT 5.5(在 Codex 中)以“高强度”破解。ChatGPT 5.5 通过两种方式进行了去混淆。首先,鉴于简单的 PUA 到字形的确定性映射,ChatGPT 5.5 将去混淆视为一种基本的密码分析练习。它识破了我们的“单字母”密码,并破解了我们“保留了侧信道的简单替换密码”。

ChatGPT’s second approach was to note that we had erroneously left the original “name” value in the glyph definition which could be reverted by reading the TTF. ChatGPT 的第二种方法是注意到我们错误地在字形定义中留下了原始的“名称”值,这可以通过读取 TTF 文件来还原。

Time to pull out the big guns: Polyalphabetic cipher. We updated noroboto.py in this commit to exclude that “name” field and in this commit to include a 4-to-1 mapping which is randomly applied by the text replacement algorithm. We also perturb the font slightly across the four separate PUAs to avoid comparing the outlines and collapsing them back to a 1-to-1 mapping. 是时候祭出大杀器了:多表密码(Polyalphabetic cipher)。我们在这次提交中更新了 noroboto.py 以排除该“名称”字段,并在这次提交中加入了一个由文本替换算法随机应用的 4 对 1 映射。我们还在四个独立的 PUA 之间对字体进行了轻微扰动,以避免通过比较轮廓将其坍缩回 1 对 1 映射。

Although these changes have limitations, they seemed to supply enough stochasticity to throw off ChatGPT’s simple cipher. But the frontier models in agentic harnesses with their inference-time computing modes enabled (aka “thinking”) all still manage to crack the “full” obfuscation document by shelling out to something, rendering the document and OCR’ing that result. 尽管这些更改有局限性,但它们似乎提供了足够的随机性来干扰 ChatGPT 的简单密码。然而,处于智能体框架下并启用了推理时计算模式(即“思考”)的前沿模型,仍然能够通过调用外部工具、渲染文档并对结果进行 OCR 来破解“完全”混淆的文档。

It turns out obfuscating the entire document is enough signal to encourage these LLMs to try different approaches. 事实证明,混淆整个文档足以作为一种信号,促使这些大语言模型尝试不同的破解方法。

Extensions: Partial Obfuscation and Replacement

扩展:部分混淆与替换

It turns out, agents are somewhat lazy. Thus, if they are presented with what appears to be a document containing legible Unicode code points, they often take that apparent happy path. Total obfuscation fails this test in the smartest models, but even the best are fooled when a document is only partially obfuscated or the text of the document is replaced. 事实证明,智能体有点懒。因此,如果向它们展示一份看起来包含可读 Unicode 码位的文档,它们通常会选择那条显而易见的“快乐路径”。完全混淆在最聪明的模型面前会失效,但即使是最好的模型,在文档仅被部分混淆或文本被替换时也会被愚弄。

Partial Obfuscation

部分混淆

What’s the point of partially obfuscating a legal document? The most obvious case is to just disguise an adversarial term with a higher probability of success. In testing our partial obfuscation example, we hid the fact that the NDA’s confidentiality terms carry on to “successors and assigns”. 部分混淆法律文档的意义何在?最明显的例子就是掩盖一个对抗性条款,以提高成功率。在测试我们的部分混淆示例时,我们隐藏了保密协议(NDA)的保密条款适用于“继任者和受让人”这一事实。