从 https://www.zhihu.com/people/weizhi-xiazhi
开始的请求分析
最终代码补全后的结果
最终目的:获取到关注了该用户的其他用户的知乎个人资料(用户名,性别,教育经历等)
- 在浏览器打开该页面。点击该用户的关注订阅。点击【关注ta的人】。在控制台中找到
https://www.zhihu.com/api/v4/members/weizhi-xiazhi/followers
接口,该接口是用来查询谁关注了当前用户的。通过修改用户id发现该接口必须要携带正确的header才行。否则会报错{"error":{"message":"请求参数异常,请升级客户端后重试。","code":10003}}
将该接口copy成fetch如下
fetch("https://www.zhihu.com/api/v4/members/weizhi-xiazhi/followers?include=data%5B*%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&offset=0&limit=20", {
"headers": {
"x-zse-93": "101_3_3.0",
"x-zse-96": "2.0_wyIkKa9pm25sNcIeX1mgLDycfir9/zpLRHQM8UrsKCQBbtgy6ken7OG5AuYi8naX"
},
});
发现里面有两个特殊的请求头,通过在页面切换不同分页后可以简单看出x-zse-93是固定的,x-zse-96是动态的,从上面x-zse-96
的内容长度可推测该参数仅仅只是对请求url仅仅加密后生成的一串token。不可能携带得有浏览器环境信息(因为长度实在太短了)。
分析x-zse-96如何生成
在源代码中全局搜“x-zse-96”。下图中标记的标记2.并在该处打一个断点。然后不停使用标记3退出当前函数。
直到右侧出现te的接口为/api/v4/members/weizhi-xiazhi/followers。
从这里可以得知这里就是设置请求头的地方。通过观察可明确知道关键逻辑应该在这一段代码内部。打上断点进入该方法
tT = ed(te, tf.body, {
zse93: tb,
dc0: tO,
xZst81: tE
}, tA)
多次尝试后可以得出结论。该加密方法是将请求路径经过一个算法生成了一串key。关键逻辑就在下图蓝色框框内。只要扣出对应代码即可
如何扣代码?
1、先把ty扣出来,要注意这个方法不带任何参数且需要返回另一个函数用来加密路径。
往上搜发现ty如下。像这种带有数字的是webpack打包时模块加载逻辑。而ty应该就是10261这个模块。直接去搜
tg = tr(10261), ty = tr.n(tg)
搜索结果如下。ty本身明显就是这一段蓝框代码。这看样子应该是一段md5算法。不用管他。先走到结尾。
ty的结尾如下
void 0 !== (ti = (function() {
return tT
}
).call(te, tr, te, tt)) && (tt.exports = ti)
从这几行代码可只ty应该就是返回了tT的。将ty处理成下面的形式
function ty(){
var tu = function(tt, te) {
var tr = (65535 & tt) + (65535 & te);
return (tt >> 16) + (te >> 16) + (tr >> 16) << 16 | 65535 & tr
}
//中间还有很多代码这里不展开了。具体代码可查看最终成果
,tT = function(tt, te, tr) {
return te ? tr ? tS(te, tt) : tO(te, tt) : tr ? tC(tt) : tE(tt)
};
return tT
}
2、tJ代码的提取.
进入到tJ后如下。通过在该方法打几次断点可知道,该方法的tt参数始终为undefined。那么最为关键的就是这个方法内部的tv如何弄了。
var tJ = function(tt) {
return tt && tt.version && "function" == typeof tt.encrypt ? tt : {
encrypt: tv.ZP,
version: tv.XL
}
}
3、tJ内部的tv方法提取
根据tv后发现tv如下,tv是1514这个模块
tp = tr(4667), th = tr.n(tp), tv = tr(1514)
在1514这个模块底部发现了上面第2步中的ZP和XL。那应该tv方法就是这没错了。
注意该段代码中使用了74185这个模块,这个模块很简单就不详细写了。就是个类似typeof的工具函数。
如何补全环境
经过上面的步骤关键代码已经扣好了,具体代码可查看
因为代码最后是要在selenium这种无头浏览器驱动中运行的。所以还需要补环境来骗过代码中环境检测逻辑。如果不补环境无法很好与其他脚本集成。补环境的前提是知道代码中使用了哪些全局变量。在js中,fromCharCode用来将UTF-16 码转为字符串。这是任何混淆方法都壁不开的一个api。我们在上面扣好的代码中找到使用了fromCharCode的地方并自己处理下,
类似下面这样。打印下看看都是啥
结果是这样的
可以看出有几个比较关键的对象
- window
- navigator
- location
- history
针对这几个对象使用Proxy看看都访问了什么属性,并把值替换下即可
let navigator = new Proxy(window.navigator, {
get(t, k) {
if (k === 'userAgent') {
return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
}
if (k === 'webdriver') {
return false
}
return t[k]
},
set(t, k, v) {
return (t[k] = v)
}
})
这样就完成了环境补全。实测下来还根据location来判断当前html是否是通过http/s协议打开。所以我们的html文件需要启动一个服务后访问才行。这样就算完成了