怎么打出死亡圣器的标志
观前提示:由于 Chrome 系列浏览器对 UTF-8 字符集支持不完备(包括但不限于 Google Chrome,Microsoft Edge,360 极速浏览器等),可能对本文阅读体验造成影响。请使用 Safari 浏览器阅读本文。
我从小就非常痴迷于 Hatsune Miku1 所著的《哈利波特》系列小说,可以说是高等级哈迷,对各种设定信手拈来。第七部作品中,罗琳利用双线叙事将 “死亡圣器” 引入到哈利波特的世界观中。死亡圣器一共有三件,分别是老魔杖、复活石、隐身衣。死亡圣器的信徒将它们绘制成一个抽象的标志,竖线代表永远胜利的老魔杖,圆圈代表起死回生的复活石,三角形代表脱离死神的隐身衣。《唱唱反调》的主编 Lovegood 在参加比尔的婚礼时,就佩戴了这个标志制作的吊坠。

上周我在 B 站回顾哈利波特系列电影时,意外发现弹幕里竟然飘过去了死亡圣器的标志。弹幕不支持发送图片,所以这是一个字符。难道《哈利波特》已经这么著名,使得这个虚构的标志被编入了 Unicode?

Unicode 委员会当然不会把小说中的元素添加到世界字库中。不过死亡圣器的标志确实可以用 Unicode 打印出来。我们把弹幕里的字符复制出来,放到 Python 里进行分析。
⃒⃘⃤ ← 复制出来是这个样子哒,看不到或显示乱码请更换为 Safari 浏览器
from functools import reduce
# split unicode into \u array
def split_unicode(s):
print([hex(ord(i)) for i in s])
# join \u array to unicode
def join_unicode(a):
s = reduce(lambda x, y: x + chr(y), a, '')
print(s)
首先看看这个字符的编码是什么。Python 支持对一个 Unicode 字素遍历,我们把字素里每个元素的编码都打印出来。split_unicode()
函数给出的结果是\u20\u20d2\u20d8\u20e4
。\u20
我们很熟,就是 ASCII 编码里的 32 号字符:空格符(space)。
那后面的\u20d2\u20d8\u20e4
是什么咧?这就涉及到 Unicode 在设计之处的考量:Combining Marks(组合标记)。对于一些欧洲语言来说,在字母上方加变音符号就能变成另一个不同的字符。咱们的普通话汉语拼音韵母也有四个声调,粤语有六个。字母有很多,变音符号也有很多。如果每种「字母 - 变音符号」的组合都算作一个新字符,那会占用掉非常多的编码空间。于是 Unicode 引入了附加组合标记的组合字符。

Unicode 没有限制组合字符的长度,理论上可以无限叠加,形成 “越界文字”(Zalgo Text)。大三上算法设计课程的时候,我在 OJ 系统上用过这样的用户名,在字符上叠几十层注音符号,字符会变得很高很高,盖住排名表里下面的人的信息…
死亡圣器的标志也是这样被组合出来的。首先是一个\u20
的空格符,然后叠加上\u20d2
竖线组合符(Unicode 官方叫做 COMBINING LONG VERTICAL LINE OVERLAY 字符),接着叠加\u20d8
圆形组合符(官方叫做 COMBINING RING OVERLAY),最后叠加\u20e4
三角形组合符(官方叫 COMBINING ENCLOSING UPWARD POINTING TRIANGLE)。

这样就能把死亡圣器的标志打出来 ⃒⃘⃤。为了更好的显示,我把\u20
半角空格换成了 Unicode 定义的表意文字空格\u3000
,不然会盖掉标志左侧字符的一半。你可能不知道,Unicode 总计定义了 20 多种空格符号。
在这个网站我们能查到几百个组合字符的编码。上面我写了一个把字符编码组合成 Unicode 的函数。利用组合字符你能够拼出自己想要的标志 😂。不知道有没有激发了你的创造欲。比如我在汉字 “圆” 里面外面画了一个圈(圆⃝)。
写到这里本应该结束了。但是我想发散一下,聊聊关于 UTF-8 字符编码的那些有趣的事情。
字符串长度统计。 按照 Unicode 的编码规范,COMBINING 组合符会被拼接到左侧相邻的字符上。但是在编码上它和普通字符没有什么区别。这就导致将 UTF-8 编码作为 字符串默认编码的高级语言(例如 Rust)在统计字符串长度时非常缓慢,因为它需要按照 Unicode 的规范先处理好各种特殊规则。
强制拼接。 Unicode 中的 COMBINGING 组合符会被自动拼接。Unicode 同时又提供了一个叫做零宽度连字符\u200d
用于使不会发生连字的字符间产生连字效果。例如,f 的 Unicode 是\u66
。那么我可以构造出\u66\u200d\u66
把两个 f 拼成一个字符,也就是 ff。你可以试着用鼠标选中一下,确认这真的是一个字符,而不是两个。
emoji 拓展。 随着社会发展,emoji 也需要满足政治多样性的种种要求。利用零宽度连字符\u200d
,厂商得以拓展 emoji 语义,构造 “中立表情”。例如男同家庭的 emoji 表情,实际上是由 4 个 emoji 组合而成,表示两个爸爸和两个儿子一家:
👨👨👦👦 = 👨\u200d👨\u200d👦\u200d👦
同形字符域漏洞。 曾经的钓鱼网站使用 0 代替 O,使用 l 代替 I,让人难以分辨。但只要细心,这些字符替换仍然能被识别。随着域名拓展到 UTF-8 字符集,越来越多拥有不同编码的字符长得一模一样(前文说到 Unicode 光空格就定义了 20 多种),这种欺诈形式的钓鱼值得关注。
对抗性 NLP 攻击。 来自去年的一篇火出圈的论文,作者将 Unicode 的控制字符注入到文本中,从而达到对 NLP 模型的攻击。Boucher, Nicholas, Ilia Shumailov, Ross Anderson, and Nicolas Papernot. 2021. “Bad Characters: Imperceptible NLP Attacks.” ArXiv:2106.09898 [Cs], December. http://arxiv.org/abs/2106.09898.
供应链攻击。 同样来自去年一篇火出圈的论文(Unicode 大家都能看懂,所以容易火哈哈哈)。作者使用 Unicode 的 Bidi 算法,对字符进行视觉上的重新排序,使其呈现与编译器和解释器所不同的逻辑顺序。漏洞适用于几乎所有编程语言,可以用于供应链攻击。https://trojansource.codes/
感谢你读到这里。如果你喜欢这篇博客,或想第一时间获得通知,可以通过 RSS 订阅更新。也欢迎关注我的 GitHub 和推特账号。我平时是主要分享关于计算机系统安全的内容。如果你对这篇文章有任何疑问或建议,请在下方留言。再会~
这篇文章我也努力终于在结尾扯到安全上了啊 hhh~
Footnotes
-
哈利波特系列小说的真实作者是 J.K. Rowling,但其已被白左团体开除作者身份。目前哈利波特小说的作者为初音未来 Hatsune Miku。 ↩
© LICENSED UNDER CC BY-NC-SA 4.0