动态字体,随风漂移
一、前言
上一道字体反扒的css加密题还是第四题,不过那道题主要还只是做了个数字的坐标偏移,逻辑分析起来不算难,这道动态字体的题算是真正的字体反扒题,做了一下感觉挺长见识的,对于了解同类型字体加密有不小帮助,闲言少叙,开搞!
二、分析过程
题目的要求是采集胜点列的数据,找出胜点最高的召唤师,那加密的数据肯定就是胜点数据了,核心应该是解析出胜点值,其次就是将这个值与对应的召唤师匹配,最后比较五十个玩家的胜点,找出胜点最高那一个。
这里仔细观察一下,可以发现胜点数字的字形都不是很规则,看着就像是图形渲染的,复制粘贴一下就会发现,这个值果然是图片符号。
打开开发者模式看一下请求的结果,不难发现这里的data应该是加密后的数值,而woff则是加密字体文件,而且这个字符串是base64加密后的。
往前查看一下请求调用栈,在网页源码这找到了对字体进行处理的部分代码:
字体文件加载是通过这两行处理的,也就是通过这里加载得到字体的ttf文件,在页面渲染的过程中对data内的value值进行渲染从而得到最后的显示结果。
ttf = data.woff;
$('.font').text('').append('<style type="text/css">@font-face { font-family:"fonteditor";src: url(data:font/truetype;charset=utf-8;base64,' + ttf + '); }</style>');
从源代码这里看不出字体的加密字符和数值的对应关系,于是把woff文件逆编码后保存为ttf格式的字体文件:
b64_code="AAEAAAAKAIAAAwAgT1MvMgPtafAAAAEoAAAAYGNtYXBBft4uAAABpAAAAYpnbHlmPD0Wg.....省略部分字符"
with open('D:\\yuan_font.ttf', 'wb') as f:
f.write(base64.b64decode(b64_code))
将保存的ttf字体文件在在线字体编辑器里打开,可以发现这里还原出了每个加密编码对应的数字字符:
这里验证了一下这个字体加密编码跟对应的数值关系,发现每次请求,返回的value值都是不一样的,以第一页为例,对于第一个胜点3236,每次value值都在变化,所以想靠固定编码关系来直接破解是不可能了:
第一次请求:
0: {
value: " 쉡  ꘓ "}
第二次请求:
0: {
value: " ꤴ  젥 "}
第三次请求:
0: {
value: " ꑱ  ꉥ "}
只好进一步查看字体文件的详细信息,利用python的fontTools
这个字体库的函数将ttf字体转换为xml格式:
font = TTFont('D:\\yuan_font.ttf')
font.saveXML('D:\\yuan_font.xml')
打开xml文件,这里的code
和name
对应的就是0-9这十个数字的加密编码:
底下的TTGlyph
内,contour
标签中就是每个数字字形的坐标,全部绘制出来就构成了网页最后渲染出的字体效果,那么既然编码对应的是具体的字形坐标,无论每次返回的数字字符编码是多少,那每个字形的坐标总不会变吧。
好家伙,我还是天真了,打开了两份文件对比了一下,发现即使是相同的数字,其字形竟然也不同,坐标值x,y
会有会有小范围的浮动,这就导致即使同样的数字每次渲染出来都会有所不同,就比如底下这个2
,虽然看着都一样,但是在轮廓上还是有明显差异:
如此看来,轻松简单的方法是不可能存在了,现在可能的解法有两种,一是虽然每个数字每次字形都有差异,但差异毕竟有限,可以尝试对同一个数字字形的两组坐标值进行对比,如果差别不超过某个阈值,那么就可以认为是同一个字;二是直接把字形文件的坐标绘制出来,生成字体图片,之后再ocr识别数字。
三、代码实现
3.1、方法一:字体映射关系识别数字
为了找到坐标跟对应字符间的映射关系,这里选了数字4的坐标来分析。可以发现,虽然每次返回的坐标值都不一样,但是同一个字体总的坐标对数量是一致的,而且pt
内的on
值是完全一致的,那么就可以尝试根据两个字体文件内所有的on
值是否完全相等来判断是不是同一个数字。
先随便找一个字体文件,手动分析出字符编码与数字的对应关系:
以这个关系作为其他字符编码相比较的参考基准,即如果下一个字体文件中某一个数字对应的所有on值都跟标准对照表里的on值相等,那就确定是同一个数字
#选定一个基准参照,确定flag和数字的关系
font = TTFont("F:\\temp\yuan_font_1.ttf")
gs = font.getGlyphSet()
glyphNames = font.getGlyphNames()
#这个数字顺序跟xml里的TTGlyph name顺序是一致的
num_list = [4,6,9,0,2,8,1,5,3,7]
map_dict={
}
for i,name in enumerate(glyphNames[1:]):
g = gs[name]
flag = list(g._glyph.flags)#读取每个坐标对应的on值
# coord=g._glyph.coordinates#获取每个字符的坐标序列
#map_dict字典里的键对应的数字,值则是on值构成的列表
map_dict[num_list[i]]=flag
print('标准对应关系为:',map_dict)
#待解析文件
font = TTFont("F:\\temp\yuan_font_2.ttf")
gs = font.getGlyphSet()
glyphNames = font.getGlyphNames()
list2=[]
for i,name in enumerate(glyphNames[1:]):
g = gs[name]
flag = list(g._glyph.flags)#读取每个坐标对应的on值
for key,value in map_dict.items():
if value==flag:
list2.append((name,key))
print('解析后的数字对应关系:')
for m in list2:
print(m)
pass
运行一下,输出结果为:
标准对应关系为: {
4: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 6: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0], 9: [1, 0, 0, 1, 0, 1, 0,