JavaScript逆向:GlidedSky 字体加密题

题目

题目地址:点击进入
在这里插入图片描述
 

解题

单个页面的数据如下,从网页元素中可以看出,网页源码中的数字和我们看到的数字并不同,如果简单爬取网页内容肯定无法达到题目要求。注意右下角篮圈中的字体样式,这就是 372 能显示成 127 的关键所在。
在这里插入图片描述
由于 font-family: glided_sky; 样式以Base64的形式存放在网页中,且网页元素每次刷新都会有不同的网页数字,这里为了便于分析就直接把第一页保存在本地用于编程演示。
在这里插入图片描述
把网页中的字体数据保存解码出来。

import requests
from lxml import etree
import tools
import base64
import xml.dom.minidom
from bs4 import BeautifulSoup
import re

fhtml = open('GlidedSky字体加密1.html', 'r', encoding='utf-8')
soup = BeautifulSoup(fhtml, 'lxml')
#解析<style>
html = str(soup.select('style'))
#匹配到Base64的字体数据部分
base64font = re.findall("base64,(.*?)\) format", html)[0]
#Base64解码字体数据
font = base64.b64decode(base64font)
#字体数据写入文件
with open("字体文件.ttf", mode="wb") as f:
    f.write(font)

FontCreator 打开 字体文件.ttf 已经可以看到映射关系了。
在这里插入图片描述
再用Python fontTools库 解析字体文件。

from fontTools.ttLib import TTFont

font = TTFont('字体文件.ttf')
font.saveXML("字体文件.xml")

解析后的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.24">

  <GlyphOrder>
    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
    <GlyphID id="0" name=".notdef"/>
    <GlyphID id="1" name="nine"/>
    <GlyphID id="2" name="three"/>
    <GlyphID id="3" name="seven"/>
    <GlyphID id="4" name="zero"/>
    <GlyphID id="5" name="one"/>
    <GlyphID id="6" name="four"/>
    <GlyphID id="7" name="eight"/>
    <GlyphID id="8" name="two"/>
    <GlyphID id="9" name="five"/>
    <GlyphID id="10" name="six"/>
  </GlyphOrder>
  
  ...省略...
  
  <cmap>
    <tableVersion version="0"/>
    <cmap_format_4 platformID="0" platEncID="3" language="0">
      <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
      <map code="0x31" name="one"/><!-- DIGIT ONE -->
      <map code="0x32" name="two"/><!-- DIGIT TWO -->
      <map code="0x33" name="three"/><!-- DIGIT THREE -->
      <map code="0x34" name="four"/><!-- DIGIT FOUR -->
      <map code="0x35" name="five"/><!-- DIGIT FIVE -->
      <map code="0x36" name="six"/><!-- DIGIT SIX -->
      <map code="0x37" name="seven"/><!-- DIGIT SEVEN -->
      <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
      <map code="0x39" name="nine"/><!-- DIGIT NINE -->
    </cmap_format_4>
    <cmap_format_4 platformID="3" platEncID="1" language="0">
      <map code="0x30" name="zero"/><!-- DIGIT ZERO -->
      <map code="0x31" name="one"/><!-- DIGIT ONE -->
      <map code="0x32" name="two"/><!-- DIGIT TWO -->
      <map code="0x33" name="three"/><!-- DIGIT THREE -->
      <map code="0x34" name="four"/><!-- DIGIT FOUR -->
      <map code="0x35" name="five"/><!-- DIGIT FIVE -->
      <map code="0x36" name="six"/><!-- DIGIT SIX -->
      <map code="0x37" name="seven"/><!-- DIGIT SEVEN -->
      <map code="0x38" name="eight"/><!-- DIGIT EIGHT -->
      <map code="0x39" name="nine"/><!-- DIGIT NINE -->
    </cmap_format_4>
  </cmap>
  
  ...省略...

由 <cmap> 和 <GlyphOrder> 可以列出下面映射。

字符code(Ascii编码值)nameidFontCreator看到的绘制文字
00x30zero43
10x31one54
20x32two87
30x33three21
40x34four65
50x35five98
60x36six109
70x37seven32
80x38eight76
90x39nine10

通过表格,我想到了三种方法还原字体:

  1. 手动映射,直接手动写一个【字符–FontCreator看到的绘制文字】关系映射表,通过这个映射表来获取实际文字。优点:几乎没有技术门槛,编码快捷。缺点:如果字符较多则手动工作量巨大,且无法应对动态字体加密。
  2. 编码中查找【字符–FontCreator看到的绘制文字】的逻辑关系,比如上面可以通过字符的Ascii查询到name,再由name查到对应的id,最后减1即是FontCreator绘制的数字。优点:技术门槛不高。缺点:字符与实际绘制文字并不一定就有逻辑关系,且无法应对动态字体加密。
  3. OCR识别,通过字符对应OCR识别出的字符建立映射关系。优点:可以应对动态字体加密。缺点:技术门槛较高,识别可能不准确。

针对这道题来说,总共也就9个数需要映射,而且每次刷新页面,网页内容的数字都会改变,但实际看到的数字不变。好在每次刷新都符合通过字符的Ascii查询到name,再由name查到对应的id,最后减1即是FontCreator绘制的数字规律。于是可以按如下编码爬取:

import requests
from lxml import etree
import tools
import base64
from fontTools.ttLib import TTFont
import xml.dom.minidom
from bs4 import BeautifulSoup
import re


def saveHtml(url, output_name):
    """保存网页内容到本地文件"""
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
        # 没有Cookie会302到登录页
        'Cookie': '__gads=ID=24468711568854be-22bdaffd29c8007c:T=1620874506:RT=1620874506:S=ALNI_MYJCrE0xSUvEfDx7UFPoqgfc0oEoQ; _ga=GA1.2.1515855357.1620874505; footprints=eyJpdiI6IkRZWEx6UFJaNUVOMHVIbkVFSTFvdkE9PSIsInZhbHVlIjoiTUFtM2xOTVZWVVJROUFuYWZuOWlIcFlsbCtaMzFxYzFocmVrZXZlenpCUDZ6K2RJdGp0a1lJOVNzU3NjWEZ6eiIsIm1hYyI6ImUyZjA5ZWZjOGE1YjFlOTcxZjkyYjZiMWJhZWI2MWUwMjQ2MGJlY2U5N2MwMDk0OGM1ZTU4YzMyMTIxMGYxYTIifQ%3D%3D; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=eyJpdiI6Im5laDlOMkFKMWx2UHFLMjliWlhHOEE9PSIsInZhbHVlIjoiaDkrZjIySWo0cnk1bEN4cmxhMGd5Z3hxTFNDc1VTejZsdTBoZTJnRVh4a3BxeXJhSWZqWXBmZnYyYzFnWjdleEg5R0hSN2traW9VcmZjZkJNXC9aaVdEMEN5UERZVktiSllUcURKT1VkZ1F6TGl3TXE2YXRoUWFBSVNhMDN5TE04d2NqTk12Y3NRa3FHdGFXbWo5azQwN1pBNnlqWGhBY0pzS0pxd0VNc2ZKdz0iLCJtYWMiOiI5OTFjNTU0YWNlYTk2YWQzZjlkNjI4NDlhYjY1NzEzYTk4YjBlNTcxNzVkNTg5OTBiNjg4NWI0ZTc5NTY4OGI4In0%3D; _gid=GA1.2.1650202755.1621993422; Hm_lvt_020fbaad6104bcddd1db12d6b78812f6=1621994369,1621994747,1621994780,1621994782; XSRF-TOKEN=eyJpdiI6InAwaURUOFNCWjdqU2lCNGV2VFNBVkE9PSIsInZhbHVlIjoidGM1RHFRYnlYQ1ZRdXpoOGJsc2JEQlc1Ym9QZ3VMMXNJY3hOM0lOQlN0VnZJOEpXOVJVUmZZblJFRVh1K0FQcCIsIm1hYyI6IjNlZGI2NGQ0Yjg3NDc2NDNmNzVkYjA2NzhiY2M0YjI2NTliMTUwMTgyZDM0ZmIzNWJlYmYyMjI0NGE0MzM0YjcifQ%3D%3D; Hm_lpvt_020fbaad6104bcddd1db12d6b78812f6=1622078279; glidedsky_session=eyJpdiI6ImRzV2xnQm9LUHJmWFJnUWsydndlUVE9PSIsInZhbHVlIjoiSHZia2dKYnViSmpcL1dZRCtBTGV4bG12RWUxa0YyZnMxaGFPR3RwSEtjWEdJc3FoU2tjd3pmUzIxTnJBa3dEVWUiLCJtYWMiOiJkZTYzMjQyZWU3ZDA4NmY4OTEwZjJiOTI4YzFmY2ZiYmM3NGJkMjQxNjU4MGE2ZTgzZTIzYzNhMWEzZTA4OWUwIn0%3D'
    }
    req = requests.get(url, headers=headers)
    f = open(output_name, 'w', encoding='utf-8')
    for i in req.text:
        f.write(i)
    print('网页保存到'+output_name)


def saveFont(input_name, output_name):
    """从网页文件中获取字体文件"""
    fhtml = open(input_name, 'rb')
    soup = BeautifulSoup(fhtml, 'lxml')
    # 解析<style>
    html = str(soup.select('style'))
    # 匹配到Base64的字体数据部分
    base64font = re.findall("base64,(.*?)\) format", html)[0]
    # Base64解码字体数据
    font = base64.b64decode(base64font)
    # 字体数据写入文件
    with open(output_name, mode="wb") as f:
        f.write(font)
    print('字体保存到'+output_name)


def saveXML(input_name, output_name):
    """字体文件中获取xml信息"""
    font = TTFont(input_name)
    font.saveXML(output_name)
    print('字体xml保存到'+output_name)


def getCodeIdMap(input_name):
    """返回 id--实际显示的数 字典"""
    fxml = open(input_name, 'rb')
    soup = BeautifulSoup(fxml, 'html.parser')

    # cmap_format_4标签下code--name字典
    items_map = soup.find('cmap').find('cmap_format_4').find_all('map')
    code_map = {}
    for item in items_map:
        code = item['code']
        name = item['name']
        code_map[code] = name

    # GlyphOrder标签下id--name字典
    items_glyphid = soup.find('glyphorder').find_all('glyphid')  # GlyphOrder和GlyphID要用小写,不然识别不到
    id_map = {}
    for item in items_glyphid:
        id = item['id']
        name = item['name']
        id_map[name] = id

    # id--实际显示的数 字典
    code_id_map = {}
    for code in code_map:
        name = code_map[code]
        id = id_map[name]
        code_id_map[code] = int(id) - 1

    return code_id_map

def getHtmlNum(input_name, code_id_map):
    """从网页文件中获取要爬取数字并转换成真实看到的数字"""
    fhtml = open(input_name, 'rb')
    soup = BeautifulSoup(fhtml, 'html.parser')
    strNums = soup.find_all('div',class_='col-md-1')
    realNums = []
    for strNum in strNums:
        strFake = strNum.get_text().strip()
        realStr = ""
        for n in strFake:
            code = hex(ord(n))
            realNum = code_id_map[str(code)]
            realStr += str(realNum)
        realNums.append(realStr)
    
    return realNums
    
if __name__ == "__main__":
    page = "1"
    url = 'http://glidedsky.com/level/web/crawler-font-puzzle-1?page=' + page
    html_name = page + ".html"
    font_name = page + ".ttf"
    xml_name = page + ".xml"
    saveHtml(url, html_name)
    saveFont(html_name, font_name)
    saveXML(font_name, xml_name)
    code_id_map = getCodeIdMap(xml_name)

    realNums = getHtmlNum(html_name,code_id_map)
    print(realNums)

运行,成功:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值