js逆向xhs

尽意
2024-08-11 / 2 评论 / 204 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2024年08月11日,已超过164天没有更新,若内容或图片失效,请留言反馈。

首先需要先确定目标接口

打开开发者工具选择网络

lzpf96ne.png
网络请求比较多,不太好定位,先Ctrl+L清空一下网络日志
lzpfccp7.png
随便点击一个标签,从上往下看可以看到 hoomfeed 的响应是有我们要的数据
lzpfexd2.png
可以多刷新几次,看一下请求头里面的参数哪些是固定的

然后右键复制,以curl bash格式
到这个网站生成一下请求代码测试一下能不能跑通
https://curlconverter.com/

我使用的okhttp
不过有一点需要注意,请求体的json字符串不能带有空格,还是要注意一下,一般还是要看下负载源代码给格式,保持一致就没问题了
lzpfpexy.png


OkHttpClient client = new OkHttpClient();
String jsonBody = "{\"cursor_score\":\"\",\"num\":18,\"refresh_type\":1,\"note_index\":32,\"unread_begin_note_id\":\"\",\"unread_end_note_id\":\"\",\"unread_note_count\":0,\"category\":\"homefeed.fashion_v3\",\"search_key\":\"\",\"need_num\":8,\"image_formats\":[\"jpg\",\"webp\",\"avif\"],\"need_filter_image\":false}";

// 注意这里是json格式
RequestBody requestBody = RequestBody.create(jsonBody,MediaType.parse("application/json; charset=utf-8"))

Request request = new Request.Builder()
        .url("https://edith.xiaohongshu.com/api/sns/web/v1/homefeed")
        .post(requestBody)
        .header("authority", "edith.xiaohongshu.com")
        .header("accept", "application/json, text/plain, */*")
        .header("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
        .header("cache-control", "no-cache")
        .header("content-type", "application/json;charset=UTF-8")
        .header("cookie",
                "abRequestId=e3bdcd27-e1b5-5f53-89b5-7e2802d396e0; xsecappid=xhs-pc-web; a1=1913b4ae942vlubadspzq6n0dzv1sn1s16vme85li50000892240; webId=f51031180df1b91abeef5db1d71629e1; web_session=030037a184491809ffa8205bcf214a315d86ed; gid=yjyqD40iYWyfyjyqD40djKY24Jh17D0fMUuAh13KI8fuh628yMIyMd888YjJJ488KD82yj0y; webBuild=4.28.5; unread={%22ub%22:%2266986e9b0000000003027356%22%2C%22ue%22:%226699c440000000000d00fd0f%22%2C%22uc%22:23}; acw_tc=7c07ab06520fd63e04c63f0273004a3337c1cd30cc32b06c921ddff420388b1e; websectiga=3fff3a6f9f07284b62c0f2ebf91a3b10193175c06e4f71492b60e056edcdebb2; sec_poison_id=8ef6b86b-8ad1-4aa5-9ec4-e3309f2368b3"
        )
        .header("origin", "https://www.xiaohongshu.com")
        .header("pragma", "no-cache")
        .header("referer", "https://www.xiaohongshu.com/")
        .header("sec-ch-ua", "\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Microsoft Edge\";v=\"122\"")
        .header("sec-ch-ua-mobile", "?0")
        .header("sec-ch-ua-platform", "\"Windows\"")
        .header("sec-fetch-dest", "empty")
        .header("sec-fetch-mode", "cors")
        .header("sec-fetch-site", "same-site")
        .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0")
        .header("x-b3-traceid", "1c3f0fbf2fac047b")
        .header("x-s",
                "XYW_eyJzaWduU3ZuIjoiNTMiLCJzaWduVHlwZSI6IngyIiwiYXBwSWQiOiJ4aHMtcGMtd2ViIiwic2lnblZlcnNpb24iOiIxIiwicGF5bG9hZCI6ImQ3ZTU0NWYyNTMzM2I2MWYxMjFlMmJmZGI4ZThhMGZkMDg2Mzg0NWY2ZTU3MzEyM2IzN2JmMjIyZDU5MGE4ZDg2NjQ3ODEyMzBkYWU5ODVjMjMzYzdiNDUxNjBkYmQ3NDdkZWY5ZDBmZDBhNWQ0YzAzMzE3OGRlNGYyYmQ3YTgyNGNkZTYxZDgzNDMxNmQ3OWQ1NTFjY2Q5YjJlYTEyZWM2YWNlMjM3ZjVjZjQzMDllZTg0OGU5NTQwZGEwY2NhN2IyZWJlNjMzODdhOGE3ZTAwNDM2Yzk2NmI5OWM4MWUzM2IxNDMyYjk0MDEyOGY3ZjcwMWU4Yjc2MTYyOTFiZDBmN2UxNDYxNWFhZjJiZDExMDBiYWRiNWM5NTFiZjc2N2M1ZWM5NDkxYWJiOTUzOGYwMzc5ZTY1YzgzOGQ1ZGVjODdkMjZlZmQzM2M5OGY0NGVlMjQzODlmNDE4YjUyNWY4ZmEyYzE0YjU4ZmZkOWZhMmYxZTcwMzhiNjQ1YTVlODhhOTBlYTRlYjRiZmM1NGJiMWIyODlkY2ExNDExYTk5In0="
        )
        .header("x-s-common", "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")
        .header("x-t", "1723275130800")
        .build();

try (Response response = client.newCall(request).execute()) {
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    def res = response.body().string();
    println res
}

直接运行是没有问题的,然后就要看哪些参数是会影响请求的结果

在请求头可以看到像 x-s x-s-common 等这一类的参数明显就是加密过的,而且一般情况也会作为必要参数,x-t很明显是个时间戳
可以试一下将这几个参数注释掉运行,发现只有x-s的值不正确或者没有的情况下是拿不到请求结果的,现在也就明确了要找x-s的加密函数

可以跟栈去找加密函数,找到在那两个函数或者文件内容由明文变成密文,或者直接搜索关键字
勾选上正则匹配,只有7个匹配项
lzpg1xhn.png
可以一个个都看一下,不放心的话也可以都先打上断点,

lzpg4gs7.png
明显可以看到只有这里才有赋值语句,那就在这打上断点,看到调用了l

(a && void 0 !== window._webmsxyw ? window._webmsxyw : encrypt_sign)(c, i) || {};

三元表达式调用了window._webmsxyw函数,传参c,i
在控制台调用这个函数
lzpga9cs.png
现在加密函数已经差不多确定了

lzpgbf0a.png
进入到函数内部查看
lzpgbyo0.png
经过了大量混淆,这段代码想要扣下来还是比较麻烦的,基本上是没有一点可读性
那就直接整个文件都拿下来,补环境,只要能跑通里面的window._webmsxyw就能拿到加密的值了

新建一个包xhs,将复制下来的源代码粘贴到course.js文件中,为了更好的解耦,我们准备三个js文件,course存放源代码,env作为补环境的代码,xhs则写我们输入加密函数的代码跟逻辑
然后在xhs文件里面引入
require("./env")
require("./sourse")
注意一下要先引入环境,不然sourse会因为没有环境报错
env使用代理看到哪些函数方法被调用了,却少什么补充什么就行了


function setProxy(proxyObjs) {
    for (let i = 0; i < proxyObjs.length; i++) {
        const handler = `{
          get: function(target, property, receiver) {
          if (property!="Math" && property!="isNaN"){
             if (target[property] && typeof target[property] !="string" &&  Object.keys(target[property]).length>3){
              }else{
            console.log("方法:", "get  ", "对象:", "${proxyObjs[i]}", "  属性:", property, "  属性类型:", typeof property, ", 属性值:", target[property]);}}
            return target[property];
          },
          set: function(target, property, value, receiver) {
            console.log("方法:", "set  ", "对象:", "${proxyObjs[i]}", "  属性:", property, "  属性类型:", typeof property, ", 属性值:", value, ", 属性值类型:", typeof target[property]);
            return Reflect.set(...arguments);
          }
        }`;
        eval(`try {
            ${proxyObjs[i]};
            ${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
        } catch (e) {
            ${proxyObjs[i]} = {};
            ${proxyObjs[i]} = new Proxy(${proxyObjs[i]}, ${handler});
        }`);
    }
}

setProxy(['window', 'document', ' navigator', 'screen', 'localStorage', 'location'])

补环境的代码

env.js

// 补环境 window
window = global
delete global
delete Buffer

// document
document = {
    createElement: function(x){
        if (x === "canvas"){
            return {
                getContext: function(){}
            }
        }
    },
    documentElement:{},
    cookie:'abRequestId=e3bdcd27-e1b5-5f53-89b5-7e2802d396e0; xsecappid=xhs-pc-web; webId=f51031180df1b91abeef5db1d71629e1; web_session=030037a184491809ffa8205bcf214a315d86ed; gid=yjyqD40iYWyfyjyqD40djKY24Jh17D0fMUuAh13KI8fuh628yMIyMd888YjJJ488KD82yj0y; webBuild=4.28.5; unread={%22ub%22:%2266986e9b0000000003027356%22%2C%22ue%22:%226699c440000000000d00fd0f%22%2C%22uc%22:23}; acw_tc=7c07ab06520fd63e04c63f0273004a3337c1cd30cc32b06c921ddff420388b1e; websectiga= sec_poison_id=8ef6b86b-8ad1-4aa5-9ec4-e3309f2368b3'
}


// localStorage
localStorage = {
    getItem: function (){
        // return '{"signVersion":"1"}'
        return '{"validate":true,"commonPatch":["/fe_api/burdock/v2/note/post","/api/sns/web/v1/comment/post","/api/sns/web/v1/note/like","/api/sns/web/v1/note/collect","/api/sns/web/v1/user/follow","/api/sns/web/v1/feed","/api/sns/web/v1/login/activate","/api/sns/web/v1/note/metrics_report","/api/redcaptcha","/api/store/jpd/main","/phoenix/api/strategy/getAppStrategy"],"signUrl":"https://fe-video-qc.xhscdn.com/fe-platform/bccb34c4f2976c51b565494c0a760c42d962b25b.js","signVersion":"1","url":"https://fe-video-qc.xhscdn.com/fe-platform/a1c872577b980b890b0850cef89371b35125649a.js","reportUrl":"/api/sec/v1/shield/webprofile","desVersion":"2"}'
    }

}

// navigator
navigator = {
    appCodeName: "Mozilla",
    appName: "Netscape",
    appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36'
}

xhs.js

require("./env")
require("./sourse")

const c = '/api/sns/web/v1/homefeed'
const i = {
    "cursor_score": "",
    "num": 18,
    "refresh_type": 1,
    "note_index": 32,
    "unread_begin_note_id": "",
    "unread_end_note_id": "",
    "unread_note_count": 0,
    "category": "homefeed.fashion_v3",
    "search_key": "",
    "need_num": 8,
    "image_formats": [
        "jpg",
        "webp",
        "avif"
    ],
    "need_filter_image": false
}
function getX(){
    return window._webmsxyw(c,i)
}

console.log(getX()['X-s'])

至此加密函数补环境就结束了

不过还是有点小坑
localStorage.getItem 直接返回一个空函数是会诱导生成错误的数据,我一开始就是返回的空函数,然后验证发现加密值是错的,因为使用了代理,可以看到最近的函数就是localStorage.getItem
lzpgvs1u.png

可以通过hook getItem函数拿到传参是多少

localStorage.getItem_ = localStorage.getItem;localStorage.getItem = function(x){console.log(x);localStorage.getItem_(x);}

再执行window._webmsxyw函数就能看到getItem传参
lzph020l.png

有了参数,直接在控制台 localStorage.getItem("sdt_source_storage_key")拿到参数即可,现在就能正确拿到正确的加密值了

4

评论 (2)

取消
  1. 头像
    kk
    Windows 10 · Google Chrome

    如果我想在自己的项目里调小红书的接口 获取到返回来的数据,前端应该怎么做啊

    回复
    1. 头像
      尽意 作者
      Android · Google Chrome
      @ kk

      现在xhs算法已经改了,要在前端调用可以通过rpc,提供一个简单的思路,通过socket将数据传输给前端,前端通过调用rpc的接口拿到数据。或者也可以将后端程序封装成http的接口,前端用axios请求接口拿到数据。

      回复