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

hresh 590 0

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

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

1.ppui_logintime

Charles抓包分析

定位到该 js 文件,分析 ppui_logintime 的产生过程。

Charles抓包分析

文件中只有这一处地方出现了 ppui_logintime,我们试着分析 timeSpan。

Charles抓包分析

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

分析上面的 js 代码可以看出,ppui_logintime 是从你打开登录界面,输入登录信息,一直到点击登录按钮进行提交的这段时间。因此 ppui_logintime 值可以采取固定值。一般在 8000~10000之间,当然如果需要手机短信验证的话,时间会更长一些。

2.ds 参数

搜索 ds 关键字,找到该字段出现的位置。

Charles抓包分析

打开这三个请求的响应结果,发现 tk、as 以及 ds 等数据。

Charles抓包分析

Charles抓包分析

Charles抓包分析

经过多次测试,将这三次的响应结果与登录时的 Form Data 数据比对发现,tk 值是一致的,关于 ds 值只有最后一个请求返回结果中的 ds 数据内容一致。

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

关于 ak 参数值,搜索其出现的位置。

Charles抓包分析

一番查看之后,最后定位到该 js 文件中。

Charles抓包分析

发现 ak 值是固定的,多次登录百度帐号,发现确实是不变的。那接下里分析 callback 参数,像上一篇文章中 callback 参数的分析一样,我们发现 callback 值都是以“jsonpCallbacka”开头,所以就搜索该字段。

Charles抓包分析

找到该字段后,发现只有一处出现,既然“jsonpCallbacka”只是 callback 开头内容,所以肯定是拼接而成的,我们接着搜索“jsonp”。

Charles抓包分析

发现 callback 后面的数字是 o 函数产生的,那我们继续往下找。

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

这样 callback 参数值的生成也就完成了。至于 v 参数值和 callback 参数值后半部分采用的是同一个函数。

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

第一个 get 请求需要的参数 ak 和 callback 都已找到,Python 代码发送 get 请求,解析 response 结果,就能找到 tk 值。

关于后续两个 get 请求必需参数 的分析,就只需要分析 as、fs,callback 参数只是换成了以“jsonpCallbackb”开头而已。
我们搜索 as 关键字,看看有什么发现。

Charles抓包分析

挨个查看相关文件发现并没有明确进行定义,这时我们回头看一下第一个 get 请求返回结果中的 as 值,发现正是后面两个请求参数中的 as 值,多次测试后发现确实如此,那么 as 值和 tk 值就一起在第一个请求结果中获取到了。

接着分析 fs 参数,这是百度模拟登录环节中最难的一部分。我们还是继续搜索 fs 关键字。

Charles抓包分析

定位到该 js 文件后,我们继续分析。

Charles抓包分析

Charles抓包分析

可以看出来,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代码分析

第二个请求涉及到的后台服务调用

js代码分析

第三个请求涉及到的后台服务调用

js代码分析

这些服务调用顺序是从下往上看的,可以看出来第二个请求和第三个请求后台服务调用还是有很大区别的,所以这也正是第三个请求返回结果集中的 ds 值为 true 的原因。接下来我们需要分析 mkd.js 文件。

首先对第二个请求进行断点调试。

js代码分析

这里可以发现采用了 AES 加密,而且是 ECB 模式,如果不了解,可以查看Python 实现 AES 加密

js代码分析

js代码分析

调试过程中可以发现,第二个请求中 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代码分析

这是第一个请求成功后的调用函数,其中 store.nameL 值已经被修改为 as 值,加上上文已经提到过三个请求返回的 as 值都是一样的,所以第二、三个请求中 store.nameL 的值都为 as 值。

js代码分析

接着分析第三个请求,对其进行断点调试。

js代码分析

js代码分析

这里我们需要对第三个请求中 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代码分析

在 js 文件中发现这样的代码:

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

因此我们继续搜索 dv_Input 关键字。

在这里插入图片描述

我们转入 g.min.js文件中查找,终于发现返回 dv 结果的代码。

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

我们需要分析 x 值的产生过程,首先是 token 值。

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

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

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

层层进行分析,最终找到 token 的源头,e 对象即为 w 对象,关于 S() 函数,我们进行断点测试。

js代码分析

js代码分析

js代码分析

dv 构建过程基本如上图所示,多次测试研究 w 对象,

js代码分析

发现 w 对象很难定义,而且没什么意义,最后,我们将 S(e, e.token) 这部分采用固定值。至此,dv 值分析完毕。

4.traceid 参数

该参数分析就较为简单了,还是同样进行搜索 traceid 关键字,最后进行查找。

js代码分析

5. callback 参数

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

6.登录验证

将各个参数进行汇总,最后发送 post 请求,返回结果如下:

百度模拟登录

或者提交 post 请求后,再访问百度首页,看看返回结果中是否有用户名。

总结:

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

发表评论 取消回复
表情 图片 链接 代码

分享