js 分析——百度模拟登录(二)

上一篇百度模拟登录(一)主要讲解了 token、gid、rsakey 以及 password 等参数的产生。好了,废话不多说,咱们进入今天的主题,主要分析 ppui_logintime、ds、tk、dv、traceid、callback 这些字段的产生。

1.ppui_logintime
js 分析——百度模拟登录(二)_第1张图片
定位到该 js 文件,分析 ppui_logintime 的产生过程。
js 分析——百度模拟登录(二)_第2张图片
文件中只有这一处地方出现了 ppui_logintime,我们试着分析 timeSpan。
js 分析——百度模拟登录(二)_第3张图片
在这里插入图片描述
分析上面的 js 代码可以看出,ppui_logintime 是从你打开登录界面,输入登录信息,一直到点击登录按钮进行提交的这段时间。因此 ppui_logintime 值可以采取固定值。一般在 8000~10000之间,当然如果需要手机短信验证的话,时间会更长一些。

2.ds 参数

搜索 ds 关键字,找到该字段出现的位置。
js 分析——百度模拟登录(二)_第4张图片
打开这三个请求的响应结果,发现 tk、as 以及 ds 等数据。
js 分析——百度模拟登录(二)_第5张图片
js 分析——百度模拟登录(二)_第6张图片
js 分析——百度模拟登录(二)_第7张图片
经过多次测试,将这三次的响应结果与登录时的 Form Data 数据比对发现,tk 值是一致的,关于 ds 值只有最后一个请求返回结果中的 ds 数据内容一致。

第一个请求需要的参数最少,需要 ak、callback、v,后面两个请求都需要 ak、as、fs、callback 以及 v。那我们就从简单开始处理,分析第一个请求 ,构建必需参数,从而获得 tk 值。

关于 ak 参数值,搜索其出现的位置。
js 分析——百度模拟登录(二)_第8张图片
一番查看之后,最后定位到该 js 文件中。
js 分析——百度模拟登录(二)_第9张图片
发现 ak 值是固定的,多次登录百度帐号,发现确实是不变的。那接下里分析 callback 参数,像上一篇文章中 callback 参数的分析一样,我们发现 callback 值都是以“jsonpCallbacka”开头,所以就搜索该字段。
js 分析——百度模拟登录(二)_第10张图片
找到该字段后,发现只有一处出现,既然“jsonpCallbacka”只是 callback 开头内容,所以肯定是拼接而成的,我们接着搜索“jsonp”。
js 分析——百度模拟登录(二)_第11张图片
发现 callback 后面的数字是 o 函数产生的,那我们继续往下找。
在这里插入图片描述
这样 callback 参数值的生成也就完成了。至于 v 参数值和 callback 参数值后半部分采用的是同一个函数。
js 分析——百度模拟登录(二)_第12张图片
第一个 get 请求需要的参数 ak 和 callback 都已找到,Python 代码发送 get 请求,解析 response 结果,就能找到 tk 值。

关于后续两个 get 请求必需参数 的分析,就只需要分析 as、fs,callback 参数只是换成了以“jsonpCallbackb”开头而已。
我们搜索 as 关键字,看看有什么发现。
js 分析——百度模拟登录(二)_第13张图片
挨个查看相关文件发现并没有明确进行定义,这时我们回头看一下第一个 get 请求返回结果中的 as 值,发现正是后面两个请求参数中的 as 值,多次测试后发现确实如此,那么 as 值和 tk 值就一起在第一个请求结果中获取到了。

接着分析 fs 参数,这是百度模拟登录环节中最难的一部分。我们还是继续搜索 fs 关键字。
js 分析——百度模拟登录(二)_第14张图片
定位到该 js 文件后,我们继续分析。
js 分析——百度模拟登录(二)_第15张图片
js 分析——百度模拟登录(二)_第16张图片
可以看出来,fs 值是将 t.rzData 数据进行 AES 加密返回的结果,具体 t.rzData 数据是什么,我们可以看下以下代码。

initMock: function() {
      this.rzData = {
          cl: [],
          mv: [],
          sc: [],
          kb: [],
          cr: this.getScreenInfo(),
          ac_c: 0
      }, this.dsData = {}
  },
  initGatherEvent: function() {
     var e = this,
            i = function(n) {
                n = n || t.event;
                var i = {},
                    o = "wap" === e.devicetype ? n.changedTouches[0] : n;
                i.x = parseInt(o.clientX, 10), i.y = parseInt(o.clientY, 10), i.t = e.getTimeStr(), e.rzData.cl.push(i), e.reportedOpportunity()
            },
            o = e.throttle(function(n) {
                n = n || t.event || arguments.callee.caller.arguments[0];
                var i = {},
                    o = "wap" === e.devicetype ? n.changedTouches[0] : n;
                i.fx = parseInt(o.clientX, 10), i.fy = parseInt(o.clientY, 10), i.t = e.getTimeStr(), e.rzData.mv.push(i), e.reportedOpportunity()
            }, 150),
            r = function() {
                var t = {};
                t.key = "a", t.t = e.getTimeStr(), e.rzData.kb.push(t), e.reportedOpportunity()
            },
            s = e.throttle(function(i) {
                i = i || t.event;
                var o = {};
                o.tx = n.documentElement.scrollLeft || n.body.scrollLeft, o.ty = n.documentElement.scrollTop || n.body.scrollTop, e.rzData.sc.push(o), e.reportedOpportunity()
            }, 300);
        e.addHandler(n, e.eventclick, i), e.addHandler(n, e.eventmove, o), e.addHandler(n, "keyup", r), e.addHandler(t, "scroll", s), e.removeGatherEvent = function() {
            e.removeHandler(n, e.eventclick, i), e.removeHandler(n, e.eventmove, o), e.removeHandler(n, "keyup", r), e.removeHandler(t, "scroll", s)
        }
    },
    throttle: function(e, t) {
        var n;
        return function() {
            return n ? void 0 : (n = setTimeout(function() {
                n = null
            }, t), e.apply(this))
        }
    },
    getScreenInfo: function() {
        try {
            var e = t.mozInnerScreenY || t.screenTop,
                i = t.mozInnerScreenX || t.screenLeft;
            "undefined" == typeof e && (e = 0), "undefined" == typeof i && (i = 0);
            var o = n.documentElement.clientWidth || n.body.clientWidth,
                r = n.documentElement.clientHeight || n.body.clientHeight,
                s = t.screen.width,
                c = t.screen.height,
                a = t.screen.availWidth,
                d = t.screen.availHeight,
                l = t.outerWidth,
                u = t.outerHeight,
                h = n.documentElement.scrollWidth || n.body.scrollWidth,
                f = n.documentElement.scrollWidth || n.body.scrollHeight;
            return {
                screenTop: e,
                screenLeft: i,
                clientWidth: o,
                clientHeight: r,
                screenWidth: s,
                screenHeight: c,
                availWidth: a,
                availHeight: d,
                outerWidth: l,
                outerHeight: u,
                scrollWidth: h,
                scrollHeight: f
            }
        } catch (p) {}
    },
    reportedOpportunity: function() {
        var e = this;
        ++e.store.count, e.store.count > e.store.countnum && e.postData()
    },

通过以上 js 代码可以大概看出,rzData 这个字典中的数据是变化的,最初只是简单的获取浏览器的宽高等相关数据,这些都可以简单固定写死,但是要命的是 initGatherEvent 函数中对 rzData 数据的填充,包括鼠标点击位置、页面水平滚动条位置、垂直滚动条等信息。这一部分的 js 代码研究了很长一段时间,由于实力不够,最后放弃了,觉得可以采取一个固定 fs 值进行分析。

至于为什么会有两次请求,第一次是点击登录按钮之前产生的,第二次是点击登录按钮时产生的,这可能也是为什么后一个请求返回结果中的 ds 值才是正确的。

为了写好这篇文章,让大家看的更明白,我又研究学习了这一部分的 js 代码,并配合开发者工具进行调试,终于知道了第三次请求中的 fs 值如何产生。

首先我们看如下三张图,虽然 js 代码之间的调用很复杂,但是还是能理清楚先后顺序。

第一个请求涉及到的后台服务调用
js 分析——百度模拟登录(二)_第17张图片
第二个请求涉及到的后台服务调用
js 分析——百度模拟登录(二)_第18张图片
第三个请求涉及到的后台服务调用
js 分析——百度模拟登录(二)_第19张图片
这些服务调用顺序是从下往上看的,可以看出来第二个请求和第三个请求后台服务调用还是有很大区别的,所以这也正是第三个请求返回结果集中的 ds 值为 true 的原因。接下来我们需要分析 mkd.js 文件。

首先对第二个请求进行断点调试。
js 分析——百度模拟登录(二)_第20张图片
这里可以发现采用了 AES 加密,而且是 ECB 模式,如果不了解,可以查看Python 实现 AES 加密。
js 分析——百度模拟登录(二)_第21张图片
js 分析——百度模拟登录(二)_第22张图片
调试过程中可以发现,第二个请求中 rzData 数据集的比较繁琐,很难从代码层面获取,所以暂不考虑。不过这里需要注意的是,进行 AES 加密时所需要的密钥值是变化的。

encrypt: function(t) {
         var n = this.store.nameL + this.store.nameR,
                i = e.CryptoJS.enc.Utf8.parse(n),
                o = e.CryptoJS.enc.Utf8.parse(t),
                r = e.CryptoJS.AES.encrypt(o, i, {
                    mode: e.CryptoJS.mode.ECB,
                    padding: e.CryptoJS.pad.Pkcs7
                });
            return r.toString()
        },

js 分析——百度模拟登录(二)_第23张图片
这是第一个请求成功后的调用函数,其中 store.nameL 值已经被修改为 as 值,加上上文已经提到过三个请求返回的 as 值都是一样的,所以第二、三个请求中 store.nameL 的值都为 as 值。
js 分析——百度模拟登录(二)_第24张图片
接着分析第三个请求,对其进行断点调试。
js 分析——百度模拟登录(二)_第25张图片
js 分析——百度模拟登录(二)_第26张图片
这里我们需要对第三个请求中 fs 的产生进行分析,fs 值是对 rzData 数据进行 AES 加密后的结果,所以我们要弄清第三个请求中 rzData 数据是什么样的。

"{"cl":[{"x":927,"y":260,"t":1560757744318}],"mv":[{"fx":921,"fy":413,"t":1560757743436},{"fx":919,"fy":324,"t":1560757743588},{"fx":927,"fy":277,"t":1560757743739}],"sc":[],"kb":[],"cr":{"screenTop":0,"screenLeft":0,"clientWidth":1903,"clientHeight":416,"screenWidth":1920,"screenHeight":1080,"availWidth":1920,"availHeight":1040,"outerWidth":1920,"outerHeight":1040,"scrollWidth":1903,"scrollHeight":1903},"ac_c":0}"

经过多次测试,发现 rzData 数据基本都为这个结构,其中 cr 数据为固定值,cl 和 mv 数据略有变动,因此在这里,我的想法是针对 x,y 采用固定值,t 时间戳在程序中获取当前时间。

终于,我们大致分析出来 fs 的产生过程,也如愿得到结果集,其中包括 ds 和 tk。

3.dv 参数

搜索 dv 关键字,结果如下。
js 分析——百度模拟登录(二)_第27张图片
在 js 文件中发现这样的代码:

a.dv = document.getElementById("dv_Input") ? document.getElementById("dv_Input").value : window.LG_DV_ARG && window.LG_DV_ARG.dvjsInput || "";

因此我们继续搜索 dv_Input 关键字。
js 分析——百度模拟登录(二)_第28张图片
我们转入 g.min.js文件中查找,终于发现返回 dv 结果的代码。
js 分析——百度模拟登录(二)_第29张图片
我们需要分析 x 值的产生过程,首先是 token 值。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
层层进行分析,最终找到 token 的源头,e 对象即为 w 对象,关于 S() 函数,我们进行断点测试。
js 分析——百度模拟登录(二)_第30张图片
js 分析——百度模拟登录(二)_第31张图片
js 分析——百度模拟登录(二)_第32张图片
dv 构建过程基本如上图所示,多次测试研究 w 对象,
js 分析——百度模拟登录(二)_第33张图片
发现 w 对象很难定义,而且没什么意义,最后,我们将 S(e, e.token) 这部分采用固定值。至此,dv 值分析完毕。

4.traceid 参数

该参数分析就较为简单了,还是同样进行搜索 traceid 关键字,最后进行查找。
js 分析——百度模拟登录(二)_第34张图片
5. callback 参数

和上文 get 请求中的 callback 基本一致,需要将“bd__cbs__”改为“bd__pcbs__”,同时加上“parent.”作为前缀就可以了。

6.登录验证

将各个参数进行汇总,最后发送 post 请求,返回结果如下:
js 分析——百度模拟登录(二)_第35张图片
或者提交 post 请求后,再访问百度首页,看看返回结果中是否有用户名。

总结:

关于 js 分析,首先要明确目标,比如说本次是百度模拟登录,所以肯定是个 POST 请求,需要分析登录请求中的参数,然后再一步步分析这些参数的产生。关于参数分析,首次肯定是没有头绪的,也不知道分析的先后顺序,参数分析的难易程度,我们只能按照某个顺序(从上到下)挨个分析参数,在分析的过程中慢慢理清参数之间的关联性。

你可能感兴趣的:(爬虫)