T8 字体反爬

一:抓包分析

image-20260507001805184

翻页后能看到获取到的值与实际值是不一致的,简单的说是有一层映射,接口返回假值,映射到实际的真值

image-20260507020145680

从请求的字体映射能看到,当前映射为

1
2
3
4
5
6
7
8
9
10
11
12
mapping = {
"0": "1",
"1": "8",
"2": "9",
"3": "3",
"4": "2",
"5": "0",
"6": "4",
"7": "6",
"8": "5",
"9": "7"
}

对比接口返回值,7281 -> 转换得到6958 与实际值一致

二:字体解密

首先查看字体文件怎么来的

从接口的返回值能看到b64Font为base64,全局搜索

image-20260507020633899

image-20260507020649930

进入函数后能看到字体文件如何得来

image-20260507020720170

我们现在就是需要一步一步获取到映射信息

核心步骤:

1
2
3
4
1、获取字体文件 略
2、解析 GlyphOrder 的顺序
3、找出每个数字字形在 GlyphOrder 中的位置
4、建立映射:字符码点 → 真实数字

本文的字体文件以第二页字体文件为例:

1
data:font/woff2;base64,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"

正确数字为:

image-20260507150525781

image-20260507154435920

正确映射为:

1
2
3
4
5
6
7
8
9
10
11
12
mapping = {
"0": "0",
"1": "2",
"2": "7",
"3": "5",
"4": "3",
"5": "9",
"6": "6",
"7": "8",
"8": "4",
"9": "1"
}
2、解析GlyphOrder 的顺序
1
2
3
4
5
6
7
# 解码字体
font_data = base64.b64decode(base64_font)
font = TTFont(io.BytesIO(font_data))

# 获取glyph_order
glyph_order = font.getGlyphOrder()
print(glyph_order)

image-20260507155103269

实际上到这里就能拿到映射信息了

1
['.notdef', 'zero', 'nine', 'one', 'four', 'eight', 'three', 'six', 'two', 'seven', 'five']

从左到右:依次为0-9,’.notdef’这个不算。将对应的英文准换为数字对上去

例如:zero为0,位置为0 -> 0:0

nine为9,位置为1 -> 9:1

1
{0: 0, 9: 1, 1: 2, 4: 3, 8: 4, 3: 5, 6: 6, 2: 7, 7: 8, 5: 9}

排序一下与实际相同。

当然你可以直接将这些丢给AI,慢慢调整,直接就能输出你想要的结果

三:编码

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
import base64
import io
from fontTools.ttLib import TTFont

mappings = {
"zero": "0",
"one": "1",
"two": "2",
"three": "3",
"four": "4",
"five": "5",
"six": "6",
"seven": "7",
"eight": "8",
"nine": "9"
}


def get_font_mapping(base64_font):
font_mappings = {}
# 解码字体
font_data = base64.b64decode(base64_font)
font = TTFont(io.BytesIO(font_data))

# 获取CMAP
cmap = font.getBestCmap()

# 获取glyph_order
glyph_order = font.getGlyphOrder()

for i in glyph_order[1:]:
font_mappings[mappings[i]] = str(glyph_order.index(i) - 1)

return font_mappings

代码写的不好请见谅

image-20260507162623025

image-20260507162643286

更多内容也在公众号更新:码字的秃猴

tuhou