吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2391|回复: 50
上一主题 下一主题
收起左侧

[Web逆向] 【JS逆向】某手机厂商登录逆向分析

  [复制链接]
跳转到指定楼层
楼主
littlewhite11 发表于 2024-12-14 17:23 回帖奖励
本帖最后由 littlewhite11 于 2024-12-14 17:38 编辑

逆向目标

  • 网址:aHR0cHM6Ly9wYXNzcG9ydC52aXZvLmNvbS5jbi8jL2xvZ2lu
  • 目标:表单数据加密,响应数据解密

抓包分析

我们选择密码登录,输入手机号和密码登录,会发起一个login请求,其中表单数据encDataencKey是需要逆向的。

响应数据是加密的,也需要进行逆向分析。

逆向分析

我们全局搜索encData,最终搜索到下图的地方,直接下断,重新请求,成功断住,encKey也在此处。

e.params中的nouncepwd也需要进行分析,但我们先分析encDataencKey的生成。

首先是y函数,将e.params处理成一个字符串。

然后是encrypt函数,对ae().enc.Utf8.parse(t)这种写法比较熟悉的大佬都知道应该是用了crypto-js这个加密库。

可以用crypto-js这个库复现加密,经验证,是标准的AES,一个随机key,一个固定iv。

const CryptoJS = require('crypto-js')
function encrypt(e){
    var t = function() {
    for (var e = "", t = "0123456789abcdef", n = 0; n < 16; n++)
            e += t.charAt(Math.floor(16 * Math.random()));
        return e
    }()
    , n = CryptoJS.enc.Utf8.parse(t)
    , a = CryptoJS.enc.Utf8.parse("16-Bytes--String");
    var r = CryptoJS.enc.Utf8.parse(e);
    i = CryptoJS.AES.encrypt(r, n, {
        iv: a,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    })
    return i.ciphertext.toString()
}

而我们要的encKey也在此处enckey: ee(t),这个跟进去会发现就是一个RSA加密,可以用node-jsencrypt这个标准库解决。

const JSEncrpyt = require('node-jsencrypt')
function rsa_encrypt(e){
    let rsa = new JSEncrpyt()
    rsa.setPublicKey("公钥")
    return rsa.encrypt(e)
}

那有人就会好奇,o.Z.commit("setEncrypt", {enckey: ee(t),aeskey: n,aesiv: a})这一步在干什么,先卖个关子,后文会分析。

该案例用了一种常见的安全设计模式,结合了AES和RSA两种加密算法的优点,将两者结合使用既保证了安全性,又能有较高的效率。

AES在对大量数据进行加解密的时候速度快

RSA的安全性基于大数分解的数学难题,破解难度极高

AES作为对称加密的一种,密钥安全性尤为重要,且密钥是前后端共享的,如果密钥在传输过程中被截获,数据的安全性将无法保证。

而RSA加密速度较慢,适合加密较小的数据(如密钥),可以用于加密AES的密钥,确保密钥在传输过程中不会被窃取。

表单参数的加密搞定了,还需要看e.params中的pwdnonce是怎么来的。

首先看nounce,就在encData上面一点的地方,nounce由一个时间戳和一个随机数生成,而m()的返回值大概率是个hash函数,其实nounce就是一个随机值。

遇到hash函数,我们可以尝试对字符串'1'进行hash看是不是标准算法,64的长度,6b8b开头,应该就是标准的sha256

记住一些常见hash函数的特征,有时候就可以在控制台验证它是不是标准的算法,能够稍微提高那么一点效率。

然后是pwd,往前看,它已经生成了,并且如果往前跟栈就到异步之前了,那没办法,我们就在s.request处下断,重新请求。

可以看到,pwd已经生成了,也就是说pwd是在异步之前就已经生成了,那我们就没必要跟异步了,这一点很重要,做逆向一定要细心,不要觉得密文往前跟就是异步了,就认为一定是异步生成的。

我们继续往前跟栈,发现pwd是在这生成的。

我们点进去看看,发现还是一个RSA,只不过是公钥换了。

至此我们的表单参数就搞定了,下面继续看响应数据加密。

老规矩还是hook JSON.parse,成功断住。

往上跟一个栈,就会找到解密的位置。encKey熟不熟悉,没错,其实就是表单的encKey

我们进去decrypt函数看看,发现是AES解密,那这就信手拈来了,导包解决即可。

const CryptoJS = require('crypto-js')
function decrypt(e, t){
    var n = CryptoJS.enc.Hex.parse(e)
      , a = CryptoJS.enc.Base64.stringify(n);
    var aeskey = CryptoJS.enc.Utf8.parse('之前的key');
    var aesiv = CryptoJS.enc.Utf8.parse("16-Bytes--String")
    var r = CryptoJS.AES.decrypt(a, aeskey, {
        iv: aesiv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return r.toString(CryptoJS.enc.Utf8).toString()
}

前文一些操作的谜底也终于揭开了,o.Z.commit("setEncrypt", {enckey: ee(t),aeskey: n,aesiv: a})其实就是将keyiv保存起来,然后解密的时候也用同样的keyiv

好了,加密和解密都解决了,直接模拟请求。

成功!!!

网站多请求几次会触发某象的验证码,以后有机会再分析怎么解决。

免费评分

参与人数 12威望 +1 吾爱币 +29 热心值 +11 收起 理由
liupin924 + 1 我很赞同!
weidechan + 1 用心讨论,共获提升!
wangxiaoqiqiqi + 1 + 1 用心讨论,共获提升!
yiqifeng + 1 + 1 我很赞同!
haishen668 + 1 + 1 谢谢@Thanks!
zctsir + 1 我很赞同!
melooon + 1 + 1 我很赞同!
Jeanbaie + 1 + 1 谢谢@Thanks!
allspark + 1 + 1 用心讨论,共获提升!
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
liuxuming3303 + 1 + 1 谢谢@Thanks!
yxnwh + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
 楼主| littlewhite11 发表于 2024-12-17 11:16 |楼主
haishen668 发表于 2024-12-17 11:06
可以贴一下参考代码吗大佬

js中的get_enc_data和decrypt两个方法需要自己封装

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
haishen668 + 1 + 1 谢谢@Thanks!

查看全部评分

推荐
haishen668 发表于 2024-12-17 10:24
本帖最后由 haishen668 于 2024-12-17 10:40 编辑
littlewhite11 发表于 2024-12-16 21:20
还是需要有点基础的

后端响应数据: { code: 10100, msg: 'Incorrect request, please try again' }
大佬我跟着你的思路做了一遍,发现有些问题,是我少传参了吗,我不太理解,能否帮我看一下我的代码
[JavaScript] 纯文本查看 复制代码
const CryptoJS = require("crypto-js");
const JSEncrypt = require("node-jsencrypt");
const fetch = require("node-fetch");
// 账号与密码
const username = "123123";
const password = "123123"; 
// RSA 公钥
const publicKey1 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgF21fulwAZs3l6ru8M2fLPDO9Y4+0zOF0Dblz7nOmrGYGDIpcPwegNYLvpQrcFq2YjfCzVF+n1xd+k8hiYxdwggp9oiB9UCN4MLr+qOZXtWKxBJDQAOn3w+tu0SwGwKsONI+CDGtF5l5yfjAunTYwLduc3aqZjPmo2UXbGdGqrGbxPS5lY3/kZykce+i+txO7vYfJevHYyg5eaOGfpjN8/666L60mv+Xpqd272c3VcbjbYW5ZJCljhZnHR+cPeAyn6P5encb0afQhoyz0LnARiRP51C9Nv4avG/RbGgD2o4asbaEXJ6zPgDxRE4e34EkhGM46XcmmJeQSA54LSJ43QIDAQAB";
const publicKey2 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvx8hiadNVZjfvJXGW++C0kDC+FJvrPpFp5ROsh7npVMdu8FyhaygNwBsaw4+6Kdbh8aJq6J1f8Ye0lwbwP9paPYUxXb3x+1tcLcMGrJ0DvJCDOVvBsjF9uFUbdjnuUX5FXfnc1jnPdBt7pq6BKdciJ8qgN5LIQnUKFP242maNDIoz6tVdnawY0LfekzMqH8xbXOz0LyX12nUnq9lqi6QHlEiS61gYeSU+lttQIFD2tDZajSWnY6Nb7T6VRKaJQKU4WiJ7frrjsGtj/eCwXO5jClRQJleED3KJ189Ii/rd4PkLOzrpryUGLBHBlDStNnYoeWIKL+8+2v+XzD5SXkZLwIDAQAB";
// 参数字符串化
function stringifyParams(params) {
    return Object.keys(params)
        .map(key => `${key}=${encodeURIComponent(params[key])}`)
        .join("&");
}
// AES 加密函数
function aesEncrypt(data) {
    const aesKey = CryptoJS.lib.WordArray.random(16); // 随机 16 字节密钥
    const aesIv = CryptoJS.enc.Utf8.parse("16-Bytes--String"); // 固定 IV
    const encrypted = CryptoJS.AES.encrypt(data, aesKey, {
        iv: aesIv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return {
        ciphertext: encrypted.ciphertext.toString(CryptoJS.enc.Base64),
        aesKey: aesKey,
        aesIv: aesIv
    };
}
// RSA 加密函数
function rsaEncrypt(data, publicKey) {
    const rsa = new JSEncrypt();
    rsa.setPublicKey(publicKey);
    return rsa.encrypt(data);
}
// 生成最终请求参数
function generateParams() {
    const timestamp = Date.now();
    const randomNum = Math.floor(1000 * Math.random()).toString();
    const sha256Nonce = CryptoJS.SHA256("" + timestamp + randomNum).toString();
    // 参数集合
    const params = {
        timeStamp: timestamp,
        nounce: sha256Nonce,
        account: username,
        pwd: rsaEncrypt(password, publicKey2), // 使用 publicKey2 加密 pwd
        areaCode: "86",                   // 区号,通常为中国的 +86
        authcookie: 3,                    // 登录认证类型,3 表示 WAP 登录
        bizCode: "BC0064",                // 业务编码,通常由后端指定
        clientType: 1,                    // 客户端类型,1 表示浏览器(WAP)
        client_id: "",                    // 客户端标识符,通常为空或特定值
        countryCode: "CN",                // 国家代码,中国为 "CN"
        deviceType: "wap",                // 设备类型,WAP 表示移动端浏览器
        e: 3,                             // 可能为固定值(业务类型或版本)
        handleCoolingOffType: 0,          // 冷却处理类型,通常为 0
        locale: "zh_CN",                  // 语言区域,简体中文
        msminv: "24061700",               // 固定参数,版本号或标识符
        redirect_uri: "",                 // 跳转地址,通常为空或指定登录后的页面
        remember: 0,                      // 记住密码选项,0 表示不记住
        sliderVersionType: 2,             // 滑块验证码类型,2 表示新版本
        ticket: "",                       // 验证票据,通常为空,后续请求中会有值
        _isSafe_: true                    // 安全校验字段,通常为 `true`
    };
    console.log("加密前的参数:", params);
    // AES 加密参数
    const aesResult = aesEncrypt(stringifyParams(params));
    // RSA 加密 AES 密钥
    const encryptedKey = rsaEncrypt(aesResult.aesKey.toString(CryptoJS.enc.Hex), publicKey1);
    return {
        encData: aesResult.ciphertext,
        encKey: encryptedKey,
        encVer: "1_1_2",
        aesKey: aesResult.aesKey, // 用于解密响应
        aesIv: aesResult.aesIv
    };
}
// 发送请求
async function sendRequest() {
    const params = generateParams();
    console.log("发送的参数:", params);
    try {
        const response = await fetch("https://passport.vivo.com.cn/v3s2/web/login/login", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                encData: params.encData,
                encKey: params.encKey,
                encVer: params.encVer
            })
        });
        const responseData = await response.json();
        console.log("后端响应数据:", responseData);
        // 解密后端响应数据
        if (responseData.enc) {
            const decrypted = CryptoJS.AES.decrypt(responseData.enc, params.aesKey, {
                iv: params.aesIv,
                mode: CryptoJS.mode.CBC,
                padding: CryptoJS.pad.Pkcs7
            });
            console.log("解密后的数据:", decrypted.toString(CryptoJS.enc.Utf8));
        }
    } catch (error) {
        console.error("请求失败:", error);
    }
}
// generateParams()
sendRequest();
沙发
Xiaosesi 发表于 2024-12-14 17:32
3#
阿清 发表于 2024-12-14 17:39
怎么hook啊
4#
 楼主| littlewhite11 发表于 2024-12-14 17:51 |楼主

hook脚本可以网上找,控制台直接注入就行
5#
duangduang 发表于 2024-12-14 18:37
楼主。推荐一下js逆向入门学习方向
6#
 楼主| littlewhite11 发表于 2024-12-14 18:43 |楼主
duangduang 发表于 2024-12-14 18:37
楼主。推荐一下js逆向入门学习方向

入门把基础打好呗,js基础,抓包,hook等等
7#
pomxion 发表于 2024-12-14 22:22
正在研究这方面的,先学习一下技术!感谢分享
8#
qianshi 发表于 2024-12-14 22:41
感谢分享
9#
52wjj 发表于 2024-12-15 01:15
太厉害了 感谢分享
10#
xiaopeng928 发表于 2024-12-15 08:19
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 18:00

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表