破解天眼查token,_utm,paaptp的过程 计算机 内容发表于2017-07-06

当发这篇文章的时候,该网站已经放弃了接口加密,我也终于敢把文章发表出来聊一聊接口加密破解的这一过程。

使用token和_utm可以去请求加密的接口,paaptp暂时不清楚它们后端用来做什么判断,很可能是这篇文章结尾说的那个问题导致的后端没有去做校验,或者这个仅仅是用来做加密接口监控用的。。。

首先在本地搭建好nginx,将它请求的js服务器ip通过修改host映射到本地,然后代理服务器打开将js请求都到本地下载好的js文件里,这样就可以自己在js里调试它的加密代码,下载过来的它的js是压缩后的,随便找个解压的网站解压一下就格式化好了可以正常看了,其实不管怎么加密,只要涉及到前后端这样交互就在js里找到它的加密方法,不外乎各种跳转各种转换各种混淆,只是复杂程度不一样罢了。

token和_utm参数的的破解在网络上可以找到,百度下就能找到有几个热心网友的解决方案,要想拿到token必须得想到转asc码,要想拿到_utm必须得找到正确的字典。

这两个参数非常简单,重点是网络上的破解方式都是有缺陷的,而且是致命的缺陷,原因是:网络上的破解方式得到_utm参数使用的字典是错误的字典,真正的字典是需要在js里一步一步调试出来的,然后自己保留下来用来得到_utm,根本不是写死在js里的那个数组字典,为什么它们能得到那个字典以为是正确的,因为使用那个错误字典的时候所有的请求只要key的首字符是2开头的都不会有问题,因为这个假的字典只有key是2的时候是正确的,所以你会发现当你的key首字符不是2的时候,能返回数据但是得到的数据都是假数据,这个错误对于爬虫来说是致命的。

网络上很多人问paaptp这个参数怎么破解,这个问题暂时在网络上没发现解决方案,下面说下我的这个破解途径。

怎么找到这个开始的呢,那就得一步步在最初的请求那里加打印cookie值,

console.log("paaptp1cookie7:"+document.cookie);,

发现在下面的这个地方出现了paaptp,而语句的最开始没有这个cookie值

生成过程:
所有的request请求有这么一段js(js在后面,先看打印的值),打印出来的那些值是如下所示:

可以看出e是$rootScope
e:打印出来是7204a5e4d515ccf8f3993,它是来自于$rootScope
t:打印出来是cookie字符
a:打印出来是app字符
i:打印出来是path字符
s:undefined
uuuuu:打印出来就是98913c3738d4060da6d71b10db2f26e105cfaf198a04ed9a9b15ccf8f39bb,这就是我们要的paaptp,可以看下面的打印就知道了。
n[0][t]打印出来是这样的:

TYCID=53ecf2b992f74da7ba44bc45084e4ebe; tnet=61.141.65.59; Qs_lvt_117460=1498117164; ssuid=3231699369; Hm_lvt_e92c8d65d92d534b0fc290df538b4758=1498117165; Hm_lpvt_e92c8d65d92d534b0fc290df538b4758=1498130888; Qs_pv_117460=346691578890329000%2C3751116267975784400%2C3842879489752609300%2C341436929071772100%2C442033311581940200; _pk_id.6835.e431=c3ab1be22fcbc2d3.1498117165.2.1498130888.1498130888.; _pk_ses.6835.e431=*; _pk_id.1.e431=610e1349b353388a.1498117165.2.1498130889.1498130888.; _pk_ses.1.e431=*; bannerStorageV2=%22true%22; paaptp=98913c3738d4060da6d71b10db2f26e105cfaf198a04ed9a9b15ccf8f39bb

它的js代码是这样的:

app.run(["$rootScope", "$window", "$document", "appName", "onner", "merge", "ErrorMsg",
function($rootScope, t, n, a, i, o, s) {
    console.log("$rootScope"+$rootScope);
    var e = $rootScope;
    console.log("eeee:"+e)
    angular.$splitNum = function(e, t) {
        console.log("e:"+e);
        console.log("t:"+t);
        console.log("a:"+a);
        console.log("i:"+i);
        console.log("s:"+s);
        console.log("paaptp1cookie7:"+document.cookie);
        for (var s = "",
        r = (new Date).getTime(), c = 0; c < a.length; c++) s += a.charCodeAt(c).toString();
        var l = Math.round(1e9 * Math.random()) % 1e8;
        for (s += l, s += r; s.length > 10;) s = (parseInt(s.substring(0, 10)) + parseInt(s.substring(10, s.length))).toString();
        for (var d = l; d.length > 3;) d = (parseInt(d.substring(0, 3)) + parseInt(d.substring(3, d.length))).toString();
        d /= a.length;
        for (var p = "",
        u = "",
        c = 0; c < e.length; c++) p = parseInt(e.charCodeAt(c) ^ parseInt((s + c * d) % 255)),
        u += 16 > p ? "0" + p.toString(16) : p.toString(16),
        d += s % d;
        for (l = l.toString(16); l.length < 8;) l = "0" + l;
        u += l,
        r = parseInt(r).toString(16),
        u += r,
        consol.log("uuuuuuuu:"+u);
        n[0][t] = o.m(a, i, "0", ".") + "=" + u + ";" + i + "=/;";
        console.log("n[0][t]:"+n[0][t]);
    },
    e.showErrorMsg = function(e) {
        s.showErrorMessage(e)
    }
}])

由此可以看出我们不知道的只有e这个值

我们去寻找谁调用的angular.$splitNum,找到这个e的值

我们看到了这么个东西:e的值是e[0][1]

i.prototype.$$safe = function(e, n, a) {
        a = a ? a: 18;
        for (var i = "",o = this.e.split(","), s = 0; a > s; s++){
            i += o[s].length > n + 1 ? o[s][n] : "";
        }
        console.log("e[0][1]:"+e[0][1]);
        console.log("e[0][1],i:"+i);
        console.log("t.$splitNum->e:"+e);
        return t.$splitNum(e[0][1], i)
    };

再去寻找是谁调用的 prototype.$$safe 方法。

看到了这个

function g(e) {
            var t = new angular.$sanitize("XMLHttpRequest"),
            console.log("tttt:"+t);
            var temp = t.$get(",", 1, 6);
            console.log("t.$get(",", 1, 6):"+temp);
            n = XMLHttpRequest.responsesType(temp);
            console.log("XMLHttpRequest.responsesType(t.$get(",", 1, 6)):"+n);
            t.$$safe(n, 2)
        }

如上面所述
打印出来那个t.$get(“,”, 1, 6)的内容是: odtib
n打印出来的内容是:sessionStorage,7204a5e4d515ccf8f3993

所以很明显,我们可以知道传入的那个未知的值就是这个n到safe去的,而safe里面那个e取的是e[0][1],可以知道7204a5e4d515ccf8f3993就是嘛,那么这个sessionStorage是什么时候放进去的呢,搜索了下sessionStorage

14个匹配的,其中这段代码吸引了我:

angular.module("app").run(["$rootScope", "maxUSize", "$window",
function(e, t, n) {
    console.log("maxUSizetttttttt:"+t);
    for (var a = "localStorage",
    i = "",
    o = (new Date).getTime(), s = 0; s < a.length; s++) i += a.charCodeAt(s).toString();
    var r = Math.round(1e9 * Math.random()) % 1e8;
    for (i += r, i += o; i.length > 10;) i = (parseInt(i.substring(0, 10)) + parseInt(i.substring(10, i.length))).toString();
    for (var c = r; c.length > 3;) c = (parseInt(c.substring(0, 3)) + parseInt(c.substring(3, c.length))).toString();
    c /= a.length;
    for (var l = "",
    d = "",
    s = 0; s < t.length; s++) l = parseInt(t.charCodeAt(s) ^ parseInt((i + s * c) % 255)),
    d += 16 > l ? "0" + l.toString(16) : l.toString(16),
    c += i % c;
    for (r = r.toString(16); r.length < 8;) r = "0" + r;
    d += r,
    o = parseInt(o).toString(16),
    d += o;
    var p = new angular.$sanitize("Storage");
    n[p.$get(",", 1, 6)] = [],
    console.log("sessionStorage d:"+d);
    n[p.$get(",", 1, 6)].push(["sessionStorage", d])
}])

看到那个push了没,说明是从这里放进去的,打个log试下,果然是这样
这段代码是用来获取odtib的,也就是那个e,那个我们要生成paaptp的钥匙,但是这个钥匙的生成需要用到参数t,参数t是来自于maxUSize,maxUSize在js里搜索了下,是这样的:

app.constant("maxUSize", "3")

所以只需要把这些js转换为java代码就ok了,但是目前遇到这些问题:

aa:9711211242
bb:2950221498117399587
cc:9711211242
dd:2950221498117399600
s:2950221507828610600

s是由cc和dd相加得到的,很明显这不可能,js里面如果调用这样的代码,如果传入的值越界了,出现的值不可预知,很神奇,不符合加减法了,应该是越界了:

var aa=9711211242;
var bb=2950221498117399587
var cc = parseInt(aa);
var dd = parseInt(bb);
console.log("cc:"+cc);
console.log("dd:"+dd);
s = ( cc+ dd ).toString();
console.log("s:"+s);

这里有一个非常有趣的事情,就是其实这个paaptp字符串的一部分的值生成可能是不可预知的,因为js的超大值加法会得到一个错误的越界值

如上面的这段代码,即使你不用程序,笔算都能知道答案不会是js输出的那个,整个js涉及到加密的那些代码很多命名都是fuxk和wtf,可以想象得出来该网站的程序员心中肯定是一万个草泥马在奔腾,当破解完它的paaptp参数的时候瞬间觉得可能这也是它们自己犯的错误,这个网站的反爬虫工程师和前端也应该在纠结问题出在哪里,前后端得到的值不一致。

巧合的是在一天前,网站撤下了接口加密,替换成了渲染好html返回回来,爬虫的抓取就方便多了,破解也不需要了,模拟浏览器也不需要了,只需要直接发起get请求即可。

爬虫和反爬就是程序员互相伤害的战场,哈。

写下这篇文章纪念下自己花了三天时间搞的东西。。。如有侵犯天眼查网站权利,无条件撤下该文章。