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