逆向案例二十九——复杂扣代码,七某数据(一)

网址:aHR0cHM6Ly93d3cucWltYWkuY24vcmFuaw==

抓包分析载荷中有加密参数analysis:

获取数据代码,经过分析,发现analysis确实是校验参数cai:

import requests

cookies = {
    'qm_check': 'A1sdRUIQChtxen8pI0dAMRcOUFseEHBeQF0JTjVBWCwycRd1QlhAXFEGFUdeSklaHQdKAAkABAsgJ1dBWD0TR1JRRAp0BQlFEBQ3TSZKFUdBbwxvBBRFIlQsSUhTFxsQU1FVV1NHXEVYVElWBRsCHAkSSQ%3D%3D',
    'gr_user_id': '7b300ae1-1a2e-48f3-ac26-ef24f571d4c9',
    'USERINFO': 'jHFy6VITNfYjO5gzKnxVCcHVKhkUrDYQL4rEoi%2Bu%2Fa3gWGxj5dQDgJzrkKFOU6FOm%2F4%2FLH%2BKRQ6Kkezd22166yCiZ%2FlYWomlIfHgN40yWpgrHONQD7IehPC71gALl3B5eweCfpHISDgX3EvMl7rYBQ%3D%3D',
    'ada35577182650f1_gr_last_sent_cs1': 'qm21331348315',
    'aso_ucenter': 'c0160lkfZZ3b6mCL6Ic%2FE9rfux%2FFzvScbLXxY1aeQwexSd5FHwlCTHtQXpRw3BnZag8',
    'AUTHKEY': 'kieguXDTNdsivnTuTu%2FDz8rEJtxANQ3H4WyYIrC6WalwuS0e3QcGe2ynYUWdRmasuVvmGU65IVQ%2BOjFG4DNkWj%2BcAjh7wM9GbQyPulS5nrkz5m8CC7qAYQ%3D%3D',
    'synct': '1714182214.703',
    'syncd': '-138',
    'PHPSESSID': '1g4mb7ol6ddoiej2h7rak6eh0k',
    'ada35577182650f1_gr_session_id': '5b1ab32f-3236-4628-b301-aea7dcc57299',
    'ada35577182650f1_gr_last_sent_sid_with_cs1': '5b1ab32f-3236-4628-b301-aea7dcc57299',
    'ada35577182650f1_gr_cs1': 'qm21331348315',
    'ada35577182650f1_gr_session_id_sent_vst': '5b1ab32f-3236-4628-b301-aea7dcc57299',
}

headers = {
    'authority': 'api.qimai.cn',
    'accept': 'application/json, text/plain, */*',
    'accept-language': 'zh-CN,zh;q=0.9',
    'cache-control': 'no-cache',
    # 'cookie': 'qm_check=A1sdRUIQChtxen8pI0dAMRcOUFseEHBeQF0JTjVBWCwycRd1QlhAXFEGFUdeSklaHQdKAAkABAsgJ1dBWD0TR1JRRAp0BQlFEBQ3TSZKFUdBbwxvBBRFIlQsSUhTFxsQU1FVV1NHXEVYVElWBRsCHAkSSQ%3D%3D; gr_user_id=7b300ae1-1a2e-48f3-ac26-ef24f571d4c9; USERINFO=jHFy6VITNfYjO5gzKnxVCcHVKhkUrDYQL4rEoi%2Bu%2Fa3gWGxj5dQDgJzrkKFOU6FOm%2F4%2FLH%2BKRQ6Kkezd22166yCiZ%2FlYWomlIfHgN40yWpgrHONQD7IehPC71gALl3B5eweCfpHISDgX3EvMl7rYBQ%3D%3D; ada35577182650f1_gr_last_sent_cs1=qm21331348315; aso_ucenter=c0160lkfZZ3b6mCL6Ic%2FE9rfux%2FFzvScbLXxY1aeQwexSd5FHwlCTHtQXpRw3BnZag8; AUTHKEY=kieguXDTNdsivnTuTu%2FDz8rEJtxANQ3H4WyYIrC6WalwuS0e3QcGe2ynYUWdRmasuVvmGU65IVQ%2BOjFG4DNkWj%2BcAjh7wM9GbQyPulS5nrkz5m8CC7qAYQ%3D%3D; synct=1714182214.703; syncd=-138; PHPSESSID=1g4mb7ol6ddoiej2h7rak6eh0k; ada35577182650f1_gr_session_id=5b1ab32f-3236-4628-b301-aea7dcc57299; ada35577182650f1_gr_last_sent_sid_with_cs1=5b1ab32f-3236-4628-b301-aea7dcc57299; ada35577182650f1_gr_cs1=qm21331348315; ada35577182650f1_gr_session_id_sent_vst=5b1ab32f-3236-4628-b301-aea7dcc57299',
    'origin': 'https://www.qimai.cn',
    'pragma': 'no-cache',
    'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
    '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/120.0.0.0 Safari/537.36',
}

params = {
    'analysis': 'ew8nHiY7SQ17cgcaKht0BCtUIRsaPjRAUG8hCwMLAwkmREcKGBReHl4NAARBZgkTFEcaCxtbVWgKAE4JdkZTVVFNQUgHAQNQUSEaBQ==',
    'brand': 'all',
    'country': 'cn',
    'device': 'iphone',
    'genre': '36',
    'date': '2024-04-27',
    'page': '7',
}

response = requests.get('https://api.qimai.cn/rank/indexPlus/brand_id/0', params=params, cookies=cookies, headers=headers)

采取搜索方法,发现搜不到,果断跟栈

分析栈,发现还有异步栈,点击第一个,开始send

发现v中有加密参数

直接跳过exports栈。从l.requests开始,遇到熟悉的n.then,打赏断点

进入第一个函数

打上断点,此时t中没有加密内容,那么肯定是在后面有加密,可能是函数后面,也可能是t中别的

在下方打上断点,嘿嘿,发现此时有了加密参数,添加到了url中,故肯定是里面某组函数进行了加密。这段代码明显是混淆代码。

 加密生成函数,e = (0,i[jt])((0, i[qt])(a, d)) ,又是i,又是a,又是d,所以先整体复制try里面的代码,在做删减。声明get_analysis函数,接收整个复制内容。

t = {
    "url": "/rank/indexPlus/brand_id/0",
    "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": {
        "brand": "all",
        "country": "cn",
        "device": "iphone",
        "genre": "36",
        "date": "2024-04-27",
        "page": 2
    },
    "baseURL": "https://api.qimai.cn",
    "transformRequest": [
        null
    ],
    "transformResponse": [
        null
    ],
    "timeout": 15000,
    "withCredentials": true,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1
}
function get_analysis(t) {
    var n;
    f || F != s || (n = (0,
        i[Wt])(m),
        s = c[x][k][Pt] = -(0,
            i[Wt])(l) || +new z[W] - a2 * n);
    var e, r = +new z[W] - (s || H) - 1661224081041, a = [];
    void 0 === t[Zt] && (t[Zt] = {})
        z[Z][i7](t[Zt])[M](function (n) {
            if (n == p)
                return !B;
            t[Zt][N2](n) && a[b](t[Zt][n])
        })
        a = a[Ot]()[I1](_)
        a = (0, i[jt])(a),
        a = (a += v + t[Jt][T](t[Mt], _)) + (v + r) + (v + 3),
        e = (0, i[jt])((0, i[qt])(a, d))
    return e


}

要获得传入的参数,先放掉后面的断点

复制传入的t

现在逐行分析代码:

1.f || F != s || (n = (0, i[Wt])(m), s = c[x][k][Pt] = -(0, i[Wt])(l) || +new z[W] - a2 * n);

在提供的代码中,没有显式使用三元运算符。但是,可以通过逻辑运算符 || 和赋值语句来模拟三元运算符的功能。JavaScript 中的 || 运算符用于逻辑或操作,如果第一个操作数为真,则返回第一个操作数,否则返回第二个操作数。返回第一个被判断为真值的操作数

var a = 0;
var b = 10;
var c = 20;

var result = a || b || c;
console.log(result); // 输出:10

在这个例子中,a 是假值,所以继续检查下一个操作数 b。因为 b 是真值,所以返回 b 的值 10

在上面的页面中,F != s 这个判断为真,f为假,因此整个代码返回F != s这个判断,因此没什么用,直接删除即可。

2.var e, r = +new z[W] - (s || H) - 1661224081041, a = [];

这明显是个混淆,我们可以在浏览器中查看混淆前是什么,从而替换一些自带的函数

z[W]是Date函数

s与H不知道怎么来的,先写成固定值 

3.void 0 === t[Zt] && (t[Zt] = {})

这段代码使用了 void 运算符,=== 严格相等运算符以及逻辑与 && 运算符。让我们一步步解释:

  • void 0: void 运算符用于生成 undefined 值。在这里,它将 undefinedt[Zt] 进行比较。
  • ===: 严格相等运算符用于比较两个值是否相等且类型相同。
  • t[Zt]: 这似乎是一个变量或对象的属性。它的值将与 undefined 进行比较。
  • &&: 逻辑与运算符用于将两个表达式连接起来,并且只有在第一个表达式为真时才会执行第二个表达式。

因此,整个表达式的含义是:如果 t[Zt] 的值严格等于 undefined,则将 t[Zt] 设置为空对象 {}

这种方式通常用于确保对象属性的存在,如果属性不存在,则进行初始化,以避免后续访问该属性时出现错误。是个判断的东西,删掉无用。

4.z[Z][i7](t[Zt])[M](function (n) { if (n == p) return !B; t[Zt][N2](n) && a[b](t[Zt][n]) })

很多逆向,我们可以在浏览器中还原。大Z是pychram中自带的object,然后是对象中的key方法,t[Zt]是传入的t字典中所带的params参数。M是forEach方法

改成下面这样 

Object.keys(t.params).forEach()

继续改写

if (n == "analysis")
                return false;
            t.params.hasOwnProperty(n) && a.push(t.params[n])

5.a = a[Ot]()[I1](_)

改写

a = a.sort().join('')

目前尝试打断点调试一下,发现a与浏览器中是一致的

6.   a = (0, i[jt])(a)

花指令,其实就是a=i[jt](a)

在页面中去找函数[jt]位置,并复制代码,将函数命名为i_jt发现也是高度混淆,且不知道里面有没有调用其他函数

7.对v函数的混淆作还原t = z[V1](t)[T](/%([0-9A-F]{2})/g, function(n, t)

t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t)
return o(Y1 + t),现在要找O函数所在的位置

对o方法,解混淆

还原后代码:

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)
            };

8.还原了o函数之后,回到i_jt函数 return o(Y1 + t)

将Y1替换为0x

   t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {
        return o('0x' + t)

接着是下面的代码进行分析 

try {
    return z[Q1](t)
} catch (n) {
    return z[W1][K1](t)[U1](Z1)
}异常捕获,不用管异常部分

故只用对这行代码解混淆,return z[Q1](t)

 return btoa(t)

目前的代码:

t = {
    "url": "/rank/indexPlus/brand_id/0",
    "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": {
        "brand": "all",
        "country": "cn",
        "device": "iphone",
        "genre": "36",
        "date": "2024-04-27",
        "page": 2
    },
    "baseURL": "https://api.qimai.cn",
    "transformRequest": [
        null
    ],
    "transformResponse": [
        null
    ],
    "timeout": 15000,
    "withCredentials": true,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1
}
var s = 238;
var H = 0;
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 i_jt(t) {
    t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {
        return o('0x' + t)
    });

    return btoa(t)

};

function get_analysis(t) {
    var n;
    var e, r = +new Date - (s || H) - 1661224081041, a = [];
    Object.keys(t.params).forEach(function (n) {
        if (n == "analysis")
            return false;
        t.params.hasOwnProperty(n) && a.push(t.params[n])
    })
    a = a.sort().join('')
    a = i_jt(a)
        a = (a += v + t[Jt][T](t[Mt], _)) + (v + r) + (v + 3),
        e = (0, i[jt])((0, i[qt])(a, d))
    return e
}

console.log(get_analysis(t))

debugger结果a的值

浏览器中结果是一致的,对于a的值

9.回到最开始的函数,a = (a += v + t[Jt][T](t[Mt], _)) + (v + r) + (v + 3)

var v='@#';
a = (a += v + t.url.replace(t.baseURL, '')) + (v + r) + (v + 3)

10.e = (0, i[jt])((0,i[qt])(a, d)) 

可以改写成e = i_jt(i[qt](a, d)) 

所以现在要找i[qt]a和d

进入i[qt],发现是一个h方法,在这里打上断点,并复制。将函数名改为i_qt。

function i_qt(n, t) {
                t = t || u();
                for (var e = (n = n[$1](_))[R], r = t[R], a = q1, i = H; i < e; i++)
                    n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));
                return n[I1](_)
            };

逐行分析i_qt代码

t = t || u();但凡t有值,t就是t,所以代码无意义哦,省略。

解混淆for (var e = (n = n[$1](_))[R], r = t[R], a = q1, i = H; i < e; i++)

 for (var e = (n = n.split('')).length, r = t.length, a = "charCodeAt", i = H; i < e; i++)

解混淆  n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H))

return n[I1](_)

其中o是已经定义过的

      n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));
                return n.join('')

完整i_qt方法

function i_qt(n, t) {
                for (var e = (n = n.split('')).length, r = t.length, a = "charCodeAt", i = H; i < e; i++)
                    n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));
                return n.join('')
            };

11.回到最开始的函数

a = (a += v + t.url.replace(t.baseURL, '')) + (v + r) + (v + 3)
e = i_jt((i_qt(a, d))

现在缺少d,需要知道d的值

e = i_jt((i_qt(a, 'xyz517cda96efgh'))

最终代码呈现:

t = {
    "url": "/rank/indexPlus/brand_id/0",
    "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": {
        "brand": "all",
        "country": "cn",
        "device": "iphone",
        "genre": "36",
        "date": "2024-04-27",
        "page": 2
    },
    "baseURL": "https://api.qimai.cn",
    "transformRequest": [
        null
    ],
    "transformResponse": [
        null
    ],
    "timeout": 15000,
    "withCredentials": true,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1
}
var s = 238;
var H = 0;
var v = '@#';

function i_qt(n, t) {
    for (var e = (n = n.split('')).length, r = t.length, a = "charCodeAt", i = H; i < e; i++)
        n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));
    return n.join('')
};

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 i_jt(t) {
    t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {
        return o('0x' + t)
    });

    return btoa(t)

};

function get_analysis(t) {
    var n;
    var e, r = +new Date - (s || H) - 1661224081041, a = [];
    Object.keys(t.params).forEach(function (n) {
        if (n == "analysis")
            return false;
        t.params.hasOwnProperty(n) && a.push(t.params[n])
    })
    a = a.sort().join('')
    a = i_jt(a)
    a = (a += v + t.url.replace(t.baseURL, '')) + (v + r) + (v + 3)
    e = i_jt(i_qt(a, 'xyz517cda96efgh'))
    return e
}

console.log(get_analysis(t))

最终结果展现:

实际上,函数只用到了t.params,t.url和t.baseURL,将其他的参数删除。

t = {
    "url": "/rank/indexPlus/brand_id/0",
    "params": {
        "brand": "all",
        "country": "cn",
        "device": "iphone",
        "genre": "36",
        "date": "2024-04-27",
        "page": 2
    },
    "baseURL": "https://api.qimai.cn",
}

我么希望是python传入这个参数,由js代码加密,然后返回python加密结果。 

  • 31
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习各种软件

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值