【JS逆向学习】五矿(rsa加密)

发布时间:2024年01月19日
逆向目标

接口:https://ec.minmetals.com.cn/open/homepage/public
加密参数:

  • param:
T9AfskFfEu75T+I8TfyL1RUpoyqg/7tlkGzMb6kgWQXxKUcG9l/NRnx91r9rBpSHahAaFpg55txBJnlfdGgTItdXVDfqPtqf2b+6Qr5xIM2FKlWn5jV4+t/j+Wu26D2415Iec8YbSghkoHUc8ksU18nO5vZX1My7KWAFEDPg4PjSU+l5tnIxOm6iPp/D39vlr0ijHwTwq5mXU8zG/uSE32hAxfw8QOcXhV7opqRchmVWOvO/c+rHNQsLJOrz/DfqSWZf7IviNN0AgqCtK3oc2avuWxhXC9v8Ui82ldSShRUkS/dyJxoST+LA2CPTID4CBmhzlbxPBrNKZwqZcyInIMMraXaMplB5G9GPYNUMlz49V1HpCILk0hZQdcPkBK3EZTB2QYLM9qLnnthDsS26YyLpNoXROQeZwtVja9lfeoeerAZtWijAMSx/o6VSTGFRnD0lBtWupXC5JfHPatTFXBb/w336gyUMUlE7MPWf0NLeXWtC7Y2jHu5Ix4bx3Y9o
逆向过程

首先翻页观察网络请求,再次翻页发现仍然是两次网络请求,如下
在这里插入图片描述
首先分析 public 接口,发现返回内容如下

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCaNrAAWGIwWxsoKlj9g/fFmbqXXa/Fi9Pim55Qo3zgWCOR4bFnQ41rQ8OZhWfSeBVG4tDy5v7Rs+tQyqszpfhXJaSLWdhIlsTzT/XX9XIsQ7slbdsBAOi5XeWj8SU+dh1wLiX2u3J03f1h1px/1 JnKa8pKMYx/dH7QwwEJ9krDYwIDAQAB

上一篇【JS逆向学习】36kr登陆逆向案例(webpack)有介绍这种数据的特点,以 MIG 开头的公钥长度基本就是 1024 位,且加密方式基本断定是 RSA 加密,第一个接口就只是发起网络请求,返回 公钥,我们接着分析第二个接口by-lx-page
我们发现 by-lx-page 返回正常,payload 只有一个加密参数 param,像这种请求参数加密的,可以直接 hook JSON的 stringify 方法,如果是response加密的就 hook JSON的parse方法

var _stringify = JSON.stringify;
JSON.stringify = function(param){
	debugger;
	return _stringify(param);
}

直接拷贝上述代码到控制台,或者使用油猴注入皆可,我们控制台注入后执行翻页动作
在这里插入图片描述
代码断住了,然后向上跟栈观察,得来全不费功夫
在这里插入图片描述

return P = p(c.a.mark((function A() {
                var e, t, n, r, a, s, i = arguments;
                return c.a.wrap((function(A) {
                    while (1)
                        switch (A.prev = A.next) {
                        case 0:
                            return e = i.length > 0 && void 0 !== i[0] ? i[0] : {},
                            t = new v["a"],
                            A.next = 4,
                            u.a.post("/open/homepage/public");
                        case 4:
                            return n = A.sent,
                            r = n.data,
                            t.setPublicKey(r),
                            a = m(m({}, e), {}, {
                                sign: f()(JSON.stringify(e)),
                                timeStamp: +new Date
                            }),
                            s = t.encryptLong(JSON.stringify(a)),
                            A.abrupt("return", s);
                        case 10:
                        case "end":
                            return A.stop()
                        }
                }
                ), A)
            }
逆向分析

我们分析下上面的代码逻辑,e 是我们的字典类型的参数{"inviteMethod":"","mc":"","lx":"JPGG","dwmc":"","pageIndex":4},通过控制台打印变量的值可以发现f()是一个 md5方法函数,m 函数解析如下
函数 m 的作用是将后续传入的参数对象合并到第一个参数 A 中,并返回合并后的结果。具体实现如下: 在 m 函数中使用 for 循环遍历从第二个参数开始的所有参数,即 arguments 对象中的每个参数。 对每个参数进行判断,如果参数不为 null 或者 undefined,则将其赋值给变量 t,否则将空对象赋值给 t。 使用条件判断语句 e % 2 来判断是偶数次参数还是奇数次参数。如果是奇数次参数,则调用 d(Object(t), !0).forEach() 方法,该方法会遍历对象 t 的可枚举属性,并调用 b(A, e, t[e]) 方法。 如果是偶数次参数,则使用 Object.getOwnPropertyDescriptors 方法判断是否支持该方法。如果支持,则使用 Object.defineProperties 方法将对象 A 的属性描述符合并到对象 t 中;如果不支持,则使用 d(Object(t)).forEach() 方法遍历对象 t 的属性,并调用 Object.defineProperty 方法将属性添加到对象 A 中。 最后返回合并后的对象 A。

function m(A) {
   for (var e = 1; e < arguments.length; e++) {
       var t = null != arguments[e] ? arguments[e] : {};
       e % 2 ? d(Object(t), !0).forEach((function(e) {
           b(A, e, t[e])
       }
    )) : Object.getOwnPropertyDescriptors ? Object.defineProperties(A, Object.getOwnPropertyDescriptors(t)) : d(Object(t)).forEach((function(e) {
	Object.defineProperty(A, e, Object.getOwnPropertyDescriptor(t, e))
	      }
	      ))
	  }

发现m方法中又使用到了 b 方法

function b(A, e, t) {
     return e in A ? Object.defineProperty(A, e, {
         value: t,
         enumerable: !0,
         configurable: !0,
         writable: !0
     }) : A[e] = t,
     A
 }

函数 b 的作用是向对象 A 中添加或者更新属性 e,并将其值设置为 t。具体实现如下: 首先判断属性 e 是否已经存在于对象 A 中,如果存在,则使用 Object.defineProperty 方法更新该属性的值为 t。 如果属性 e 不存在于对象 A 中,则直接通过赋值运算符 = 将属性 e 添加到对象 A 中,并将其值设置为 t。 最后返回对象 A。

这两个函数的目的是为了实现对象的属性合并和更新操作,可以方便地扩展对象。

由此我们得出结论,a 是在原字典参数的基础上合并更新了其他参数

a = m(m({}, e), {}, {
	sign: f()(JSON.stringify(e)),
	timeStamp: +new Date
})

我们来看下原始参数及合并更新后的参数

// 原始参数
{
	"inviteMethod": "",
	"mc": "",
	"lx": "JPGG",
	"dwmc": "",
	"pageIndex": 4
}

// 合并更新后的参数
{
	"inviteMethod": "",
	"mc": "",
	"lx": "JPGG",
	"dwmc": "",
	"pageIndex": 4,
	"sign": "91fe481fd8bc81099ea47d4dbadb396a",
	"timeStamp": 1705632669125
}

可以发现多了 sign 参数及 timeStamp两个参数,sign是对原始参数做了 md5 操作,而 timeStamp 就是一个 13 位的时间戳,至此,用于 RSA 加密的参数我们已经分析完了,接下来我们来分析 RSA 加密本身,用于加密的对象 t 看到应该是在上面位置初始化的,然后我们控制台打印下 v,如下图
在这里插入图片描述
我们直接跟进去看下 v 的定义,可以看到setPrivateKey、setPublicKey、encrypt等 加密相关的函数都有了
在这里插入图片描述
为了验证下我们的猜想,我们新建个Snippet,然后把上述函数代码拷贝进来并补全参数,然后执行
在这里插入图片描述

逆向结果

至此,整个加密过程我们已经分析完毕,代码如下,需要注意的是 publicKey 是每次通过网络请求动态获取的

let test = function() {
            function e(e) {
                void 0 === e && (e = {}),
                e = e || {},
                this.default_key_size = e.default_key_size ? parseInt(e.default_key_size, 10) : 1024,
                this.default_public_exponent = e.default_public_exponent || "010001",
                this.log = e.log || !1,
                this.key = null
            }
            return e.prototype.setKey = function(e) {
                this.log && this.key && console.warn("A key was already set, overriding existing."),
                this.key = new ve(e)
            }
            ,
            e.prototype.setPrivateKey = function(e) {
                this.setKey(e)
            }
            ,
            e.prototype.setPublicKey = function(e) {
                this.setKey(e)
            }
            ,
            e.prototype.decrypt = function(e) {
                try {
                    return this.getKey().decrypt(m(e))
                } catch (t) {
                    return !1
                }
            }
            ,
            e.prototype.encrypt = function(e) {
                try {
                    return p(this.getKey().encrypt(e))
                } catch (t) {
                    return !1
                }
            }
            ,
            e.prototype.sign = function(e, t, n) {
                try {
                    return p(this.getKey().sign(e, t, n))
                } catch (r) {
                    return !1
                }
            }
            ,
            e.prototype.verify = function(e, t, n) {
                try {
                    return this.getKey().verify(e, m(t), n)
                } catch (r) {
                    return !1
                }
            }
            ,
            e.prototype.getKey = function(e) {
                if (!this.key) {
                    if (this.key = new ve,
                    e && "[object Function]" === {}.toString.call(e))
                        return void this.key.generateAsync(this.default_key_size, this.default_public_exponent, e);
                    this.key.generate(this.default_key_size, this.default_public_exponent)
                }
                return this.key
            }
            ,
            e.prototype.getPrivateKey = function() {
                return this.getKey().getPrivateKey()
            }
            ,
            e.prototype.getPrivateKeyB64 = function() {
                return this.getKey().getPrivateBaseKeyB64()
            }
            ,
            e.prototype.getPublicKey = function() {
                return this.getKey().getPublicKey()
            }
            ,
            e.prototype.getPublicKeyB64 = function() {
                return this.getKey().getPublicBaseKeyB64()
            }
            ,
            e.version = ge.version,
            e
        }();

let ret = new v["a"]
ret.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwLqkVF4/cvyMYw/x0bmRad8Uzsd815sFv37SEkFWWYMoZRM1ONY2ROHcDvCBHSAflAv0bSVLAn3L1Gq4JaS5JhzU29AIah4xx7E0+DyIpiaYxiu+47rkgEUgYzl23uo8RkDjkCtlhq5hUjOc99YVu8rd10UR6o2AU0YE6J5FwFwIDAQAB')
let a = {
	"inviteMethod": "",
	"mc": "",
	"lx": "JPGG",
	"dwmc": "",
	"pageIndex": 4,
	"sign": "91fe481fd8bc81099ea47d4dbadb396a",
	"timeStamp": 1705632669125
};
let s = ret.encryptLong(JSON.stringify(a));
console.log(s);

-------------------------------------------------------------------------------------------------------------------
原创声明:未经许可,不得转载
如有侵权,请联系作者删除删除

文章来源:https://blog.csdn.net/andrew_wf/article/details/135691425
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。