大众点评是中国领先的本地生活信息及交易平台,也是全球最早建立的独立第三方消费点评网站。大众点评不仅为用户提供商户信息、消费点评及消费优惠等信息服务,同时亦提供团购、餐厅预订、外卖及电子会员卡等O2O(Online To Offline)交易服务。
通过上面的简单分析发现,大众点评的部分数字被加密,如何找到代码和数字的对应关系,成了我们的破解加密的关键,我们很容易发现,被加密的数字都是在d标签内,且具有共同的class属性:num, 那此时猜想,会不会是num通过某种规则,被js转换成数字?我们先尝试着用Google调试工具的全局搜索(ctrl+shift+f)num关键字,看会不会有什么惊喜的发现
第一个被检索出来的是一个css文件,尝试着点进去,发现如下css片段
@font-face {
font-family: "Microsoft YaHei";
src: url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/53cfe63b.eot");
src: url("//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"的标签,指定了字体库地址,此时大胆猜测,是不是使用了所谓的字体加密。为了验证猜想,我们需要:
下载地址 http://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/53cfe63b.woff
为了查看.woff文件内容,我们需要一个字体编辑工具FontCreator,(或者使用百度字体库的字体编辑工具:http://fontstore.baidu.com/static/editor/index.html#)发现文件内容长这样
,我们发现每个新字体上方都有个编码,咦,这个编码怎么有点熟悉,前面提到""代表数字0,
字体文件里也有个0,其上方代码是**$EC2D**,刚好是前面编码的后四位,这不正是我们要的映射关系吗?其实这个字体文件,就是web前端使用的字体库。
- 字体库文件记录了字体编码和字形的映射关系,字形相对稳定,编码可能会变,因此我们可以下载一份原始的字体库文件,形成字形和真实字符的映射关系,实际使用中,拿
新获取的字体库的字形和老字体库的字形作比较,从而得到新字体库字形和字符的映射关系,达到解密目的- 至于字形怎么比较,fonttools包帮我们做了这个事情(pip install fonttools )
- 如何形成初始的字形和字符的映射关系,大概有下面几种方式
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['' + v_new.strip('uni')] = base_str[k_base]
break
return decrypt_woff_results
至此,字体库加密告一段落,拿着获得的编码和字符的对照字典,把html源码中的编码替换成对应的字符,就可以拿到正确的数据了。
字体库完成解密之后,你会发现,有些数据还是被加密着,比如地址,工作时间,评论中的部分文字也被加密了,且不能被字体库解密
标签字体加密
通过定位元素发现,这些被加密的文字,其实是图片,图片链接是一个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'(.*?)<' .format(yid))
# 记录x,y以及该行的文本
svg_data["decrypt"].append({'x': 0, 'y': y, 'text': textpath_pattern.findall(content)[0]})
else:
paths = re.findall(r'(.*?)<' , 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
同样,最后形成了类名和字符的映射关系,将源码中对应类名的标签替换成相应的文字即可完成破解
- 文章为首次研究时记录的分析步骤,部分分析描述可能存在错误,大方向没问题
- 需要注意的是,不同的类前缀使用不用的svg文件,一定要注意区分,不然会错
- 使用字体库加密的文字也不只有数字,字体库中的字符,理论上都有可能用于加密
- 相同模块的加密方式,会经常变,有时候使用字体库加密,有时使用svg加密,有时混用,甚至有时不加密,在编写程序的时候应避免写‘死’
- 代码是从我封装好的类里直接copy过来,应该不能直接运行,有兴趣的伙伴请自行完善程序
- 本人小白,强烈欢迎大牛指点,交流学习
文档仅作学习和记录使用,请不要用于商业和违法用途,如有侵权,请告知删除。