记一次对某某通网页版字体的混淆加密分析

本文最后更新于 2026年6月29日 晚上

声明:本文仅用于学习与研究字体混淆的实现思路,严禁用于任何未授权的非法用途。

好久没发博客了,来水一篇~~~

前言

打开某某通网页做题的时候本来想美美地复制给 AI 大人答疑(bushi),结果发现复制出来是一串生僻字?打开网页源码发现对字符进行了加密(恕我孤陋寡闻了~)

加密分析

可以看到这个 div 元素使用的 font-cxsecret 字体,也就是它用于混淆显示内容的自定义字体。

1
<div class="clearfix font-cxsecret fontLabel" style="line-height: 1.5; font-size: 14px; padding-right: 15px;"tabindex="0" role="option">

搜索找到可以找到对应的字体声明。其本质上是把字体文件直接以内联 Base64 的方式放进 CSS 中(太多了省略了部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@font-face{
font-family:'font-cxsecret';
src:url('data:application/font-ttf;charset=utf-8;base64,AAEAAAAMAIAAAwBAQkFTRRuOGNgAAH88AAAA5E9TLzKUGwCtAAABSAAAAGBWT1JHUavDeAAAgCAAAAN0Y21hcJ99mAgAAALUAAADFGdseWYryQfIAAAIQAAAZqBoZWFkBmbCYQAAAMwAAAA2aGhlYQzu/tUAAAEEAAAAJGhtdHgSYA6nAAABqAAAASxsb2NhABskJAAABegAAAJYbWF4cAC/ASsAAAEoAAAAIG5hbWWAs0JrAABu4AAAEDlwb3N0/4YAMgAAfxwAAAAgAAEAAAABAAA+S/skXw889QADA+gAAAAAz87zegAAAADP+IsO/Bj76AtwBxAAAAADAAIAAQAAAAAAAQAAA3D/iAH0A+j8GPtjC...') format('truetype');
}

.font-cxsecret,
.font-cxsecret p,
.font-cxsecret div,
.font-cxsecret i,
.font-cxsecret em,
.font-cxsecret b,
.font-cxsecret strong,
.font-cxsecret a,
.font-cxsecret font,
.font-cxsecret span,
.font-cxsecret pre,
.font-cxsecret code{
font-family: 'font-cxsecret' !important;
}

解码之后保存为了 ttf 格式

找了个网站查看了一下,字体查看器和分析器 - 在线预览和检查字体,字体是 Source Han Sans CN Normal,版本 Version 1.000;PS 1;hotconv 1.0.78;makeotf.lib2.5.61930,看着是正常的,这些字符本身都属于合法 Unicode,但大多比较冷门。也就是说,它这里是挑了一批合法但很少见的 Unicode 字符,当成占位符塞进 HTML,再让自定义字体把这些占位符显示成真正想显示的文字。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
冩 U+51A9
孇 U+5B47
孈 U+5B48
孉 U+5B49
孊 U+5B4A
孋 U+5B4B
孌 U+5B4C
孍 U+5B4D
孎 U+5B4E
孏 U+5B4F
孞 U+5B5E
孠 U+5B60
孡 U+5B61
孢 U+5B62
孥 U+5B65
孧 U+5B67
孨 U+5B68
孫 U+5B6B
孬 U+5B6C
孭 U+5B6D
孮 U+5B6E
孯 U+5B6F
孰 U+5B70
孱 U+5B71
孲 U+5B72
孳 U+5B73
孴 U+5B74
孵 U+5B75
孶 U+5B76
孷 U+5B77
孹 U+5B79
孻 U+5B7B
孼 U+5B7C
孾 U+5B7E
孿 U+5B7F
宄 U+5B84
宆 U+5B86
宊 U+5B8A
宍 U+5B8D
宎 U+5B8E
宐 U+5B90
宑 U+5B91
宒 U+5B92
宓 U+5B93
宔 U+5B94
宖 U+5B96
実 U+5B9F
宥 U+5BA5
宧 U+5BA7
宨 U+5BA8
宩 U+5BA9
宬 U+5BAC
宭 U+5BAD
宯 U+5BAF
宱 U+5BB1
宲 U+5BB2
宷 U+5BB7
宸 U+5BB8
宺 U+5BBA
宻 U+5BBB
宼 U+5BBC
寀 U+5BC0
寁 U+5BC1
寃 U+5BC3
寈 U+5BC8
寉 U+5BC9
寊 U+5BCA
寋 U+5BCB
寍 U+5BCD
寎 U+5BCE
寏 U+5BCF
寑 U+5BD1
寔 U+5BD4
寕 U+5BD5
寖 U+5BD6
寗 U+5BD7
寘 U+5BD8
寙 U+5BD9
寚 U+5BDA
寜 U+5BDC
寠 U+5BE0
寡 U+5BE1
寣 U+5BE3
寥 U+5BE5
實 U+5BE6
寨 U+5BE8
寪 U+5BEA
寬 U+5BEC
寭 U+5BED
寮 U+5BEE
寯 U+5BEF
寱 U+5BF1
寲 U+5BF2
寴 U+5BF4
寵 U+5BF5
寶 U+5BF6
寷 U+5BF7
寽 U+5BFD
対 U+5BFE
尀 U+5C00
専 U+5C02
尃 U+5C03
尅 U+5C05
將 U+5C07
尌 U+5C0C
尐 U+5C10
尗 U+5C17
尛 U+5C1B
尜 U+5C1C
尞 U+5C1E
尟 U+5C1F
尠 U+5C20
尢 U+5C22
尣 U+5C23
尥 U+5C25
尦 U+5C26
尨 U+5C28
尩 U+5C29
尪 U+5C2A
尫 U+5C2B
尬 U+5C2C
尭 U+5C2D
尮 U+5C2E
尯 U+5C2F
尰 U+5C30
尲 U+5C32
尳 U+5C33
屆 U+5C46
屇 U+5C47
屈 U+5C48
屍 U+5C4D
屐 U+5C50
屑 U+5C51
屒 U+5C52
屓 U+5C53
屔 U+5C54
屖 U+5C56
屗 U+5C57
屘 U+5C58
屙 U+5C59
屟 U+5C5F
摶 U+6476
敩 U+6569
潯 U+6F6F
糵 U+7CF5
苧 U+82E7
谉 U+8C09
迉 U+8FC9

这些字本身都是真实存在的 Unicode 字符,所以浏览器渲染、复制、存储都不会报错。

逆向分析

没找到同版本的 TTF 文件字符集,用的同一个版本的 OTF 字符集,统一渲染成位图,再做批量相似度匹配(如果有 TTF 就不用这样)。

逆向思路很简单:

  1. 准备同版本完整字符集 SourceHanSansCN-Normal.otf
  2. 分别读取两份字体的 cmap
  3. 把混淆字符和完整字库里的所有候选字符都渲染成统一尺寸的黑白位图
  4. 用异或距离统计位图差异,给每个混淆字符找出最相近的候选

这里的核心思路是把字符统一转换成二值位图,再用 NumPy 统计当前混淆字符位图与参考字符集位图之间的逐像素异或差异,差异值越小就越接近:

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
def char_to_bitmap(
font: ImageFont.FreeTypeFont,
text: str,
canvas_size: int,
bitmap_size: int,
) -> np.ndarray:
canvas = Image.new("L", (canvas_size, canvas_size), 255)
draw = ImageDraw.Draw(canvas)
bbox = draw.textbbox((0, 0), text, font=font)
width = bbox[2] - bbox[0]
height = bbox[3] - bbox[1]
x = (canvas_size - width) // 2 - bbox[0]
y = (canvas_size - height) // 2 - bbox[1]
draw.text((x, y), text, font=font, fill=0)
bitmap = canvas.resize((bitmap_size, bitmap_size), Image.Resampling.LANCZOS)
data = np.asarray(bitmap, dtype=np.uint8)
return (data < 200).astype(np.uint8)

for fake_codepoint in sorted(download_ttfont["cmap"].getBestCmap()):
fake_char = chr(fake_codepoint)
fake_bitmap = char_to_bitmap(
download_font,
fake_char,
args.canvas_size,
args.bitmap_size,
)
distances = np.bitwise_xor(reference_bitmaps, fake_bitmap).sum(
axis=(1, 2),
dtype=np.uint32,
)
top_indices = np.argsort(distances)[: args.top_k]

跑出来结果如下,和在线网页转换的一样结果

替代字符 替代码点 第一候选 码点 距离 次佳距离
U+51A9 U+4E3A 46 120
U+5B47 U+8F91 62 222
U+5B48 U+8981 4 153
U+5B49 U+6BB5 19 241
U+5B4A U+4EC0 1 1
U+5B4B U+7684 33 190
U+5B4C U+903B 40 231
U+5B4D U+4E3B 33 135
U+5B4E U+53F7 6 147
U+5B4F U+786E 54 184
U+5B5E U+5B9A 28 109
U+5B60 U+5177 3 164
U+5B61 U+7EDC 9 178
U+5B62 U+4F53 17 118
U+5B65 U+8BBE 17 180
U+5B67 U+534F 27 226
U+5B68 U+5212 11 230
U+5B6B U+914D 14 219
U+5B6C U+8BA1 9 156
U+5B6D 线 U+7EBF 17 168
U+5B6E U+8FDB 31 110
U+5B6F U+884C 43 43
U+5B70 U+5E03 4 160
U+5B71 U+518C 9 220
U+5B72 U+7F16 38 131
U+5B73 U+5199 5 166
U+5B74 U+64CD 49 195
U+5B75 U+4F5C 3 94
U+5B76 U+7CFB 46 195
U+5B77 U+5DE5 46 46
U+5B79 U+2F00 0 0
U+5B7B U+7EDF 17 192
U+5B7C U+6210 14 90
U+5B7E U+7ED3 16 150
孿 U+5B7F U+67B6 16 178
U+5B84 U+FA02 16 16
U+5B86 U+6251 1 114
U+5B8A U+5546 19 224
U+5B8D U+5907 20 213
U+5B8E U+9009 28 187
U+5B90 U+5E94 63 149
U+5B91 U+62E9 35 129
U+5B92 U+4F9B 6 138
U+5B93 U+9700 7 164
U+5B94 U+6790 19 100
U+5B96 U+6C42 39 195
U+5B9F U+5236 27 249
U+5BA5 U+7B56 27 139
U+5BA7 U+5B89 34 178
U+5BA8 U+5168 12 148
U+5BA9 U+8303 2 177
U+5BAC U+7A0B 16 137
U+5BAD U+54EA 18 139
U+5BAF U+662F 2 168
U+5BB1 U+529F 16 168
U+5BB2 U+56E0 30 158
U+5BB7 U+5173 7 186
U+5BB8 U+7D20 17 180
U+5BBA U+6280 30 104
U+5BBB U+672F 5 89
U+5BBC U+6742 8 198
U+5BC0 U+65B9 7 7
U+5BC1 U+590D 37 163
U+5BC3 U+6848 4 217
U+5BC8 U+2F64 19 19
U+5BC9 U+2F3E 23 23
U+5BCA U+660E 39 121
U+5BCB U+54C1 3 210
U+5BCD U+4EBA 7 7
U+5BCE U+91CF 17 17
U+5BCF U+5458 4 174
U+5BD1 U+901A 16 167
U+5BD4 U+65BD 17 175
U+5BD5 U+6570 37 242
U+5BD6 U+952E 9 139
U+5BD7 U+5305 3 198
U+5BD8 U+62EC 19 95
U+5BD9 U+6CE2 10 141
U+5BDA U+4FE1 52 192
U+5BDC U+2F76 5 5
U+5BE0 U+5207 26 176
U+5BE1 U+8FDF 3 103
U+5BE3 U+8F93 21 200
U+5BE5 U+5EF6 23 147
U+5BE6 U+4F20 37 146
U+5BE8 U+7EA4 29 100
U+5BEA U+2F6C 1 1
U+5BEC U+4E2D 1 4
U+5BED U+8BE6 13 117
U+5BEE U+5F55 5 206
U+5BEF U+5206 20 191
U+5BF1 U+544A 37 105
U+5BF2 U+9A8C 48 203
U+5BF4 U+9879 19 182
U+5BF5 U+6536 17 178
U+5BF6 U+2F3F 32 32
U+5BF7 U+2F42 27 27
U+5BFD U+4EE5 14 222
U+5BFE U+5B9E 24 202
U+5C00 U+679C 32 104
U+5C02 U+6863 31 88
U+5C03 U+89C4 25 129
U+5C05 U+8BF4 43 140
U+5C07 U+4E66 4 231
U+5C0C U+4E0D 52 52
U+5C10 U+7BA1 9 182
U+5C17 U+8BB0 10 144
U+5C1B U+5C55 67 178
U+5C1C U+63D0 27 133
U+5C1E U+6301 8 94
U+5C1F U+56E2 16 140
U+5C20 U+4FC3 29 160
U+5C22 U+5EA6 31 190
U+5C23 U+961F 16 193
U+5C25 U+9AD8 51 51
U+5C26 U+578B 8 253
U+5C28 U+5178 12 246
U+5C29 U+673A 16 117
U+5C2A U+8D41 27 206
U+5C2B U+62DF 59 184
U+5C2C U+79DF 47 134
U+5C2D U+529E 16 240
U+5C2E U+8F6F 19 166
U+5C2F U+5982 23 232
U+5C30 U+516C 31 163
U+5C32 U+5728 49 165
U+5C33 U+4EF6 12 125
U+5C46 U+5E93 22 148
U+5C47 U+7406 16 16
U+5C48 U+636E 35 187
U+5C4D U+5217 33 226
U+5C50 U+8D22 51 156
U+5C51 U+52A1 10 218
U+5C52 U+9884 36 124
U+5C53 U+4EA7 4 180
U+5C54 U+6750 3 170
U+5C56 广 U+5E7F 4 4
U+5C57 U+88C5 18 102
U+5C58 U+5BA3 85 110
U+5C59 U+4E8E 32 88
U+5C5F U+8FC7 45 155
U+6476 U+683C 20 100
U+6569 U+2F79 3 3
U+6F6F U+62A5 37 189
U+7CF5 U+9636 49 145
U+82E7 U+5149 20 167
U+8C09 U+5230 5 201
U+8FC9 U+5C5E 18 140

这里的距离越小,说明形状越接近,像 冩 -> 为孉 -> 段孍 -> 主 这种,第一候选和第二候选差距比较明显,可信度就更高。

总结

其实原理很简单,某某通网页版的这层混淆本质上:

  1. 页面里放的是合法 Unicode 生僻字。
  2. 让它看起来正常的是内联的自定义字体。
  3. 只要拿到子集字体,再找到同版本完整字符集,就可以通过位图相似匹配把映射关系恢复出来。

说白了就是底层源码里塞的生僻字,渲染成了常见的字,复制粘贴的还是底层的生僻字。这种混淆防君子不防小人~(已学习到如何阴一手hhh),更多是为了干扰复制、搜索和简单脚本提取,不过只需要把“字符”和“字形”分开看就行了。做个作业可太难了~

还可以看看 52 的这篇文章 https://www.52pojie.cn/thread-1631357-1-1.html


记一次对某某通网页版字体的混淆加密分析
http://example.com/2026/06/29/记一次对超星学习通网页版字体的加密混淆分析/
作者
butt3rf1y
发布于
2026年6月29日
许可协议