近期又更新了,先是改了x-s生成,然后又加上了a1校验。
后面可能会全参校验,比如再加上gid、deviceId、profileData、x-s-common、smidV2之类。
估计以后不能写xhs了,大家且看且珍惜吧。
X-s
还是先找x-s。全局搜关键词"X-s"
c = (a || void 0 !== window._webmsxyw ? window._webmsxyw : sign)(u, i) || {};
if a:
return a
elif "window._webmsxyw" in globals() and window._webmsxyw is not None:
return window._webmsxyw
else:
return sign
可发现feed接口没走sign,走的方法是在 window._webmsxyw = _garp_dc33b
可以测试下把 window._webmsxyw置为null是否能正常返回。
window._webmsxyw
跳转过去能发现代码混淆过了,并且自执行了,不便阅读。
Shanks
先看外部Shanks。
(function() {
function Shanks() {
var _garp_d6c76 = 2147483647
, _garp_a0c6d = 1
, _garp_33d28 = 0
, _garp_cb56b = !!_garp_a0c6d
, _garp_35e13 = !!_garp_33d28;
return function(_garp_d3e7c, _garp_d0ad1, _garp_8d381){}
;
}
;Shanks()(window, {});
}
)();
这段代码定义了Shanks函数,定义了一个闭包,返回一个匿名函数,并使用立即执行函数将其调用。
在调用Shanks函数时,它接受两个参数,分别是window和对象{}。
decode
不难看出 decode 是一段实现了 Base64 解码的方法,包括了将 Base64 编码的字符串解码为正常字符串的逻辑,以及处理 Unicode 字符的逻辑。
var decode = function(j) {
if (!j) {
return ""
}
var n = function(e) {
var f = []
, t = e.length;
var u = 0;
for (var u = 0; u < t; u++) {
var w = e.charCodeAt(u);
if (((w >> 7) & 255) == 0) {
f.push(e.charAt(u))
} else {
if (((w >> 5) & 255) == 6) {
var b = e.charCodeAt(++u);
var a = (w & 31) << 6;
var c = b & 63;
var v = a | c;
f.push(String.fromCharCode(v))
} else {
if (((w >> 4) & 255) == 14) {
var b = e.charCodeAt(++u);
var d = e.charCodeAt(++u);
var a = (w << 4) | ((b >> 2) & 15);
var c = ((b & 3) << 6) | (d & 63);
var v = ((a & 255) << 8) | c;
f.push(String.fromCharCode(v))
}
}
}
}
return f.join("")
};
var k = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");
var p = j.length;
var l = 0;
var m = [];
while (l < p) {
var s = k.indexOf(j.charAt(l++));
var r = k.indexOf(j.charAt(l++));
var q = k.indexOf(j.charAt(l++));
var o = k.indexOf(j.charAt(l++));
var i = (s << 2) | (r >> 4);
var h = ((r & 15) << 4) | (q >> 2);
var g = ((q & 3) << 6) | o;
m.push(String.fromCharCode(i));
if (q != 64) {
m.push(String.fromCharCode(h))
}
if (o != 64) {
m.push(String.fromCharCode(g))
}
}
return n(m.join(""))
_garp_dc33b
简单调了一下,大面积的自增运算,让人看不下去。
补环境
直接把上面代码copy到本地先补环境看看,看日志要补的东西挺多,具体怎么用的没细看。(后面没用的我会删掉)
RegExp、Math、indexedDB、eval、localStorage、isNaN、unescape、Array、Function、setInterval、encodeURIComponent、decodeURIComponent、CanvasRenderingContext2D、HTMLCanvasElement、navigator、location、String、Date、Object、screen、document、
还有追加的 openDatabase、devicePixelRatio、AudioContext、webkitAudioContext、setInterval、getAttribute、xhsFingerprintV3
主要都是window对象的方法和属性,有两处报错的地方,补好后try-catch一下。
另外输出中还看到了process,先不管这个。
等到报错:
补到这里就快了。
还是catch下,把_webmsxyw给到window上。
这里补好就可以生成了。
但是发现本地生成的和浏览器的差了很多位数,调试发现是由于cookie中没有xsecappid和a1参数。
把cookie加上后发现还是少了24位,再检查下发现本地生成的x-s中没有加 "signVersion":"1" 。
signVersion追加在localStorage中,默认是1。
简单一点,可以做类型判断然后给上值,注意signVersion的值是string类型。
if (_garp_a3d40 && typeof _garp_a3d40==="object" && _garp_a3d40.hasOwnProperty('signVersion')){
_garp_a3d40["signVersion"] = "1"
}
现在生成的位数就一致了,但是测了下还是不可用。
X-s生成流程
继续调试分析,看流程是先去生成X1,然后构建payload。
payload由x1、x2、x3、x4组成。x1是api+formdata md5生成的,x2固定,x3是a1,x4是时间戳。
拼接为:x1=72ff6a81a0474a774ad1db6681c9614a;x2=0|0|0|1|0|0|1|0|0|1|1|0|0|0|0;x3=188392ccdbbk41ajfrbba1ngli7wcccg88o04w12v50000303000;x4=1684603371781;
然后做一些编码和加密生成密文payload,payload生成后再跟几个参数拼起来。
拼接完进行base64编码,然后和XYW_拼起来生成了最终的X-s。
流程并不复杂,现在需要找到本地生成不可用的原因,排除了请求报文和生成环境的可能后,需要从payload加密的位置入手。
前面已经看了x2固定,x3是a1,x4是时间戳。大概率问题在x1上面。确定下md5结果是否和浏览器一样。
控制台覆盖JS文件,然后查看输出。
一看不太对,feed接口在本地的x1是7开头的,控制台x1是1开头的。
本地 Nodejs 和浏览器的 MD5 算法结果是相同的,那说明本地md5前的参数不对。
本地x1一直是72ff6a81a0474a774ad1db6681c96,浏览器是会根据不同作品ID变化的。
调试发现,本地x1参数生成时没有加formdata。
确认原因发现是我在调用的时候传了string类型的formdata,修改为对象就能解决问题。
window._webmsxyw("/api/sns/web/v1/feed",{"source_note_id":"62d4e8a800000000120019dd"})
改完发现还是不能用。。。继续调试,重新检查,结果发现本地x2和浏览器不一样,应该是有环境没补上,省时间就直接写死吧。
判断后赋值。
if (_garp_a3d40 && Array.isArray(_garp_a3d40)){
const lastElement = _garp_a3d40[_garp_a3d40.length - 3];
if (typeof lastElement === "object" && lastElement.hasOwnProperty("key")&& lastElement.key === "x2") {
_garp_a3d40[_garp_a3d40.length - 3].value = '0|0|0|1|0|0|1|0|0|0|1|0|0|0|0'
}
}
然后再将时间戳固定后,生成的x-s和浏览器一致。
坑
有插件检测和一些hook检测。注意还有个 crawler-spam ,用的异常点检测,影响调试。
如果断到这部分直接跳过,重试吧。
备注
注意请求时的a1要和xs生成时的a1一致,然后注意formdata参数格式的问题。
大家加油, 本文内容建立在feed接口上,gid和web_session注册不再说了,其他接口自行修改。
有问题欢迎留言,估计文章过几天又被下架了 。
另外,祝更新签名的你也好,O.o
Python请求测试
import requests,json
import execjs
def feed(source_note_id,web_session,a1):
headers= {
"accept":"application/json, text/plain, */*",
"accept-language":"zh-CN,zh;q=0.9",
"cache-control":"no-cache",
"content-type":"application/json;charset=UTF-8",
"cookie":f"a1={a1}; web_session={web_session};",
"origin":"https://www.xiaohongshu.com",
"pragma":"no-cache",
"referer":"https://www.xiaohongshu.com/",
"sec-ch-ua":"\"Chromium\";v=\"112\", \"Google Chrome\";v=\"112\", \"Not:A-Brand\";v=\"99\"",
"sec-ch-ua-mobile":"?0",
"sec-ch-ua-platform":"\"Windows\"",
"sec-fetch-dest":"empty",
"sec-fetch-mode":"cors",
"sec-fetch-site":"same-site",
"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
"x-b3-traceid":"a31fffc0ee4f5d8f",
"x-s-common":"",
}
data = {"source_note_id":source_note_id}
exc = execjs.compile(open('xs.js','r',encoding='utf-8').read())
xs_xt = exc.call('get_xs','/api/sns/web/v1/feed',data,a1)
xs_xt['X-t'] = str(xs_xt['X-t'])
print(xs_xt)
headers.update(xs_xt)
feed = 'https://edith.xiaohongshu.com/api/sns/web/v1/feed'
print(requests.post(url=feed, data=json.dumps(data, separators=(",", ":")), headers=headers).text)
if __name__ == '__main__':
source_note_id = "646840370000000027028120"
web_session = "030037a35bebc65742dcfef910234ae23a23fc"
a1 = "188392ccdbbk41ajfrbba1ngli7wcccg88o04w12v50000303000"
feed(source_note_id,web_session,a1)
JS代码部分
【温馨提示:此处隐藏内容需要付费订阅后才能查看!】