系列文章目录
- [Python] js逆向初探: 某麦榜单
免责声明
本篇文章仅用学习交流和日常记录,请勿转载或用于任何商业用途!违者责任自负!如侵删
前言
作为一个刚接触爬虫的小白, 之前都是练习爬一些简单的网站。遇到js加密的就只能束手无策了, 有一些可以使用selenium, 但是速度太慢,而且人生不可以总是选择轻松地道路,学习爬虫不可以避免学习js逆向。所以决定开始自己学习js逆向,本文章选择某麦网作为练习对象, 本文章仅做学习记录, 供日后参考。请勿转载或用于任何商业用途!违者责任自负!如侵删
分析
目标网站:https://www.qimai.cn/rank
此处以iPhone总榜为例, 页面如下:
首先F12, 刷新, 打开rank。嗯, 空空如也。再搜一下淘特
, 还是没有, 再试Taobao
, 这次找到了!(TODO: 这里有一个问题,为什么我搜淘特搜不到, 搜淘特的unicode ‘\u6dd8\u7279’ 也搜不到, 有没有大佬可以教教我)
数据都是明文json,1, 0, 2分别是免费, 付费, 畅销的榜单数据
我们可以看到这里只有一个请求参数analysis
。也就是说我们得到analysis
就可以得到数据。显然这个参数加密了。试着用base64 decode, 得到一堆垃圾
xGD[YTU@4[MTQ^ToYT $ UTxG
好了,事情没那么简单了, 来找找analysis
从哪来的。
通用的抓包方法。。。
搜一下, 没有, 在source里打XHR/fetch Breakpoint。
接下来就是一番顺藤摸瓜, 暂时按下不表。因为我没用这个方法, 我也不太会。。。TODO: 有空试一下
偷懒作弊的抓包方法
我用了另一个神器 ast-hook-for-js-RE。膜拜大佬, 点个star!具体使用方法就不赘述了, 可以看大佬的github,改天可能我也会写一个教程, 这里如果没有链接的话说明我还没写。。。
万恶之源
经过一番操作, 找到了analysis生成的js代码, a的值就是analysis
把这段js复制到pycharm, 运行(如何在pycharm里运行js,等我有时间再写), 嗯肯定会报错. 因为代码混淆压缩过, 没关系, 我们已经找到了analysis生成的位置, 我们只要把代码补全就好了. 虽然看着恶心, 耐心多打几个断点慢慢看是可以看懂的. 首先, 把try catch删掉, 这个if判断看着也没啥用,删掉!
function(e) {
var t = (0, n.ej)(u);
f = c.default.prototype.difftime = -(0, n.ej)(m) || +new Date - 1e3 * t
var a, o = +new Date - (f || 0) - 1515125653845, r = [];
return void 0 === e.params && (e.params = {
}),
Object.keys(e.params).forEach((function(t) {
if (t == h)
return !1;
e.params.hasOwnProperty(t) && r.push(e.params[t])
}
)),
r = r.sort().join(""),
r = (0, n.cv)(r),
r += d + e.url.replace(e.baseURL, ""),
r += d + o,
r += d + 1,
a = (0,n.cv)((0,n.oZ)(r, l)),
-1 == e.url.indexOf(h) && (e.url += (-1 != e.url.indexOf("?") ? "&" : "?") + h + "=" encodeURIComponent(a)),
e
}
大概是这样, 还是很恶心的.虽然大二学过js,但是已经忘得差不多了, 这个 (0, n.ej)(u);
是什么鬼?这是什么语法?打断点试试, 发现是调用了一个函数, input是e = "synct"
function u(e) {
//e = "synct"
var t, a = new RegExp("(^| )" + e + "=([^;]*)(;|$)"); //t = (4) [" synct=1626088744.311;", " ", "1626088744.311", ";", index: 980, input: "qm_check=SxJXQEUSChd2fHd1dRQQeV5EVVwcEHxZRlVVGGYRE…lejc2Qhu7%2BLw;
return (t = document.cookie.match(a)) ? unescape(t[2]) : null
}
大概意思就是用正则在cookie中匹配出t, 然后输出unescape(t[2]). 我们可以看到返回值是1626088744.311
, 看着像时间戳, 在线转化一下试一下
果然是时间戳, 而且就是当前的时间. 所以 var t = current time 也就是cookie中synct的值.
var t = (0, n.ej)(u); // cookie 中 synct的值
继续下一行,
f = c.default.prototype.difftime = -(0,n.ej)(m) || +new Date - 1e3 * t
这里是把-(0,n.ej)(m) || +new Date - 1e3 * t
赋值给 f
和 c.default.prototype.difftime
.
-(0,n.ej)(m)
这个和t
一样, 调用函数ej, 传入参数m. 单步调试可以得到这里传入的是syncd
, 也就是说在cookie中匹配到syncd
的值, 加负号. 在这里我踩了一个坑, 因为我发现第一次访问得到的cookie中没有syncd
, 于是我这个小白花了一上午试图在这堆恶心的代码中找出syncd
值生成的地方. 无果之后我发现, 这里赋值用的是 “|| 或”. 我根本不用去管前面, 直接通过后面的计算结果赋值就可以了. 所以
f = +new Date - 1e3 * t
后面以此类推, 补全n.cv 和 n.oZ即可, 把函数名改一改,整理一下,最后得到
function rematch(e, cookie) {
// e = "synct"
// var cookie = 'PHPSESSID=pnf2q6jupfr40lsel4hjg9hnpn; qm_check=SxJXQEUSChd2fHd1dRQQeV5EVVwcEHxZRlVVGGYREGV4dBB3QlRHWllaQxQOAwAQdFlCVVZDAXQIARROQ28FbwAQQEZoB28JHBR8A3QBAR0CBxsFAh4IAAQWCQEGCBkSHBdUWlVaWxYCEgAcABwAHAUbAhJE; gr_user_id=00af8731-c2cb-4c61-8d15-d630f53b7be0; ada35577182650f1_gr_session_id=8e8d8095-b4d9-461b-a92d-b7896498f164; ada35577182650f1_gr_session_id_8e8d8095-b4d9-461b-a92d-b7896498f164=true; synct=1625827840.059; syncd=-260';
var t, a = new RegExp("(^| )" + e + "=([^;]*)(;|$)"); //t = undefined, a = /(^| )synct=([^;]*)(;|$)/
return (t = cookie.match(a)) ? unescape(t[2]) : null
}
function cv(e) {
return function(e) {
try {
return btoa(e)
} catch (t) {
return Buffer.from(e).toString("base64")
}
}(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g, (function(e, t) {
return i("0x" + t)
}
)))
}
function oZ(e, t) {
t || (t = s());
for (var a = (e = e.split("")).length, n = t.length, o = "charCodeAt", r = 0; r < a; r++)
e[r] = i(e[r][o](0) ^ t[(r + 10) % n][o](0));
return e.join("")
}
function get_analysis (e) {
var t = rematch('synct', e.cookie);
var f = -rematch('syncd', e.cookie) || +new Date - 1e3 * t
console.log(t, f)
var a, o = +new Date - (f || 0) - 1515125653845, r = [];
return void 0 === e.params && (e.params = {
}),
Object.keys(e.params).forEach((function(t) {
if (t == h)
return !1;
e.params.hasOwnProperty(t) && r.push(e.params[t])
}
)),
r = r.sort().join(""),
r = (0, cv)(r),
r += d + e.url.replace(e.baseURL, ""),
r += d + o,
r += d + 1,
a = (0, cv)((0, oZ)(r, l)),
-1 == e.url.indexOf(h) && (e.url += (-1 != e.url.indexOf("?") ? "&" : "?") + h + "=" + encodeURIComponent(a)),
e
}
var res = {
baseURL: "https://api.qimai.cn", url: "/rank/indexPlus/brand_id/0", cookie = "PHPSESSID=pnf2q6jupfr40lsel4hjg9hnpn; qm_check=SxJXQEUSChd2fHd1dRQQeV5EVVwcEHxZRlVVGGYREGV4dBB3QlRHWllaQxQOAwAQdFlCVVZDAXQIARROQ28FbwAQQEZoB28JHBR8A3QBAR0CBxsFAh4IAAQWCQEGCBkSHBdUWlVaWxYCEgAcABwAHAUbAhJE; gr_user_id=00af8731-c2cb-4c61-8d15-d630f53b7be0; ada35577182650f1_gr_session_id=8e8d8095-b4d9-461b-a92d-b7896498f164; ada35577182650f1_gr_session_id_8e8d8095-b4d9-461b-a92d-b7896498f164=true; synct=1625827840.059; syncd=-260" params:{
brand_id: "0"}};
console.log(get_analysis(res))
run! 见证奇迹吧!!!
{
baseURL: 'https://api.qimai.cn',
url: '/rank/indexPlus/brand_id/0?analysis=dSUJCyETH0JRXlsfUQpTXRxkWhRDH1JCUV5UZw1TF1R0FVABAAkGBQMGAFEODCQXBw%3D%3D',
cookie: 'PHPSESSID=pnf2q6jupfr40lsel4hjg9hnpn;qm_check=SxJXQEUSChd2fHd1dRQQeV5EVVwcEHxZRlVVGGYREGV4dBB3QlRHWllaQxQOAwAQdFlCVVZDAXQIARROQ28FbwAQQEZoB28JHBR8A3QBAR0CBxsFAh4IAAQWCQEGCBkSHBdUWlVaWxYCEgAcABwAHAUbAhJE;gr_user_id=00af8731-c2cb-4c61-8d15-d630f53b7be0;ada35577182650f1_gr_session_id=8e8d8095-b4d9-461b-a92d-b7896498f164; ada35577182650f1_gr_session_id_8e8d8095-b4d9-461b-a92d-b7896498f164=true; synct=1625827840.059; syncd=-260',
params: {
brand_id: '0' }
}
analysis 的值就在 url 中, 尝试使用这个参数获取数据, 可以得到
{
'code': 10000, 'msg': None, 'maxPage': 29, 'rankInfo': [{
'index': 1, 'appInfo': {
'appId': '1564619070', 'appName': '兽人争霸-魔幻之诗', 'icon': 'https://is3-ssl.mzstatic.com/image/thumb/Purple115/v4/ba/d6/25/bad62563-0296-939d-6e46-b77fc8590427/AppIcon-1x_U007emarketing-0-10-0-85-220.png/180x180bb.png', 'publisher': '山海经游戏工作室', 'country': 'cn', 'file_size': '', 'price': '1.00', 'continuousFirstDays': 1}, 'lastReleaseTime': '2021-07-05', 'keywordCover': '6736', 'keywordCoverTop3': '154', 'company': {
'id': 0, 'name': '-'}, 'rank_a': {
'ranking': 1, 'change': 0, 'genre': '总榜'}, 'rank_b': {
'ranking': 1, 'change': 0, 'genre': '游戏'}, 'rank_c': {
'ranking': 1, 'change': 0, 'genre': '卡牌'}, 'comment': {
'rating': 4.5, 'num': 141}}, {
'index': 2, 'appInfo': {
'appId': '1369418571', 'appName': '英雄棋士团', 'icon': 'https://is1-ssl.mzstatic.com/image/thumb/Purple125/v4/5a/c2/85/5ac285e6-b269-2a99-537d-37406a638f8c/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/180x180bb.png', 'publisher': 'Beijing Pengling Technology Co., Ltd', 'country': 'cn', 'file_size': '', 'price': '1.00'}, 'lastReleaseTime': '2021-07-01', 'keywordCover': '18798', 'keywordCoverTop3': '1153', 'company': {
'id': 0, 'name': '-'}, 'rank_a': {
'ranking': 2, 'change': 4, 'genre': '总榜'}, 'rank_b': {
'ranking': 2, 'change': 4, 'genre': '游戏'}, 'rank_c': {
'ranking': 2, 'change': 0, 'genre': '卡牌'}, 'comment': {
'rating': 3.9, 'num': 1796}}, {
'index': 3, 'appInfo': {
'appId': '1544202139', 'appName': '傲世群英传:无双乱斗', 'icon': 'https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/e1/70/28/e170288c-88aa-b0a0-0755-0b9ea7213754/AppIcon-1x_U007emarketing-0-6-0-85-220.png/180x180bb.png', 'publisher': '嗨逗游戏', 'country': 'cn', 'file_size': '', 'price': '1.00'}, 'lastReleaseTime': '2020-12-30', 'keywordCover': '8470', 'keywordCoverTop3': '352', 'company': {
'id': 0, 'name': '-'}, 'rank_a': {
'ranking': 3, 'change': 0, 'genre': '总榜'}, 'rank_b': {
'ranking': 3, 'change': 0, 'genre': '游戏'}, 'rank_c': {
'ranking': 2, 'change': -1, 'genre': '策略'}, 'comment': {
'rating': 4.6, 'num': 195}}, {
'index': 4, 'appInfo': {
'appId': '1552187597', 'appName': '修罗剑尊-仙灵幻想', 'icon': 'https://is1-ssl.mzstatic.com/image/thumb/Purple125/v4/ff/36/b9/ff36b990-4a58-6bb9-5248-737390e93d5c/source/100x100bb.jpg', 'publisher': '神启工作室', 'country': 'cn', 'file_size': '', 'price': '1.00'}, 'lastReleaseTime': '2021-05-24', 'keywordCover': '8748', 'keywordCoverTop3': '506', 'company': {
'id': 0, 'name': '-'}, 'rank_a': {
'ranking': 4, 'change': -2, 'genre': '总榜'}, 'rank_b': {
'ranking': 4, 'change': -2, 'genre': '游戏'}, 'rank_c': {
'ranking': 2, 'change': 0, 'genre': '角色扮演'}, 'comment': {
'rating': 4.9, 'num': 6076}}, {
'index': 5, 'appInfo': {
'appId': '1571705052', 'appName': '奇迹仙侠—3D梦幻情缘手游:笑傲天下', 'icon': 'https://is5-ssl.mzstatic.com/image/thumb/Purple115/v4/f2/70/60/f2706078-dae4-8a80-63bf-858e6a074347/AppIcon-1x_U007emarketing-0-10-0-85-220.png/180x180bb.png', 'publisher': 'Guangzhou Menwee Technologies Co., Ltd.', 'country': 'cn', 'file_size'