知乎APP协议分析--签名分析
package com.zhihu.crawl.device.register;
import com.alibaba.fastjson.JSONObject;
import com.zhihu.crawl.utils.HttpTools;
import com.zhihu.crawl.utils.HttpsTools;
import java.net.URLEncoder;
import java.util.Map;
import java.util.TreeMap;
/**
* @project: zhihuDemo
* @package: com.zhihu.crawl.device.register
* @Description: 注册设备, 获取设备ID
* @Since: 1.8.0_111
* @Auther MyPC, QuJianJun
* @Email: 8577352@qq.com
* @Date: 2021-01-09 01:36:21
*/
public class RegisterDevice {
static String ENCRYPT_API_URL = "http://127.0.0.1:7788/?type=zhihu";
static String ZHIHU_DEVICE_REGISTER_URL = "https://appcloud.zhihu.com/v1/device";
public static RegisterDevice get(){
return new RegisterDevice();
}
/**
* 注册设备,获取DID
* @return
*/
public static String getUdid(Map<String,String> map){
String time = String.valueOf(System.currentTimeMillis() / 1000);
ENCRYPT_API_URL += String.format("&t=%s",time);
Map<String,String> headerMap = new TreeMap<>();
headerMap.put("Content-Type","application/json;charset=UTF-8");
String res = HttpTools.sendPost(ENCRYPT_API_URL, get().mapToParam(map), headerMap);
int code = JSONObject.parseObject(res).getInteger("code");
if (code == 200){
//获取签名成功,
String sign = JSONObject.parseObject(res).getString("data");
// System.out.println("[*] sign: " + sign);
//请求 知乎服务器获取设备ID udid
headerMap.remove("Content-Type");
headerMap.put("user-agent","Mozilla/5.0 (Linux; Android 10; M2003J15SC Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36");
headerMap.put("x-req-signature",sign);
headerMap.put("content-type","application/x-www-form-urlencoded");
headerMap.put("accept-encoding","gzip");
headerMap.put("x-sign-version","2");
headerMap.put("x-req-ts",time);
headerMap.put("x-app-id","1355");
res = HttpsTools.Getcode(ZHIHU_DEVICE_REGISTER_URL,"POST","UTF-8",get().mapToParam(map),headerMap);
String udid = JSONObject.parseObject(res).getString("udid");
return udid;
}else{
return "-1";
}
}
/**
* map转url参数
* @param map
* @return
*/
public String mapToParam(Map<String,String> map){
StringBuilder sb = new StringBuilder();
for(Map.Entry entry: map.entrySet()){
String key = entry.getKey().toString();
//特殊原因 Url编码后 空格会变成加号服务器接收到后就丢失了所以这里编码后替换成%20,
sb.append(key + "=" + URLEncoder.encode(map.get(key)).replace("+","%20") + "&");
}
return sb.substring(0,sb.toString().length()-1);
}
}
0x010. udid已经可以成功从服务器获取到,接下来看一下搜索接口,搜索请求中的t参数是搜索类型(视频 t=zvideo ,综合搜索 t=general, 搜索用户 t=people, 搜索话题 t=topic, 电子书 t=publication, 其他类型自行抓包)
0x011. 把curl命令行复制出来,丢到pyCharm里面的http文件粘贴执行一下,经过测试没被注释掉的参数 都是不可以丢失的
0x012.老规矩直接找头部的key然后看看搜索的到不(这次好像失败了,搜索大法失效了.),所有的字段我都搜索的但是没找到。
0x013.冥想5分钟(我的第一想法是在so内做的,我hook了网络请求函数打印堆栈信息,但是一点结果没有),那是不是有可能在网页里做的操作呢?抱着试试看的想法,重新找一下请求的数据包,还真发现了
0x014. 看到这个关键字应该是搜索的索引页,把html代码复制出来,在桌面新建一个html页面然后浏览器打开,看控制台,他都做了些啥...
0x015. 还是老样子,准备发起请求前给断点,然后一步一步调试...(我们现在已有的参数 udid, app-version, api-version, x-hd),所以我们只需要把x-zse-86的值给调出来就可以了.
0x016. 打好断点后,刷新网页.一直往下跟,直到出现我们需要的参数
0x017. 为了快一点,我这里直接找这个字符串,在他加载的js里面挨个搜索,我在inxta.a*********文件里找到了这个参数来源,然后断点直接断在了参数生成之前,刷新网页重新加载到断点处(要记得把右边浏览器之前勾选的地方给去掉)
0x018. 找到加密前的拼接参数
0x019. 拼接的参数(4_2.0+/udid+5.17.1+8e9b8c23bb7fe86fed551c8322fda1ae+ALDQSZbyeBJLBZch-SAhAMuR-UHW60jSkC4=) 我们之前做了一个截图,用python发送http请求的时候数据的格式应该是这样的(x-zse-83+要请求地址的url部分不包含域名+x-hd+udid)这个格式我们先记下来,继续跟
0x020. 它这里还没走完,还得继续往下跟,因为已知x-zse-86的值格式不是这样子的,再继续跟
0x021. 得到最终返回值,我们直接把它的js代码算法复制出来.写在一个html里面做验证.控制台直接调用b函数传参
var A = "2.0"
, __g = {};
function s() {}
function i(t) {
this.t = (2048 & t) >> 11,
this.s = (1536 & t) >> 9,
this.i = 511 & t,
this.h = 511 & t
}
function h(t) {
this.s = (3072 & t) >> 10,
this.h = 1023 & t
}
function a(t) {
this.a = (3072 & t) >> 10,
this.c = (768 & t) >> 8,
this.n = (192 & t) >> 6,
this.t = 63 & t
}
function c(t) {
this.s = t >> 10 & 3,
this.i = 1023 & t
}
function n() {}
function e(t) {
this.a = (3072 & t) >> 10,
this.c = (768 & t) >> 8,
this.n = (192 & t) >> 6,
this.t = 63 & t
}
function o(t) {
this.h = (4095 & t) >> 2,
this.t = 3 & t
}
function r(t) {
this.s = t >> 10 & 3,
this.i = t >> 2 & 255,
this.t = 3 & t
}
s.prototype.e = function(t) {
t.o = !1
}
,
i.prototype.e = function(t) {
switch (this.t) {
case 0:
t.r[this.s] = this.i;
break;
case 1:
t.r[this.s] = t.k[this.h]
}
}
,
h.prototype.e = function(t) {
t.k[this.h] = t.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(t) {
t.Q.push(t.C),
t.B.push(t.k),
t.C = t.r[this.s],
t.k = [];
for (var e = 0; e < this.i; e++)
t.k.unshift(t.f.pop());
t.g.push(t.f),
t.f = []
}
,
n.prototype.e = function(t) {
t.C = t.Q.pop(),
t.k = t.B.pop(),
t.f = t.g.pop()
}
,
e.prototype.e = function(t) {
switch (this.t) {
case 0:
t.u = t.r[this.a] >= t.r[this.c];
break;
case 1:
t.u = t.r[this.a] <= t.r[this.c];
break;
case 2:
t.u = t.r[this.a] > t.r[this.c];
break;
case 3:
t.u = t.r[this.a] < t.r[this.c];
break;
case 4:
t.u = t.r[this.a] == t.r[this.c];
break;
case 5:
t.u = t.r[this.a] != t.r[this.c];
break;
case 6:
t.u = t.r[this.a];
break;
case 7:
t.u = !t.r[this.a]
}
}
,
o.prototype.e = function(t) {
switch (this.t) {
case 0:
t.C = this.h;
break;
case 1:
t.u && (t.C = this.h);
break;
case 2:
t.u || (t.C = this.h);
break;
case 3:
t.C = this.h,
t.w = null
}
t.u = !1
}
,
r.prototype.e = function(t) {
switch (this.t) {
case 0:
for (var e = [], n = 0; n < this.i; n++)
e.unshift(t.f.pop());
t.r[3] = t.r[this.s](e[0], e[1]);
break;
case 1:
for (var r = t.f.pop(), o = [], i = 0; i < this.i; i++)
o.unshift(t.f.pop());
t.r[3] = t.r[this.s][r](o[0], o[1]);
break;
case 2:
for (var a = [], c = 0; c < this.i; c++)
a.unshift(t.f.pop());
t.r[3] = new t.r[this.s](a[0],a[1])
}
}
;
var k = function(t) {
for (var e = 66, n = [], r = 0; r < t.length; r++) {
var o = 24 ^ t.charCodeAt(r) ^ e;
n.push(String.fromCharCode(o)),
e = o
}
return n.join("")
};
function Q(t) {
this.t = (4095 & t) >> 10,
this.s = (1023 & t) >> 8,
this.i = 1023 & t,
this.h = 63 & t
}
function C(t) {
this.t = (4095 & t) >> 10,
this.a = (1023 & t) >> 8,
this.c = (255 & t) >> 6
}
function B(t) {
this.s = (3072 & t) >> 10,
this.h = 1023 & t
}
function f(t) {
this.h = 4095 & t
}
function g(t) {
this.s = (3072 & t) >> 10
}
function u(t) {
this.h = 4095 & t
}
function w(t) {
this.t = (3840 & t) >> 8,
this.s = (192 & t) >> 6,
this.i = 63 & t
}
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(t) {
switch (this.t) {
case 0:
t.f.push(t.r[this.s]);
break;
case 1:
t.f.push(this.i);
break;
case 2:
t.f.push(t.k[this.h]);
break;
case 3:
t.f.push(k(t.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(t) {
t.r[this.s] = k(t.b[this.h])
}
,
f.prototype.e = function(t) {
t.w = this.h
}
,
g.prototype.e = function(t) {
throw t.r[this.s]
}
,
u.prototype.e = function(t) {
var e = this
, n = [0];
t.k.forEach(function(t) {
n.push(t)
});
var r = function(r) {
var o = new G;
return o.k = n,
o.k[0] = r,
o.v(t.G, e.h, t.b, t.F),
o.r[3]
};
r.toString = function() {
return "() { [native code] }"
}
,
t.r[3] = r
}
,
w.prototype.e = function(t) {
switch (this.t) {
case 0:
for (var e = {}, n = 0; n < this.i; n++) {
var r = t.f.pop();
e[t.f.pop()] = r
}
t.r[this.s] = e;
break;
case 1:
for (var o = [], i = 0; i < this.i; i++)
o.unshift(t.f.pop());
t.r[this.s] = o
}
}
,
G.prototype.D = function(t) {
for (var e = atob(t), n = e.charCodeAt(0) << 8 | e.charCodeAt(1), r = [], o = 2; o < n + 2; o += 2)
r.push(e.charCodeAt(o) << 8 | e.charCodeAt(o + 1));
this.G = r;
for (var i = [], a = n + 2; a < e.length; ) {
var c = e.charCodeAt(a) << 8 | e.charCodeAt(a + 1)
, u = e.slice(a + 2, a + 2 + c);
i.push(u),
a += c + 2
}
this.b = i
}
,
G.prototype.v = function(t, e, n) {
for (e = e || 0,
n = n || [],
this.C = e,
"string" == typeof t ? this.D(t) : (this.G = t,
this.b = n),
this.o = !0,
this.R = Date.now(); this.o; ) {
var r = this.G[this.C++];
if ("number" != typeof r)
break;
var o = Date.now();
if (500 < o - this.R)
return;
this.R = o;
try {
this.e(r)
} catch (t) {
this.U = t,
this.w && (this.C = this.w)
}
}
}
,
G.prototype.e = function(t) {
var e = (61440 & t) >> 12;
new this.J[e](t).e(this)
}
,
"undefined" != typeof window && (new G).v("AxjgB5MAnACoAJwBpAAAABAAIAKcAqgAMAq0AzRJZAZwUpwCqACQACACGAKcBKAAIAOcBagAIAQYAjAUGgKcBqFAuAc5hTSHZAZwqrAIGgA0QJEAJAAYAzAUGgOcCaFANRQ0R2QGcOKwChoANECRACQAsAuQABgDnAmgAJwMgAGcDYwFEAAzBmAGcSqwDhoANECRACQAGAKcD6AAGgKcEKFANEcYApwRoAAxB2AGcXKwEhoANECRACQAGAKcE6AAGgKcFKFANEdkBnGqsBUaADRAkQAkABgCnBagAGAGcdKwFxoANECRACQAGAKcGKAAYAZx+rAZGgA0QJEAJAAYA5waoABgBnIisBsaADRAkQAkABgCnBygABoCnB2hQDRHZAZyWrAeGgA0QJEAJAAYBJwfoAAwFGAGcoawIBoANECRACQAGAOQALAJkAAYBJwfgAlsBnK+sCEaADRAkQAkABgDkACwGpAAGAScH4AJbAZy9rAiGgA0QJEAJACwI5AAGAScH6AAkACcJKgAnCWgAJwmoACcJ4AFnA2MBRAAMw5gBnNasCgaADRAkQAkABgBEio0R5EAJAGwKSAFGACcKqAAEgM0RCQGGAYSATRFZAZzshgAtCs0QCQAGAYSAjRFZAZz1hgAtCw0QCQAEAAgB7AtIAgYAJwqoAASATRBJAkYCRIANEZkBnYqEAgaBxQBOYAoBxQEOYQ0giQKGAmQABgAnC6ABRgBGgo0UhD/MQ8zECALEAgaBxQBOYAoBxQEOYQ0gpEAJAoYARoKNFIQ/zEPkAAgChgLGgkUATmBkgAaAJwuhAUaCjdQFAg5kTSTJAsQCBoHFAE5gCgHFAQ5hDSCkQAkChgBGgo0UhD/MQ+QACAKGAsaCRQCOYGSABoAnC6EBRoKN1AUEDmRNJMkCxgFGgsUPzmPkgAaCJwvhAU0wCQFGAUaCxQGOZISPzZPkQAaCJwvhAU0wCQFGAUaCxQMOZISPzZPkQAaCJwvhAU0wCQFGAUaCxQSOZISPzZPkQAaCJwvhAU0wCQFGAkSAzRBJAlz/B4FUAAAAwUYIAAIBSITFQkTERwABi0GHxITAAAJLwMSGRsXHxMZAAk0Fw8HFh4NAwUABhU1EBceDwAENBcUEAAGNBkTGRcBAAFKAAkvHg4PKz4aEwIAAUsACDIVHB0QEQ4YAAsuAzs7AAoPKToKDgAHMx8SGQUvMQABSAALORoVGCQgERcCAxoACAU3ABEXAgMaAAsFGDcAERcCAxoUCgABSQAGOA8LGBsPAAYYLwsYGw8AAU4ABD8QHAUAAU8ABSkbCQ4BAAFMAAktCh8eDgMHCw8AAU0ADT4TGjQsGQMaFA0FHhkAFz4TGjQsGQMaFA0FHhk1NBkCHgUbGBEPAAFCABg9GgkjIAEmOgUHDQ8eFSU5DggJAwEcAwUAAUMAAUAAAUEADQEtFw0FBwtdWxQTGSAACBwrAxUPBR4ZAAkqGgUDAwMVEQ0ACC4DJD8eAx8RAAQ5GhUYAAFGAAAABjYRExELBAACWhgAAVoAQAg/PTw0NxcQPCQ5C3JZEBs9fkcnDRcUAXZia0Q4EhQgXHojMBY3MWVCNT0uDhMXcGQ7AUFPHigkQUwQFkhaAkEACjkTEQspNBMZPC0ABjkTEQsrLQ==");
var b = function(t) {
return __g._encrypt(encodeURIComponent(t))
};
0x022. 验证结果如下图(是验证成功的,所以只要把JavaScript代码用java或者python直接调用就行了),值得注意的是js内有window对象的调用,所以在用java或者python调用的时候要支持window等浏览器内置对象,java默认的js执行是不行的,无法调用起来,所以我这里采用的是 htmlunit-driver 这个三方包,maven的pom配置文件新增以下jar包配置即可.
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>htmlunit-driver</artifactId>
<version>2.43.1</version>
</dependency>
0x023. 下面是java代码请求我自己写的html,然后html引入这个js,通过三方的jar包进行访问并且传参,获取html页面的body内容即可
HtmlUnitDriver webDriver = new HtmlUnitDriver();
webDriver.setJavascriptEnabled(true);
String url = this.getClass().getResource("/encrypt.html").toString()+"?str="+strMd5;
webDriver.get(url);
WebElement webElement = webDriver.findElement(By.xpath("/html/body"));
String sig = webElement.getText();
System.out.println("x_zse_86 签名为:" + "2.0_"+sig);
webDriver.close();
return "2.0_"+sig;
0x024. 在js的尾部增加如下代码,window.onload() 方法用于在网页加载完毕后立刻执行的操作,即当 HTML 文档加载完毕后,立刻执行某个方法,通过js获取参数,然后传入b函数,得到返回值 赋值给body,给了之后,java代码通过xpath取body的值,这样就可以拿到返回值了
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
window.onload = function (){
const param = getQueryVariable("str");
const str = b(param)
const body = document.getElementById("body");
body.innerText = str;
}
0x025. 生成udid和x-zse-86参数使用python发送curl请求测试是否可以成功.(获得返回数据后,可以请求video_url地址,正则匹配页面内的mp4下载地址,即可下载视频.)
0x026. 测试代码并解析返回数据.
0x027. 如果需要写好的js文件,请付费下载,付费后可以提供完整项目代码
温馨提示:登录付款后可永久阅读隐藏内容。
付费可读