破解知乎了!

本文由作者 胡川港 投稿,如果你在 CSDN、博客园、掘金等平台有写技术博客的习惯,想让自己的原创博客被更多人看到,可以来 Java后端 投稿。

最近在新公司学习 Golang,心血来潮想写一个脚手架,脚手架的简单的功能就是 输入 关键词,然后工具帮我自动的去搜索比如 掘金 知乎 CSDN 的热门博客返回给我。

但是在调用知乎的接口发现 知乎的接口有签名 不能修改参数。修改了参数就让提示升级客户端了。这怎么能忍,岂能因为一个小小的知乎搜索阻挠我学习 Golang 的脚步呢「手动狗头」,今天就来逆向破解知乎。

dbc98cb5d785116514a165ab43ca3283.png

现象

先用 Chrome 将 知乎的搜索 API 导出为 cURL

1c0818a7277a2a19481858ebe882fa55.png

并将导出的 cURL 导入到 postman 中

0862bc2cdda45c291302486ff5a7a81e.png

在 postman 这里直接发送请求是可以的

5327f44a6cf90df1959c0ee9b9f362d7.png

但是如果我们一旦尝试修改查询参数的时候就会报错

9633d3f4c42a0ece96ed1ce9211f477a.png

分析原因

其实到这里很明显的知道肯定是因为参数加签导致的,但是我们需要知道具体是哪个参数导致,才好解决问题

bba0c12252c39cee8da6df97d60f21cb.png

咋一看 url 里面的这些参数 都很标准 应该没有动过什么手脚,我们把目标转向 header


7dde16adbfb54a3ca9ba73451ade0e2f.png

请求头里面这么多参数感觉是有问题的

测试方法也很简单,依次去除 单个字段,然后请求接口看是否可行即可,最后发现在没有修改请求参数 的情况下 去除 请求头里面的 x-zse-96 就会出现同样的问题,问题复现

39714625e0c9768ed73b5bc14278cfba.png

定位加密文件

在分析问题环节我们已经找到了加签后的验证字段 x-zse-96,最后其实我们值需要找到这个字段对应的加签规则即可。

基本的思路为:通过 Chrome 中的 JS 栈调用为入口

c90ec3efa713ad09ecf8879459a1d91f.png

如上图所示,在 Network 中 搜索 API 请求记录中有一项属性 Initiator [发起者],将鼠标移动上去就会显示 JS 的调用栈情况

JS call stack

f84a11b41bf0480a9b0297d62a5dcc76.png

有一点可以明确下来就是 针对参数 x-zse-96 这个参数的加签肯定是在这个调用栈中完成的,我的思路就是一次在这些调用过的 js 文件中搜索这个字段,先定位到在哪个 JS 文件中的,好巧不巧的事知乎 的js 应该是打包后的 所有的 js 文件都在一个代码文件中。

我直接在这个文件中进行搜索就行了

20d740c971c8bdcb46a7c165efdc45ad.png

代码位置已找到,开始表演😈

还原加密过程

接下来就是分析加密代码的时候了

c9f7b76d624e6d5b3174667fb689082c.png

从图中可以看出,x-zse-96 是由 2.0_ + signature 组成的

然后我们就需要查看组成 signature 代码的 a()(l()(s))的组成

0da5852cd0156caf6b6b04e5c37110c2.png

在 signature 上方打上一个断点, 再次点击搜索,让断点停在此处

f42acf3c8f55146367f20ea96fc09e8a.png

a() 直接在控制台中打印一下 a 方法,a 方法返回的一个方法为 __g._encrypt(encodeURIComponent(e))

dda7f0aa50bb7466968ee70bee1070ca.png

这里我们先将 a 方法放置在这里,先看里面的方法 l()(s),先看参数 s 基本上就是查询参数拼接成一个字符串, 暂时先可以不看

f7af6db8e16fbc6c11389eb5104639e7.png

l(), 我现在带入几个参数对这个加密算法进行了尝试,我实在感觉这个有点像 MD5

b13b87e9142349de4a9b30d5ce1de8a6.png

3680a93bfc70181a8a30d654419490a7.png

\

抱着试一试的心态找了一个在线的 Md5 加密网站测试了一下 1,卧槽 🐂🍺 还真的是,我直呼 666

回顾下加密算法

c47b426507dcea01c29ac68d4d46bc12.png

现在我们只需要展开 外层的 a()() 方法就 OK 了,在展开一点点__g._encrypt(encodeURIComponent(md5(s)))

82acc5e2b33ec52abf5683c5e3d04386.pngce35c9a790e29db994dc59cd251e1536.png

接下其实就去找这个 __g._encrypt 这个方法就可以了,打上断点在执行一下,尝试运行,看到这个玩意确实是没啥思路,而且在整个 JS 文件中 __g._encrypt 这个方法就只出现了这一次,就在没有出现过了, 所以通过猜测加密算法的方式就不太可行了

思路就是直接将加密算法模块的 JS 文件提取出来 然后 用 golang 的 otto 包来直接调用 JS 代码即可

提取出来的加密 JS 文件为:

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
window = dom.window;
document = window.document;
XMLHttpRequest = window.XMLHttpRequest;
function t(e) {
    return (t = "function" == typeof Symbol && "symbol" == typeof Symbol.A ? function(e) {
            return typeof e
        }
        : function(e) {
            return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e
        }
    )(e)
}
Object.defineProperty(exports, "__esModule", {
    value: !0
});
var A = "2.0"
  , __g = {};
function s() {}
function i(e) {
    this.t = (2048 & e) >> 11,
      this.s = (1536 & e) >> 9,
      this.i = 511 & e,
      this.h = 511 & e
}
function h(e) {
    this.s = (3072 & e) >> 10,
      this.h = 1023 & e
}
function a(e) {
    this.a = (3072 & e) >> 10,
      this.c = (768 & e) >> 8,
      this.n = (192 & e) >> 6,
      this.t = 63 & e
}
function c(e) {
    this.s = e >> 10 & 3,
      this.i = 1023 & e
}
function n() {}
function e(e) {
    this.a = (3072 & e) >> 10,
      this.c = (768 & e) >> 8,
      this.n = (192 & e) >> 6,
      this.t = 63 & e
}
function o(e) {
    this.h = (4095 & e) >> 2,
      this.t = 3 & e
}
function r(e) {
    this.s = e >> 10 & 3,
      this.i = e >> 2 & 255,
      this.t = 3 & e
}
s.prototype.e = function(e) {
    e.o = !1
}
  ,
  i.prototype.e = function(e) {
      switch (this.t) {
          case 0:
              e.r[this.s] = this.i;
              break;
          case 1:
              e.r[this.s] = e.k[this.h]
      }
  }
  ,
  h.prototype.e = function(e) {
      e.k[this.h] = e.r[this.s]
  }
  ,
  a.prototype.e = function(e) {
      switch (this.t) {
          case 0:
              e.r[this.a] = e.r[this.c] + e.r[this.n];
              break;
          case 1:
              e.r[this.a] = e.r[this.c] - e.r[this.n];
              break;
          case 2:
              e.r[this.a] = e.r[this.c] * e.r[this.n];
              break;
          case 3:
              e.r[this.a] = e.r[this.c] / e.r[this.n];
              break;
          case 4:
              e.r[this.a] = e.r[this.c] % e.r[this.n];
              break;
          case 5:
              e.r[this.a] = e.r[this.c] == e.r[this.n];
              break;
          case 6:
              e.r[this.a] = e.r[this.c] >= e.r[this.n];
              break;
          case 7:
              e.r[this.a] = e.r[this.c] || e.r[this.n];
              break;
          case 8:
              e.r[this.a] = e.r[this.c] && e.r[this.n];
              break;
          case 9:
              e.r[this.a] = e.r[this.c] !== e.r[this.n];
              break;
          case 10:
              e.r[this.a] = t(e.r[this.c]);
              break;
          case 11:
              e.r[this.a] = e.r[this.c]in e.r[this.n];
              break;
          case 12:
              e.r[this.a] = e.r[this.c] > e.r[this.n];
              break;
          case 13:
              e.r[this.a] = -e.r[this.c];
              break;
          case 14:
              e.r[this.a] = e.r[this.c] < e.r[this.n];
              break;
          case 15:
              e.r[this.a] = e.r[this.c] & e.r[this.n];
              break;
          case 16:
              e.r[this.a] = e.r[this.c] ^ e.r[this.n];
              break;
          case 17:
              e.r[this.a] = e.r[this.c] << e.r[this.n];
              break;
          case 18:
              e.r[this.a] = e.r[this.c] >>> e.r[this.n];
              break;
          case 19:
              e.r[this.a] = e.r[this.c] | e.r[this.n];
              break;
          case 20:
              e.r[this.a] = !e.r[this.c]
      }
  }
  ,
  c.prototype.e = function(e) {
      e.Q.push(e.C),
        e.B.push(e.k),
        e.C = e.r[this.s],
        e.k = [];
      for (var t = 0; t < this.i; t++)
          e.k.unshift(e.f.pop());
      e.g.push(e.f),
        e.f = []
  }
  ,
  n.prototype.e = function(e) {
      e.C = e.Q.pop(),
        e.k = e.B.pop(),
        e.f = e.g.pop()
  }
  ,
  e.prototype.e = function(e) {
      switch (this.t) {
          case 0:
              e.u = e.r[this.a] >= e.r[this.c];
              break;
          case 1:
              e.u = e.r[this.a] <= e.r[this.c];
              break;
          case 2:
              e.u = e.r[this.a] > e.r[this.c];
              break;
          case 3:
              e.u = e.r[this.a] < e.r[this.c];
              break;
          case 4:
              e.u = e.r[this.a] == e.r[this.c];
              break;
          case 5:
              e.u = e.r[this.a] != e.r[this.c];
              break;
          case 6:
              e.u = e.r[this.a];
              break;
          case 7:
              e.u = !e.r[this.a]
      }
  }
  ,
  o.prototype.e = function(e) {
      switch (this.t) {
          case 0:
              e.C = this.h;
              break;
          case 1:
              e.u && (e.C = this.h);
              break;
          case 2:
              e.u || (e.C = this.h);
              break;
          case 3:
              e.C = this.h,
                e.w = null
      }
      e.u = !1
  }
  ,
  r.prototype.e = function(e) {
      switch (this.t) {
          case 0:
              for (var t = [], n = 0; n < this.i; n++)
                  t.unshift(e.f.pop());
              e.r[3] = e.r[this.s](t[0], t[1]);
              break;
          case 1:
              for (var r = e.f.pop(), i = [], o = 0; o < this.i; o++)
                  i.unshift(e.f.pop());
              e.r[3] = e.r[this.s][r](i[0], i[1]);
              break;
          case 2:
              for (var a = [], c = 0; c < this.i; c++)
                  a.unshift(e.f.pop());
              e.r[3] = new e.r[this.s](a[0],a[1])
      }
  }
;
var k = function(e) {
    for (var t = 66, n = [], r = 0; r < e.length; r++) {
        var i = 24 ^ e.charCodeAt(r) ^ t;
        n.push(String.fromCharCode(i)),
          t = i
    }
    return n.join("")
};
function Q(e) {
    this.t = (4095 & e) >> 10,
      this.s = (1023 & e) >> 8,
      this.i = 1023 & e,
      this.h = 63 & e
}
function C(e) {
    this.t = (4095 & e) >> 10,
      this.a = (1023 & e) >> 8,
      this.c = (255 & e) >> 6
}
function B(e) {
    this.s = (3072 & e) >> 10,
      this.h = 1023 & e
}
function f(e) {
    this.h = 4095 & e
}
function g(e) {
    this.s = (3072 & e) >> 10
}
function u(e) {
    this.h = 4095 & e
}
function w(e) {
    this.t = (3840 & e) >> 8,
      this.s = (192 & e) >> 6,
      this.i = 63 & e
}
function G() {
    this.r = [0, 0, 0, 0],
      this.C = 0,
      this.Q = [],
      this.k = [],
      this.B = [],
      this.f = [],
      this.g = [],
      this.u = !1,
      this.G = [],
      this.b = [],
      this.o = !1,
      this.w = null,
      this.U = null,
      this.F = [],
      this.R = 0,
      this.J = {
          0: s,
          1: i,
          2: h,
          3: a,
          4: c,
          5: n,
          6: e,
          7: o,
          8: r,
          9: Q,
          10: C,
          11: B,
          12: f,
          13: g,
          14: u,
          15: w
      }
}
Q.prototype.e = function(e) {
    switch (this.t) {
        case 0:
            e.f.push(e.r[this.s]);
            break;
        case 1:
            e.f.push(this.i);
            break;
        case 2:
            e.f.push(e.k[this.h]);
            break;
        case 3:
            e.f.push(k(e.b[this.h]))
    }
}
  ,
  C.prototype.e = function(A) {
      switch (this.t) {
          case 0:
              var t = A.f.pop();
              A.r[this.a] = A.r[this.c][t];
              break;
          case 1:
              var s = A.f.pop()
                , i = A.f.pop();
              A.r[this.c][s] = i;
              break;
          case 2:
              var h = A.f.pop();
              A.r[this.a] = eval(h)
      }
  }
  ,
  B.prototype.e = function(e) {
      e.r[this.s] = k(e.b[this.h])
  }
  ,
  f.prototype.e = function(e) {
      e.w = this.h
  }
  ,
  g.prototype.e = function(e) {
      throw e.r[this.s]
  }
  ,
  u.prototype.e = function(e) {
      var t = this
        , n = [0];
      e.k.forEach((function(e) {
            n.push(e)
        }
      ));
      var r = function(r) {
          var i = new G;
          return i.k = n,
            i.k[0] = r,
            i.v(e.G, t.h, e.b, e.F),
            i.r[3]
      };
      r.toString = function() {
          return "() { [native code] }"
      }
        ,
        e.r[3] = r
  }
  ,
  w.prototype.e = function(e) {
      switch (this.t) {
          case 0:
              for (var t = {}, n = 0; n < this.i; n++) {
                  var r = e.f.pop();
                  t[e.f.pop()] = r
              }
              e.r[this.s] = t;
              break;
          case 1:
              for (var i = [], o = 0; o < this.i; o++)
                  i.unshift(e.f.pop());
              e.r[this.s] = i
      }
  }
  ,
  G.prototype.D = function(e) {
      for (var t = window.atob(e), n = t.charCodeAt(0) << 8 | t.charCodeAt(1), r = [], i = 2; i < n + 2; i += 2)
          r.push(t.charCodeAt(i) << 8 | t.charCodeAt(i + 1));
      this.G = r;
      for (var o = [], a = n + 2; a < t.length; ) {
          var c = t.charCodeAt(a) << 8 | t.charCodeAt(a + 1)
            , u = t.slice(a + 2, a + 2 + c);
          o.push(u),
            a += c + 2
      }
      this.b = o
  }
  ,
  G.prototype.v = function(e, t, n) {
      for (t = t || 0,
             n = n || [],
             this.C = t,
             "string" == typeof e ? this.D(e) : (this.G = e,
               this.b = n),
             this.o = !0,
             this.R = Date.now(); this.o; ) {
          var r = this.G[this.C++];
          if ("number" != typeof r)
              break;
          var i = Date.now();
          if (500 < i - this.R)
              return;
          this.R = i;
          try {
              this.e(r)
          } catch (e) {
              this.U = e,
              this.w && (this.C = this.w)
          }
      }
  }
  ,
  G.prototype.e = function(e) {
      var t = (61440 & e) >> 12;
      new this.J[t](e).e(this)
  }
  ,
1  && (new G).v("AxjgB5MAnACoAJwBpAAAABAAIAKcAqgAMAq0AzRJZAZwUpwCqACQACACGAKcBKAAIAOcBagAIAQYAjAUGgKcBqFAuAc5hTSHZAZwqrAIGgA0QJEAJAAYAzAUGgOcCaFANRQ0R2QGcOKwChoANECRACQAsAuQABgDnAmgAJwMgAGcDYwFEAAzBmAGcSqwDhoANECRACQAGAKcD6AAGgKcEKFANEcYApwRoAAxB2AGcXKwEhoANECRACQAGAKcE6AAGgKcFKFANEdkBnGqsBUaADRAkQAkABgCnBagAGAGcdKwFxoANECRACQAGAKcGKAAYAZx+rAZGgA0QJEAJAAYA5waoABgBnIisBsaADRAkQAkABgCnBygABoCnB2hQDRHZAZyWrAeGgA0QJEAJAAYBJwfoAAwFGAGcoawIBoANECRACQAGAOQALAJkAAYBJwfgAlsBnK+sCEaADRAkQAkABgDkACwGpAAGAScH4AJbAZy9rAiGgA0QJEAJACwI5AAGAScH6AAkACcJKgAnCWgAJwmoACcJ4AFnA2MBRAAMw5gBnNasCgaADRAkQAkABgBEio0R5EAJAGwKSAFGACcKqAAEgM0RCQGGAYSATRFZAZzshgAtCs0QCQAGAYSAjRFZAZz1hgAtCw0QCQAEAAgB7AtIAgYAJwqoAASATRBJAkYCRIANEZkBnYqEAgaBxQBOYAoBxQEOYQ0giQKGAmQABgAnC6ABRgBGgo0UhD/MQ8zECALEAgaBxQBOYAoBxQEOYQ0gpEAJAoYARoKNFIQ/zEPkAAgChgLGgkUATmBkgAaAJwuhAUaCjdQFAg5kTSTJAsQCBoHFAE5gCgHFAQ5hDSCkQAkChgBGgo0UhD/MQ+QACAKGAsaCRQCOYGSABoAnC6EBRoKN1AUEDmRNJMkCxgFGgsUPzmPkgAaCJwvhAU0wCQFGAUaCxQGOZISPzZPkQAaCJwvhAU0wCQFGAUaCxQMOZISPzZPkQAaCJwvhAU0wCQFGAUaCxQSOZISPzZPkQAaCJwvhAU0wCQFGAkSAzRBJAlz/B4FUAAAAwUYIAAIBSITFQkTERwABi0GHxITAAAJLwMSGRsXHxMZAAk0Fw8HFh4NAwUABhU1EBceDwAENBcUEAAGNBkTGRcBAAFKAAkvHg4PKz4aEwIAAUsACDIVHB0QEQ4YAAsuAzs7AAoPKToKDgAHMx8SGQUvMQABSAALORoVGCQgERcCAxoACAU3ABEXAgMaAAsFGDcAERcCAxoUCgABSQAGOA8LGBsPAAYYLwsYGw8AAU4ABD8QHAUAAU8ABSkbCQ4BAAFMAAktCh8eDgMHCw8AAU0ADT4TGjQsGQMaFA0FHhkAFz4TGjQsGQMaFA0FHhk1NBkCHgUbGBEPAAFCABg9GgkjIAEmOgUHDQ8eFSU5DggJAwEcAwUAAUMAAUAAAUEADQEtFw0FBwtdWxQTGSAACBwrAxUPBR4ZAAkqGgUDAwMVEQ0ACC4DJD8eAx8RAAQ5GhUYAAFGAAAABjYRExELBAACWhgAAVoAQAg/PTw0NxcQPCQ5C3JZEBs9fkcnDRcUAXZia0Q4EhQgXHojMBY3MWVCNT0uDhMXcGQ7AUFPHigkQUwQFkhaAkEACjkTEQspNBMZPC0ABjkTEQsrLQ==");

var b = function(e) {
    return __g._encrypt(encodeURIComponent(e))
};

module.exports = {
    Q: b
}

712717ab36714c36d29b02eedc37bf5c.png1fbc535fc67594576fca3b8f63aadaa0.png

最后本地测试一下,对上了 对上了

cdd1bd83e3ba332f7be75d419065e3b9.png

b658b8a0887bf11311129f3b625dd601.png

在看一下 s 的值 s=r+c+i, 101_3_3.0+/api/v4/search/suggest?qv=vim+AHCY5gT4iBKPTsBQivYRPUIbDLBctuaZZzs=|1611209467 其他都是固定的 只需要改一下请求的 url query 就可以了

76ea6410a5b98455e1e8fe3ef957b09f.png

在经过测试后 成功 修改参数 起飞

接入 Go otto 调用

在接入 Go 调用过程中发现一个问题,不知道是 golang otto 包的问题还是怎样,就是在 js 文件里面 如果有 require() 第三包的情况下 就会提示语法错误, 因为这个加密 js 文件中会调用 windows.atob 方法 所以需要借助第三方包 jsdom 来实现

e3f11aa960b458faba071f9d0e01a467.png

错误表现

33a3af41daa4bc3812d251d7c9a737a3.png

在经过冥思苦想不知道怎么解决的时候,去问了下团队里的爬虫老师傅,他说一般遇到这种情况就直接起一个 Node 的服务专门来处理这种解密的事就 OK 了,恍然大悟 原来如此

ServerLess

有点偷懒不想自己搞个服务来玩这个了,想直接用云厂商的试用版,看了一下 以前 用过的 LeanCloud 的 感觉不太行,后面选择了腾讯的 ServerLess 直接部署了一个服务器

https://service-denf06ck-1253616191.gz.apigw.tencentcs.com/release/secret/:content

替换掉上面的 content 就可以了,最后 Go 脚手架项目也完成了,项目地址:https://github.com/xiaoxiunique/go-cli/tree/master

投稿作者:胡川港

知乎主页:zhihu.com/people/hu-chuan-gang-58

GitHub主页:https://github.com/xiaoxiunique

#投 稿 通 道#

 让你的博客被更多人看到 

如果你在 CSDN、博客园、掘金等平台有写技术博客的习惯,想让自己的原创博客被更多人看到,可以来 Java后端 投稿。

Java后端 鼓励读者投稿个人技术博客、面试经验、教程。不管是入门的图文教程、还是热门技术讲解,只要你喜欢写东西,我们欢迎你来投稿。

📝 稿件基本要求:

• 文章确系个人原创作品,如果在其他非公众号渠道有过发表也可以,只要是个人原创即可。

• 稿件建议以 markdown 格式撰写,文中配图以附件形式发送,要求图片清晰、语句通顺。

• 如果被采纳的原创稿件,我们将提供稿费以及个人影响力曝光,具体依据文章阅读量和质量结算稿费。

📬 投稿通道:

• 投稿请联系下方微信,备注:原创投稿

feaf7466a1bbf51ee8166f8df42cedd1.png

△长按添加 Java后端 小编

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值