- Promise对象
在ES6中,Promise
对象是用来解决回调地狱(callback hell)问题的。回调地狱发生的原因是,当我们有多个异步操作时,每个操作的回调函数都嵌套在前一个操作的回调中,导致代码层级过深,难以阅读和维护。
传统回调的示例:
javascript
function fn() {
let username = "alex";
let password = "123456";
console.log("发送请求出去,尝试登录");
setTimeout(function () {
console.log("服务器返回了一个结果");
let result_1 = true;
if(result_1 === true) { // 登录成功
console.log("准备加载菜单信息");
setTimeout(function () {
console.log("显示菜单的信息");
console.log("准备加载用户信息");
setTimeout(function () {
console.log("显示用户信息");
}, 1000);
}, 1000);
}
}, 1000);
}
从上面的代码中可以看到,setTimeout
被层层嵌套,逻辑变得非常复杂。如果多个异步操作都使用这种方式,代码的可读性就会变差,维护起来也很困难。
Promise的解决方案:
Promise
让异步操作的流程更直观,通过链式调用,使得每个异步操作的顺序可以清晰表达。
javascript
function send(url) {
return new Promise(function (resolve, reject) {
console.log("帮你发送一个请求到", url);
let result = 123;
if (result) {
resolve("请求成功"); // 解决问题
} else {
reject("请求失败"); // 处理失败的情况
}
});
}
function fn() {
send("xxxxx")
.then(function (data) {
console.log("登录的结果:", data);
return send("加载菜单");
})
.then(function (data) {
console.log("加载菜单的信息:", data);
return send("加载个人信息");
})
.then(function (data) {
console.log("加载个人信息的信息:", data);
})
.catch(function (err) {
console.log("程序出错,请联系管理员....", err);
});
}
解析:
send
函数返回一个Promise
对象,它有两个参数:resolve
和reject
。如果操作成功,调用resolve
,否则调用reject
。- 在
fn
函数中,我们通过.then()
来依次链式处理异步操作,catch()
用来处理所有可能的错误。
Promise链式调用:
- 每个
.then()
会等待前一个操作完成并返回结果。这样就避免了嵌套的回调。 - 如果在某个步骤出错,错误会被
.catch()
捕获,避免了每个.then()
都需要自己处理错误。
2. Axios拦截器
Axios
是一个基于 Promise
的 HTTP 请求库。它的强大之处在于,它支持请求拦截器和响应拦截器。你可以在请求发送之前对请求进行修改(比如添加认证信息、加密等),以及在响应返回后对响应数据进行处理(比如解密数据、统一错误处理等)。
请求拦截器:
请求拦截器是指在请求发出之前,我们可以对请求做一些操作,例如添加认证头、加密请求参数等。
arduino
axios.interceptors.request.use(function (config) {
console.log(config, "请求拦截");
// 在发送请求之前对数据进行修改
config.data['token'] = "i love you"; // 假设添加一个 token
return config; // 返回修改后的配置对象
}, function (err) {
console.log(err);
});
在上面的例子中,config
是请求的配置对象。我们可以在这里修改请求的配置,比如给请求数据添加一个 token
,然后返回修改后的配置。如果发生错误,则会进入错误回调。
响应拦截器:
响应拦截器是指在接收到响应数据之后,可以对数据做一些处理,比如解密操作、错误处理等。
javascript
axios.interceptors.response.use(function (response) {
console.log(response, "响应拦截");
// 假设对响应数据进行解密
// const decryptedData = decrypt(response.data);
return response.data; // 返回处理后的数据给调用者
}, function (err) {
console.log(err);
});
在响应拦截器中,我们通常会对返回的数据做一些处理,或者统一处理所有请求的错误。在这个例子中,我们假设对返回的数据进行了解密操作,然后返回解密后的数据。
七麦数据实战
url:七麦数据 -专业移动产品商业分析平台-关键词优化-ASA优化-七麦科技
滑动页面,抓包,老样子还是看Fetch/XHR
类型的。
编辑
有三个数据包,样式都一样,看下它们的请求参数和响应数据。
编辑
编辑
编辑
编辑
编辑
编辑
这样子就知道0
对应的是付费榜,1
对应的是免费榜,2
对应的是畅销榜。既然三个请求参数都一样,那就以其中一个为例即可。
请求头中就一个analysis
参数的值是加密的,那目标就是知道该参数的值如何加密的。
按照惯例,搜索url。
编辑
编辑
总共三处地方,但这三处全是赋值操作,没有其他的代码,那么搜索url就失效了,接下来搜索analysis
关键词。
编辑
三处地方,但analysis
都位于url地址中,根本不可能是给analysis
参数赋值的,所以这也失效了,最后只能通过Initiator来找了。
编辑
明显的看到了Promise对象,就可以联想到axios拦截器了,搜索interceptors
。
编辑
也是三处,第一处是个赋值,不可能是加密逻辑,看下第二处和第三处整个的逻辑。
ini
l.prototype.request = function(e) {
// 判断传入的参数类型,e是一个字符串时,表示url,其他参数会被合并
if ("string" == typeof e) {
e = arguments[1] || {}; // 如果参数数组有第二个参数则使用第二个参数,否则使用空对象
e.url = arguments[0]; // 将第一个参数作为url
} else {
e = e || {}; // 如果没有传入参数,则使用空对象
}
// 将默认的配置和传入的配置合并
e = s(this.defaults, e);
// 确保method是小写字母(如果没有传入method,则使用默认的method,默认为"get")
if (e.method) {
e.method = e.method.toLowerCase();
} else if (this.defaults.method) {
e.method = this.defaults.method.toLowerCase();
} else {
e.method = "get";
}
// 初始化t数组,包含两个元素o和undefined
var t = [o, void 0];
// 创建一个Promise对象,初始化为传入的配置e
var n = Promise.resolve(e);
// 遍历请求拦截器,依次将fulfilled和rejected放入t数组
this.interceptors.request.forEach(function(e) {
t.unshift(e.fulfilled, e.rejected);
});
// 遍历响应拦截器,依次将fulfilled和rejected放入t数组
this.interceptors.response.forEach(function(e) {
t.push(e.fulfilled, e.rejected);
});
// 依次执行请求和响应拦截器,形成一个链式调用
while (t.length) {
n = n.then(t.shift(), t.shift());
}
// 返回最终的Promise对象
return n;
}
先对e
进行类型判断和值的重新赋值,然后声明t
为数组和n
为Promise对象,接着两个for循环,请求拦截器中遍历往t
数组的头部插入元素,响应拦截器遍历往t
数组的尾部插入元素,可以看到遍历完成后,t
数组中总共有6个对象,最后从t
数组的头部弹出两个元素交给Promise对象的then函数执行。
编辑
根据Promise对象的then函数可以知道,会给其传两个参数,成功了执行第一个参数,失败了执行第二个参数。所以如果这里存在加密逻辑的话,那么一定在t
数组的第一个参数处,定位。
编辑
编辑
从以下三个变量的值也可以看出没找错地方。
编辑
这段代码中存在非常多的花指令,得先将其还原,打断点进行调试,还原出来的代码如下。(catch中函数就不用管了)
css
function fn(t) {
// 定义变量n
var n;
// 通过i["ej"]方法获取"synct"并赋值给n
n = i["ej"]("synct");
// 设置s为计算得到的值
s = c["default"]["prototype"]["difftime"] = -i["ej"]("syncd") || +new z["Date"] - 1000 * n;
// 定义变量e和r
var e,
r = +new z["Date"] - (s || 0) - 1661224081041,
a = [];
// 如果t.params是undefined,则初始化为一个空对象
if (void 0 === t["params"]) {
t["params"] = {};
}
// 遍历t.params的每个键,并将符合条件的值添加到数组a中
z["Object"]["keys"](t["params"]).forEach(function (n) {
// 如果键为"analysis",则跳过
if (n === "analysis") {
return false;
}
// 如果t.params对象中存在当前键,则将对应的值加入数组a
if (t["params"].hasOwnProperty(n)) {
a.push(t["params"][n]);
}
});
// 对数组a进行排序并连接成一个字符串
a = a.sort().join("");
// 对a进行cv处理
a = i["cv"](a);
// 拼接URL和其他参数,准备加密
a = (a += "@#" + t["url"]["replace"](t["baseURL"], "")) + ("@#" + r) + ("@#" + 3);
// 使用cv方法对加密数据进行处理
e = i["cv"](i["oZ"](a, "xyz517cda96efgh"));
// 如果URL中不包含"analysis",则附加分析信息
if (-B === t["url"]["indexOf"]("analysis")) {
t["url"] += (-B !== t["url"]["indexOf"]("?") ? "&" : "?") + "analysis=" + z["encodeURIComponent"](e);
}
// 返回修改后的t对象
return t;
}
接下来分析这段代码。
n = i["ej"]("synct")
用于获取cookie中synct
的值。
编辑
s = c["default"]["prototype"]["difftime"] = -i["ej"]("syncd") || +new z["Date"] - 1000 * n;
用于获取cookie中syncd
中的值,如果cookie中没有syncd
,则s=new Date()-1000*n
。
编辑
r = +new z["Date"] - (s || 0) - 1661224081041
就是计算一个时间差,这个值不是固定的,所以我们可以直接把s
的值固定,上面两行代码就没用了。
void 0 === t[Zt] && (t[Zt] = {})
就是false。
编辑
z["Object"]["keys"]
等同于Object.keys
z["Object"]["keys"]
就是获取对象t["params"]
中的所有键名,类似于直接使用Object.keys(t["params"])
。这部分代码遍历t["params"]
的每个键,除了"analysis"
键外,其他键对应的值会被存储到数组a
中。a = a.sort().join("")
对a
数组进行排序,并将其元素通过空字符串连接成一个单一的字符串。a = i["cv"](a)
调用i["cv"]
函数对a
进行处理,这个方法会对数据进行加密或某种转换(具体功能取决于cv
函数的实现)。- 拼接字符串
a
a
会继续拼接一些参数,首先是url
去掉baseURL
后的部分,再加上r
和数字3
。这构成了新的加密输入。 e = i["cv"](i["oZ"](a, "xyz517cda96efgh"))
先使用i["oZ"](a, "xyz517cda96efgh")
对a
进行加密处理,然后用i["cv"]
再次处理,最终得到加密后的值e
。url
拼接分析参数
最后,判断url
中是否已经有查询字符串,如果没有,拼接上?
,如果有,则拼接上&
。然后将加密后的e
作为"analysis"
的值添加到url
查询参数中。
css
function fn(t) {
// 初始化变量
var n;
// 获取同步时间差,n是通过i["ej"]("synct")获取的值
n = i["ej"]("synct");
// 设置s为当前时间与同步时间的差值
s = c["default"]["prototype"]["difftime"] = -i["ej"]("syncd") || +new z["Date"] - 1000 * n;
// 定义e、r和a数组
var e,
r = +new z["Date"] - (s || 0) - 1661224081041, // 计算时间差
a = [];
// 如果t.params没有定义,则初始化为空对象
if (void 0 === t["params"]) {
t["params"] = {};
}
// 遍历t.params对象的所有键(除了"analysis"键)
z["Object"]["keys"](t["params"]).forEach(function (n) {
// 如果键名是"analysis",跳过
if (n === "analysis") {
return false;
}
// 如果t.params对象具有该键,则将对应的值加入数组a
if (t["params"].hasOwnProperty(n)) {
a.push(t["params"][n]);
}
});
// 对数组a进行排序并用空字符串连接
a = a.sort().join("");
// 使用i["cv"](a)对字符串进行加密处理
a = i["cv"](a);
// 拼接URL和其他参数,用于生成加密输入
a = (a += "@#" + t["url"]["replace"](t["baseURL"], "")) + ("@#" + r) + ("@#" + 3);
// 使用i["oZ"]和i["cv"]进行加密处理
e = i["cv"](i["oZ"](a, "xyz517cda96efgh"));
// 如果URL中没有包含"analysis",则在URL后添加"analysis"参数
if (-B === t["url"]["indexOf"]("analysis")) {
t["url"] += (-B !== t["url"]["indexOf"]("?") ? "&" : "?") + "analysis=" + z["encodeURIComponent"](e);
}
// 返回修改后的t对象
return t;
}
下面就是要去补全i["cv"]
和i["oZ"]
和这两个函数中用到的其他变量,花指令该还原就还原。
补全和还原后的代码如下:
点击查看代码
javascript
// 将16进制字符串转换为Unicode字符
function o(n) {
var t = "";
// 遍历16进制数组,将每个元素转换为Unicode字符并拼接
['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65'].forEach(function(n) {
t += unescape("%u00" + n); // 使用unescape将16进制转换为Unicode
});
// 返回转换后的字符串
return String[t](n); // 使用动态调用转换后的函数
}
// 返回一个字符串,字符串中的数字会被解码为Unicode字符
function u() {
return unescape("861831832863830866861836861862839831831839862863839830865834861863837837830830837839836861835833"
.replace(/8/g, "%u00")); // 将'8'替换为'%u00'
}
// 定义一个对象i,其中包含两个加密相关的函数
var i = {
// cv函数:对字符串进行加密和编码
cv: function v(t) {
// 对字符串进行URL编码
t = encodeURIComponent(t)
.replace(/%([0-9A-F]{2})/g, function(n, t) {
// 使用o函数对每个编码后的字符进行转换
return o("0x" + t);
});
try {
// 返回Base64编码的字符串
return btoa(t);
} catch (n) {
// 如果btoa不可用,使用Node.js的Buffer进行Base64编码
return Buffer.from(t).toString("base64");
}
},
// oZ函数:对字符串进行异或加密处理
oZ: function h(n, t) {
t = t || u(); // 如果t为空,则使用u函数的结果
var e = n.split(""); // 将字符串分割为数组
var r = t.length;
var a = "charCodeAt"; // 定义字符的编码方法
// 遍历字符串中的每个字符,进行异或操作
for (var i = 0; i < e.length; i++) {
e ^ t ); // 异或运算
}
return e.join(""); // 返回处理后的字符串
}
};
// fn函数:处理传入对象t,进行参数加密和URL拼接
function fn(t) {
// 获取当前时间,并计算出时间差r
var e, r = new Date() + 226 - 1661224081041;
var a = [];
// 遍历t.params对象的所有键,排除"analysis"键,并将其对应的值加入数组a
Object.keys(t["params"]).forEach(function(n) {
if (n == "analysis") return false; // 跳过"analysis"键
if (t["params"].hasOwnProperty(n)) {
a.push(t["params"][n]); // 将值添加到数组a中
}
});
// 对数组a进行排序,并将排序后的元素连接成一个字符串
a = a.sort().join("");
// 对字符串a进行加密处理
a = i["cv"](a);
// 拼接a、URL、时间戳和数字3
a = (a += "@#" + t["url"].replace(t["baseURL"], "")) + ("@#" + r) + ("@#" + 3);
// 使用oZ函数进行异或加密处理
e = i["cv"](i["oZ"](a, "xyz517cda96efgh"));
// 返回加密后的结果
return e;
}
测试一下。
javascript
// 请求配置对象 t
var t = {
"url": "/rank/indexPlus/brand_id/1", // 请求的 URL
"method": "get", // 请求方法
"headers": {
"common": { // 通用请求头
"Accept": "application/json, text/plain, */*"
},
"delete": {}, // 删除请求特有的头部
"get": {}, // GET 请求特有的头部
"head": {}, // HEAD 请求特有的头部
"post": { // POST 请求特有的头部
"Content-Type": "application/x-www-form-urlencoded"
},
"put": { // PUT 请求特有的头部
"Content-Type": "application/x-www-form-urlencoded"
},
"patch": { // PATCH 请求特有的头部
"Content-Type": "application/x-www-form-urlencoded"
}
},
"params": {}, // 请求参数
"baseURL": "https://api.qimai.cn", // 基本 URL
"transformRequest": [null], // 请求数据的转换
"transformResponse": [null], // 响应数据的转换
"timeout": 15000, // 请求超时时间(毫秒)
"withCredentials": true, // 是否携带凭证
"xsrfCookieName": "XSRF-TOKEN", // XSRF Cookie 名称
"xsrfHeaderName": "X-XSRF-TOKEN", // XSRF 请求头名称
"maxContentLength": -1, // 最大内容长度
"maxBodyLength": -1 // 最大请求体长度
};
// 调用 fn 函数并打印结果
console.log(fn(t));
运行结果如下:
编辑
得到了跟analysis
参数值相似的字符串,说明我们找到了加密的逻辑,接下来就可以写python代码爬取数据了,完整的python代码和JavaScript代码如下:
JavaScript代码:
javascript
// 将16进制字符串转换为Unicode字符
function o(n) {
var t = "";
// 遍历16进制数组,将每个元素转换为Unicode字符并拼接
['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65'].forEach(function(n) {
t += unescape("%u00" + n); // 使用unescape将16进制转换为Unicode
});
// 返回转换后的字符串
return String[t](n); // 使用动态调用转换后的函数
}
// 返回一个字符串,字符串中的数字会被解码为Unicode字符
function u() {
return unescape("861831832863830866861836861862839831831839862863839830865834861863837837830830837839836861835833"
.replace(/8/g, "%u00")); // 将'8'替换为'%u00'
}
// 定义一个对象i,其中包含两个加密相关的函数
var i = {
// cv函数:对字符串进行加密和编码
cv: function v(t) {
// 对字符串进行URL编码
t = encodeURIComponent(t)
.replace(/%([0-9A-F]{2})/g, function(n, t) {
// 使用o函数对每个编码后的字符进行转换
return o("0x" + t);
});
try {
// 返回Base64编码的字符串
return btoa(t);
} catch (n) {
// 如果btoa不可用,使用Node.js的Buffer进行Base64编码
return Buffer.from(t).toString("base64");
}
},
// oZ函数:对字符串进行异或加密处理
oZ: function h(n, t) {
t = t || u(); // 如果t为空,则使用u函数的结果
var e = n.split(""); // 将字符串分割为数组
var r = t.length;
var a = "charCodeAt"; // 定义字符的编码方法
// 遍历字符串中的每个字符,进行异或操作
for (var i = 0; i < e.length; i++) {
e[i] = o(e[i].charCodeAt(0) ^ t[(i + 10) % r].charCodeAt(0)); // 异或运算
}
return e.join(""); // 返回处理后的字符串
}
};
// fn函数:处理传入对象t,进行参数加密和URL拼接
function fn(t) {
var e, r = new Date() + 226 - 1661224081041;
var a = [];
// 遍历t.params对象的所有键,排除"analysis"键,并将其对应的值加入数组a
Object.keys(t["params"]).forEach(function(n) {
if (n == "analysis") return false; // 跳过"analysis"键
if (t["params"].hasOwnProperty(n)) {
a.push(t["params"][n]); // 将值添加到数组a中
}
});
// 对数组a进行排序,并将排序后的元素连接成一个字符串
a = a.sort().join("");
// 对字符串a进行加密处理
a = i["cv"](a);
// 拼接a、URL、时间戳和数字3
a = (a += "@#" + t["url"].replace(t["baseURL"], "")) + ("@#" + r) + ("@#" + 3);
// 使用oZ函数进行异或加密处理
e = i["cv"](i["oZ"](a, "xyz517cda96efgh"));
// 返回加密后的结果
return e;
}
// final函数:生成最终的加密结果
function final(url, pm) {
// 创建一个params对象,用于传递给fn函数
var params = {
"url": url, // 请求的URL
"baseURL": "https://api.qimai.cn", // 基本URL
"params": pm, // 请求参数
};
// 返回加密后的结果
return fn(params);
}
python代码:
python
import subprocess
from functools import partial
import execjs
import json
import requests
# 修改 subprocess.Popen 默认编码为 "utf-8"
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
# 读取并编译 JavaScript 文件
with open("拦截器逻辑二.js", mode="r", encoding="utf-8") as f:
js = execjs.compile(f.read())
# 请求参数数据
data = {
"brand": "all",
"country": "cn",
"date": "2024-03-18",
"device": "iphone",
"genre": "36",
"page": 2,
}
# 定义主机和请求路径
host = "https://api.qimai.cn"
url = "/rank/indexPlus/brand_id/1"
# 调用 JS 中的 final 函数生成 analysis 参数
analysis = js.call("final", url, data)
# 拼接最终的请求 URL,将 analysis 参数附加到 URL 查询字符串中
final_url = host + url + "?analysis=" + analysis
# 输出最终 URL(可取消注释进行调试)
# print(final_url)
# 创建 requests 会话并设置请求头(User-Agent)
session = requests.session()
session.headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36"
)
}
# 预加载 Cookie:加载最开始的 cookie
session.get("https://www.qimai.cn/rank")
# 手动设置 qm_check Cookie(经过测试该 Cookie 没什么用)
session.cookies["qm_check"] = (
"A1sdRUIQChtxen8pI0dAMRcOUFseEHBeQF0JTjVBWCwycRd1QlhAXFEGFUdeS0laHQdKAAkABAsgXyVBWD0TR1JRRAp0BQlFEBQ3TSZKFUdBbwxvBBRFIlQsSUhTFxsQU1FVV1NHXEVYVElWBRsCHAkSSQ%3D%3D"
)
# 发起 GET 请求获取数据
resp = session.get(final_url)
# 对响应文本进行解码,将 unicode 转义字符转换为对应字符
decoded_text = bytes(resp.text, 'utf-8').decode('unicode_escape')
# 打印最终解码后的响应结果
print(decoded_text)
运行python代码结果如下:
编辑
成功拿到数据。
补充
在python代码中可以看到添加了cookie,虽然测试后得知这个参数没有用,但是出于学习的目的,也可以来看一下这个参数的加密逻辑。
全局搜索qm_check
。
编辑
一个都没搜到,这时候就要用到上节讲过的webhook工具了,选择Hook Setcookie
后,刷新界面。
编辑
看到val
中还没有出现qm_check
,一直放,直到看到qm_check
。
编辑
这里的qm_check
已经被加密了,要想找到加密逻辑,就得往上看,通过Call Stack往上找。
编辑
编辑
v(p(z[Y3][V3](n), s))
这段代码执行得到的结果就是加密后的字符串,并且n
和s
都是明文,所以加密逻辑肯定跟v
、p
、z
这几个有关。
编辑
找到这几个函数的实现代码。
z[Y3][V3]
相当于JSON.stringify
p
函数的实现如下。
编辑
花指令处理过后如下:(u()函数生成的是一个固定的字符串)
点击查看代码
ini
function p(n, t) {
// 如果未传入第二个参数t,则使用默认值
t = t || 'a12c0fa6ab9119bc90e4ac7700796a53';
// 将字符串n拆分为字符数组
n = n.split("");
// 获取n和t的长度
var e = n.length;
var r = t.length;
// 定义字符串"charCodeAt",便于动态调用字符编码方法
var a = "charCodeAt";
// 遍历字符串n的每个字符
for (var i = 0; i < e; i++) {
// 对当前字符的字符码和t中对应字符的字符码进行异或操作,
// 然后调用o函数将结果转换成对应字符
n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
}
// 将处理后的字符数组重新合并成字符串并返回
return n.join("");
}
v
函数的实现如下
编辑
花指令处理过后如下:
点击查看代码
scss
function v(t) {
t = encodeURIComponent(t)["replace"](/%([0-9A-F]{2})/g, function(n, t) {
return o("0x" + t)
});
try {
return btoa(t)
} catch (n) {
return Buffer["from"](t)["toString"]("base64")
}
}
把这两个函数用到的其他函数找到,补充完整,整体代码如下:
点击查看代码
javascript
// 定义p函数,使用异或加密字符串
function p(n, t) {
// 如果没有传入t参数,则使用默认值
t = t || 'a12c0fa6ab9119bc90e4ac7700796a53';
// 将n拆分成字符数组
n = n.split("");
// 获取n和t的长度
var e = n.length;
var r = t.length;
// 使用charCodeAt方法获取字符编码
var a = "charCodeAt";
// 遍历每个字符并进行异或操作
for (var i = 0; i < e; i++) {
// 对n中的字符与t中的字符进行异或运算,结果通过o函数处理
n[i] = o(n[i].charCodeAt(0) ^ t[(i + r) % r].charCodeAt(0));
}
// 将处理后的字符数组重新拼接成字符串并返回
return n.join("");
}
// 定义v函数,对字符串进行URL编码并Base64编码
function v(t) {
// URL编码,并替换每个字符的编码为Unicode字符
t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function(n, t) {
return o("0x" + t); // 使用o函数处理每个字符
});
try {
// 尝试使用btoa将编码后的字符串转为Base64
return btoa(t);
} catch (n) {
// 如果btoa不可用,使用Buffer.from进行Base64编码
return Buffer.from(t).toString("base64");
}
}
// 定义o函数,用于将16进制字符串转为Unicode字符
function o(n) {
var t = "";
// 生成一个Unicode字符并拼接
['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65'].forEach(function(n) {
t += unescape("%u00" + n); // 使用unescape将16进制转换为Unicode字符
});
// 返回通过动态调用String对象方法的字符
var e = t;
return String[e](n); // 调用String方法
}
// 定义一个数据对象n
var n = {
"gpu": "ANGLE (Intel, Intel(R) UHD Graphics 630 (0x00003E9B) Direct3D11 vs_5_0 ps_5_0, D3D11)",
"check": "0,0,0,0,0"
};
// 定义字符串s
var s = "xyz57209048efgh";
// 输出v函数处理后的结果
console.log(v(p(JSON.stringify(n), s)));
运行得到的结果如下:
编辑
页面上存储的值如下:
编辑
两个值相同,说明加密逻辑没有找错。
编辑