初探js逆向(二)

前言

如果你是没有任何 js 逆向经验的爬虫萌新,且没看过上篇的《初探js逆向》,建议先移步去看,因为本篇所使用的案例相对上篇,难度会更大一些。

在上一篇中,我们学习了一个入门案例的逆向流程。然而,加密的方式有很多,不可能以一概全。在进入本篇正题前,我们先回忆一下上篇的逆向思路:

不妨思考一下,参数虽做了加密,但网页毕竟要正常显示内容,所以在网页渲染的过程中,一定有个地方对这个参数做了解密,然后将数据写入html。

也就是说,我们需要在网页渲染的过程里,一步步观察,看看到底是哪个位置对这个参数做了解密。

其实,这个思路有个前提:加密参数必须是请求返回的结果参数。如果网站在请求发起时就对请求参数做了加密,这个思路就不管用了。

另外,在上篇中,因为案例比较简单,在找到解密函数之后工作就完成了 90%,所以抠代码的部分我们一笔带过。而本篇的案例,即便找到了加密位置,可能也只完成了一半工作。

所以本篇将以七麦数据这个网站为例,介绍‘当请求参数被加密时的逆向思路’以及‘抠代码’的正确姿势。

网站分析

访问 https://www.qimai.cn/rank/marketRank/market/3/category/-2/date/2019-05-18 这个地址,可以看到:

1558176719120

红框中的App榜单列表即为我们的目标数据。来看看它发起了哪些请求:

1558176929238

marketRank 这个请求的响应内容里能够找到我们的目标数据,而且是清晰的 json 结构。但不要高兴得太早,我们再看一下它的请求参数:

1558177495621

market(3)category(-2)date 分别表示应用商店(应用宝)、类别(全部游戏)和日期,想构造它们都很简单,为了后文描述方便,我们暂且称之为‘简单三参’。

需要重点关注的是这个 analysis,它是一个被加密的必选参数,请求时必须携带,否则无法正常返回数据。这时候你可能会想:“那我直接把它拷贝下来,模拟请求时再带上不就行了?”,然而,只要你稍微分析下就会发现,这个参数并不是固定的,它会随着简单三参的变化而改变。且这类榜单数据通常具有时效性,如果你想进行批量持久地爬取有效数据,这种‘提前收集 analysis ’的方式是不现实的。

说了这么多,好像有些偏离主题了,我们的目的不是爬取网站,而是学习 js 逆向。

下面进入正题。

逆向思路

我们先用上篇中提到的方式,在xhr请求里打上断点,刷新一下网页。

1558180850101

代码执行到了 h.send(f),等等,好像哪里不对。send 不就是把请求发送出去吗?那是不是意味着请求参数在这步之前就已经生成完毕?观察一下上面几行代码,果然如此:

1558181366544

t 对象的 url 属性中可以看到,analysis 已经生成好了。那么我们再往下执行也没意义了,因为这个请求已经被发送到了服务端,客户端没必要再对它的参数进行解密。况且我们的目的是研究如何生成 analysis,而不是如何解密。

那怎样才能找到生成 analysis 的位置呢?我们可以先把它的值记录下来,

1
fGR5SX10dQ0oY3lVeGIkBH1HDQ1wExcWVlYPG1sAQltVRGJRXlMkFAxXBANUAQUHBQQFcBtV

然后把断点打在一个比较早的地方(analysis 生成之前),一步步往下执行,这个值首次出现的位置,就是它生成的位置。

想把断点打在 analysis 生成之前,可以在 Network 选项卡下,该请求的 Initiator 列里看到它的调用栈,调用顺序由上而下:

1558182881027

在前几条里随便选一个,点进去打上断点,这里我看get比较顺眼,就选了这条,并在默认位置打上断点。打上断点后别急着刷新网页,这里有个坑需要先避一下。因为除了marketRank之外,marketList这个请求也有一个 analysis 参数,它请求的是应用商店列表(百度、应用宝、360…)。为了避免干扰,我们不要刷新网页,而是切换类别:

1558184074261

通过这种方式来触发调试界面,就不会再去请求应用商店列表了。切换类别后即可触发弹出调试界面:

1558183179819

可以发现,简单三参都已经出现了,但还没发现 analysis,它应该还没生成,让我们继续往下执行。注意,执行过程中时刻关注是否出现类似(切换了类别,analysis肯定会变化,所以是类似)我们之前记录下来的那个值。

此处省略一万步调试……,只要你耐心足够,就能找到下图中的代码:

1558184945255

可以看到,r 的值与我们之前记录的值很类似,为了进一步确定,可以在调试执行完成后看下请求中的 analysis 值是否与这个 r 的值一致。

答案是一致的。也就是说,接下来只要把 r 的生成代码全部抠下来,我们就能生成 analysis 了。感觉也没那么难对吧?其实这个网站,抠代码才是重头戏。

抠代码

开头中提到,本案例即便找到了加密位置,也只完成了一半工作。因为加密函数中做了大量的代码混淆和迷惑眼球的函数调用,想把它完整抠下来也不是件容易的事。如果你没有强大的心脏和足够的耐心,请止步于此;但如果你就喜欢折腾,请接着往下看。

鉴于本文的目的主要在于介绍‘请求参数被加密时的逆向思路’,所以不会对‘抠代码’的部分做详细讲解,也不会提供完整代码,但会稍微做一些提示,希望能对你有所帮助。以下提示内容只有真正去尝试抠代码的人才能看懂了。

  1. b 写死。
  2. 抠出 e 的生成函数,生成 e
    • 时间戳与difftime(写死好像不影响)
  3. 抠出 m 的生成函数,通过简单三参+URL 后缀+e 生成 m
    • 简单三参 sort 与 join
    • 忽略迷惑代码 a[no](n)[Ao]...
    • 进入 e 内部打断点
    • 只关注被调用并执行的代码
    • new一个Unit8Array
    • 找到写入Unit8Array的代码
    • 转base64
    • 封装f[La],便于步骤4调用
  4. 抠出 r 的生成函数,通过 mb 生成 r
    • 搞定步骤3之后心态千万不要崩,因为胜利就在眼前
    • 几个写死的变量
    • String["fromCharCode"]

贴张逆向成功的截图:

1558189992861

总结

当请求的参数被加密时,将断点打在加密参数生成之前,在它的值首次出现的位置找到它的加密函数。

抠代码时,如果代码做了大量混淆。需要辨别哪些代码是迷惑你的,哪些代码是真正起作用的,哪些代码是不需要抠的,哪些代码是可以自己用其他方式替代的。