专栏首页日常学python数据抓取实践:对加密参数及压缩混淆 JS 的逆向分析

数据抓取实践:对加密参数及压缩混淆 JS 的逆向分析

转载自 https://www.v2ex.com/t/455492#reply15 如需要转载请联系作者本人

本文会介绍几种分析技巧,需要一点前端知识(总感觉在前端做防爬没什么意义,因为源码都是公开的)。文末附上爬虫 Demo 验证,虽然对于这个案例来说使用 Selenium 可能才是合适的解决方法,但暴力破解才是男人的浪漫!...嗯本文的重点只是在于分析解密的过程。有些图片因代码过长未包含在内,意会即可。

1. 一夫当关 - XHR Breakpoints

网站是七麦数据。我们要抓取的内容是页面上的 App Store 排行榜数据。

通过分析网络请求我们可以发现,榜单数据是通过 Ajax 请求来获取的。返回的数据格式是明文 Json。

请求参数如下:

analysis: dDB4Fi8wUEF...(太长,略)
brand: all
country: cn
device: iphone
genre: 36
date: 2018-05-14
page: 1

各个参数所代表的含义都较为易懂,除了 analysis。猜测是一个经过 Base64 编码后的加密参数,事实上的确如此,隔一段时间再利用相同的 analysis 提交请求时会被拒绝。

要解密参数,只能去看 JS 的加密代码。我们需要查看是哪部分的 JS 代码发起了请求,一般的方法是点击请求列表的 Initiator 跳转到代码部分。

不过我更推荐另一种方法:添加 XHR Breakpoints 拦截请求。这样有两个好处,一是可以直接观察代码上下文的变量在发出请求前的数值,二是方便直接调试。此处填入 URL 包含的关键词 indexPlus

我们也可以在 Watch 处添加变量 h 进行观察,得知 h 是一个 XHR 对象。

然后再来看代码……写得乱七八糟的是什么鬼!

2. 穿针引线 - Module Require

为了应对 Web 应用越来越复杂的趋势,前端趋向模块化开发,各种自动化构建工具成为必不可少的开发利器。跳蚤大神说得好呀,若想真正掌握爬虫技术,就要了解一个网站是怎样构建起来的。在此意义上,学习爬虫所需的前置知识还挺多的。

来观察这堆 JS 中的一段代码。

"7O1s": function(t, e, n) {
    var r = n("DIVP")
        , i = n("XSOZ")
        , o = n("kkCw")("species");
    t.exports = function(t, e) {
        var n, a = r(t).constructor;
        return void 0 === a || void 0 == (n = r(a)[o]) ? e : i(n)
    }
},
"7UMu": function(t, e, n) {
    var r = n("R9M2");
    t.exports = Array.isArray || function(t) {
        return "Array" == r(t)
    }
},
"7gX0": function(t, e) {
    var n = t.exports = {
        version: "2.5.5"
    };
    "number" == typeof __e && (__e = n)
},

虽然代码经过了混淆,但是依然可以看出类似 7O1s 之类的随机字符串是模块名称,而 var r = n("DIVP") 即引入模块,正常的写法可能是 import a from 'b' 或者 const a = require('b')

这里发起 Ajax 请求的函数很可能只是一个被封装了的模块供整个项目调用,粗略看一下函数代码也没有发现计算加密的部分。

针对这种模块化开发,一个逆向的思路是,只要查看该模块被引用的情况,不断向上追溯,总能找到最初发起请求和加密的函数。

将网站所有 JS 文件拷贝到本地,检索断点所在的模块名 7GwW

得知其由模块 KCLY 引入,接着检索 KCLY

得知其有三个模块引入,不怕,我们先选第一个XmWM继续检索,如果没找到再回来

得知其由模块 tIFN 引入,继续检索,由mtWM引入,继续检索

得出最终结果,是由模块 gXmS 组装的请求参数。

只要找到组装请求的代码,分析过程就算完成了一半。

3. 顺藤摸瓜 - Call Stack

可能有人要抱怨了,感觉这样查找好麻烦呀,有没有更简便的方法?

当然有。在理解了第二点分析的模块化组织代码的原理后,我们可以使用更简便的方法——Call Stack。

如图,通过从上至下依次查看调用栈上的代码,检查一下其所在的模块是否是要查找的目标。

get 方法即是模块 gXmS 中的函数。需要注意的是,此方法在模块的末尾,如果不细心可能会错过。

4. 偷天换日 - Hijack The Response

先在目标模块 gXmS 下个断点吧

可以看到虽然变量 f 被很机智地用 Base64 重新编码了(不完全是,还有个解密函数,注意这里的 p.g 和 p.a),但是在调试器下其解码值 analysis 很容易暴露。

不过重点还是在 l.a.interceptors.request.use 的那行,里面是完整的参数组装过程,附这一段代码:

l.a.interceptors.request.use(function(a) {
    try {
        var n = +new Date - (h.difftime ? h.difftime : 0) - 1515125653845
            , e = ""
            , t = [];
        return void 0 === a.params && (a.params = {}),
        m()(a.params).forEach(function(n) {
            if (n == f)
                return !1;
            a.params.hasOwnProperty(n) && t.push(a.params[n])
        }),
        t = t.sort().join(""),
        t = Object(p.d)(t),
        t += "@#" + a.url.replace(a.baseURL, ""),
        t += "@#" + n,
        t += "@#1",
        e = Object(p.d)(Object(p.g)(t)),
        -1 == a.url.indexOf(f) && (a.url += "?" + f + "=" + encodeURIComponent(e)),
        a
    } catch (a) {}
}, function(a) {
    return i.a.reject(a)
}),

代码被用逗号运算符精简了。由于 Chrome 调试器的单步执行是以表达式为单位,因此这里我们无法对重要的变量 t 的每一步转换进行观察调试。

我们需要对代码进行修改,然后让浏览器运行修改后的代码。方法是劫持 JS 文件。具体手段有很多种,比如 Nginx 反代,Fiddler 等。在此我选择 ReRes 插件,用法很简单就不具体介绍了。

劫持后,我们可以随意更改代码以方便输出获取我们想要的信息,如下图我加了一个 v1 中间变量。

通过单步调试后,得出组装的过程,大致步骤如下:

  1. 设置一个时间差变量
  2. 提取查询参数值(除了 analysis)
  3. 排序拼接参数值字符串并 Base64 编码
  4. 拼接自定义字符串
  5. 自定义加密后再 Base64 编码
  6. 拼接 URL

那么如何得知自定义加密函数和 Base64 编码函数?

通过这一行 var v1 = Object(p.d)(t); 我们可得知 p.d 是 Base64 编码函数,因为单步调试时可观察到变量 t 经过这行代码后被编码。

然后看这一行 e = Object(p.d)(Object(p.g)(t));,我们已知 p.d 是 Base64 编码函数,那么 p.g 就是一个未知函数。需要找到 p.g 的函数本体。

在 Watch 一栏添加 p.g,点击 FunctionLocation 的值,即可跳转到该函数。其实 p.g 同时也是一个解密函数,异或运算经常被用来加解密。

5. 东搜西罗 - Searching

这里再多说一点技巧。由于模块化开发,可能会引用到第三方库的模块,因此我们可以利用模块中的一些附带字符串在网上搜索也许会得到额外的信息。

搜索相关错误信息后得知网页用了 Node.js 的 Buffer 模块。

进一步分析还发现引入 Buffer 模块的目的之一就是为了方便 Base64 编码。

6. 一锤定音 - Crawler

最后写一个50行的简单爬虫来验证分析,抓取 iPhone 免费榜单。

#!/usr/bin/env python3

import time
import json
import base64
import requests
from urllib.parse import urlencode


headers = {
    "Accept": "application/json, text/plain, */*",
    "Referer": "https://www.qimai.cn/rank",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/59.0"
}

params = {
    "brand": "all",
    "country": "cn",
    "date": "2018-05-14",
    "device": "iphone",
    "genre": "36",
    "page": 1
}


# 自定义加密函数
def encrypt(a, n="9d1abd758c043319aee5ee1c0e3f26c6"):
    s = list(a)
    n = list(n)
    sl = len(s)
    nl = len(n)
    for i in range(0, sl):
        s[i] = chr(ord(s[i]) ^ ord(n[i % nl]))
    return "".join(s)


def main():
    # iPhone 免费榜单
    # 提取查询参数值并排序
    s = "".join(sorted([str(v) for v in params.values()]))
    # Base64 Encode
    s = base64.b64encode(bytes(s, encoding="ascii"))
    # 时间差
    t = str(int((time.time() * 1000 - 1515125653845)))
    # 拼接自定义字符串
    s = "@#".join([s.decode(), "/rank/indexPlus/brand_id/1", t, "1"])
    # 自定义加密 & Base64 Encode
    s = base64.b64encode(bytes(encrypt(s), encoding="ascii"))
    # 拼接 URL
    params["analysis"] = s.decode()
    url = "https://api.qimai.cn/rank/indexPlus/brand_id/1?{}".format(urlencode(params))
    # 发起请求
    res = requests.get(url, headers=headers)
    j = json.loads(res.text)
    print(j)


if __name__ == '__main__':
    main()

ps:如果觉得文章不错的话,欢迎随手点赞转发支持

本文分享自微信公众号 - 日常学python(daily_learn)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 某网站js混淆结构简单分析

    经过测试,他直接扣出来也能跑,不过我们看下他这个奇怪的混淆思路,我们分析然后记下来,方便我们以后写混淆工具的时候作为其中一种方式,为我们所用。

    sergiojune
  • 爬取《悲伤逆流成河》猫眼信息 | 郭敬明五年电影最动人之作

    知道《悲伤逆流成河》上映还是在qq空间看见学弟发了说说,突然想起初中追小四的书,每天看到晚上10点多,昨天看了枪版的《悲伤逆流成河》,整个故事情节几乎和小说一模...

    sergiojune
  • 用python来分析一波股票

    Python是一门广泛在各个行业应用的语言,包括计算机,生物学,金融。可以说,python除了不会生孩子,其他啥都行。本文将使用python来玩转股票数据,让你...

    sergiojune
  • 今日 Paper | 人脸数据隐私;神经符号推理;深度学习聊天机器人等

    为了帮助各位学术青年更好地学习前沿研究成果和技术,AI科技评论联合Paper 研习社(paper.yanxishe.com),推出【今日 Paper】栏目, 每...

    AI科技评论
  • 自学社交的人工智能,会不会有一天取人类而代之?

    镁客网
  • R语言实现:基于GARCH模型的股市危机预警

    作者:huozi07 http://blog.csdn.net/huoz07/artile/details/48176587 为防范股票市场上的不确定性和风险,...

    机器学习AI算法工程
  • Java web开发,在一个jsp里放太多java代码的后果,摘自 java web轻量级开发面试教程

    现要做一个简单的登录页面,如果用户通过验证,会显示Welcome用户名的欢迎词,反之则返回登录页面让用户再次输入 这部分的完整代码是JSPDemo项目里的log...

    用户1153489
  • 马蜂窝联合腾讯文旅联合发布《2020年春节旅游趋势报告》

    ? 2020年1月10日,春运的号角正式吹响。这场全球年度最大规模的人口“迁徙”,也拉开了春节假期的序幕。 伴随着人们生活水平的提高,越来越多的国人在春节期间...

    腾讯文旅
  • 二氢卡宾单重态和三重态稳定性的比较

    当然,教材上的图是重绘的,原图来自J. Am. Chem. Soc. 1996, 118, 9908-9914。本文就试着重复该图,并将关于自旋极化单重态的计算...

    用户7592569
  • Wolfram|Alpha自然语言帮你做计算系列(04)四:函数单调性判定、极值点、拐点、驻点、鞍点、极值与最值的计算

    本文将以具体实例形式,介绍线上判定一元函数的单调性,计算单调性区间的分界点、极值点与拐点,一元函数的极值与最值;判定多元函数的极值点、鞍点以及无条件极值、条件极...

    WolframChina

扫码关注云+社区

领取腾讯云代金券