快手H5逆向,字体反爬,互动量获取

目标

获取快手视频点赞数、评论数、 播放数

分析视频接口

https://m.gifshow.com/fw/photo/5232901352181146092
image.png

请求头:

1
2
3
4
5
6
7
8
9
10
11
12
13
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
sec-ch-ua-mobile: ?1
sec-ch-ua-platform: "Android"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: didv=1644894188000; did=web_160a5eac2ed14dbda625edcf277953d1; sid=9995d540410efc97b28229c2; Hm_lvt_86a27b7db2c5c0ae37fee4a8a35033ee=1644894193; Hm_lpvt_86a27b7db2c5c0ae37fee4a8a35033ee=1645090238

响应(关键信息 window.pageData=):略

互动量相关 JSON.obfuseData:

1
2
3
4
5
6
7
8
{
"id": 263,
"fontCdnUrl": "https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_263f9585.ttf",
"fontCdnStyle": "<style>\n @font-face {\n font-family: kwaiFont;\n src: url(https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_263f9585.ttf) format(\"truetype\");\n }\n .font {\n font-family: \"kwaiFont\";\n font-style: normal;\n font-weight: normal;\n font-variant: normal;\n text-transform: none;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n }\n </style>",
"commentCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>",
"likeCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>",
"viewCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>"
}

页面中显示:

1
<p class="video-like">  <span style="FONT-FAMILY: kwaiFont;"></span>  </p>

可以明显看出是有字体反爬处理,需要下载字体文件进行解密

什么是字体反爬?

使用自定义的 ttf 文件来渲染网页中的文字,而网页中的文字不再是文字,而是相应的字体编码,通过复制或者简单的采集是无法采集到编码后的文字内容

字体反爬实现原理

厂商会随机生成成千上万套字库,并且保存好编码、字库文件、字的映射关系。文章显示的时候会从库中随机查询一套字库,并把文章中的替换成 unicode 编码,以达到字体加密的效果。

字体反爬的前世今生

初级难度:一套字体做加密

做好映射解密即可

中级难度:生成多套字体和编码

比如:猫眼电影,58 同城等等,汽车之家
虽然生成了多套,但是每个字对应的字体信息是不变
比如一个字的笔画数,和 x,y 变化都很小,可以利用这个特征解析字体的 xml 文件,做特征映射

高级难度:字体变形,字体信息随机化

比如:快手

静态字体解密

下载字体用 FontCeator 打开

image.png

将字体文件转为 xml

先用 FontCreator 将字体转为 woff 文件,再用 python 转为 xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="uniEAC6"/>
<GlyphID id="2" name="uniEC96"/>
<GlyphID id="3" name="uniF38C"/>
<GlyphID id="4" name="uniF088"/>
<GlyphID id="5" name="uniEC7E"/>
<GlyphID id="6" name="uniF5D6"/>
<GlyphID id="7" name="uniF20E"/>
<GlyphID id="8" name="uniF7A6"/>
<GlyphID id="9" name="uniF5B9"/>
<GlyphID id="10" name="uniE9D5"/>
<GlyphID id="11" name="uniE43E"/>
<GlyphID id="12" name="uniE729"/>
<GlyphID id="13" name="uniE47F"/>
<GlyphID id="14" name="uniE7CA"/>
<GlyphID id="15" name="uniF6C7"/>

注意:这个 xml 表示的是 id 和值的映射关系。比如
uniE43E 字体对应真实的值:4 , 对应的 glyph11

查看字体解密规则

1
"commentCount": "<SPAN STYLE='FONT-FAMILY: kwaiFont;'></SPAN>",


规则:&#+16 进制加上;号

解密

逆推字体加密
将字体的 key 转为 16 进制,&#+16 进制与加密的对比,得到对应的值

1
2
3
4
5
[ #编写对应的列表
'w', '5', 'm', 'k', '6', '.', '1', '3', '+', '0', '4', '8', '9',
'7', '2'
]

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
fontCdnUrl = "https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_263f9585.ttf"
key_map = {}

font_content = requests.get(fontCdnUrl).content
font = TTFont(BytesIO(font_content))
code = font.getGlyphOrder()[1:]
nums = [
'w', '5', 'm', 'k', '6', '.', '1', '3', '+', '0', '4', '8', '9',
'7', '2'
]
temp = dict(zip(code, nums))
res = font.getBestCmap()
for k, v in res.items(): #解析字体
print("字体的原始:" +str(k))
kk = str(hex(k))
print("字体的转换成16进制:" + str(kk))
kk = kk[1:]
print("去除前缀:" + str(kk))
kk ='&#' + kk
print("拼接&#得到最后加密的:" + str(kk))
print(str(v)+"字体对应真实的值:" + str(temp[v]))
key_map[kk] = temp[v]
re_html_code = re.compile(r'&#x[\da-f]{4}')
text = "" #7751confusionLikeCount=
words = re_html_code.findall(text)
result = ''.join(key_map[i] for i in words)
print(result)

得到 7762, 静态字体破解搞定~

动态字体解密

每次请求的字体都不一样
image.png

image.pngimage.png
可以发现字体的形状不同,无法通过笔画来映射

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
<pt x="-10158" y="550" on="1"/>
<pt x="-10154" y="545" on="0"/>
<pt x="-10161" y="534" on="1"/>
<pt x="-10160" y="350" on="0"/>
<pt x="-10490" y="54" on="1"/>
<pt x="-10492" y="27" on="1"/>
<pt x="-10492" y="5" on="1"/>
<pt x="-10285" y="0" on="1"/>
<pt x="-10156" y="4" on="1"/>
<pt x="-10031" y="-5" on="1"/>
<pt x="-10035" y="16" on="1"/>
<pt x="-10029" y="58" on="1"/>
<pt x="-10034" y="87" on="1"/>
<pt x="-10232" y="75" on="1"/>
<pt x="-10236" y="87" on="1"/>
<pt x="-10256" y="83" on="0"/>
<pt x="-10300" y="83" on="1"/>
<pt x="-10323" y="77" on="0"/>
<pt x="-10356" y="78" on="1"/>
<pt x="-10116" y="306" on="0"/>
<pt x="-10077" y="472" on="1"/>
<pt x="-10071" y="499" on="0"/>
<pt x="-10066" y="529" on="1"/>
<pt x="-10070" y="610" on="0"/>
<pt x="-10110" y="665" on="1"/>
<pt x="-10119" y="688" on="0"/>
<pt x="-10124" y="692" on="1"/>
<pt x="-10156" y="716" on="0"/>
<pt x="-10201" y="737" on="1"/>
<pt x="-10237" y="742" on="0"/>
<pt x="-10282" y="743" on="1"/>
<pt x="-10394" y="740" on="0"/>
<pt x="-10497" y="632" on="1"/>
<pt x="-10442" y="594" on="1"/>
<pt x="-10441" y="592" on="1"/>
<pt x="-10372" y="671" on="0"/>
<pt x="-10293" y="672" on="1"/>
<pt x="-10226" y="677" on="0"/>
<pt x="-10191" y="630" on="1"/>
<pt x="-10163" y="603" on="0"/>
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
<pt x="-9192" y="740" on="0"/>
<pt x="-9129" y="740" on="1"/>
<pt x="-9114" y="755" on="0"/>
<pt x="-9099" y="746" on="1"/>
<pt x="-9037" y="747" on="0"/>
<pt x="-8991" y="706" on="1"/>
<pt x="-8986" y="698" on="0"/>
<pt x="-8973" y="696" on="1"/>
<pt x="-8973" y="695" on="0"/>
<pt x="-8975" y="695" on="1"/>
<pt x="-8917" y="624" on="0"/>
<pt x="-8920" y="525" on="1"/>
<pt x="-8920" y="337" on="0"/>
<pt x="-9203" y="69" on="1"/>
<pt x="-9180" y="74" on="0"/>
<pt x="-9159" y="75" on="1"/>
<pt x="-9102" y="79" on="0"/>
<pt x="-9086" y="83" on="1"/>
<pt x="-9016" y="81" on="1"/>
<pt x="-8878" y="77" on="1"/>
<pt x="-8881" y="66" on="1"/>
<pt x="-8882" y="13" on="1"/>
<pt x="-8881" y="-3" on="1"/>
<pt x="-8917" y="-1" on="1"/>
<pt x="-9346" y="2" on="1"/>
<pt x="-9340" y="50" on="1"/>
<pt x="-9345" y="58" on="1"/>
<pt x="-9008" y="348" on="0"/>
<pt x="-9006" y="534" on="1"/>
<pt x="-9003" y="552" on="0"/>
<pt x="-9008" y="574" on="1"/>
<pt x="-9016" y="607" on="0"/>
<pt x="-9036" y="635" on="1"/>
<pt x="-9049" y="639" on="0"/>
<pt x="-9063" y="648" on="1"/>
<pt x="-9072" y="660" on="0"/>
<pt x="-9080" y="658" on="1"/>
<pt x="-9077" y="662" on="0"/>
<pt x="-9083" y="669" on="1"/>
<pt x="-9107" y="671" on="0"/>
<pt x="-9144" y="676" on="1"/>
<pt x="-9163" y="678" on="0"/>
<pt x="-9187" y="672" on="1"/>
<pt x="-9206" y="651" on="0"/>
<pt x="-9229" y="648" on="1"/>
<pt x="-9263" y="626" on="0"/>
<pt x="-9293" y="586" on="1"/>
<pt x="-9314" y="600" on="1"/>
<pt x="-9326" y="628" on="1"/>
<pt x="-9349" y="639" on="1"/>
<pt x="-9304" y="686" on="0"/>
<pt x="-9259" y="716" on="1"/>
<pt x="-9251" y="717" on="0"/>
<pt x="-9242" y="719" on="1"/>

经过下载多个字体文件查看,发现如下:

  • 每次请求都是不同的字体,字体库不是简单的几套
  • 笔画数不同
  • x,y 坐标不同,并且移动很大

所以无法通过解析字体的 xml 利用有规律的特征来做映射

思路一:

将获取到的加密字段自己组装成 html 显示后,截图,ocr 识别出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
@font-face {
font-family: kwaiFont;
src: url(https://tx2.a.kwimgs.com/kos/nlav10312/mdata/pkg/kwai-font/fontscn_28789ff8.ttf)
format("truetype");
}
.font {
font-family: "kwaiFont";
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
}
</style>
<div>
<span STYLE="FONT-FAMILY: kwaiFont;"></span>
</div>

image.png

保存为 html

截图

OCR 识别

思路二

将字体文件字体分割为 png,ocr 识别成出来。放入集合按顺序排列

下载安装 tesseract-ocr

下载语言包

https://tesseract-ocr.github.io/tessdoc/Data-Files
放入 Tesseract-OCR\tessdata

配置环境变量

将字体切割成图片代码

1
2
3
4
5
6
7
8
def uni_2_png_stream(txt, font, img_size=512): """将字形转化为图片流 Args: txt
([type]): [description] font ([type]): [description] img_size (int, optional):
[description]. Defaults to 512. Returns: [type]: [description] """ img =
Image.new('1', (img_size, img_size), 255) draw = ImageDraw.Draw(img) font =
ImageFont.truetype(font, int(img_size * 0.7)) txt = chr(txt) x, y =
draw.textsize(txt, font=font) draw.text(((img_size - x) // 2, (img_size - y) //
2), txt, font=font, fill=0) # draw.text((0,0), txt, font=font, fill=0) return
img

对比 ocr 效果

image.png
uniE43E 字体对应真实的值:4
uniE47F 字体对应真实的值:9
uniE729 字体对应真实的值:8
uniE7CA 字体对应真实的值:7
uniE9D5 字体对应真实的值:0
uniEAC6 字体对应真实的值:w
uniEC7E 字体对应真实的值:6
uniEC96 字体对应真实的值:D
uniF088 字体对应真实的值:k
uniF20E 字体对应真实的值:1
uniF38C 字体对应真实的值:m
uniF5B9 字体对应真实的值:+
uniF5D6 字体对应真实的值:.
uniF6C7 字体对应真实的值:2
uniF7A6 字体对应真实的值:3

可以看出来识别率 90%,数字 5 识别成 D 了。

优化数字识别率 和 ocr 执行时间

1
2
3
4
pytesseract.pytesseract.tesseract_cmd = filename # text =
pytesseract.image_to_string(image, lang='chi_sim', config='--psm 10') # 注意
这里替换语言库,提升纯数字识别准确率 和 执行时间 11秒 提升到6秒 text =
pytesseract.image_to_string(image, config='--psm 10')

image.png
测试了几个 font 文件,数字识别率 100%

继续优化

tesseract:

nums:[‘Ww’, ‘+’, ‘4’, ‘9’, ‘2’, ‘6’, ‘mM’, ‘8’, ‘’, ‘7’, ‘3’, ‘k’, ‘5’, ‘0’, ‘1’]
INFO: 127.0.0.1:2629 - “GET /api/ks_interactive_data/3xdyxk622whqx2q HTTP/1.1” 200 OK
2022-03-01 12:28:14.721 | WARNING | utils:ocr_processor:146 - 识别耗时:2227.240800857544
2022-03-01 12:28:14.722 | WARNING | main:request:123 - result:0_2_65
2022-03-01 12:28:14.723 | WARNING | main:request:124 - 耗时:3415.691375732422

更换 ocr 识别后:

2022-03-01 12:31:36.279 | WARNING | utils:ocr_processor:146 - 识别耗时:131.30450248718262
nums:[‘’, ‘7’, ‘w’, ‘2’, ‘0’, ‘9’, ‘6’, ‘3’, ‘十’, ‘4’, ‘8’, ‘5’, ‘k’, ‘1’, ‘m’]
INFO: 127.0.0.1:2923 - “GET /api/ks_interactive_data/3xdyxk622whqx2q HTTP/1.1” 200 OK
2022-03-01 12:31:36.290 | WARNING | main:request:123 - result:0_2_65
2022-03-01 12:31:36.290 | WARNING | main:request:124 - 耗时:1669.9013710021973

DID 风控解决

待下篇分解

大规模测试:

10 个线程,单 IP,5 个小时一共跑了 62264 次,失败次数 3266,成功率 94%

平均一个小时 12452 次。

单个请求 2 秒以内完成。1 秒 4 和 1 秒 8 居多,通过 redis 预存 did 的话,可以进一步提升耗时到 1 秒以内。

警告:仅供学习和参考,不可用于非法用途!


快手H5逆向,字体反爬,互动量获取
http://blog.uzilol.cn/2022/02/24/yuque/%E5%BF%AB%E6%89%8BH5%E9%80%86%E5%90%91%EF%BC%8C%E5%AD%97%E4%BD%93%E5%8F%8D%E7%88%AC%EF%BC%8C%E4%BA%92%E5%8A%A8%E9%87%8F%E8%8E%B7%E5%8F%96/
作者
ive_e (leoli)
发布于
2022年2月24日
许可协议