js逆向实战之一品威客signature参数解密
url: aHR0cHM6Ly93d3cuZXB3ay5jb20vbG9naW4uaHRtbA==
分析过程
输入用户名和密码,看触发的流量包。
-
signature
参数明显是被加密过的,接下来就是去寻找加密的过程。关键词搜索signature
。有两处,第二处是个固定值不需要看,关注点在第一处。点进去看对应的代码,并打断点,重新登录,触发该断点。
编辑编辑
-
查看该行代码所有变量的值。
总共是4个关注点,
Object(f.a)
是个函数,U
、M
、l.j ? l.g : l.c
是传给Object(f.a)
函数的三个变量。先看三个变量中的l.j ? l.g : l.c
。l.j
为false,所以会取l.c
。
编辑编辑再看
U
变量。
-
编辑 编辑
U
的定义如下:其中需要考虑的变量只有
Timestemp
、NonceStr
和App-Id
,其余值都是固定的。
App-Id
的值最容易得到,由于l.j
为false,故App-Id
的值为l.b
的值。
Timestemp
的值就是个时间戳。
NonceStr
的变量稍微麻烦点,由Timestemp
和Object(h.e)()
拼接而成。看Object(h.e)()
是什么。
该函数本质上就是生成一个随机字符串并截取字符串中的第3位到第8位,既然是个随机函数,所以该值取什么无所谓。最后看
M
变量。
编辑编辑
-
其中的username和password和code就是我们输入的账号密码和图片验证码。refer可以理解成一个固定值,不用变。变量看完了,最后来看
Object(f.a)
函数。
编辑编辑
-
看到函数中的变量都跟
arguments
有关,先看arguments
是什么。
arguments
是个字典,第一个键值对中的值就是U
变量,第二个键值对中的值就是M
变量,第三个键值对中的值是个定值。那么变量data
和e
很容易就能得到。
n = e + f(data) + f(t) + e
中需要知道f
函数的作用和变量t
是什么。
变量
t
跟变量U
是一致的。
函数f
的实现如下。
如果我们看不懂具体代码是什么意思,可以将结果输出看看。
大概就是一个字符串的拼接。
最后看d
和v
函数。
d
函数是一个md5算法,v
算法是一个AES加密,只要知道了密钥、偏移量和加密模式即可。
上图中显示的key和iv都是字节,想要看到字符串类型的话可以采用toString函数。
-
一切加密过程都知道了,把相关代码复制,完整代码如下。
// 引入 CryptoJS 库
var CryptoJS = require("crypto-js");
// 主函数
function test(t, arguments) {
// 设置默认参数值
var data = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {};
var e = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : "a75846eb4ac490420ac63db46d2a03bf";
// 构建加密字符串
var n = e + f(data) + f(t) + e;
// 对字符串进行 MD5 加密
n = d(n);
// 对 MD5 加密结果进行 AES 加密
n = v(n);
return n;
}
// r 对象,包含一个用于检查类型的函数
var r = {};
r.a = function(e) {
return typeof e; // 返回类型
}
// 将对象转换为排序后的字符串
function f(t) {
var e = "";
// 遍历对象的所有属性并按字典序排序
Object.keys(t).sort().forEach(function(n) {
e += n + (typeof t[n] === "object"
? JSON.stringify(t[n], function(t, e) {
// 将数值转换为字符串
return typeof e === "number" ? String(e) : e;
}).replace(/\//g, "\\/")
: t[n]);
});
return e;
}
// 加密所需的密钥和 IV(初始向量)
var l = {
"key": {
"words": [
1717059670,
2034454870,
1987077226,
944915297
],
"sigBytes": 16
},
"iv": {
"words": [
0, 0, 0, 0
],
"sigBytes": 16
}
};
// 使用 MD5 算法进行哈希加密
function d(data) {
return CryptoJS.MD5(data).toString();
}
// 使用 AES 算法进行加密
function v(data) {
return function(data) {
// 使用 CBC 模式和 PKCS7 填充方式进行加密
return CryptoJS.AES.encrypt(data, l.key, {
iv: l.iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString();
}(data);
}
测试一下。
// 请求的参数 t
const t = {
"App-Ver": "",
"Os-Ver": "",
"Device-Ver": "",
"Imei": "",
"Access-Token": "",
"Timestemp": 1713833424,
"NonceStr": "1713833424aoi0s",
"App-Id": "4ac490420ac63db4",
"Device-Os": "web"
};
// 请求的参数 arguments
const arguments = {
"0": {
"App-Ver": "",
"Os-Ver": "",
"Device-Ver": "",
"Imei": "",
"Access-Token": "",
"Timestemp": 1713833424,
"NonceStr": "1713833424aoi0s",
"App-Id": "4ac490420ac63db4",
"Device-Os": "web"
},
"1": {
"username": "111111",
"password": "111111",
"code": "qwrd",
"hdn_refer": "https://www.epwk.com/"
},
"2": "a75846eb4ac490420ac63db46d2a03bf"
};
// 调用 test 函数并打印结果
console.log(test(t, arguments));
结果如下:
可以得到正确的加密结果。
如果想要在python中运行,需要加入下述代码:
import time
import execjs
# 定义请求参数
app_version = ""
os_version = ""
device_version = ""
imei = ""
access_token = ""
app_id = "4ac490420ac63db4"
device_os = "web"
# 获取当前时间戳
timestamp = int(time.time())
# 构造 NonceStr,确保每次请求的唯一性
nonce_str = "{}fj1e".format(timestamp)
# 构建请求参数 t
request_params = {
"App-Ver": app_version,
"Os-Ver": os_version,
"Device-Ver": device_version,
"Imei": imei,
"Access-Token": access_token,
"Timestemp": timestamp,
"NonceStr": nonce_str,
"App-Id": app_id,
"Device-Os": device_os
}
# 构建额外的参数 arguments
arguments = {
"0": request_params,
"1": {
"username": "111111",
"password": "123456",
"code": "zyp6",
"hdn_refer": "https://www.epwk.com/"
},
"2": "a75846eb4ac490420ac63db46d2a03bf"
}
# 读取并执行解密的 JavaScript 文件
with open("解密.js", mode="r", encoding="utf-8") as f:
exec_js = f.read()
# 编译并执行 JavaScript 代码
exec_code = execjs.compile(exec_js)
# 调用 JavaScript 中的 test 函数,传入参数并打印结果
result = exec_code.call("test", request_params, arguments)
print(result)