网址:aHR0cHM6Ly93d3cucWltYWkuY24vcmFuaw==
抓包分析载荷中有加密参数analysis:
获取数据代码,经过分析,发现analysis确实是校验参数cai:
import requestscookies = {'qm_check': 'A1sdRUIQChtxen8pI0dAMRcOUFseEHBeQF0JTjVBWCwycRd1QlhAXFEGFUdeSklaHQdKAAkABAsgJ1dBWD0TR1JRRAp0BQlFEBQ3TSZKFUdBbwxvBBRFIlQsSUhTFxsQU1FVV1NHXEVYVElWBRsCHAkSSQ%3D%3D','gr_user_id': '7b300ae1-1a2e-48f3-ac26-ef24f571d4c9','USERINFO': 'jHFy6VITNfYjO5gzKnxVCcHVKhkUrDYQL4rEoi%2Bu%2Fa3gWGxj5dQDgJzrkKFOU6FOm%2F4%2FLH%2BKRQ6Kkezd22166yCiZ%2FlYWomlIfHgN40yWpgrHONQD7IehPC71gALl3B5eweCfpHISDgX3EvMl7rYBQ%3D%3D','ada35577182650f1_gr_last_sent_cs1': 'qm21331348315','aso_ucenter': 'c0160lkfZZ3b6mCL6Ic%2FE9rfux%2FFzvScbLXxY1aeQwexSd5FHwlCTHtQXpRw3BnZag8','AUTHKEY': 'kieguXDTNdsivnTuTu%2FDz8rEJtxANQ3H4WyYIrC6WalwuS0e3QcGe2ynYUWdRmasuVvmGU65IVQ%2BOjFG4DNkWj%2BcAjh7wM9GbQyPulS5nrkz5m8CC7qAYQ%3D%3D','synct': '1714182214.703','syncd': '-138','PHPSESSID': '1g4mb7ol6ddoiej2h7rak6eh0k','ada35577182650f1_gr_session_id': '5b1ab32f-3236-4628-b301-aea7dcc57299','ada35577182650f1_gr_last_sent_sid_with_cs1': '5b1ab32f-3236-4628-b301-aea7dcc57299','ada35577182650f1_gr_cs1': 'qm21331348315','ada35577182650f1_gr_session_id_sent_vst': '5b1ab32f-3236-4628-b301-aea7dcc57299',
}headers = {'authority': 'api.qimai.cn','accept': 'application/json, text/plain, */*','accept-language': 'zh-CN,zh;q=0.9','cache-control': 'no-cache',# 'cookie': 'qm_check=A1sdRUIQChtxen8pI0dAMRcOUFseEHBeQF0JTjVBWCwycRd1QlhAXFEGFUdeSklaHQdKAAkABAsgJ1dBWD0TR1JRRAp0BQlFEBQ3TSZKFUdBbwxvBBRFIlQsSUhTFxsQU1FVV1NHXEVYVElWBRsCHAkSSQ%3D%3D; gr_user_id=7b300ae1-1a2e-48f3-ac26-ef24f571d4c9; USERINFO=jHFy6VITNfYjO5gzKnxVCcHVKhkUrDYQL4rEoi%2Bu%2Fa3gWGxj5dQDgJzrkKFOU6FOm%2F4%2FLH%2BKRQ6Kkezd22166yCiZ%2FlYWomlIfHgN40yWpgrHONQD7IehPC71gALl3B5eweCfpHISDgX3EvMl7rYBQ%3D%3D; ada35577182650f1_gr_last_sent_cs1=qm21331348315; aso_ucenter=c0160lkfZZ3b6mCL6Ic%2FE9rfux%2FFzvScbLXxY1aeQwexSd5FHwlCTHtQXpRw3BnZag8; AUTHKEY=kieguXDTNdsivnTuTu%2FDz8rEJtxANQ3H4WyYIrC6WalwuS0e3QcGe2ynYUWdRmasuVvmGU65IVQ%2BOjFG4DNkWj%2BcAjh7wM9GbQyPulS5nrkz5m8CC7qAYQ%3D%3D; synct=1714182214.703; syncd=-138; PHPSESSID=1g4mb7ol6ddoiej2h7rak6eh0k; ada35577182650f1_gr_session_id=5b1ab32f-3236-4628-b301-aea7dcc57299; ada35577182650f1_gr_last_sent_sid_with_cs1=5b1ab32f-3236-4628-b301-aea7dcc57299; ada35577182650f1_gr_cs1=qm21331348315; ada35577182650f1_gr_session_id_sent_vst=5b1ab32f-3236-4628-b301-aea7dcc57299','origin': 'https://www.qimai.cn','pragma': 'no-cache','sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"','sec-ch-ua-mobile': '?0','sec-ch-ua-platform': '"Windows"','sec-fetch-dest': 'empty','sec-fetch-mode': 'cors','sec-fetch-site': 'same-site','user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
}params = {'analysis': 'ew8nHiY7SQ17cgcaKht0BCtUIRsaPjRAUG8hCwMLAwkmREcKGBReHl4NAARBZgkTFEcaCxtbVWgKAE4JdkZTVVFNQUgHAQNQUSEaBQ==','brand': 'all','country': 'cn','device': 'iphone','genre': '36','date': '2024-04-27','page': '7',
}response = requests.get('https://api.qimai.cn/rank/indexPlus/brand_id/0', params=params, cookies=cookies, headers=headers)
采取搜索方法,发现搜不到,果断跟栈
分析栈,发现还有异步栈,点击第一个,开始send
发现v中有加密参数
直接跳过exports栈。从l.requests开始,遇到熟悉的n.then,打赏断点
进入第一个函数
打上断点,此时t中没有加密内容,那么肯定是在后面有加密,可能是函数后面,也可能是t中别的
在下方打上断点,嘿嘿,发现此时有了加密参数,添加到了url中,故肯定是里面某组函数进行了加密。这段代码明显是混淆代码。
加密生成函数,e = (0,i[jt])((0, i[qt])(a, d)) ,又是i,又是a,又是d,所以先整体复制try里面的代码,在做删减。声明get_analysis函数,接收整个复制内容。
t = {"url": "/rank/indexPlus/brand_id/0","method": "get","headers": {"common": {"Accept": "application/json, text/plain, */*"},"delete": {},"get": {},"head": {},"post": {"Content-Type": "application/x-www-form-urlencoded"},"put": {"Content-Type": "application/x-www-form-urlencoded"},"patch": {"Content-Type": "application/x-www-form-urlencoded"}},"params": {"brand": "all","country": "cn","device": "iphone","genre": "36","date": "2024-04-27","page": 2},"baseURL": "https://api.qimai.cn","transformRequest": [null],"transformResponse": [null],"timeout": 15000,"withCredentials": true,"xsrfCookieName": "XSRF-TOKEN","xsrfHeaderName": "X-XSRF-TOKEN","maxContentLength": -1,"maxBodyLength": -1
}
function get_analysis(t) {var n;f || F != s || (n = (0,i[Wt])(m),s = c[x][k][Pt] = -(0,i[Wt])(l) || +new z[W] - a2 * n);var e, r = +new z[W] - (s || H) - 1661224081041, a = [];void 0 === t[Zt] && (t[Zt] = {})z[Z][i7](t[Zt])[M](function (n) {if (n == p)return !B;t[Zt][N2](n) && a[b](t[Zt][n])})a = a[Ot]()[I1](_)a = (0, i[jt])(a),a = (a += v + t[Jt][T](t[Mt], _)) + (v + r) + (v + 3),e = (0, i[jt])((0, i[qt])(a, d))return e}
要获得传入的参数,先放掉后面的断点
复制传入的t
现在逐行分析代码:
1.f || F != s || (n = (0, i[Wt])(m), s = c[x][k][Pt] = -(0, i[Wt])(l) || +new z[W] - a2 * n);
在提供的代码中,没有显式使用三元运算符。但是,可以通过逻辑运算符 ||
和赋值语句来模拟三元运算符的功能。JavaScript 中的 ||
运算符用于逻辑或操作,如果第一个操作数为真,则返回第一个操作数,否则返回第二个操作数。返回第一个被判断为真值的操作数
var a = 0;
var b = 10;
var c = 20;var result = a || b || c;
console.log(result); // 输出:10
在这个例子中,a
是假值,所以继续检查下一个操作数 b
。因为 b
是真值,所以返回 b
的值 10
。
在上面的页面中,F != s 这个判断为真,f为假,因此整个代码返回F != s这个判断,因此没什么用,直接删除即可。
2.var e, r = +new z[W] - (s || H) - 1661224081041, a = [];
这明显是个混淆,我们可以在浏览器中查看混淆前是什么,从而替换一些自带的函数
z[W]是Date函数
s与H不知道怎么来的,先写成固定值
3.void 0 === t[Zt] && (t[Zt] = {})
这段代码使用了 void
运算符,===
严格相等运算符以及逻辑与 &&
运算符。让我们一步步解释:
void 0
:void
运算符用于生成undefined
值。在这里,它将undefined
与t[Zt]
进行比较。===
: 严格相等运算符用于比较两个值是否相等且类型相同。t[Zt]
: 这似乎是一个变量或对象的属性。它的值将与undefined
进行比较。&&
: 逻辑与运算符用于将两个表达式连接起来,并且只有在第一个表达式为真时才会执行第二个表达式。
因此,整个表达式的含义是:如果 t[Zt]
的值严格等于 undefined
,则将 t[Zt]
设置为空对象 {}
。
这种方式通常用于确保对象属性的存在,如果属性不存在,则进行初始化,以避免后续访问该属性时出现错误。是个判断的东西,删掉无用。
4.z[Z][i7](t[Zt])[M](function (n) { if (n == p) return !B; t[Zt][N2](n) && a[b](t[Zt][n]) })
很多逆向,我们可以在浏览器中还原。大Z是pychram中自带的object,然后是对象中的key方法,t[Zt]是传入的t字典中所带的params参数。M是forEach方法
改成下面这样
Object.keys(t.params).forEach()
继续改写
if (n == "analysis")return false;t.params.hasOwnProperty(n) && a.push(t.params[n])
5.a = a[Ot]()[I1](_)
改写
a = a.sort().join('')
目前尝试打断点调试一下,发现a与浏览器中是一致的
6. a = (0, i[jt])(a)
花指令,其实就是a=i[jt](a)
在页面中去找函数[jt]位置,并复制代码,将函数命名为i_jt发现也是高度混淆,且不知道里面有没有调用其他函数
7.对v函数的混淆作还原t = z[V1](t)[T](/%([0-9A-F]{2})/g, function(n, t)
t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t)
return o(Y1 + t),现在要找O函数所在的位置
对o方法,解混淆
还原后代码:
function o(n) {t = "",['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65'].forEach(function(n) {t += unescape('%u00' + n)});var t, e = t;return String[e](n)};
8.还原了o函数之后,回到i_jt函数 return o(Y1 + t)
将Y1替换为0x
t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {return o('0x' + t)
接着是下面的代码进行分析
try {return z[Q1](t) } catch (n) {return z[W1][K1](t)[U1](Z1) }异常捕获,不用管异常部分
故只用对这行代码解混淆,return z[Q1](t)
return btoa(t)
目前的代码:
t = {"url": "/rank/indexPlus/brand_id/0","method": "get","headers": {"common": {"Accept": "application/json, text/plain, */*"},"delete": {},"get": {},"head": {},"post": {"Content-Type": "application/x-www-form-urlencoded"},"put": {"Content-Type": "application/x-www-form-urlencoded"},"patch": {"Content-Type": "application/x-www-form-urlencoded"}},"params": {"brand": "all","country": "cn","device": "iphone","genre": "36","date": "2024-04-27","page": 2},"baseURL": "https://api.qimai.cn","transformRequest": [null],"transformResponse": [null],"timeout": 15000,"withCredentials": true,"xsrfCookieName": "XSRF-TOKEN","xsrfHeaderName": "X-XSRF-TOKEN","maxContentLength": -1,"maxBodyLength": -1
}
var s = 238;
var H = 0;
function o(n) {t = "",['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65'].forEach(function(n) {t += unescape('%u00' + n)});var t, e = t;return String[e](n)};
function i_jt(t) {t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {return o('0x' + t)});return btoa(t)};function get_analysis(t) {var n;var e, r = +new Date - (s || H) - 1661224081041, a = [];Object.keys(t.params).forEach(function (n) {if (n == "analysis")return false;t.params.hasOwnProperty(n) && a.push(t.params[n])})a = a.sort().join('')a = i_jt(a)a = (a += v + t[Jt][T](t[Mt], _)) + (v + r) + (v + 3),e = (0, i[jt])((0, i[qt])(a, d))return e
}console.log(get_analysis(t))
debugger结果a的值
浏览器中结果是一致的,对于a的值
9.回到最开始的函数,a = (a += v + t[Jt][T](t[Mt], _)) + (v + r) + (v + 3)
var v='@#';
a = (a += v + t.url.replace(t.baseURL, '')) + (v + r) + (v + 3)
10.e = (0, i[jt])((0,i[qt])(a, d))
可以改写成e = i_jt(i[qt](a, d))
所以现在要找i[qt]与a和d
进入i[qt],发现是一个h方法,在这里打上断点,并复制。将函数名改为i_qt。
function i_qt(n, t) {t = t || u();for (var e = (n = n[$1](_))[R], r = t[R], a = q1, i = H; i < e; i++)n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));return n[I1](_)};
逐行分析i_qt代码
t = t || u();但凡t有值,t就是t,所以代码无意义哦,省略。
解混淆for (var e = (n = n[$1](_))[R], r = t[R], a = q1, i = H; i < e; i++)
for (var e = (n = n.split('')).length, r = t.length, a = "charCodeAt", i = H; i < e; i++)
解混淆 n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H))
return n[I1](_)
其中o是已经定义过的
n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));return n.join('')
完整i_qt方法
function i_qt(n, t) {for (var e = (n = n.split('')).length, r = t.length, a = "charCodeAt", i = H; i < e; i++)n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));return n.join('')};
11.回到最开始的函数
a = (a += v + t.url.replace(t.baseURL, '')) + (v + r) + (v + 3) e = i_jt((i_qt(a, d))
现在缺少d,需要知道d的值
e = i_jt((i_qt(a, 'xyz517cda96efgh'))
最终代码呈现:
t = {"url": "/rank/indexPlus/brand_id/0","method": "get","headers": {"common": {"Accept": "application/json, text/plain, */*"},"delete": {},"get": {},"head": {},"post": {"Content-Type": "application/x-www-form-urlencoded"},"put": {"Content-Type": "application/x-www-form-urlencoded"},"patch": {"Content-Type": "application/x-www-form-urlencoded"}},"params": {"brand": "all","country": "cn","device": "iphone","genre": "36","date": "2024-04-27","page": 2},"baseURL": "https://api.qimai.cn","transformRequest": [null],"transformResponse": [null],"timeout": 15000,"withCredentials": true,"xsrfCookieName": "XSRF-TOKEN","xsrfHeaderName": "X-XSRF-TOKEN","maxContentLength": -1,"maxBodyLength": -1
}
var s = 238;
var H = 0;
var v = '@#';function i_qt(n, t) {for (var e = (n = n.split('')).length, r = t.length, a = "charCodeAt", i = H; i < e; i++)n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));return n.join('')
};function o(n) {t = "",['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65'].forEach(function (n) {t += unescape('%u00' + n)});var t, e = t;return String[e](n)
};function i_jt(t) {t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {return o('0x' + t)});return btoa(t)};function get_analysis(t) {var n;var e, r = +new Date - (s || H) - 1661224081041, a = [];Object.keys(t.params).forEach(function (n) {if (n == "analysis")return false;t.params.hasOwnProperty(n) && a.push(t.params[n])})a = a.sort().join('')a = i_jt(a)a = (a += v + t.url.replace(t.baseURL, '')) + (v + r) + (v + 3)e = i_jt(i_qt(a, 'xyz517cda96efgh'))return e
}console.log(get_analysis(t))
最终结果展现:
实际上,函数只用到了t.params,t.url和t.baseURL,将其他的参数删除。
t = {"url": "/rank/indexPlus/brand_id/0","params": {"brand": "all","country": "cn","device": "iphone","genre": "36","date": "2024-04-27","page": 2},"baseURL": "https://api.qimai.cn",
}
我么希望是python传入这个参数,由js代码加密,然后返回python加密结果。