js逆向分析实战之七麦数据

  • Post author:
  • Post category:其他




1.介绍:

要分析的网站是:

https://www.qimai.cn/rank


①当你打开网站,打开控制台的时候,网站有debugger检测,会陷入一个死循环,让你无法调试,如下图:

在这里插入图片描述
②在解决掉debugger问题后(稍后介绍如何解决),点击到你想爬取的数据页面如:

https://www.qimai.cn/rank/index/brand/paid/device/iphone/country/cn/genre/36/date/2018-11-11

查看ajax请求,你会看到你想要的数据,but每个重要请求都会有个analysis字段,此字段便是本文的重点分析对象。如下图:

在这里插入图片描述
下面正式介绍如何解决检测deugger及其analysis的逆向分析



2.准备工具:

抓包工具:fiddler、charles

浏览器:firefox、chrome

工具凭个人喜好选择



3.解决debugger检测:

①回到本文第一张图片,查看黄色框部分。查看堆栈,点击anonymous下的e,跳转到了

app.xxxxxxx.js

文件的debugger执行函数位置了,如下图:

在这里插入图片描述

②分析下

var a = [ 'r','e', 'g','g', 'u', 'b','e', 'd'].reverse().join('');

这个变量a不就是“debugger”嘛,往下继续自动执行递归函数e调用debugger,造成了无法让我们正常调试。

③ok!我们已经知道是哪个函数在搞的鬼了,也知道这个函数在哪个文件了。下边我们改如何注释掉这段函数呢?我的解决办法是抓包!关于抓包工具如何使用这里就不介绍了,不会的同学请自行百度“抓包https”

打开抓包工具,拦截app.xxxxxx.js文件注释掉以下代码块


!function e(n) { (1 !== ('' + n / n).length || 0 === n) && function () { }.constructor(a) (), e(++n) }(0)


如下图:

在这里插入图片描述
在这里插入图片描述
已经可以正常调试了



4.analysis逆向分析:

经过以上步骤已经可以正常调试了,下边我们讲下我的分析思路。


①定位analysis字段的生成位置(这一步有很多方法)


我的第一反应是全项目搜索,command+shift+f(mac版本firefox控制台快捷键) 搜索“analysis”,然而并没有搜到想要的结果。

尝试第二种办法,添加xhr断点,关键词api,即api.qimai.cn中的api,刷新网页。如下图:

在这里插入图片描述
分析下代码h为XMLHttpRequest对象,便是发出请求的最后一步,然后看下图右侧红框堆栈部分,如下图:

在这里插入图片描述
自上而下点击堆栈开始做断点吧,看看哪部分代码块有自己想要的线索。

花费好长时间,最终在get堆栈定位到

app.xxxxx.js

文件的以下位置,找到了“analysis”,继续断点得出r便是analysis的值。r由两个函数l.d和l.h处理o后得出

在这里插入图片描述


②解决变量o


分析下代码:这个变量o初始值不就是url的参数值然后做了个排序,然后拼成的字符串嘛,如上图url:


www.qimai.cn/rank/marketRank/market/3/category/-2/country/cn/collection/topselling_free/date/2018-11-13—–

>[3,-2,cn,topselling_free,2018-11-13],

然后做了个排序拼接在一起就是-22018-11-113cntopselling_free,这里经过一个l.d函数,让我们看下这个函数做了什么事,选中l.d进去h(a)函数:

在这里插入图片描述

        function h(a) {
            return x()(encodeURIComponent(a).replace(/%([0-9A-F]{2})/g, function(a, e) {
                return r("0x" + e)
            }))
        }

又出来个x(),继续步进,到了下图位置,下图命名为图1

在这里插入图片描述

t就是上述的o,也就是url参数;

console下e.from(t.toString(),“binary”)获得了一个数组, 赋值给了n

n.toSting(‘base64’)即是加密后的o

下面我们看看七脉是如何给o进行加密的

首先我们先看看是如何获得的数组,先进入e.from函数,又到s函数,参数依然是url的参数,这网站弄的真够绕的,下图命名为图2

在这里插入图片描述
继续进入s函数,传参url参数,下图命名为图3:

在这里插入图片描述

分析下上图中f函数参数,重点来了!!!!!!!

t=null,

e=url参数

n=“binary”,

函数o(t,r)生成了一个长度和url参数等长度且全部为0的数组,并赋值给t

继续进入t.write(e,n)

下图命名为图4

在这里插入图片描述

继续write函数调用k函数,进入k函数传参t=url参数,e=“binary”, n =undefined!

下图命名为图5

在这里插入图片描述

k调C,C调K(W(e)),console下W(e)得到了数组,这个数组就是图1中出现的数组!!!!继续步进进入W看看url参数怎么转成数组的,下图命名为图6

在这里插入图片描述

            function W(t) {
                for (var e = [], n = 0; n < t.length; ++n)
                    e.push(255 & t.charCodeAt(n));
                return e
            }


遍历t,获取每个下标元素对应的 ASCII 数值,然后和255进行&运算。charCodeAt 和python中ord函数作用相同


ok!步出!步进看K函数如何将W得到的数组进行加密的

下图命名为图7

在这里插入图片描述

K函数并没有对此数组进行操作,return i; i的数值和e相同!!

至此我们得到了图1中的数组,将数组进行一个toString(‘base64’)操作即是加密结果

如果至此你已经蒙圈了,可以将断点打到图一中的n.toString(‘base64’),为了防止其他断点的干预,我习惯将其他断点先关闭,然后刷新,重新看数组如何变成base64的。

如果你还很清楚,那直接步出步出步出。。。,到图1所示代码块,如下状态

在这里插入图片描述

继续步进

在这里插入图片描述

进入到M

在这里插入图片描述
console下Q.fromByteArray(t),即是加密后的o,进入到fromByteArray

在这里插入图片描述

以下函数是对数组加密,即生成base64字符串的过程,直接调用u即可

//对数组进行加密
        function u(t) {  //用到了s
            for (var e, n = t.length, r = n % 3, i = "", o = [], a = 16383, u = 0, c = n - r; u < c; u += a)
                o.push(s(t, u, u + a > c ? c : u + a));
            return 1 === r ? (e = t[n - 1],
            i += l[e >> 2],
            i += l[e << 4 & 63],
            i += "==") : 2 === r && (e = (t[n - 2] << 8) + t[n - 1],
            i += l[e >> 10],
            i += l[e >> 4 & 63],
            i += l[e << 2 & 63],
            i += "="),
            o.push(i),
            o.join("")
        }
        
	function s(t, e, n) { //用到了a
	    for (var r, i = [], o = e; o < n; o += 3) r = (t[o] << 16 & 16711680) + (t[o + 1] << 8 & 65280) + (255 & t[o + 2]),
	        i.push(a(r));
	    return i.join('')
	}
	
	function a(t) { //用到了l
    return l[t >> 18 & 63] + l[t >> 12 & 63] + l[t >> 6 & 63] + l[63 & t]
}

//l就是A-Za-z0-9+ 生成l
for (var l = [], c = [], f = 'undefined' != typeof Uint8Array ? Uint8Array : Array, d = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', h = 0, p = d.length; h < p; ++h) l[h] = d[h],
    c[d.charCodeAt(h)] = h;
c['-'.charCodeAt(0)] = 62,
    c['_'.charCodeAt(0)] = 63

综合W函数得到以下js

命名为

qimai.js


经过u函数后,便拿到了加密后的o了,剩下的分析便大同小异了,没什么难度了。


    function W(t) {
    //t为排序后url参数值,返回数组
    t = t.toString()
    for (var e = [], n = 0; n < t.length; ++n) e.push(255 & t.charCodeAt(n));
    return e
}


//u函数将数组转化为base64
for (var l = [], c = [], f = 'undefined' != typeof Uint8Array ? Uint8Array : Array, d = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', h = 0, p = d.length; h < p; ++h) l[h] = d[h],
    c[d.charCodeAt(h)] = h;
c['-'.charCodeAt(0)] = 62,
    c['_'.charCodeAt(0)] = 63

function a(t) {
    return l[t >> 18 & 63] + l[t >> 12 & 63] + l[t >> 6 & 63] + l[63 & t]
}

function s(t, e, n) {
    for (var r, i = [], o = e; o < n; o += 3) r = (t[o] << 16 & 16711680) + (t[o + 1] << 8 & 65280) + (255 & t[o + 2]),
        i.push(a(r));
    return i.join('')
}

function u(t) {

    for (var e, n = t.length, r = n % 3, i = '', o = [], a = 16383, u = 0, c = n - r; u < c; u += a) o.push(s(t, u, u + a > c ? c : u + a));
    return 1 === r ? (e = t[n - 1], i += l[e >> 2], i += l[e << 4 & 63], i += '==') : 2 === r && (e = (t[n - 2] << 8) + t[n - 1], i += l[e >> 10], i += l[e >> 4 & 63], i += l[e << 2 & 63], i += '='),
        o.push(i),
        o.join('')
}

//直接传入排序后的url参数值,返回base64
function decrypt(o) {
    return u(W(o))
}


下边附上Python完整版本Demo

import execjs
import time

class Decrypt():
    def __init__(self):
        js_path = ’qimai.js'  #上述的qimai.js
        f = open(js_path)
        js = f.read()
        self.ctx = execjs.compile(js)
        f.close()

    def __f(self, a):
        '''
        解密第一步
        :param a:
        :return:
        '''
        e = 'a12c0fa6ab9119bc90e4ac7700796a53'
        a = list(a)
        for i in range(len(a)):
            a[i] = chr(ord(a[i]) ^ ord(e[i % len(e)]))
        return ''.join(a)

    @classmethod
    def decrypt(cls, tags, params={}):
        '''
        :param tags: url的分类 如应用宝页面 https://www.qimai.cn/rank/marketRank  则传入rank/marketRank
        :return:
        '''
        self = cls()

        now_date = round(time.time() * 1000)

        t = now_date - 708 - 1515125653845

        params_ = ''
        if params:
            o_ = ''.join(sorted(list(params.values())))
            params_ = self.ctx.call('decrypt', o_)
        o = '%s@#/%s@#%s@#1' % (params_, tags, t)
        s = self.__f(o)
        analysis = self.ctx.call('decrypt', s)
        return analysis
 if __name__ == '__main__':
    now_date = time.strftime('%Y-%m-%d', time.localtime())
    params_list = [
        ('rank/marketRank', {'market': '1', 'category': '6', 'date': now_date}, '360'),
        ('rank/marketRank', {'market': '3', 'category': '-2', 'date': now_date}, '应用宝'),
        ('rank/marketRank', {'market': '5', 'category': '5', 'date': now_date}, '豌豆荚'),
        ('rank/marketRank', {'market': '4', 'category': '6', 'date': now_date}, '小米'),
        ('rank/marketRank', {'market': '7', 'category': '7', 'date': now_date}, '魅族'),
        ('rank/marketRank', {'market': '9', 'category': '4', 'date': now_date}, 'oppo'),
        ('rank/release', {'genre': '36', 'date': now_date}, 'App Store')
    ]
    for tags, params, store in params_list:
        analysis = Decrypt.decrypt(tags, params)
        print(analysis)

明天附上github



版权声明:本文为qq_32735511原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。