# 准备
闲来无事,发篇小帖!
废话不多说,今天的主角:烯牛数据
# 正片!
# js 加密部分
找到需要逆向的接口,即:
看看其负载和返回的数据:
负载
数据
可以看到,负载有两个加密的参数,返回的数据也是加密的,先搞负载!
跟到栈堆,搜索 sig,可以找到两个,都打上断点,刷新网页,然后便断在了一个点,如下
观察这断代码,可以大致分析出,sig 就是 p,p 调用了一个 c.e 的函数和 f,f 调用了 c.c 和 c.d 以及 l,那么就先拿下来:
现在分析其中的函数是什么,先搞 c.c,跳转过去发现是一个叫 e1 的函数
直接拿下来
c.d 也是一样的跳转到一个叫 e2 的函数,直接拿下来,然后稍微改写下代码
然后搞 c.e,跳转到一个叫 sig 的函数,如下
发现是一个 MD5 加密(这个就是原版的 MD5,不要问我怎么知道,因为我试过了,所以这里就不分析是不是魔改 MD5 了,其实你跳转过去也会发现这个就是原版的)
因此直接写个 MD5 实现
最后补补环境,缺啥补啥,然后你看看运行生成的结果和浏览器的结果,肯定是一样的!
完整代码如下
const crypto = require('crypto'); | |
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" | |
, _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O"; | |
function md5(text) { | |
return crypto.createHash("md5").update(text).digest("hex") | |
} | |
function sig(e) { | |
return md5(e + _p).toUpperCase() | |
} | |
function e1(e) { | |
if (null == e) | |
return null; | |
for (var t, n, r, o, i, a, c, u = "", s = 0; s < e.length;) | |
o = (t = e.charCodeAt(s++)) >> 2, | |
i = (3 & t) << 4 | (n = e.charCodeAt(s++)) >> 4, | |
a = (15 & n) << 2 | (r = e.charCodeAt(s++)) >> 6, | |
c = 63 & r, | |
isNaN(n) ? a = c = 64 : isNaN(r) && (c = 64), | |
u = u + _keyStr.charAt(o) + _keyStr.charAt(i) + _keyStr.charAt(a) + _keyStr.charAt(c); | |
return u | |
} | |
function _u_e(e) { | |
if (null == e) | |
return null; | |
e = e.replace(/\r\n/g, "\n"); | |
for (var t = "", n = 0; n < e.length; n++) { | |
var r = e.charCodeAt(n); | |
r < 128 ? t += String.fromCharCode(r) : r > 127 && r < 2048 ? (t += String.fromCharCode(r >> 6 | 192), | |
t += String.fromCharCode(63 & r | 128)) : (t += String.fromCharCode(r >> 12 | 224), | |
t += String.fromCharCode(r >> 6 & 63 | 128), | |
t += String.fromCharCode(63 & r | 128)) | |
} | |
return t | |
} | |
function e2(e) { | |
if (null == (e = _u_e(e))) | |
return null; | |
for (var t = "", n = 0; n < e.length; n++) { | |
var r = _p.charCodeAt(n % _p.length); | |
t += String.fromCharCode(e.charCodeAt(n) ^ r) | |
} | |
return t | |
} | |
var n = { | |
"payload": { | |
"sort": 2, //1 为最近热门,2 为最近更新 | |
"start": 0, // 每次 + 20 | |
"limit": 20 | |
} | |
} | |
var s = JSON.stringify(n) | |
var l = JSON.parse(s); | |
var f = Object(e1)(Object(e2)(JSON.stringify(l.payload))); | |
var p = Object(sig)(f); | |
console.log(f) | |
console.log(p) |
# js 解密部分
现在搞返回的数据解密,先观察观察返回的数据,一个 code,一个 d,一个 v。如果是你,你要怎么搜索?该不会搜索 d 吧?那还不得亖啊,全篇都是 d,因此我们直接搜 code,因为这也是它返回的数据。
然后我们就发现刚刚加密的下面就有个 code,而且下面还跟着一个 JSON.parse
如果是你,你会不会看这个地方?
我们直接打上断点,刷新,然后看看这个 v 是个啥
断住后在控制台输入 v
这个 v 不就是我们要的明文数据吗?
直接开扣
注:l 就是返回数据里的 d,不信你自己看哈哈哈
然后扣出 c.a 和 c.b
c.a 就是 d1,c.b 就是 d2
然后缺啥补啥,运行看看结果,没问题的!
完整代码
var l = "这里放加密的结果"; | |
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" | |
, _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O"; | |
function d1(e) { | |
var t, n, r, o, i, a, c = "", u = 0; | |
for (e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); u < e.length;) | |
t = _keyStr.indexOf(e.charAt(u++)) << 2 | (o = _keyStr.indexOf(e.charAt(u++))) >> 4, | |
n = (15 & o) << 4 | (i = _keyStr.indexOf(e.charAt(u++))) >> 2, | |
r = (3 & i) << 6 | (a = _keyStr.indexOf(e.charAt(u++))), | |
c += String.fromCharCode(t), | |
64 != i && (c += String.fromCharCode(n)), | |
64 != a && (c += String.fromCharCode(r)); | |
return c | |
} | |
function d2(e) { | |
for (var t = "", n = 0; n < e.length; n++) { | |
var r = _p.charCodeAt(n % _p.length); | |
t += String.fromCharCode(e.charCodeAt(n) ^ r) | |
} | |
return t = _u_d(t) | |
} | |
function _u_d(e) { | |
for (var t = "", n = 0, r = 0, o = 0, i = 0; n < e.length;) | |
(r = e.charCodeAt(n)) < 128 ? (t += String.fromCharCode(r), | |
n++) : r > 191 && r < 224 ? (o = e.charCodeAt(n + 1), | |
t += String.fromCharCode((31 & r) << 6 | 63 & o), | |
n += 2) : (o = e.charCodeAt(n + 1), | |
i = e.charCodeAt(n + 2), | |
t += String.fromCharCode((15 & r) << 12 | (63 & o) << 6 | 63 & i), | |
n += 3); | |
return t | |
} | |
var d = Object(d1)(l) | |
, v = Object(d2)(d) | |
console.log(v) |