暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

大众点评字体加密破解

Python爬虫和数据挖掘 2021-07-08
2484

文章目录


  • 文章目录

    • 1. 大众点评简单介绍

    • 2. 页面分析

    • 3. 数字加密破解

    • 4. 文字加密

    • 5. 总结


1. 大众点评简单介绍


大众点评是中国领先的本地生活信息及交易平台,也是全球最早建立的独立第三方消费点评网站。大众点评不仅为用户提供商户信息、消费点评及消费优惠等信息服务,同时亦提供团购、餐厅预订、外卖及电子会员卡等 O2O(Online To Offline)交易服务。

2. 页面分析


  1. 「http://www.dianping.com/shop/5717186」为例,正常的页面如图
  2. 通过 F12 审查元素,却发现数字是加密的,如图
  3. 查看源码(ctrl+u)发现,数字对应的源码是诸如"&#xec2d"类型的代码,且该代码代表数字 0,如何找到其对应关系呢?
  4. 另外发现部分文字也被加密了,如上图绿色框里的 e 标签,部分文字被加密。

至此,我们的目标就很明确了,需要破解数字和文字的加密,我们一个个来

3. 数字加密破解


3.1 加密分析

通过上面的简单分析发现,大众点评的部分数字被加密,如何找到代码和数字的对应关系,成了我们的破解加密的关键,我们很容易发现,被加密的数字都是在 d 标签内,且具有共同的 class 属性:num, 那此时猜想,会不会是 num 通过某种规则,被 js 转换成数字?我们先尝试着用 Google 调试页面的全局搜索工具(快捷键ctrl+shift+f),搜索 num 关键字,看会不会有什么惊喜的发现
第一个被检索出来的是一个 css 文件,尝试着点进去,发现如下 css 片段

@font-face {
    font-family"Microsoft YaHei";
    srcurl("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/53cfe63b.eot");
    srcurl("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/53cfe63b.eot?#iefix"format("embedded-opentype"),url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/53cfe63b.woff");
}

.num {
    font-family'Microsoft YaHei';
}


从这段 css 可以看出,class="num"的标签,指定了字体库地址,猜测大概率是使用了所谓的字体加密。为了验证猜想,我们需要看下研究这个字体文件

3.2 字体库加密破解

3.2.1 下载字体库文件

下载地址 http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/53cfe63b.woff

3.2.2 查看并分析.woff 字体文件

为了查看.woff 文件内容,我们需要一个字体编辑工具 FontCreator,(或者使用百度字体库的字体编辑工具:http://fontstore.baidu.com/static/editor/index.html#)发现文件内容长这样,我们发现每个新字体上方都有个编码,咦,这个编码怎么有点熟悉,前面提到"&#xec2d"代表数字 0 。字体文件里也有个 0,其上方代码是$EC2D,刚好是前面编码的后四位,这不正是我们要的映射关系吗?下面我们要做的就是生成字体库的编码和字符的对照关系,从而完成加密破解

3.2.3 使用 python 解析.woff 文件

  1. 字体库文件记录了字体编码和字形的映射关系,字形相对稳定,编码可能会变,因此我们可以下载一份原始的字体库文件,形成字形和真实字符的映射关系,实际使用中,拿新获取的字体库的字形和老字体库的字形作比较,从而得到新字体库字形和字符的映射关系,达到解密目的
  2. 至于字形怎么比较,fonttools 包帮我们做了这个事情(pip install fonttools )
  3. 如何形成初始的字形和字符的映射关系,大概有下面几种方式
    A.手动整理,形成字典表,适合加密字符不多的情况,不然要死
    B.图像识别,写个本地的html文件,调用字体库字体,在页面进行展示,然后截图,进行图像识别,我用的是这种,但是识别率不理想,需要手动处理一部分
    C.接入图像识别平台
    D.机器学习,将字体库字体挨个截图,算了,这个更复杂,有兴趣的同伴自行研究吧

关键代码如下:

    def decrypt_woff(self,):
        decrypt_woff_results = {}
        basettf = TTFont(self.basewoff)
        cmap = basettf.getBestCmap()
        cmap_results = {}
        newttf = TTFont(self.woff_name+'.woff')
        newttf.getBestCmap()
        neworder = newttf.glyphOrder[2:]
        # 字典表
        base_str = ['1''2''3''4''5''6''7''8''9''0''店''中''美''家''馆''小''车''大''市''公',
                    '酒''行''国''品''发''电''金''心''业''商''司''超''生''装''园''场''食''有''新''限',
                    '天''面''工''服''海''华''水''房''饰''城''乐''汽''香''部''利''子''老''艺''花''专',
                    '东''肉''菜''学''福''饭''人''百''餐''茶''务''通''味''所''山''区''门''药''银''农',
                    '龙''停''尚''安''广''鑫''一''容''动''南''具''源''兴''鲜''记''时''机''烤''文''康',
                    '信''果''阳''理''锅''宝''达''地''儿''衣''特''产''西''批''坊''州''牛''佳''化''五',
                    '米''修''爱''北''养''卖''建''材''三''会''鸡''室''红''站''德''王''光''名''丽''油',
                    '院''堂''烧''江''社''合''星''货''型''村''自''科''快''便''日''民''营''和''活''童',
                    '明''器''烟''育''宾''精''屋''经''居''庄''石''顺''林''尔''县''手''厅''销''用''好',
                    '客''火''雅''盛''体''旅''之''鞋''辣''作''粉''包''楼''校''鱼''平''彩''上''吧''保',
                    '永''万''物''教''吃''设''医''正''造''丰''健''点''汤''网''庆''技''斯''洗''料''配',
                    '汇''木''缘''加''麻''联''卫''川''泰''色''世''方''寓''风''幼''羊''烫''来''高''厂',
                    '兰''阿''贝''皮''全''女''拉''成''云''维''贸''道''术''运''都''口''博''河''瑞''宏',
                    '京''际''路''祥''青''镇''厨''培''力''惠''连''马''鸿''钢''训''影''甲''助''窗''布',
                    '富''牌''头''四''多''妆''吉''苑''沙''恒''隆''春''干''饼''氏''里''二''管''诚''制',
                    '售''嘉''长''轩''杂''副''清''计''黄''讯''太''鸭''号''街''交''与''叉''附''近''层',
                    '旁''对''巷''栋''环''省''桥''湖''段''乡''厦''府''铺''内''例''元''购''前''幢''滨',
                    '处''向''座''下''晴''凤''港''开''关''景''泉''塘''放''昌''线''湾''政''步''宁''解',
                    '白''田''町''溪''十''八''古''双''胜''本''单''同''九''迎''第''台''玉''锦''底''后',
                    '七''斜''期''武''岭''松''角''纪''朝''峰''六''振''珠''局''岗''洲''横''边''济''井',
                    '办''汉''代''临''弄''团''外''塔''杨''铁''浦''字''年''岛''陵''原''梅''进''荣''友',
                    '虹''央''桂''沿''事''津''凯''莲''丁''秀''柳''集''紫''旗''张''谷''的''是''不''了',
                    '很''还''个''也''这''我''就''在''以''可''到''错''没''去''过''感''次''要''比''觉',
                    '看''得''说''常''真''们''但''最''喜''哈''么''别''位''能''较''境''非''为''欢''然',
                    '他''挺''着''价''那''意''种''想''出''员''两''推''做''排''实''分''间''甜''度''起',
                    '满''给''热''完''格''荐''喝''等''其''再''几''只''现''朋''候''样''直''而''买''于',
                    '般''豆''量''选''奶''打''每''评''少''算''又''因''情''找''些''份''置''适''什''蛋',
                    '师''气''你''姐''棒''试''总''定''啊''足''级''整''带''虾''如''态''且''尝''主''话',
                    '强''当''更''板''知''己''无''酸''让''入''啦''式''笑''赞''片''酱''差''像''提''队',
                    '走''嫩''才''刚''午''接''重''申''回''晚''微''周''值''费''性''桌''拍''跟''块''调''糕',
                    ]
        base_results = {}
        for k_new,v_new in enumerate(newttf.glyphOrder[2:]):
            for k_base, v_base in enumerate(basettf.glyphOrder[2:]):
                if basettf['glyf'][v_base] == newttf['glyf'][v_new]:
                    decrypt_woff_results['&#x' + v_new.strip('uni')] = base_str[k_base]
                    break
        return decrypt_woff_results


至此,字体库加密告一段落,拿着获得的编码和字符的对照字典,把 html 源码中的编码替换成对应的字符,就可以拿到正确的数据了。

4. 文字加密


字体库完成解密之后,你会发现,有些数据还是被加密着,比如地址,工作时间,评论中的部分文字也被加密了,且不能被字体库解密,如下图通过定位元素发现,这些被加密的文字,其实是图片,图片链接是一个 svg 文件。svg 文件的分析这里不做过多介绍了,网上有很多分析教程。关键代码如下,传入 svg 源码文件,返回类型 svg 解密的关键属性,x,y,文本,font-size

    def decrypt_svg(self,content):
     # content 传入svg文件源码
     # svg_data 将svg源码解析结果存入字典
        svg_data = {"fontsize""""decrypt": []}
        fontsize = 0
        try:
            fontsize = re.search(r'font-size:(\d+)px;', content).group(1)
        except Exception as e:
            print(e)
        svg_data.update({"fontsize": float(fontsize)})

        ypaths = re.findall(r'id=\"(.*?)\" d="M0 (\d+)', content)
        if ypaths:
            for (yid, y) in ypaths:
                textpath_pattern = re.compile(r'<textPath xlink:href=\"#{}\".*?>(.*?)<'.format(yid))
                # 记录x,y以及该行的文本
                svg_data["decrypt"].append({'x'0'y': y, 'text': textpath_pattern.findall(content)[0]})
        else:
            paths = re.findall(r'<text x="([\d ]+)" y="(\d+)">(.*?)<', content)
            for (x, y, decrypt_str) in paths:
                svg_data["decrypt"].append({'x': x, 'y': y, 'text': decrypt_str})
        return svg_data

     def class2str(self,classname,x,y,tagname=''):
      rst = ''
      for k,v in self.svg_json.items():
          # print(k)
          if classname.startswith(v.get('startswith')):
              fontsize = v.get('fontsize')
              decrypt = v.get('fontsize')
              for item in v.get('decrypt'):
                  # 根据css属性y值找到svg结果对照文本
                  if float(item.get('y')) >= float(y):
                      #  根据css属性x值/fontzize值,找到文本索引,取出值即为该类映射的字符
                      xindex = math.floor(float(x)/fontsize)
                      rst = item.get('text')[xindex]
                      # print('rst:',rst)
                      break
        return rst

同样,最后形成了类名和字符的映射关系,将源码中对应类名的标签替换成相应的文字即可完成破解

执行结果

5. 总结


  1. 文章为首次研究时记录的分析步骤,部分分析描述可能存在错误,大方向没问题
  2. 需要注意的是,不同的类前缀使用不用的 svg 文件,一定要注意区分,不然会错
  3. 使用字体库加密的文字也不只有数字,字体库中的字符,理论上都有可能用于加密
  4. 相同模块的加密方式,会经常变,有时候使用字体库加密,有时使用 svg 加密,有时混用,甚至有时不加密,在编写程序的时候应避免写‘死’
  5. 代码是从我封装好的类里直接 copy 过来,应该不能直接运行,有兴趣的伙伴请自行完善程序


文章转载自Python爬虫和数据挖掘,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论