Python爬虫:网页字体加密与解密实践

在某网站发现一个字体加密,今天来尝试破解

一、查找代码

看到一个日期

我尝试复制,发现复制结果是乱码的

驋龤驋龤-龒驋-驋驋

查看源码发现是这样的

<span class="strongbox">驋龤驋龤-龒驋-驋驋</span>

感觉应该是字体加密了,看到这个类名上有一个特殊的字体cyzone-secret

.strongbox {
    font-family: 'cyzone-secret','Hiragino Sans GB','Microsoft yahei',Arial,sans-serif,'宋体'!important;
}

点击源码文件,看到了一段字体样式,

<style>
/* 省略了很长的内容*/
@font-face{
	font-family:'cyzone-secret';
	src:url('data:application/font-ttf;charset=utf-8;base64,AAEAAAALAIAAA... AAAAAAAAAAAAAAAAAAAAA') format('truetype')
}
.strongbox{
	font-family:'cyzone-secret','Hiragino Sans GB','Microsoft yahei',Arial,sans-serif,'宋体'!important
}
</style>

查看网页源码,没有看到相关代码,断定是动态生成的样式片段。找遍网页所有文件,都没有发现这个字体cyzone-secret 的代码

经过观察,发现页面里边有这么一段自执行的代码

<script>
        ! function (w, d) {
            if (!w.ActiveXObject) {
               // 省略了很长的内容
                let code = unescape("%64%2E%77%72%69%...%65%3E%22%29");
                console.log(code); // 打印语句是我自己加入的
                eval(code);
            }
        }(window, document);
    </script>

单独放入一个html文件中,控制台打印出了如下代码
在这里插入图片描述
所以这段字体就是这个eval函数生成的了。

提取字体

将base64的数据,提取后保存为ttf文件

# -*- coding: utf-8 -*-
import base64

# 省略了很长的...
b64_code = 'AAEAAAALAIAAAwAwR1NV...kBCgELAQwAAAAAAAAAAAAAAAAAAAAA...'

with open('font.ttf', 'wb') as f:
    f.write(base64.decodebytes(b64_code.encode()))

查看ttf文件字体对应的编码:
http://fontstore.baidu.com/static/editor/index.html
在这里插入图片描述

安装依赖

pip3 install fonttools

将ttf文件保存成xml文件

from fontTools.ttLib import TTFont  # 导包

font = TTFont('font.ttf')
font.saveXML('font.xml')

可以再font.xml中看到字体编码对应的文字
在这里插入图片描述
字体坐标,就是字体显示出来的样子
在这里插入图片描述
再刷新页面,再生成一组字体文件ttf和xml文件。

观察发现:

  1. 发现字体文件【code编码 - name】的对应关系发生了变化
  2. 【name-TTGlyph坐标】没有发生变化,而TTGlyph坐标就是显示的文字轮廓,所以可以确定,无论文件怎么变,name所显示的内容不变

所需要做的就是每次确定【code编码 - name】之间的关系

code编码 <-> name - TTGlyph坐标

完整的代码

# -*- coding: utf-8 -*-
"""
字体反爬测试文件
https://data.cyzone.cn/event/list-0-1-0-0-0-0-1/0?clear=1

"""

import base64
import re
from io import BytesIO
from urllib.parse import unquote

import requests
from fontTools.ttLib import TTFont  # 导包
from scrapy.selector import Selector


def get_page():
    """获取页面内容"""
    headers = {
        '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',
        'accept-encoding': 'gzip, deflate, br',
        'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'cache-control': 'no-cache',
        'pragma': 'no-cache',
        'sec-ch-ua': '"Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"',
        'sec-ch-ua-mobile': '?0',
        'sec-fetch-dest': 'document',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'none',
        'sec-fetch-user': '?1',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
    }

    url = 'https://data.cyzone.cn/event/list-0-1-0-0-0-0-1/0?clear=1'
    response = requests.get(url, headers=headers)
    return response.text


def get_ttf_base64(text):
    """通过正则表达式,查找到表示字体文件的编码"""
    result = re.search(r'let code = unescape\((.*)\);', text)

    code = result.group(1)

    # url解码
    unquote_code = unquote(code)

    result2 = re.search(r"base64,(.*?)'\)", unquote_code)

    b64_code = result2.group(1)

    return b64_code


def parse_time(text):
    """找到页面中所需要的文字"""
    sel = Selector(text=text)
    string = sel.css(".list-table3 .table-plate3 .strongbox::text").extract()[-1]
    return string


def get_mapping(b64_code):
    """找到字体新的映射关系"""
    text_font = TTFont(BytesIO(base64.decodebytes(b64_code.encode())))
    text_font.saveXML("text.xml")

    text_mapping = text_font['cmap'].tables[0].ttFont.tables['cmap'].tables[0].cmap

    # 通过比对发现, glyph* 对应的坐标没有变化,即数字值没有变化
    _mapping = {
        'glyph00009': '8',
        'glyph00005': '4',
        'glyph00001': '0',
        'glyph00008': '7',
        'glyph00010': '9',
        'glyph00002': '1',
        'glyph00006': '5',
        'glyph00003': '2',
        'glyph00004': '3',
        'glyph00007': '6',
    }

    __mapping = {}
    for key, val in text_mapping.items():
        __mapping[key] = _mapping[val]

    # print(text_mapping)
    return __mapping


def decode_text(mapping, string):

    ret_list = []
    for char in string:
        value = mapping.get(ord(char), char)
        ret_list.append(value)

    return ''.join(ret_list)


def main():
    page = get_page()
    print(page)

    b64_code = get_ttf_base64(page)
    mapping = get_mapping(b64_code)

    string = parse_time(page)
    decode_string = decode_text(mapping, string)

    print(decode_string)


if __name__ == '__main__':
    main()

参考文章

  1. 破解字体加密解决思路
  2. 反反爬技术,破解猫眼网加密数字
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页