逆向目标
接口:https://ec.minmetals.com.cn/open/homepage/public
加密参数:
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);
-------------------------------------------------------------------------------------------------------------------
原创声明:未经许可,不得转载
如有侵权,请联系作者删除删除