Captain不是船长
2023/05/18阅读:15主题:橙心
财务说
本文由 简悦 SimpRead 转码, 原文地址 juejin.cn
昨天一个后端朋友给我发了一张截图,问我这个 2023 年河南省大学生国家安全知识竞赛网页[1] f12 内 DOM 节点显示韩文,但是页面显示是中文,复制出来的结果是韩文,问我怎么回事。我也是第一次见这种情况,便研究了起来。本文主要记录我调试和发现答案的流程,涉及两部分,一个是该网页字体解密,一个是 JSON 数据 AES 解密
刚写完这篇文章,刷新就打不开这个网页了,发现竞赛学习时间过了🥲
网页字体解密
注意:以下截图均为 github 链接,不用 vpn 很大可能打不开
定位原因
通过调试发现,这类字体都有. secret 类名,该类名指定使用一个名叫 ddjdt 的 font-family,去掉该 font-family 属性页面正常显示为韩文,因此确定是字体原因
寻找资料 了解原理
在网络上搜索字体加密有很多文档,大部分都是拿猫眼的网页当做例子,但是由于每个网站的加密方式不一样,所以那些教程不一定适用于该网页,但是整体的流程大概明白了。
简单来说字体加密主要是为了反爬虫和防复制,比如猫眼这些网站就是为了防止别人爬数据,对于这个知识竞赛网页主要是为了防止复制搜答案。
对于汉字 我 来说,他可以有宋体、黑体、微软雅黑等不同的字形展示,但是他们对应的 Unicode 编码是同一个,同理也可以创造一个新的字体库叫 animal,把 我 对应的字形描绘成老虎,这样浏览器在碰到 我 的 Unicode 时,就会把它渲染成老虎的样式。字体库内部使用 TTGlyph 来存储字形轮廓信息,它和 SVG 一样都是矢量图形格式。
所以该网页 DOM 内部是韩文,这些韩文的 Unicode 编码对应的字形实际上是微软雅黑或者其他字体库的汉字字形 TTGlyph,导致页面渲染出来是汉字。
字体库 xml 分析验证
打开 css 发现该字体使用的是 2023gjaqzsjs.haedu.cn/fonts/wryh2…[2] 这个字体文件,下载文件,然后用 python 把 ttf 转换成 xml 文件看到字体库内部结构,代码如下
from fontTools.ttLib import TTFont, TTCollection
# 某些字体库是集合,TTFont会报错,需要用TTCollection
font = TTFont("wryh2.ttf")
font.saveXML("wryh2.xml")
解析后的 xml 文件首先看到的是 GlyphOrder,内部有很多 GlyphID
<GlyphOrder>
<GlyphID id="0" />
<GlyphID id="1" />
<GlyphID id="2" />
...
</GlyphOrder>
搜索 glyph00002,可以看到对应一个 Unicode 编码 0xb460
<cmap_format_4 platformID="0" platEncID="3" language="0">
...
<map code="0xb45c" /><!-- HANGUL SYLLABLE DULS -->
<map code="0xb460" /><!-- HANGUL SYLLABLE DUM -->
...
</cmap_format_4>
转换 0xb460 打印出来是韩文,也证实了上面的说法
String.fromCharCode('0xb460') // '둠'
同时与 glyph00002 相关的还有这个 TTGlyph,也就是它的字形文件了
<TTGlyph >
<contour>
<pt x="1077" y="0" on="1"/>
<pt x="189" y="0" on="1"/>
<pt x="189" y="169" on="1"/>
<pt x="536" y="169" on="1"/>
<pt x="536" y="1345" on="1"/>
<pt x="180" y="1242" on="1"/>
<pt x="180" y="1422" on="1"/>
<pt x="731" y="1582" on="1"/>
<pt x="731" y="169" on="1"/>
<pt x="1077" y="169" on="1"/>
</contour>
<instructions/>
</TTGlyph>
操作、预览字体库
在查找的文档中,很多人说可以用 FontCreator 加载字体库,但是它只适用于 windows,并且有试用期,故放弃。问了 chatGPT 后说可以用 BirdFont 和 FontDrop,
-
BirdFont[3]:免费,有 mac 客户端,可以根据 Unicode 搜索 -
FontDrop[4]:免费,网页端,没有搜索功能 -
百度字体编辑器[5]:免费,网页端,可以根据 Unicode 搜索 (吐个槽,百度好像把这个产品下架了,这个链接还是 github 的)
我使用的是 FontDrop,很方便,把字体文件拖进来就能解析,可以看到字体名称和字体作者等信息 下面是字体展示,鼠标滑过会展示该字形的 Unicode
分析 DOM 结构,获取全部字形的 Unicode
[...document.querySelector('#glyph-list-end').children].forEach(item => {
const regex = /Unicode:\s+(\w+)/;
const match = item.children[0].textContent.match(regex);
if (match) {
const unicode = match[1];
console.log(unicode); // 输出: BFEE
} else {
console.log('未找到匹配的内容');
}
})
执行结果如下 emmmm。竟然还有未找到匹配的内容,调试后发现该字形的 Unicode 是鼠标滑过字形后动态插入到 DOM 的,鼠标没有滑过的字形就是未找到
简单,给全部字形节点加 mouseover 事件,手动触发,再获取全部 Unicode 就正常了
[...document.querySelector('#glyph-list-end').children].forEach(item => {
var event = new MouseEvent('mouseover');
item.dispatchEvent(event);
})
获取 Unicode 和字形的映射,创建密码表
没办法,这里只能手动建立映射了,或者有 OCR 接口的话可以优化一下
var unicodeArr = ["none","BFEE","B460","BA67"...]
var decryptArr = ["","0","1","2"...]
var unicodeMap = {}
unicodeArr.forEach((item, index) => {
unicodeMap[item] = decryptArr[index]
})
console.log(unicodeMap)
映射结果如下
解密
复制一句密文来测试下效果
var encryptStr = '욜춼헵풱쐋뒈킧젋쉍쯃总쉫욜춼헵풱칌,以( )为宗旨。'
var decryptSte = encryptStr.split('')
.map(item => {
const HEX = item.charCodeAt().toString(16).toUpperCase()
return unicodeMap[HEX] || item
})
.join('')
console.log('encryptStr', encryptStr)
console.log('decryptSte', decryptSte)
解密效果如下 看起来解密效果还可以,一部分字体没有解密出来是因为字体文件的问题,多个 Unicode 对应同一个字体文件 TTGlyph,用上面说的百度字体编辑器可以看到 国 这个字形文件有两个 Unicode,把这些特殊 Unicode 手动加到之前的密码表就好了
JSON 数据解密
确认接口
这些加密字体很明显是后端接口传过来的,但是这个分页接口返回的竟然是加密后的字符串,真有你的
定位代码
在返回的 js 文件内搜索这个接口地址 2023gjaqzsjs.haedu.cn 和 /json/,找到相关代码,看到返回的加密字符串被 $decrypt 方法解密
搜索 $decrypt 方法发现是用 AES 解密
通过其他的一些变量名我猜用的很可能是 crypto-js 这个库,但是看 crypto-js 源码的 decrypt 函数第三个参数是一个配置对象啊,这里怎么是个字符串,难道是用的其他解密库?我在这里卡了半天,打断点才发现是作者创建了一个 AES 对象,把 crypto-js 的 decrypt 又封装了一下
这里的 o 方法就是把 16 进制转为 10 进制 index,然后获取 t[index], 比如 r["AES"][o("0xa")] 实际就是 r["AES"]["decrypt"]
引入 crypto-js 的 cdn 文件,然后复制相关解密变量到本地测试
结果如下

和知识竞赛题目内的加密字体一样,JSON 解密完成
总结
学习了字体加密解密的一些知识,加深了对字体文件的理解。而且在我以往的印象里反爬虫就是 ip 限制,请求频率限制,没想到还有 JSON 加密和字体加密。但是目前还有个问题:后端怎么生成的加密字体文件和字体 JSON,通过密码表还是加密规则,这个字体文件只加密了一部分常用字
参考资料
https://2023gjaqzsjs.haedu.cn/gajs/#/questionBank: https://link.juejin.cn?target=https%3A%2F%2F2023gjaqzsjs.haedu.cn%2Fgajs%2F%23%2FquestionBank
[2]https://2023gjaqzsjs.haedu.cn/fonts/wryh2.ttf: https://link.juejin.cn?target=https%3A%2F%2F2023gjaqzsjs.haedu.cn%2Ffonts%2Fwryh2.ttf
[3]https://birdfont.org/: https://link.juejin.cn?target=https%3A%2F%2Fbirdfont.org%2F
[4]https://fontdrop.info/: https://link.juejin.cn?target=https%3A%2F%2Ffontdrop.info%2F
[5]https://kekee000.github.io/fonteditor/: https://link.juejin.cn?target=https%3A%2F%2Fkekee000.github.io%2Ffonteditor%2F
作者介绍