手记

小 i 机器人聊天协议分析

地址:http://i.xiaoi.com/

需要:抓包工具(笔者用的是浏览器自带的开发人员工具。快捷键F12)

                                                                                图 1

  1. 在跳转到 url:http://i.xiaoi.com/  之前打开开发人员工具,跳转之后,发送“测试”二字,得到以下回复


                                                                                图 2

可以看到,该数据包来自于URL:http://i.xiaoi.com/robot/webrobot?&callback=__webrobot_processMsg&data=%7B%22sessionId%22%3A%2237360e51d94e4cfcb357e3d6ed9015e1%22%2C%22robotId%22%3A%22webbot%22%2C%22userId%22%3A%2294b8b3aa68524a9cbaab4353c64bc233%22%2C%22body%22%3A%7B%22content%22%3A%22%E6%B5%8B%E8%AF%95%22%7D%2C%22type%22%3A%22txt%22%7D&ts=1560763111783

URL解码之后:

                                                                                图 3

                                                                                图 4

可以看到一共有以下参数:callback、sessionId、robotId、userId、body、ts

通过这些参数名我们可以假设:callback的值可能是回调函数名,sessionId、robotId和userId就不用说了,如名所示,body可能是关于所发送内容的相关信息:发送内容和内容类型;ts可能是timestamp的缩写,可能是13位的时间戳

    2.接下来验证猜想:

(1)在开发人员工具的搜索栏中搜索“sessionId :”以定位到参数所在的位置

                                                                                图 5

单击第一次涉及到sessionId的地方,第844行,格式化代码

                                                                                图 6

从图中可以看到sessionId就是data对象的sessionId属性的值,然后我们往上追溯,看看data的值又是什么

                                                                                图 7

发现其是作为processOpenResponse函数的一个参数传进来的,而该程序片段是processOpenResponse函数的定义,那么我们看看这个函数又是在哪里被调用的?在开发人员工具的搜索框搜索“processOpenResponse”转到911行,格式化之后

                                                                                图 8

                                                                                图 9

该函数是客户端发送请求之后,在接收到返回数据之后进行调用的,而data就是服务器返回的数据。这样的话,那就简单了,直接在开发人员工具的搜索框搜索“sessionId”,为什么搜sessionId不是搜data呢?因为data这个变量名是在太普遍了,一般情况下很多地方都会调用到,为了更加简洁方便地分析,所以这里我选择搜sessionId。过滤掉JS文件之后,发现还是很多,还需要进一步过滤。这里有个思路,由于上文提到data就是服务器返回的数据,当前的数据包URL是http://i.xiaoi.com/robot/webrobot?&callback=__webrobot_processMsg&data=%7B%22sessionId%22%3A%2237360e51d94e4cfcb357e3d6ed9015e1%22%2C%22robotId%22%3A%22webbot%22%2C%22userId%22%3A%2294b8b3aa68524a9cbaab4353c64bc233%22%2C%22body%22%3A%7B%22content%22%3A%22%E6%B5%8B%E8%AF%95%22%7D%2C%22type%22%3A%22txt%22%7D&ts=1560763111783,那么我们在它之前的一些数据包中寻找就行。通过此方法,我们定位到了该URL:http://i.xiaoi.com/robot/webrobot?&callback=__webrobot__processOpenResponse&data=%7B%22type%22%3A%22open%22%7D&ts=1560763093937

                                                                                图 10

                                                                                图 11

通过对比这两个包的参数,发现是一致的。这样的话就省事多了,分析一下剩下的参数:callback和ts,先搜索“&ts=”,通过对比URL,发现正确的定位应该是638行,而ts的值也确实是时间戳:

_url += '&callback=' + conf.callback + '&data=' + _data + "&ts=" + new Date().getTime();

                                                                                图 12

最后就剩callback这个参数的分析了,这里我们不妨在进入638行格式化后在该处下个断点,并在输入框内输入一些字符看看会不会触发到这一个断点上。结果证明,确实断到了。

                                                                                图 13

                                                                                图 14

往上追溯发现,conf.callback所在的jsonp函数是fn对象的一个属性,那这样就好办了,直接在该页面搜索一下“fn.jsonp(”,然后在搜到的两处下断点,并在聊天输入框随便输入些字符,看看断到哪里。结果我们看到断到了这里,这里的callback值是写死的,就是__webrobot_processMsg,这与发送消息的包中的callback是一致的。到此为止,发送消息的包中参数已经是分析完了

                                                                                图 15

我的Chrome有点问题,接下来将使用Firefox进行分析。分析完了URL的参数,接下来就需要分析发送消息的包需要携带的cookie

Cookie: cnonce=688226; sig=792fc785bef5073ae374c2ec525f10e2cb4abfb5; XISESSIONID=gho0x08t7ws11eteyziynepf8; Hm_lvt_822805145daedc4d66ed5fdf3d12cc8b=1560784903; Hm_lpvt_822805145daedc4d66ed5fdf3d12cc8b=1560784903; nonce=428933

                                                                                图16   抓包显示的cookie  

Hm_lvt_8228等模样的cookie通常不需要带上,我之前查了下说是相关的cookie,因此不带上也没关系。

                                                                                 图17   Hm_lvt_cookie相关说明 

因此,需要带上的cookie仅剩:cnonce、sig、nonce、XISESSIONID,其中XISESSIONID应该就是在cookie中存储的sessionId。nonce和XISESSIONID从URL:http://i.xiaoi.com/robot/webrobot?&callback=__webrobot__processOpenResponse&data=%7B%22type%22%3A%22open%22%7D&ts=1560763093937  通过返回协议头中的set-cookie中发现,cnonce和sign的值则是通过JS进行计算得来的。

                                                                                 图18 

在开发人员工具的搜索栏中搜“cnonce”,只有一条结果,转到调用的地方,格式化

                                                                                 图19

o函数的定义就在上面,往上翻

                                                                                 图20

可以看到,o函数穿进去的参数分别是cookie的键和值,这样与我们的分析是一致的,cnonce的值就是t的值了。接下来分析sig的值,由图可知

sig = h(j(r) + t)

t是已知的,接下来看h函数和j函数,h函数里面层层调用,但大多都在_verify这个函数里面、

  _verify: function () {    var d = document;    function n() {      return Math.PI + 'I'    }    function c() {      return 'no'    }    function h(k) {      return g(f(p(k)))    }    function f(K) {      var H = K;      var I = Array(80);      var G = 1732584193;      var F = - 271733879;      var E = - 1732584194;      var D = 271733878;      var C = - 1009589776;      for (var z = 0; z < H.length; z += 16) {        var B = G;        var A = F;        var y = E;        var v = D;        var k = C;        for (var u = 0; u < 80; u++) {          if (u < 16) {            I[u] = H[z + u]          } else {            I[u] = l(I[u - 3] ^ I[u - 8] ^ I[u - 14] ^ I[u - 16], 1)          }          var J = q(q(l(G, 5), s(u, F, E, D)), q(q(C, I[u]), i(u)));          C = D;          D = E;          E = l(F, 30);          F = G;          G = J        }        G = q(G, B);        F = q(F, A);        E = q(E, y);        D = q(D, v);        C = q(C, k)      }      return new Array(G, F, E, D, C)    }    function s(u, k, w, v) {      if (u < 20) {        return (k & w) | ((~k) & v)      }      if (u < 40) {        return k ^ w ^ v      }      if (u < 60) {        return (k & w) | (k & v) | (w & v)      }      return k ^ w ^ v    }    function i(k) {      return (k < 20) ? 1518500249 : (k < 40) ? 1859775393 : (k < 60) ? - 1894007588 : - 899497514    }    function q(k, w) {      var v = (k & 65535) + (w & 65535);      var u = (k >> 16) + (w >> 16) + (v >> 16);      return (u << 16) | (v & 65535)    }    function l(k, u) {      return (k << u) | (k >>> (32 - u))    }    function p(v) {      var k = ((v.length + 8) >> 6) + 1,      w = new Array(k * 16);      for (var u = 0; u < k * 16; u++) {        w[u] = 0      }      for (u = 0; u < v.length; u++) {        w[u >> 2] |= v.charCodeAt(u) << (24 - (u & 3) * 8)      }      w[u >> 2] |= 128 << (24 - (u & 3) * 8);      w[k * 16 - 1] = v.length * 8;      return w    }    function g(v) {      var u = '0123456789abcdef';      var w = '';      for (var k = 0; k < v.length * 4; k++) {        w += u.charAt((v[k >> 2] >> ((3 - k % 4) * 8 + 4)) & 15) + u.charAt((v[k >> 2] >> ((3 - k % 4) * 8)) & 15)      }      return w    }    function e(B) {      var v,      u,      A,      w = d.cookie.split(';');      for (v = 0; v < w.length; v++) {        var z = w[v];        var k = z.indexOf('=');        u = z.substr(0, k);        A = z.substr(k + 1);        u = u.replace(/^\s+|\s+$/g, '');        if (u == B) {          return unescape(A)        }      }    }    function b() {      return 'n'    }    function o(u, k) {      document.cookie = u + '=' + escape(k) + ';path=' + WebRobot.cookiePath    }    function a() {      return 'ce'    }    function j(u) {      var w = '',      x = n().substr(0, 7);      for (var v = 0; v < x.length; v++) {        var y = x.charAt(v);        if (y != '.') {          w = y + w        }      }      return h(u + w)    }    function m() {      return c() + b() + a()    }    var r = e(m());    if (r) {      var t = '' + (Math.ceil(Math.random() * 899999) + 100000);      o('cnonce', t);      o('sig', h(j(r) + t))    }  }

然后这是我自己整理的关于计算sig的源码

function calcSig() {                function n() {            return Math.PI + "I"        }        function c() {            return "no"        }        function h(k) {            return g(f(p(k)))        }        function f(K) {            var H = K;            var I = Array(80);            var G = 1732584193;            var F = -271733879;            var E = -1732584194;            var D = 271733878;            var C = -1009589776;            for (var z = 0; z < H.length; z += 16) {                var B = G;                var A = F;                var y = E;                var v = D;                var k = C;                for (var u = 0; u < 80; u++) {                    if (u < 16) {                        I[u] = H[z + u]                    } else {                        I[u] = l(I[u - 3] ^ I[u - 8] ^ I[u - 14] ^ I[u - 16], 1)                    }                    var J = q(q(l(G, 5), s(u, F, E, D)), q(q(C, I[u]), i(u)));                    C = D;                    D = E;                    E = l(F, 30);                    F = G;                    G = J                }                G = q(G, B);                F = q(F, A);                E = q(E, y);                D = q(D, v);                C = q(C, k)            }            return new Array(G,F,E,D,C)        }        function s(u, k, w, v) {            if (u < 20) {                return (k & w) | ((~k) & v)            }            if (u < 40) {                return k ^ w ^ v            }            if (u < 60) {                return (k & w) | (k & v) | (w & v)            }            return k ^ w ^ v        }        function i(k) {            return (k < 20) ? 1518500249 : (k < 40) ? 1859775393 : (k < 60) ? -1894007588 : -899497514        }        function q(k, w) {            var v = (k & 65535) + (w & 65535);            var u = (k >> 16) + (w >> 16) + (v >> 16);            return (u << 16) | (v & 65535)        }        function l(k, u) {            return (k << u) | (k >>> (32 - u))        }        function p(v) {            var k = ((v.length + 8) >> 6) + 1              , w = new Array(k * 16);            for (var u = 0; u < k * 16; u++) {                w[u] = 0            }            for (u = 0; u < v.length; u++) {                w[u >> 2] |= v.charCodeAt(u) << (24 - (u & 3) * 8)            }            w[u >> 2] |= 128 << (24 - (u & 3) * 8);            w[k * 16 - 1] = v.length * 8;            return w        }        function g(v) {            var u = "0123456789abcdef";            var w = "";            for (var k = 0; k < v.length * 4; k++) {                w += u.charAt((v[k >> 2] >> ((3 - k % 4) * 8 + 4)) & 15) + u.charAt((v[k >> 2] >> ((3 - k % 4) * 8)) & 15)            }            return w        }        function e(B) {        var cookie="Hm_lvt_822805145daedc4d66ed5fdf3d12cc8b=1516801663; Hm_lpvt_822805145daedc4d66ed5fdf3d12cc8b=1516801687; nonce=355117";            var v, u, A, w = cookie.split(";");            for (v = 0; v < w.length; v++) {                var z = w[v];                var k = z.indexOf("=");                u = z.substr(0, k);                A = z.substr(k + 1);                u = u.replace(/^\s+|\s+$/g, "");                if (u == B) {                    return unescape(A)                }            }        }        function b() {            return "n"        }        function o(u, k) {            document.cookie = u + "=" + escape(k) + ";path=" + WebRobot.cookiePath        }        function a() {            return "ce"        }        function j(u) {            var w = ""              , x = n().substr(0, 7);            for (var v = 0; v < x.length; v++) {                var y = x.charAt(v);                if (y != ".") {                    w = y + w                }            }            return h(u + w)        }        function m() {            return c() + b() + a()        }        var r = e(m());        if (r) {            var t = "" + (Math.ceil(Math.random() * 899999) + 100000);                       var sig=h(j(r) + t);                        return  sig        }    }function Cnonce(){return "" + (Math.ceil(Math.random() * 899999) + 100000)}

调用calcSig函数即可返回所计算的值

                                                                                 图21 计算结果

好啦,分析已经结束,要是还有不明白的或者文中所述有误的可以在下方评论,感谢各位大牛的指点。

0人推荐
随时随地看视频
慕课网APP