woff字體反爬實戰,10分鐘就能學會

聲明:本帖子僅是用於學習用途,請勿與用於惡意破壞別人網站,本人不承擔法律責任。

來繼續學爬蟲呀!

前言
簡單描述一下這種手段,html源碼的數字跟頁面展示的數字是不一致的!當時就一臉黑人問號,嗯???
經過分析,當前這種字體反爬機制是:通過獲取指定鏈接的woff字體文件,然後根據html源碼的數字
去woff字體文件裏面查找真正的數字,講到底就是一個映射關係/查找字典。如html源碼是123,去woff文件裏面
查找出來的是:623。好了,看到這裏,你一定想說:廢話講那麼多幹嘛?趕緊上教程啊!!
那先來看一下大致流程唄:

大致流程圖
分析目標網站頁面(在這裏我不打算貼出網站地址,請大家自己找網站練習),這裏看到html源碼和頁面展示的數字是不一致的,如下圖:
html源碼和頁面看到的數字對不上

tips:
一開始不知道是怎麼下手,只能谷歌搜索字體反爬,一搜果然很多說法,有說woff文件的、有說CSS的、還有說svg曲線啥的,
然後我就去查看Network裏面的All,就發現關鍵字眼woff,就開始猜測可能是屬於這種類型的反爬手段,接着開始幹活。
混淆前字體:

映射前字體

混淆後的字體:

woff字體混淆了的

找了一會,發現.woff2文件和woff文件前後不一樣,然後開始着手解決

如需下載woff文件,請點擊這裏

但是本地打不開woff字體文件,需要藉助的軟件是fontcreator,這個你自己去找一下,很多破解的

軟件打開字體視圖
但是這好像看不出什麼,然後我們接着需要從另外一方面下手,重點來了》將woff文件轉換爲xml文件
如下:

import os
import requests
from fontTools.ttLib import TTFont

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

url = "http://xxxxxx.xxx.woff"
    
woff_dir = os.path.join(base_dir, "statics/woffs/")
file_name = url.split("/")[-1]
xml_name = file_name.replace(file_name.split(".")[-1], "xml")
save_woff = os.path.join(woff_dir, file_name)
save_xml = os.path.join(woff_dir, xml_name)

resp = requests.get(url="xxx")
with open(save_woff, "wb") as f:
    f.write(resp.content)
    f.close()
font = TTFont(save_woff)
font.saveXML(save_xml)  # 轉換爲xml文件

然後打開xml文件看,先來查看一下縮略的內容,紅色圈圈的那兩個是本次重點破解的分析的內容:
主要涉及內容破解版塊

然後先查看cmap,發現線索,裏面標註了,0x30對應的就是zero,這翻譯過來就是48對應0啊!!
48對應的是0

接着來看一下code=0x30,其對應的name=cid00024,然後我們拿這個cid00024去搜索,發現在部分裏面看到:
,這表明什麼呢?然後連起來猜測一下,可能是0對應9?
爲了驗證這個猜想,繼續再找一下其他例子,發現還真的是!所以這就是破解了嘛
cid00024對應的是id爲9

好了下面來看一下核心代碼,如下:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2019/8/19 13:08
# @Author  : qizai
# @File    : crawl_woff.py
# @Software: PyCharm

# 先安裝:pip3 install fontTools
import os
import requests
from fake_useragent import UserAgent
from fontTools.ttLib import TTFont  # 對字體文件進行格式轉換

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ua = UserAgent()
header = {
    "user-agent": ua.chrome,
}


def parse_woff(url=""):
    """這裏是下載字體並且解析對應的值"""
    global cookie
    global header
    
    woff_dir = os.path.join(base_dir, "statics/woffs/")
    file_name = url.split("/")[-1]
    xml_name = file_name.replace(file_name.split(".")[-1], "xml")
    save_woff = os.path.join(woff_dir, file_name)
    save_xml = os.path.join(woff_dir, xml_name)

    if os.path.exists(save_woff):  # 存在本地的話直接提取本地的文件去解析即可省去下載,避免浪費資源
        font = TTFont(save_woff)
    else:
        resp = requests.get(url=url, cookies=cookie, headers=header)
        with open(save_woff, "wb") as f:
            f.write(resp.content)
            f.close()
        font = TTFont(save_woff)
        font.saveXML(save_xml)  # 轉換爲xml文件

    cmap = font.getBestCmap()  # 這個是xml源碼裏面的【數值-中間人code】映射,數值還不一定是html源碼裏面的數值,而是每位數經過加上一定的數值之後的
    tmp = {  # 這個是對應的纔是我們需要的值,或者你也可以在每次獲取的時候,將這個值對應減去48即可,就可以省去這這個映射
        48: 0,  # html源碼裏面的0對應xml源碼裏面的48
        49: 1,  # html源碼裏面的1對應xml源碼裏面的49
        50: 2,  # html源碼裏面的2對應xml源碼裏面的50
        51: 3,  # html源碼裏面的3對應xml源碼裏面的51
        52: 4,  # html源碼裏面的4對應xml源碼裏面的52
        53: 5,  # html源碼裏面的5對應xml源碼裏面的53
        54: 6,  # html源碼裏面的6對應xml源碼裏面的54
        55: 7,  # html源碼裏面的7對應xml源碼裏面的55
        56: 8,  # html源碼裏面的8對應xml源碼裏面的56
        57: 9,  # html源碼裏面的9對應xml源碼裏面的57
    }    # 注意:個人猜測以上這個tmp字典,xml源碼的數字跟html源碼數字的映射關係可能會定期改變的

    before_code_id = {}  # 轉換之後before_code_id爲:1:cid00019  key就是html源碼數字,value就是用來查詢的中間人code
    for k, v in cmap.items():
        if k not in set(range(48, 58)):
            continue
        before_code_id[tmp.get(k)] = v  # 這一步其實是將49:cid00019的映射格式轉換爲好理解的1:cid00019映射關係

    code_id_list = font.getGlyphOrder()[2:]  # 這個返回的值有11個,但是我這裏只是取了第三個到最後一個,是用來取計算前端看到的真正的數值
    affter_code_id = {k:v for k,v in zip(code_id_list, range(2, 12))}  # 將每一個按照順序映射爲cid00562:2這種

    return before_code_id, affter_code_id


if __name__ == '__main__':
    """使用如下"""
    before_code_id, affter_code_id = parse_woff(url="xxxx")
    
    # html源碼數字:假設爲0
    html_number = 0
    tmp_code = before_code_id.get(html_number)  # 先匹配中間人code
    real_number = affter_code_id.get(tmp_code) - 2  # 再提取中間人code對應的真正的數字,記得要減去2,因爲本來是每位數字已經多了2
    print("當前html源碼數字html_number:{} 真正的數字爲real_number:{}".format(html_number, real_number))
```

至此,當前的woff字體反爬已經破解了,如果有不妥的地方請指出,大家一起學習。


感謝兄弟帖子:
https://www.jianshu.com/p/5aef3d70f264
https://blog.csdn.net/xkx_07_10/article/details/82429812
https://www.cnblogs.com/gl1573/p/9994286.html
https://blog.csdn.net/zjkkg1/article/details/89678132
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章