JavaScript逆向-七麦数据实战

  1. 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 对象,它有两个参数:resolvereject。如果操作成功,调用 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))这段代码执行得到的结果就是加密后的字符串,并且ns都是明文,所以加密逻辑肯定跟vpz这几个有关。

编辑

找到这几个函数的实现代码。
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)));

运行得到的结果如下:

编辑

页面上存储的值如下:

编辑

两个值相同,说明加密逻辑没有找错。

​编辑

相关推荐
橘猫云计算机设计2 分钟前
springboot基于hadoop的酷狗音乐爬虫大数据分析可视化系统(源码+lw+部署文档+讲解),源码可白嫖!
数据库·hadoop·spring boot·爬虫·python·数据分析·毕业设计
YOULANSHENGMENG8 分钟前
linux 下python 调用c++的动态库的方法
c++·python
SsummerC24 分钟前
【leetcode100】零钱兑换Ⅱ
数据结构·python·算法·leetcode·动态规划
一眼青苔1 小时前
切割PDF使用python,库PyPDF2
服务器·python·pdf
电商数据girl1 小时前
产品经理对于电商接口的梳理||电商接口文档梳理与接入
大数据·数据库·python·自动化·产品经理
三道杠卷胡2 小时前
【AI News | 20250424】每日AI进展
人工智能·pytorch·python·语言模型·github
T糖锅G2 小时前
小白自学python第二天
python
满怀10153 小时前
【OpenCV图像处理实战】从基础操作到工业级应用
图像处理·人工智能·python·opencv·计算机视觉·编程入门
AI视觉网奇3 小时前
四元数转旋转矩阵
人工智能·pytorch·python
Bruce_Liuxiaowei4 小时前
基于Python+Flask的MCP SDK响应式文档展示系统设计与实现
开发语言·python·flask·mcp