丁香公开课请求 sign(签名) 分析过程讲解

背景分析

1.网站链接:https://class.dxy.cn/

2.所有异步请求都会带上 sign=xxxx,并且每次sign只能用一次。

3.目的:解决sign的算法,得到正确的值。

4.初步定位算法js为:https://assets.dxycdn.com/gitrepo/dxycourse-pc/dist/index.e8a8a63d2fc74a69.js 格式化一看发现有6万多行的JS。有点头大,但是JS没有加密,只是打包了。

0x0、定位 sign 具体位置

发现只有8处位置,通过经验+第六感得出,大概在43895这行这个sign可能是最终算法。估计你们看到说凭借经验+第六感,就慌了。接下来我们来验证吧。

使用 charles 工具,Mapping Local 到本地格式化的 js 中,添加一处debugger

return {
    sign: function () {
        debugger;//增加debug
        console.log('arguments',arguments);
        var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
            t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "省略长串",
            r = n({}, e), i = {appSignKey: t};
        r.timestamp = Date.now(), r.noncestr = a(8, "number");
        var o = Object.keys(r).filter(function (e) {
            return void 0 != r[e] && "" !== r[e] || (delete r[e], !1)
        }).concat("appSignKey").sort().map(function (e) {
            var t = i[e] || (void 0 == r[e] ? "" : r[e]);
            return "".concat(e, "=").concat(t)
        }).join("&");
        return r.sign = u(o), r
    }
}

然后刷新页面,果然是此处。

0x1、提取JS代码

既然是这里,那么就提取此处代码用于单独调用,毕竟6万多行的js,并且是打包的,外部是无法调用的。

//提取算法代码
var CORE = (function () {
    function e(e, t, n) {
        return t in e ? Object.defineProperty(e, t, {
            value: n,
            enumerable: !0,
            configurable: !0,
            writable: !0
        }) : e[t] = n, e
    }

    function n(t) {
        for (var n = 1; n < arguments.length; n++) {
            var r = null != arguments[n] ? arguments[n] : {}, a = Object.keys(r);
            "function" === typeof Object.getOwnPropertySymbols && (a = a.concat(Object.getOwnPropertySymbols(r).filter(function (e) {
                return Object.getOwnPropertyDescriptor(r, e).enumerable
            }))), a.forEach(function (n) {
                e(t, n, r[n])
            })
        }
        return t
    }

    function r(e, t) {
        return t = {exports: {}}, e(t, t.exports), t.exports
    }

    function a() {
        for (var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 8, t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "alphabet", n = "", r = {
            alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
            number: "0123456789"
        }[t], a = 0; a < e; a++) n += r.charAt(Math.floor(Math.random() * r.length));
        return n
    }

    var i = r(function (e) {
        !function () {
            var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", n = {
                rotl: function (e, t) {
                    return e << t | e >>> 32 - t
                }, rotr: function (e, t) {
                    return e << 32 - t | e >>> t
                }, endian: function (e) {
                    if (e.constructor == Number) return 16711935 & n.rotl(e, 8) | 4278255360 & n.rotl(e, 24);
                    for (var t = 0; t < e.length; t++) e[t] = n.endian(e[t]);
                    return e
                }, randomBytes: function (e) {
                    for (var t = []; e > 0; e--) t.push(Math.floor(256 * Math.random()));
                    return t
                }, bytesToWords: function (e) {
                    for (var t = [], n = 0, r = 0; n < e.length; n++, r += 8) t[r >>> 5] |= e[n] << 24 - r % 32;
                    return t
                }, wordsToBytes: function (e) {
                    for (var t = [], n = 0; n < 32 * e.length; n += 8) t.push(e[n >>> 5] >>> 24 - n % 32 & 255);
                    return t
                }, bytesToHex: function (e) {
                    for (var t = [], n = 0; n < e.length; n++) t.push((e[n] >>> 4).toString(16)), t.push((15 & e[n]).toString(16));
                    return t.join("")
                }, hexToBytes: function (e) {
                    for (var t = [], n = 0; n < e.length; n += 2) t.push(parseInt(e.substr(n, 2), 16));
                    return t
                }, bytesToBase64: function (e) {
                    for (var n = [], r = 0; r < e.length; r += 3) for (var a = e[r] << 16 | e[r + 1] << 8 | e[r + 2], i = 0; i < 4; i++) 8 * r + 6 * i <= 8 * e.length ? n.push(t.charAt(a >>> 6 * (3 - i) & 63)) : n.push("=");
                    return n.join("")
                }, base64ToBytes: function (e) {
                    e = e.replace(/[^A-Z0-9+\/]/gi, "");
                    for (var n = [], r = 0, a = 0; r < e.length; a = ++r % 4) 0 != a && n.push((t.indexOf(e.charAt(r - 1)) & Math.pow(2, -2 * a + 8) - 1) << 2 * a | t.indexOf(e.charAt(r)) >>> 6 - 2 * a);
                    return n
                }
            };
            e.exports = n
        }()
    }), o = {
        utf8: {
            stringToBytes: function (e) {
                return o.bin.stringToBytes(unescape(encodeURIComponent(e)))
            }, bytesToString: function (e) {
                return decodeURIComponent(escape(o.bin.bytesToString(e)))
            }
        }, bin: {
            stringToBytes: function (e) {
                for (var t = [], n = 0; n < e.length; n++) t.push(255 & e.charCodeAt(n));
                return t
            }, bytesToString: function (e) {
                for (var t = [], n = 0; n < e.length; n++) t.push(String.fromCharCode(e[n]));
                return t.join("")
            }
        }
    }, s = o, u = r(function (e) {
        !function () {
            var n = i, r = s.utf8, a = s.bin, o = function (e) {
                e.constructor == String ? e = r.stringToBytes(e) : "undefined" !== typeof t && "function" == typeof t.isBuffer && t.isBuffer(e) ? e = Array.prototype.slice.call(e, 0) : Array.isArray(e) || (e = e.toString());
                var a = n.bytesToWords(e), i = 8 * e.length, o = [], s = 1732584193, u = -271733879,
                    l = -1732584194, c = 271733878, d = -1009589776;
                a[i >> 5] |= 128 << 24 - i % 32, a[15 + (i + 64 >>> 9 << 4)] = i;
                for (var f = 0; f < a.length; f += 16) {
                    for (var p = s, h = u, m = l, v = c, y = d, g = 0; g < 80; g++) {
                        if (g < 16) o[g] = a[f + g]; else {
                            var _ = o[g - 3] ^ o[g - 8] ^ o[g - 14] ^ o[g - 16];
                            o[g] = _ << 1 | _ >>> 31
                        }
                        var b = (s << 5 | s >>> 27) + d + (o[g] >>> 0) + (g < 20 ? 1518500249 + (u & l | ~u & c) : g < 40 ? 1859775393 + (u ^ l ^ c) : g < 60 ? (u & l | u & c | l & c) - 1894007588 : (u ^ l ^ c) - 899497514);
                        d = c, c = l, l = u << 30 | u >>> 2, u = s, s = b
                    }
                    s += p, u += h, l += m, c += v, d += y
                }
                return [s, u, l, c, d]
            }, u = function (e, t) {
                var r = n.wordsToBytes(o(e));
                return t && t.asBytes ? r : t && t.asString ? a.bytesToString(r) : n.bytesToHex(r)
            };
            u._blocksize = 16, u._digestsize = 20, e.exports = u
        }()
    });
    return {
        sign: function () {
            var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
                t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "...",
                r = n({}, e), i = {appSignKey: t};
            var mt = Date.now();
            r.timestamp =mt, r.noncestr = a(8, "number");
            var o = Object.keys(r).filter(function (e) {
                return void 0 != r[e] && "" !== r[e] || (delete r[e], !1)
            }).concat("appSignKey").sort().map(function (e) {
                var t = i[e] || (void 0 == r[e] ? "" : r[e]);
                return "".concat(e, "=").concat(t)
            }).join("&");
            return r.sign = u(o), r
        }
    }
})(n("EuP9").Buffer);

在提取代码的过程中,发现依赖了一个外部调用 n("EuP9").Buffer 然后跟踪下,发现里面还有调用,一坨很大,然后我们分析下当前 sign 算法是否使用了,发现没有明确的地方使用,直接去掉先。

接着我们开始来测试。先找了一个无参数的   Get  请求试一下。

var res = CORE.sign();
//得到 {timestamp: 1600356565232, noncestr: "51070119", sign: "c2d89f55ca8c4e1da93002e274739e70e43fdf89"}

好像成功了,然后尝试一下。

拼接无参数链接请求:

https://class.dxy.cn/pcweb/user/info?timestamp=1600356565232&noncestr=51070119&sign=c2d89f55ca8c4e1da93002e274739e70e43fdf89

返回签名错误。

这就有点蛋疼了,刚刚的喜悦被当头一棒。冷静冷静。仔细分析下。

调用sign获取签名是不是有参数?看下面部分代码。

var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
    t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "太长省略",
    r = n({}, e), i = {appSignKey: t};

这里sign直接读取了参数判断,然后去取值,看语义分析下,第一个应该是一个 json 对象格式的参数,第二个是一个字符串,并且是个key,如果得不到就给一个默认的值。咱们直接在js里输出 “arguments”看看。

return {
    sign: function () {
        debugger;//增加debug
        //输出参数
        console.log('arguments',arguments);
        var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
            t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "...",
            r = n({}, e), i = {appSignKey: t};
        r.timestamp = Date.now(), r.noncestr = a(8, "number");
        var o = Object.keys(r).filter(function (e) {
            return void 0 != r[e] && "" !== r[e] || (delete r[e], !1)
        }).concat("appSignKey").sort().map(function (e) {
            var t = i[e] || (void 0 == r[e] ? "" : r[e]);
            return "".concat(e, "=").concat(t)
        }).join("&");
        return r.sign = u(o), r
    }
}

 

看到了吧,第一个是 {} ,第二个参数是一个key ,而且这个key和代码里默认的key不一样。

那我们就也带上这个key吧。或者把这个key写死在代码里,然后不传第二个参数也可以。

这里观察了多个请求并 console.log('arguments',arguments);后发现第一个参数是提交到后台的参数值。

好,然后优化下代码结果是:

//有参数,data就给参数。
var data = {courseId: 402,courseType: 2};
var res = s.sign(data,"695e7084696bf4a6b4c73dc67da4f5df41405763ef4a2a854e3af96b141254b0050986ccf2b8cd9a6ab5cfabc6e4bb30");
var url = "https://class.dxy.cn/pcweb/user/course/like/status?courseId=402&courseType=2&timestamp="+res.timestamp+"&noncestr="+res.noncestr+"&sign="+res.sign;

获取一个分页信息:

var data = {pageNum:1,pageSize:4,courseId:402,courseType:2};
var res = CORE.sign(data,"695e7084696bf4a6b4c73dc67da4f5df41405763ef4a2a854e3af96b141254b0050986ccf2b8cd9a6ab5cfabc6e4bb30");
console.log("签名",res.sign);
console.log("timestamp",res.timestamp);
console.log("noncestr",res.noncestr);
var url = "https://class.dxy.cn/pcweb/user/pack/comment/list?pageNum=1&pageSize=4&courseId=402&courseType=2&timestamp="+res.timestamp+"&noncestr="+res.noncestr+"&sign="+res.sign;
console.log("请求连接",url);

通过在线JS运行工具:https://www.sojson.com/runjs.html

 得到链接,直接浏览器打开(因为这个是个get请求),然后就得到如下内容

结果拿到了。好了,分析到此结束,其实 JS 算法也好,只要是重要的部分,最好还是加密一下,使用本站的JS最牛加密,或者先用JS方法加密,加密后在用JS最牛加密加密JS,这样整个逻辑就打乱了,可以使分析者第一步就很难。

申明:当前内容只能用于学习,不能用于其他。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值