首页 > 编程语言 >JavaScript逆向之七麦数据实战

JavaScript逆向之七麦数据实战

时间:2024-03-18 12:22:17浏览次数:20  
标签:function 逆向 之七麦 return url JavaScript analysis params console

知识点

Promise对象

Promise对象是ES6版本中提供的,主要是为了解决死亡回调的问题。
先看一段代码:

点击查看代码
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);
}

该代码是登录网站后网站一步步显示信息的一个demo,可以看到里面存在很多的嵌套,如果想要解决多层嵌套的问题,就可以采用Promise对象,看如下demo:

点击查看代码
function send(url) {
  // promise:确保,保证
  // reslove:解决了
  // reject:拒绝

  return new Promise(function (resolve, reject) {
      console.log("帮你发送一个请求到", url);   // 答应你的一件事
      let result = 123;
      if (result) {
          // 这件事我办成了
          // 接下来你要做的事应该是调用这个函数的那个人去写
          resolve(i);    //这里代表当前任务被解决
      } else {
          // 这件事没办成
          reject(i);     // 这里代表当前任务没解决
      }
  });
}

function fn() {
    let username = "";
    let password = "";

    //发送请求到登录
    send("xxxxx").then(function (data) {
        console.log("登录的结果");
        console.log("登录返回的结果是",data);
        return send("加载菜单");
    }).then(function (data) {
        // 加载菜单
        console.log("加载菜单得到的信息",data);
        return send("加载个人信息")
    }).then(function (data) {
        // 加载个人信息
        console.log("加载个人信息得到的信息", data);
    })
}

send函数中会返回一个Promise对象,如果成功了就会执行resolve对应的函数,失败了就执行reject对应的函数。在fn函数中省略了reject对应的函数,因为一般会在Promise对象的最后加一个catch,只要失败了,就直接走catch中的函数,把整个代码抽象一下如下:

点击查看代码
//链式逻辑
new Promise(function (a, b) {}).then(function () {
    return new Promise();
},function () {

}).then(function () {
    return new Promise();
}).then(function () {

}).catch(function () {
    console.log("程序出错,请联系管理员....")
})

axios拦截器

axios是一个基于Promise的网络请求库,网站如果采用的是axios方法,那么加密和解密的逻辑大概率存在于axios拦截器中。
axios拦截器分为请求拦截器和响应拦截器,加密逻辑大概率在请求拦截器中,解密逻辑大概率在响应拦截器中,下面看axios拦截器使用的代码:

点击查看代码
// 请求的拦截器
axios.interceptors.request.use(function (config) {
    console.log(config, "你好啊");
    // 尝试修改请求参数
    config.data['hehe'] = "i love you";
    return config;
}, function (err) {
    console.log(err);
});

// 响应的拦截器
axios.interceptors.response.use(function (response) {
    console.log(response);
    // 这里一般会有什么???    解密操作

    return response.data;   // 拦截器返回的东西直接给到then中的函数
}, function (err) {
    console.log(err);
});

上面两个知识点讲完,就该进入正篇了。

七麦数据实战

url:https://www.qimai.cn/rank
滑动页面,抓包,老样子还是看Fetch/XHR类型的。

有三个数据包,样式都一样,看下它们的请求参数和响应数据。




这样子就知道0对应的是付费榜,1对应的是免费榜,2对应的是畅销榜。既然三个请求参数都一样,那就以其中一个为例即可。
请求头中就一个analysis参数的值是加密的,那目标就是知道该参数的值如何加密的。
按照惯例,搜索url。


总共三处地方,但这三处全是赋值操作,没有其他的代码,那么搜索url就失效了,接下来搜索analysis关键词。

三处地方,但analysis都位于url地址中,根本不可能是给analysis参数赋值的,所以这也失效了,最后只能通过Initiator来找了。

明显的看到了Promise对象,就可以联想到axios拦截器了,搜索interceptors

也是三处,第一处是个赋值,不可能是加密逻辑,看下第二处和第三处整个的逻辑。

点击查看代码
l.prototype.request = function(e) {
            "string" == typeof e ? (e = arguments[1] || {}).url = arguments[0] : e = e || {},
            (e = s(this.defaults, e)).method ? e.method = e.method.toLowerCase() : this.defaults.method ? e.method = this.defaults.method.toLowerCase() : e.method = "get";
            var t = [o, void 0]
              , n = Promise.resolve(e);
            for (this.interceptors.request.forEach((function(e) {
                t.unshift(e.fulfilled, e.rejected)
            }
            )),
            this.interceptors.response.forEach((function(e) {
                t.push(e.fulfilled, e.rejected)
            }
            )); t.length; )
                n = n.then(t.shift(), t.shift());
            return n
        }

先对e进行类型判断和值的重新赋值,然后声明t为数组和n为Promise对象,接着两个for循环,请求拦截器中遍历往t数组的头部插入元素,响应拦截器遍历往t数组的尾部插入元素,可以看到遍历完成后,t数组中总共有6个对象,最后从t数组的头部弹出两个元素交给Promise对象的then函数执行。

根据Promise对象的then函数可以知道,会给其传两个参数,成功了执行第一个参数,失败了执行第二个参数。所以如果这里存在加密逻辑的话,那么一定在t数组的第一个参数处,定位。


从以下三个变量的值也可以看出没找错地方。

这段代码中存在非常多的花指令,得先将其还原,打断点进行调试,还原出来的代码如下。(catch中函数就不用管了)

点击查看代码
function fn(t) {
    var n;
    n = i["ej"]("synct"),
        s = c["default"]["prototype"]["difftime"] = -i["ej"]("syncd") || +new z["Date"] - 1000 * n;
    var e, r = +new z["Date"] - (s || 0) - 1661224081041, a = [];
    return void 0 === t["params"] && (t["params"] = {}),
        z["Object"]["keys"](t["params"])["forEach"](function (n) {
            if (n == "analysis")
                return !1;
            t["params"]["hasOwnProperty"](n) && a["push"](t["params"][n])
        }),
        a = a["sort"]()["join"](""),
        a = i["cv"](a),
        a = (a += "@#" + t["url"]["replace"](t["baseURL"], "")) + ("@#" + r) + ("@#" + 3),
        e = i["cv"](i["oZ"](a, "xyz517cda96efgh")),
    -B == t["url"]["indexOf"]("analysis") && (t["url"] += (-B != t["url"]["indexOf"]("?") ? "&" : "?") + "analysis" + "=" + z["encodeURIComponent"](e)),
        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"](t["params"])["forEach"](function (n) { if (n == "analysis") return !1; t["params"]["hasOwnProperty"](n) && a["push"](t["params"][n]) })遍历t["params"]中的所有键,将对应的值全部存放到a数组中,z是window对象,故z["Object"]["keys"]等同于Object["keys"]
a = a["sort"]()["join"]("")对a数组进行排序,并用空字符串连接。
a = i["cv"](a)需要知道i["cv"]是什么,等下直接把源代码复制进来即可。
a = (a += "@#" + t["url"]["replace"](t["baseURL"], "")) + ("@#" + r) + ("@#" + 3)a的值进行拼接。
e = i["cv"](i["oZ"](a, "xyz517cda96efgh"))同理,直接复制源代码。
-B == t["url"]["indexOf"]("analysis") && (t["url"] += (-B != t["url"]["indexOf"]("?") ? "&" : "?") + "analysis" + "=" + z["encodeURIComponent"](e))判断url背后的参数是用&还是?连接。这里最主要的是要得到e的值,直接返回e即可。
到目前为止,化简后的代码为:

点击查看代码
function fn(t) {
   var e, r = new Date() + 226 - 1661224081041, a = [];
    return false,
        Object["keys"](t["params"])["forEach"](function (n) {
            if (n == "analysis")
                return !1;
            t["params"]["hasOwnProperty"](n) && a["push"](t["params"][n])
        }),
        a = a["sort"]()["join"](""),
        a = i["cv"](a),
        a = (a += "@#" + t["url"]["replace"](t["baseURL"], "")) + ("@#" + r) + ("@#" + 3),
        e = i["cv"](i["oZ"](a, "xyz517cda96efgh")),
        e;
}

下面就是要去补全i["cv"]i["oZ"]和这两个函数中用到的其他变量,花指令该还原就还原。
补全和还原后的代码如下:

点击查看代码
function o(n) {
    t = "",
    ['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65']["forEach"](function(n) {
        t += unescape("%u00" + n)
    });
    var t, e = t;
    return String[e](n)
}

function u() {
    return unescape("861831832863830866861836861862839831831839862863839830865834861863837837830830837839836861835833"["replace"](/8/g, "%u00"))
}

var i = {
    cv: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")
        }
    },
    oZ:function h(n, t) {
        t = t || u();
        for (var e = (n = n["split"](""))["length"], r = t["length"], a = "charCodeAt", i = 0; i < e; i++)
            n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
        return n["join"]("")
    }
};


function fn(t) {
   var e, r = new Date() + 226 - 1661224081041, a = [];
    return false,
        Object["keys"](t["params"])["forEach"](function (n) {
            if (n == "analysis")
                return !1;
            t["params"]["hasOwnProperty"](n) && a["push"](t["params"][n])
        }),
        a = a["sort"]()["join"](""),
        a = i["cv"](a),
        a = (a += "@#" + t["url"]["replace"](t["baseURL"], "")) + ("@#" + r) + ("@#" + 3),
        e = i["cv"](i["oZ"](a, "xyz517cda96efgh")),
        e;
}

测试一下。

点击查看代码
var t = {
    "url": "/rank/indexPlus/brand_id/1",
    "method": "get",
    "headers": {
        "common": {
            "Accept": "application/json, text/plain, */*"
        },
        "delete": {},
        "get": {},
        "head": {},
        "post": {
            "Content-Type": "application/x-www-form-urlencoded"
        },
        "put": {
            "Content-Type": "application/x-www-form-urlencoded"
        },
        "patch": {
            "Content-Type": "application/x-www-form-urlencoded"
        }
    },
    "params": {},
    "baseURL": "https://api.qimai.cn",
    "transformRequest": [
        null
    ],
    "transformResponse": [
        null
    ],
    "timeout": 15000,
    "withCredentials": true,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1
}

console.log(fn(t));

运行结果如下:

得到了跟analysis参数值相似的字符串,说明我们找到了加密的逻辑,接下来就可以写python代码爬取数据了,完整的python代码和JavaScript代码如下:
JavaScript代码:

点击查看代码
function o(n) {
    t = "",
    ['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65']["forEach"](function(n) {
        t += unescape("%u00" + n)
    });
    var t, e = t;
    return String[e](n)
}

function u() {
    return unescape("861831832863830866861836861862839831831839862863839830865834861863837837830830837839836861835833"["replace"](/8/g, "%u00"))
}

var i = {
    cv: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")
        }
    },
    oZ:function h(n, t) {
        t = t || u();
        for (var e = (n = n["split"](""))["length"], r = t["length"], a = "charCodeAt", i = 0; i < e; i++)
            n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
        return n["join"]("")
    }
};


function fn(t) {
   var e, r = new Date() + 226 - 1661224081041, a = [];
    return false,
        Object["keys"](t["params"])["forEach"](function (n) {
            if (n == "analysis")
                return !1;
            t["params"]["hasOwnProperty"](n) && a["push"](t["params"][n])
        }),
        a = a["sort"]()["join"](""),
        a = i["cv"](a),
        a = (a += "@#" + t["url"]["replace"](t["baseURL"], "")) + ("@#" + r) + ("@#" + 3),
        e = i["cv"](i["oZ"](a, "xyz517cda96efgh")),
        e;
}

function final(url, pm) {
    var params = {
        "url": url,
        "baseURL": "https://api.qimai.cn",
        "params":pm,
    };
    return fn(params);
}

python代码:

点击查看代码
import subprocess
from functools import partial

subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")

import execjs
import json
import requests

f = open("拦截器逻辑二.js", mode="r", encoding="utf-8")
js = execjs.compile(f.read())
f.close()

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"
analysis = js.call("final", url, data)
final_url = host+url+"?analysis=" + analysis
# print(final_url)

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
session.get("https://www.qimai.cn/rank")

# 经过测试,这玩意没什么用
session.cookies["qm_check"] = "A1sdRUIQChtxen8pI0dAMRcOUFseEHBeQF0JTjVBWCwycRd1QlhAXFEGFUdeS0laHQdKAAkABAsgXyVBWD0TR1JRRAp0BQlFEBQ3TSZKFUdBbwxvBBRFIlQsSUhTFxsQU1FVV1NHXEVYVElWBRsCHAkSSQ%3D%3D"

# 开干
resp = session.get(final_url)
decoded_text = bytes(resp.text, 'utf-8').decode('unicode_escape')
print(decoded_text)

运行python代码结果如下:

成功拿到数据。

标签:function,逆向,之七麦,return,url,JavaScript,analysis,params,console
From: https://www.cnblogs.com/sbhglqy/p/18080090

相关文章

  • 《手把手教你》系列技巧篇(四十)-java+ selenium自动化测试-JavaScript的调用执行-下篇(
    1.简介 在实际工作中,我们需要对处理的元素进行高亮显示,或者有时候为了看清楚做跟踪鼠标点击了哪些元素需要标记出来。今天宏哥就在这里把这种测试场景讲解和分享一下。2.用法创建一个执行JS的对象,也就是JavascriptExecutor对象,这个对象是由driver进行强制类型转......
  • 滴水逆向笔记系列-win32总结8-59.枚举窗口_鼠标键盘事件函数-60.加密壳项目
    第五十九课win32枚举窗口_鼠标键盘事件函数1.查找指定窗口::FindWindow()函数获取窗口句柄,再通过句柄控制窗口,函数的参数可以通过vs的spy++工具获得TCHARszTitle[MAX_PATH]={0}; HWNDhwnd=::FindWindow(TEXT("#32770"),TEXT("飞鸽传书IPMessenger")); ......
  • 滴水逆向笔记系列-win32总结9-61.CE使用-62.ShellCode_远程线程注入
    第六十一课CE使用下载完CE后用ce自带的小作业练练1.第二题先打开进程Firstscan搜索100,发现有很多100,我们先让右边程序Hitme,然后Nextscan搜索96,发现已经搜出来了,正常数据会很多,就需要继续改继续搜,最后点击下面value修改为1000即可2.第三题先NewScan搜索小于500的,点击......
  • 滴水逆向笔记系列-win32总结10-63.IAT HOOK-64.Inline HOOK
    第六十三课IATHOOK这节课得把前面PE部分的IAT表复习好,再来做就简单多了1.IATHOOK是什么其实就是找到IAT表的位置再换成自己定义的函数,只是我们替换的函数需要和原函数的结构保持一直,比如我们要HOOKMessagebox函数,那么我们需要定义一个MyMessagebox函数,他的结构应该与Messa......
  • 滴水逆向笔记系列-win32总结7-57.进程创建-58.挂起方式创建进程
    第五十七课win32进程创建1.进程创建的过程父进程创建子进程,父进程挂了子进程不会挂0x00程序、imagebuffer、进程的关系程序就是一个普通的二进制文件;imagebuffer就是程序拉伸到内存后的二进制文件,但是没有线程去执行他,进程则是有线程去运行这个imagebuffer0x01过程......
  • JavaScript基础 —— 学习 第四天(完结)
    一、对象(一)对象介绍对象:object是JavaScript里面的一种数据类型可以看作一种无序的数据的集合可以详细的描述某个事物null是空对象对象是由属性和方法组成的属性:手机特征大小颜色什么的方法:能进行的一些行为手机打电话<body><script>let对象名={......
  • 新一代 Kaldi: 支持 JavaScript 进行本地语音识别和语音合成啦!
    简介新一代 Kaldi 部署框架 sherpa-onnx 支持的编程语言 API 大家庭中,最近增加了一个新成员: JavaScript。为了方便大家查看,我们把目前所有支持的编程语言汇总成如下一张图。注:这个家庭还在不断的扩充,总有一款适合你!后续我们会增加 Dart、Rust、WebAssembly 等支持......
  • 滴水逆向笔记系列-win32总结4-50.创建线程-51.线程控制_CONTEXT结构
    第五十课win32创建线程1.进程与线程程序就是在硬盘里还没跑起来的二进制文件,进程就是已经运行中的程序,一个进程至少有一个线程,比如一个正在举行的活动需要几十个人帮忙干活,进程就是那个活动,线程就是那几十个人一个线程启动是需要占用一个cpu的一个新线程也会创建一个新堆......
  • 滴水逆向笔记系列-win32总结5-52.临界区-53.互斥体
    第五十二课win32临界区1.线程安全问题其实就是多个线程同时对一个资源(即全局变量等)进行操作2.临界区设计图临界区的使用1、创建CRITICAL_SECTION: CRITICAL_SECTIONcs; 2、在使用前进行初始化 InitializeCriticalSection(&cs); ......
  • 滴水逆向笔记系列-win32总结1-43.宽字节-44.事件_消息_消息处理函数
    第四十三课win32宽字节1.编码0x00.ASCII码1、ASCII码使用指定的7位或8位二进制数组合来表示128或256种可能的字符2、标准ASCII码使用7位二进制数来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。3、扩展ASCII码允许将......