反爬之字体加密与破解

栏目: 编程工具 · 发布时间: 6年前

内容简介:最近看到不少网站都使用了字体库对数据进行加密,即页面源码中的数据与显示出来的数据不同,用户也无法直接进行复制。例如企信宝页面中的字母与数字,58房产频道中的数字:

*文章原创作者:manwu91,本文属于FreeBuf原创奖励计划,未经许可禁止转载。

最近看到不少网站都使用了字体库对数据进行加密,即页面源码中的数据与显示出来的数据不同,用户也无法直接进行复制。

例如企信宝页面中的字母与数字,58房产频道中的数字:

反爬之字体加密与破解 反爬之字体加密与破解

经过对字体库进行研究,找到了加密与解密方案。

加密

准备字体库样本

笔者在网上随便下载了一个ttf字体库,保存为’origin.ttf’,使用 fonttools 的命令行工具pyftsubset提取需要加密的字符:

pyftsubset origin.ttf --text='1234567890'

参数text为要提取字体的字符,运行结束后会在当前目录生成’origin.subset.ttf’,该字体库只包含’1234567890′共10个字符。

生成加密字体库

这里使用 [http://fontello.com/](http://fontello.com/) 网站提供的在线服务对上一步生成的字体库进行定制。首先将生成的subset.ttf转为svg,笔者使用的是 cloudconvert 提供的服务。然后将svg上传到fontello,选中要定制的字符,因为我们上传的字体库只包含0到9,所以这里全选,然后在 Customize Codes 功能下自定义码值。

反爬之字体加密与破解

码值与字符的关系可以看作是一种映射关系,比如 Unicode E801 对应字符 1 , Unicode E802 对应字符 2 。我们可以随意修改字符的unicode值,但一定要记住这个值与真实字符的对应关系,来对要显示在页面上的数据加密。这里使用该网站默认生成的unicode。对应关系如下:

CIPHER_BOOK = {
    '0': '\uE800',
    '1': '\uE801',
    '2': '\uE802',
    '3': '\uE803',
    '4': '\uE804',
    '5': '\uE805',
    '6': '\uE806',
    '7': '\uE807',
    '8': '\uE808',
    '9': '\uE809'
}

定制完成后下载字体文件。

使用

在css中定义字体,名为 fontello

@font-face {
    font-family: 'fontello';
    src: url('/static/fontello.woff2') format('woff');
    font-weight: normal;
    font-style: normal;
}

然后定义使用该字体的class:

.demo-icon {
    font-family: "fontello";
}

这样只需要为页面标签添加上’demo-icon’的class就可以了。如:

<h1><small class="demo-icon">就是这串数字:<b>{{string}}</b></small></h1>

服务端在返回数据前需要需要将数字用CIPHER_BOOK进行转换。

CIPHER_BOOK = {
    '0': '\uE800',
    '1': '\uE801',
    '2': '\uE802',
    '3': '\uE803',
    '4': '\uE804',
    '5': '\uE805',
    '6': '\uE806',
    '7': '\uE807',
    '8': '\uE808',
    '9': '\uE809'
}

def _encrypt_secret(secret):
    return ''.join(CIPHER_BOOK[c] for c in secret)

@app.route('/')
def index():
    if 'guess' in request.values:
        ts = session['ts'] if 'ts' in session else 0
        secret = session['secret'] if 'secret' in session else None
        if time.time() - ts < 2 and request.values['guess'] == secret:
            return render_template('index.html', success=True)
    secret = ''.join([random.choice('0123456789') for _ in range(20)])
    # 通过CIPHER_BOOK将数字转换为不可见字符
    s = _encrypt_secret(secret)
    session['secret'] = secret
    session['ts'] = time.time()
    return render_template("index.html", string=s)

查看页面源码,会发现源码是无法显示的字符,且复制出来的是乱码。

反爬之字体加密与破解

58产房频道使用的就是本文介绍的方案,只加密了数字。但是不同页面的字体库是变化的。在字体加密破解中我们会详细介绍如何破解58的字体加密。示例代码已上传到 github ,有兴趣的可以看看。

破解

前面已经介绍如何制作加密字体库并在demo项目中使用来防止数据被抓取,下面介绍破解方法。

true-type字体简介

我们已经知道字体加密其实是一种明文到密文的双向映射,所以只要找到映射表就可以了。但我们在破解的时候只能拿到字体库文件,所以需要通过该文件找到CIPHER_BOOK。这就需要对字体库结构有一定了解。在查阅 相关文档 后,可以简单地将字体的绘制过程为理解为:

1.根据字符的unicode编码找到glyph名称 (cmap);

2.根据glyph名称找到glyph (glyf);

3.使用glyph进行绘制。

其中glyph可以理解为字体的绘制所需的数据,如点、线等。

一个TrueType Font字体文件包含几个table。这里需要用到的两个table如下(tag为table的名称):

tag table
cmap character to glyph mapping
glyf glyph data

根据字体的绘制过程,可以猜测有两种方式实现字体加密:

1.打乱字符编码

2.打乱glyph名称

下面笔者就这两种情况用两个案例进行讲解。

破解demo

首先在页面中找到字体库的url并下载,得到fontello.woff2,然后用fonttools将文件转为ttx方便肉眼分析。

from fontTools.ttLib import TTFont

font = TTFont('fontello.woff2')
font.saveXML('fontello.ttx')

得到的ttx为xml文档,打开并查找cmap节点:

反爬之字体加密与破解

据此我们可以还原加密时的映射表(即cmap表):

CIPHER_BOOK = {
    '\ue800': '0',
    '\ue801': '1',
    '\ue802': '2',
    '\ue803': '3',
    '\ue804': '4',
    '\ue805': '5',
    '\ue806': '6',
    '\ue807': '7',
    '\ue808': '8',
    '\ue809': '9'
}

由于demo使用了静态的字体库,所以这个表不会变化,写死就可以了,破解代码如下:

import requests
from bs4 import BeautifulSoup as BS

CIPHER_BOOK = {
    '\ue800': '0',
    '\ue801': '1',
    '\ue802': '2',
    '\ue803': '3',
    '\ue804': '4',
    '\ue805': '5',
    '\ue806': '6',
    '\ue807': '7',
    '\ue808': '8',
    '\ue809': '9'
}
URL = 'http://127.0.0.1:5000'

sess = requests.Session()
resp = sess.get(URL).text
bs = BS(resp, 'lxml')
string = bs.select_one('.demo-icon b').text
guess = ''.join(CIPHER_BOOK[c] if c in CIPHER_BOOK else c
                for c in string)
print('guess:', guess)
resp = sess.get(URL, params={'guess': guess}).text
assert 'Congratulations' in resp

破解58

demo中的字体库不会变化,所以映射表写死就可以了。但分析发现58房产频道不同页面的字体库是不一样的,而且glyph name与真实字符有差异,所以需要根据字体库动态处理。

首先页面中的字体文件是经过base64编码的,直接解码并保存到文件即可。

反爬之字体加密与破解

然后用上面的代码转为ttx文件,查看cmap节点:

反爬之字体加密与破解

通过观察对比发现,字符编码相同,但glyph名称是变化的,且glyph名称与真实数字的关系为:

glyph_name = 'glyph00%02d' % (real_num + 1)

据此我们可以还原glyph名称与真实字符的映射表(即glyf表):

GLYF_TABLE = {
        'glyph00001': '0',
        'glyph00002': '1',
        'glyph00003': '2',
        'glyph00004': '3',
        'glyph00005': '4',
        'glyph00006': '5',
        'glyph00007': '6',
        'glyph00008': '7',
        'glyph00009': '8',
        'glyph00010': '9'
    }

另外由于cmap表是变化的,所以需要在解密时提取,使用fonttools库可以实现:

cmap = font['cmap'].getBestCmap()

返回一个dict,其中key为int型编码,v为glyph名称。整个解密过程为:

1.解析字库库,取得cmap;

2.根据cmap查询字符编码,得到glyph名称;

3.根据GLYF_TABLE查询glyph名称,得到真实字符。

代码有点长就不贴了,已上传到 gayhub ,有兴趣的可以下载看看。

*文章原创作者:manwu91,本文属于FreeBuf原创奖励计划,未经许可禁止转载。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

社交网站界面设计

社交网站界面设计

Christian Crumlish、Erin Malone / 樊旺斌、师蓉 / 机械工业出版社 / 2010-9-1 / 69.00元

《社交网站界面设计》提供100多种模式、原则以及最佳实践,并针对在设计社交网站时经常遇到的问题给出明确建议。本书将提供给你培养用户交互习惯和构建社区最具价值的参考。 本书作者将与你分享难得的经验,教会你平衡各种不同的因素,并与你的用户共同构建和谐健康的网络社区。 本书教会你 掌握创建任何网站时都会用到的原则 学习基本设计模式,以便向现有的网站中添加新的社交组件 学会在......一起来看看 《社交网站界面设计》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具