总体概览:使用Session登录该网站,其中包括对password参数进行js逆向破解
(涉及加密:md5加密+AES-CBC加密)
难度:两颗星
目标网址:aHR0cHM6Ly93d3cuZnhiYW9nYW8uY29tLw==
下面文章将分为四个部分:
1、定位主体加密函数,进行断点
2、分析整体加密流程
3、对JS代码进行扣取、补充、重写
4、使用Session进行登录
运行效果展示:
JS逆向运行截图:
Session登陆成功截图:
正文开始:
第一步、定位主体加密函数,进行断点
首先进入网站,点击登陆后进行抓包,很明显可以看出这里的密码(data参数)被进行了加密。
由于data参数比较常见不太好搜索,因此我们对url的接口byPhoneNumber进行搜索
搜索一共只有三个结果,比较少就都点进去看看,发现第一个很明显就是加密的主体函数并且 l 为加密的数据,直接对 l 参数进行断点后再次点击登陆,确定这个就是我们所需要的函数
第二步、分析整体加密流程
var o = e.password
, s = e.mobile
, a = A.dnGetter()
, l = D().ne("".concat(a).concat(o), _()("".concat(s.slice(3)).concat(a)));
直接看代码的话初步得出以下结论:
o为密码,s为账号,a疑似一个时间戳(这个后面再去验证),l 为加密后的一串数据
其中 l 为核心,下面对 l 进行分析:
1、其中可以看出括号内前半截是a与o的一个简单的拼接
2、后半截的话是先对密码部分截取第三位数之后的数据并与a进行拼接,并通过 _() 函数进行处理,发现 _() 是对拼接后的数据进行了md5的加密
3、在括号内的数据处理完成后调用D函数下的ne方法再进行一次处理
得到这些信息后开始扣取JS代码并补充缺少的函数
第三步、对JS代码进行扣取、补充、重写
步骤1:对a参数进行补充
首先将主体的加密函数复制下来写到自己定义的一个方法里,文中可以看出需要传入e参数(账号密码信息),下面是主体函数的代码:
var get_password = function (e){
var o = e.password
, s = e.mobile
, a = A.dnGetter()
, l = D().ne("".concat(a).concat(o), _()("".concat(s.slice(3)).concat(a)));
return l
}
var e = {
"mobile": "17688888888",
"password": "Wu123123123"
}
console.log(get_password(e))
很明显这里缺少了 A.dnGetter() 函数,找到这个函数点击进入发现它是一个名为 ot 的函数,写入JS代码中,并将原来的 A.dnGetter() 改写为 ot()
点击运行,发现缺少参数c,在此设置断点,运行了几次之后发现c为固定值false,因此我们直接令他为false,同时我们发现这句话还缺少了 window.D 参数 ,运行发现 这个也是固定值,补充完后运行一下结果没毛病。
window = global
window.D = 1
var ot = function() {
return false || "number" !== typeof window.D ? +String(Date.now()).substr(0, 10) : +String(Date.now()).substr(0, 10) + window.D
}
接下来就是我们的重头戏了!!
步骤2:对D.ne函数进行补充
首先是进入D函数下的ne方法,进入发现这里是一个很明显的AES-CBC加密,将D改为一个对象,将ne方法放入D中,并将原本的D().ne() 改写为 D.ne (这里有两种写法,我个人更喜欢写成D.ne一点看着会简单一些,如果想要 D().ne()的写法的话也可以,都放在下面了)
写法1:
写法2:
再次运行发现缺少了对象,由于这里的格式与AES加密一模一样,因此在这里我将o替换成了JavaScript中的 crypto-js 库,这是一个经常用于加解密的库,调用前需要安装 jsdom
安装及使用方法可以看我上一篇文章所讲的:AES加密和解密详解_浩·的博客-CSDN博客
let CryptoJS = require('crypto-js'); // 调用crypto-js 模块
let D = {
ne: function(e, t) {
return CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(e), CryptoJS.enc.Utf8.parse(t.substr(0, 32)), {
iv: CryptoJS.enc.Utf8.parse(t.substr(-16)),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString().toUpperCase()
}
}
步骤3:对 _() 函数进行补充与改写
前面分析过了在这下面的部分其实是将_()后面的数据进行了md5加密,会改写JS代码的或者使用python替代的可以直接跳过这一步不看。下面这部分主要还是练习一下对JS的逆向能力。对md5加密函数进行还原操作。
这里有一个小坑,将鼠标移到 _() 上后发现调用的是a函数,需要将a先展开再进入a中。
可以看见这里传入了我们前面所讲的后半截数据,因此可以将原本的
_()(后半截数据) 改写为 _(后半截数据)
同时为了美观,我们将 _ 函数改名为exports,具体改写后的代码如下:
exports = function(e, n) {
if (void 0 === e || null === e)
throw new Error("Illegal argument " + e);
var r = t.wordsToBytes(i(e, n));
return n && n.asBytes ? r : n && n.asString ? a.bytesToString(r) : t.bytesToHex(r)
}
var get_password = function (e){
var o = e.password
, s = e.mobile
, a = ot()
, l = D.ne("".concat(a).concat(o), exports(("".concat(s.slice(3)).concat(a))));
return l
}
再次运行,发现对象 t 及其附带的函数wordsToBytes、bytesToHex都未被定义,给t下断点,进入t中。刚好发现我们想要的两个函数都在里面,直接带走。新建一个对象t,并放入这两个函数。
let t = {
wordsToBytes: function(e) {
for (var t = [], n = 0; n < 32 * e.length; n += 8)
t.push(e[n >>> 5] >>> 24 - n % 32 & 255);
return t
},
bytesToHex: function(e) {
for (var t = [], n = 0; n < e.length; n++)
t.push((e[n] >>> 4).toString(16)),
t.push((15 & e[n]).toString(16));
return t.join("")
}
}
再次运行再 t 的旁边还缺少了个 i
下面对 i 进行补充,进入 i 并打下断点,将i先全扣下来再运行
这里报错 r 未被定义,这个比较明显,因为r就在上面,可以看见 r 为一个对象,包含了两个方法
可以看见 r 调用的utf8就在这里,同时utf8的两个方法还用到了下面的bin,因此在这我们直接扣过去 并新建一个 r 对象保存。由于单个单词 t 容易造成重复,我们这里对 对象t改了一下名字,最后再 i 函数中调用
再次运行,缺少了t.bytesToWords,回到刚刚的地方继续重复操作,定位过去发现这里是刚刚扣 t 对象的地方,我们只需要将新的给补充进去
然后重复,运行、下断点、进入函数,发现v函数是再上面被定义的,
我们进入定义它的地方 i.ff 函数中,顺路吧剩下的四个一起扣下来
继续重复刚刚的操作扣 t.endian([l, s, f, d]) ,发现这里还调用了上面两个函数,一起带走。
要注意一下这里名字变了,扣过去的时候要改回来,并且把下面的 n.rotl 也改成 t.rotl
改完之后发现这里有个bug,如果要将n改为t的话这就与上面的for 循环中的t变量冲突了,因此我们将for循环的变量改为其他的。 于是这里改为图二。
再次点击运行,发现已经大功告成啦!!!!!
到这里逆向部分就已经完成了,下面进行登录操作
第四步、使用Session进行登录
查看登录发送的包,发现这里有一个时间戳,根据逆向时候知道的这个时间戳需要用来加密,因此我们需要将这个时间戳与JS的时间戳保持一直。所以我们需要在python上生成时间戳,然后发送给JS,对此我们还要将JS的时间戳改为一个传入的参数,即传入a(时间戳参数)
// 主函数改写后:
var get_pas_word = function(e,a){
var o = e.password
, s = e.mobile
// , a = ot()
var l = D.ne("".concat(a).concat(o), aaa(("".concat(s.slice(3)).concat(a))));
return l
}
var e_dic = {
"mobile": "17836797789",
"password": "123wu123123"
}
a = 1686207919
console.log(get_pas_word(e_dic,a))
全部代码:Session登录+JS逆向代码
JS加密代码:
window = global
var ot = function() {
return false || "number" !== typeof window.D ? +String(Date.now()).substr(0, 10) : +String(Date.now()).substr(0, 10) + window.D
}
let CryptoJS = require('crypto-js'); // 调用crypto-js 模块
let D = {
ne: function(e, t) {
return CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(e), CryptoJS.enc.Utf8.parse(t.substr(0, 32)), {
iv: CryptoJS.enc.Utf8.parse(t.substr(-16)),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).ciphertext.toString().toUpperCase()
}
}
let r_obj = {
utf8: {
stringToBytes: function(e) {
return r_obj.bin.stringToBytes(unescape(encodeURIComponent(e)))
},
bytesToString: function(e) {
return decodeURIComponent(escape(r_obj.bin.bytesToString(e)))
}
},
bin: {
stringToBytes: function(e) {
for (var t = [], n = 0; n < e.length; n++)
t.push(255 & e.charCodeAt(n));
return t
},
bytesToString: function(e) {
for (var t = [], n = 0; n < e.length; n++)
t.push(String.fromCharCode(e[n]));
return t.join("")
}
}
}
i = function(e, n) {
var r = r_obj.utf8
e.constructor == String ? e = n && "binary" === n.encoding ? a.stringToBytes(e) : r.stringToBytes(e) : o(e) ? e = Array.prototype.slice.call(e, 0) : Array.isArray(e) || e.constructor === Uint8Array || (e = e.toString());
for (var c = t.bytesToWords(e), u = 8 * e.length, l = 1732584193, s = -271733879, f = -1732584194, d = 271733878, p = 0; p < c.length; p++)
c[p] = 16711935 & (c[p] << 8 | c[p] >>> 24) | 4278255360 & (c[p] << 24 | c[p] >>> 8);
c[u >>> 5] |= 128 << u % 32,
c[14 + (u + 64 >>> 9 << 4)] = u;
i._ff = function(e, t, n, r, o, a, i) {
var c = e + (t & n | ~t & r) + (o >>> 0) + i;
return (c << a | c >>> 32 - a) + t
}
,
i._gg = function(e, t, n, r, o, a, i) {
var c = e + (t & r | n & ~r) + (o >>> 0) + i;
return (c << a | c >>> 32 - a) + t
}
,
i._hh = function(e, t, n, r, o, a, i) {
var c = e + (t ^ n ^ r) + (o >>> 0) + i;
return (c << a | c >>> 32 - a) + t
}
,
i._ii = function(e, t, n, r, o, a, i) {
var c = e + (n ^ (t | ~r)) + (o >>> 0) + i;
return (c << a | c >>> 32 - a) + t
}
var v = i._ff
, m = i._gg
, h = i._hh
, y = i._ii;
for (p = 0; p < c.length; p += 16) {
var g = l
, b = s
, x = f
, w = d;
l = v(l, s, f, d, c[p + 0], 7, -680876936),
d = v(d, l, s, f, c[p + 1], 12, -389564586),
f = v(f, d, l, s, c[p + 2], 17, 606105819),
s = v(s, f, d, l, c[p + 3], 22, -1044525330),
l = v(l, s, f, d, c[p + 4], 7, -176418897),
d = v(d, l, s, f, c[p + 5], 12, 1200080426),
f = v(f, d, l, s, c[p + 6], 17, -1473231341),
s = v(s, f, d, l, c[p + 7], 22, -45705983),
l = v(l, s, f, d, c[p + 8], 7, 1770035416),
d = v(d, l, s, f, c[p + 9], 12, -1958414417),
f = v(f, d, l, s, c[p + 10], 17, -42063),
s = v(s, f, d, l, c[p + 11], 22, -1990404162),
l = v(l, s, f, d, c[p + 12], 7, 1804603682),
d = v(d, l, s, f, c[p + 13], 12, -40341101),
f = v(f, d, l, s, c[p + 14], 17, -1502002290),
l = m(l, s = v(s, f, d, l, c[p + 15], 22, 1236535329), f, d, c[p + 1], 5, -165796510),
d = m(d, l, s, f, c[p + 6], 9, -1069501632),
f = m(f, d, l, s, c[p + 11], 14, 643717713),
s = m(s, f, d, l, c[p + 0], 20, -373897302),
l = m(l, s, f, d, c[p + 5], 5, -701558691),
d = m(d, l, s, f, c[p + 10], 9, 38016083),
f = m(f, d, l, s, c[p + 15], 14, -660478335),
s = m(s, f, d, l, c[p + 4], 20, -405537848),
l = m(l, s, f, d, c[p + 9], 5, 568446438),
d = m(d, l, s, f, c[p + 14], 9, -1019803690),
f = m(f, d, l, s, c[p + 3], 14, -187363961),
s = m(s, f, d, l, c[p + 8], 20, 1163531501),
l = m(l, s, f, d, c[p + 13], 5, -1444681467),
d = m(d, l, s, f, c[p + 2], 9, -51403784),
f = m(f, d, l, s, c[p + 7], 14, 1735328473),
l = h(l, s = m(s, f, d, l, c[p + 12], 20, -1926607734), f, d, c[p + 5], 4, -378558),
d = h(d, l, s, f, c[p + 8], 11, -2022574463),
f = h(f, d, l, s, c[p + 11], 16, 1839030562),
s = h(s, f, d, l, c[p + 14], 23, -35309556),
l = h(l, s, f, d, c[p + 1], 4, -1530992060),
d = h(d, l, s, f, c[p + 4], 11, 1272893353),
f = h(f, d, l, s, c[p + 7], 16, -155497632),
s = h(s, f, d, l, c[p + 10], 23, -1094730640),
l = h(l, s, f, d, c[p + 13], 4, 681279174),
d = h(d, l, s, f, c[p + 0], 11, -358537222),
f = h(f, d, l, s, c[p + 3], 16, -722521979),
s = h(s, f, d, l, c[p + 6], 23, 76029189),
l = h(l, s, f, d, c[p + 9], 4, -640364487),
d = h(d, l, s, f, c[p + 12], 11, -421815835),
f = h(f, d, l, s, c[p + 15], 16, 530742520),
l = y(l, s = h(s, f, d, l, c[p + 2], 23, -995338651), f, d, c[p + 0], 6, -198630844),
d = y(d, l, s, f, c[p + 7], 10, 1126891415),
f = y(f, d, l, s, c[p + 14], 15, -1416354905),
s = y(s, f, d, l, c[p + 5], 21, -57434055),
l = y(l, s, f, d, c[p + 12], 6, 1700485571),
d = y(d, l, s, f, c[p + 3], 10, -1894986606),
f = y(f, d, l, s, c[p + 10], 15, -1051523),
s = y(s, f, d, l, c[p + 1], 21, -2054922799),
l = y(l, s, f, d, c[p + 8], 6, 1873313359),
d = y(d, l, s, f, c[p + 15], 10, -30611744),
f = y(f, d, l, s, c[p + 6], 15, -1560198380),
s = y(s, f, d, l, c[p + 13], 21, 1309151649),
l = y(l, s, f, d, c[p + 4], 6, -145523070),
d = y(d, l, s, f, c[p + 11], 10, -1120210379),
f = y(f, d, l, s, c[p + 2], 15, 718787259),
s = y(s, f, d, l, c[p + 9], 21, -343485551),
l = l + g >>> 0,
s = s + b >>> 0,
f = f + x >>> 0,
d = d + w >>> 0
}
return t.endian([l, s, f, d])
}
t = {
wordsToBytes: function(e) {
for (var t = [], n = 0; n < 32 * e.length; n += 8)
t.push(e[n >>> 5] >>> 24 - n % 32 & 255);
return t
},
bytesToWords: function(e) {
for (var t = [], n = 0, r = 0; n < e.length; n++,
r += 8)
t[r >>> 5] |= e[n] << 24 - r % 32;
return t
},
bytesToHex: function(e) {
for (var t = [], n = 0; n < e.length; n++)
t.push((e[n] >>> 4).toString(16)),
t.push((15 & e[n]).toString(16));
return t.join("")
},
rotl: function(e, t) {
return e << t | e >>> 32 - t
},
rotr: function(e, t) {
return e << 32 - t | e >>> t
},
endian: function(e) {
if (e.constructor == Number)
return 16711935 & t.rotl(e, 8) | 4278255360 & t.rotl(e, 24);
for (var x = 0; x < e.length; x++)
e[x] = t.endian(e[x]);
return e
}
}
aaa = function (e,n){
// if (void 0 === e || null === e)
// throw new Error("Illegal argument " + e);
var r = t.wordsToBytes(i(e, n));
return n && n.asBytes ? r : n && n.asString ? a.bytesToString(r) : t.bytesToHex(r)
}
var get_pas_word = function(e,a){
var o = e.password
, s = e.mobile
// , a = ot()
var l = D.ne("".concat(a).concat(o), aaa(("".concat(s.slice(3)).concat(a))));
return l
}
var e_dic = {
"mobile": "17836797789",
"password": "123wu123123"
}
a = 1686207919
console.log(get_pas_word(e_dic,a))
最终python登录代码:
# -*- coding: UTF-8 -*-
'''
@Project :wbh_pj
@File :111.py
@Author :hao
@Date :2023/6/8 15:11
'''
import time
import execjs
import requests
headers = {
'authority': 'api.fxbaogao.com',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7',
'content-type': 'application/json; charset=UTF-8',
'origin': 'https://www.fxbaogao.com',
'referer': 'https://www.fxbaogao.com/',
'sec-ch-ua': '"Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
'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/113.0.0.0 Safari/537.36',
'user-id': '0',
'user-token': 'QQ',
}
def get_pas_word(e_dic,now_time):
filename_js = '123.js'
with open(filename_js, encoding='utf-8', mode='r') as f:
pw_js = f.read()
f.close()
js1 = execjs.compile(pw_js)
print('********* 正在生成 -- pas_word *********')
pas_word = js1.call('get_pas_word', e_dic,now_time) # 获取token
print(pas_word)
return pas_word
def login():
now_time = int(time.time()) # 获取时间戳
e_dic = {
"mobile": "17688888888",
"password": "123wu123123"
}
pas_word = get_pas_word(e_dic,now_time)
json_data = {
'mobile': '17688688515',
'data': pas_word,
'time': now_time,
}
session = requests.session()
# response = requests.post('https://api.fxbaogao.com/mofoun/user/login/byPhoneNumber', headers=headers,
# json=json_data)
response1 = session.post('https://api.fxbaogao.com/mofoun/user/login/byPhoneNumber', headers=headers,json=json_data)
print(response1.text)
if __name__ == '__main__':
login()
JS逆向部分总结:
1、在登录的时候对password参数与时间戳拼接;
2、对账号参数切及时间戳的拼接;
3、将部分关键数据转数组后先进行MD5加密处理;
4、最终将前面数据全部拼接对数据使用AES下的CBC加密
5、得到password的参数之后使用request模块的Session进行登录操作
AES加密解密:AES加密和解密详解_aesdecrypt_浩·的博客-CSDN博客
知名外卖平台项目:JS逆向---获取某知名外卖平台数据(_token)_浩·的博客-CSDN博客
更多爬虫项目请看:浩 的个人爬虫项目