【JavaScript 逆向】今日头条 jsvmp,_signature 参数分析

前言

现在一些网站对 JavaScript 代码采取了一定的保护措施,比如变量名混淆、执行逻辑混淆、反调试、核心逻辑加密等,有的还对数据接口进行了加密,这次案例是通过补环境过 jsvmp。

声明

本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!

案例分析

目标网址:

aHR0cHM6Ly93d3cudG91dGlhby5jb20v

数据接口:

aHR0cHM6Ly93d3cudG91dGlhby5jb20vYXBpL3BjL2xpc3QvZmVlZD9jaGFubmVsX2lkPTA=

以上均做了脱敏处理,Base64 编码及解码方式:

import base64
# 编码
# result = base64.b64encode('待编码字符串'.encode('utf-8'))
# 解码
result = base64.b64decode('待解码字符串'.encode('utf-8'))
print(result)

常规 JavaScript 逆向思路

一般情况下,JavaScript 逆向分为三步:

  • 寻找入口:逆向在大部分情况下就是找一些加密参数到底是怎么来的,关键逻辑可能写在某个关键的方法或者隐藏在某个关键的变量里,一个网站可能加载了很多 JavaScript 文件,如何从这么多的 JavaScript 文件的代码行中找到关键的位置,很重要;
  • 调试分析:找到入口后,我们定位到某个参数可能是在某个方法中执行的了,那么里面的逻辑是怎么样的,调用了多少加密算法,经过了多少赋值变换,需要把整体思路整理清楚,以便于断点或反混淆工具等进行调试分析;
  • 模拟执行:经过调试分析后,差不多弄清了逻辑,就需要对加密过程进行逻辑复现,以拿到最后我们想要的数据

接下来开始正式进行案例分析:

寻找入口

进入到头条首页,能看到一些推荐新闻,右键查看网页源代码,ctrl + f 能搜到这部分内容,证明在 HTML 页面的原始文档中,所以一开始的这些新闻标题加 url 是能直接通过 requests + xpath 获取到的:

但是接着往下滑,页面在未刷新的情况下加载出了新的新闻, 且在网页源代码中搜索不到这些标题,证明这些新闻是同过 ajax 加载的,需要先通过抓包到数据接口再进行分析,F12 打开开发者人员工具,切换到 network 查看网络抓包请求,ajax 加载有特殊的请求类型 xhr,筛选栏中选择 xhr 寻找接口,接口为 feed?channel_id=XXX:

请求链接中有个加密参数 _signature:

逆向分析

通过 Fiddler 对其进行 Hook,当然也可以直接 ctrl + shift + f 全局搜索 _signature 定位:

(function () {
    var open = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function (method, url, async) {
        if (url.indexOf("_signature") != -1) {
            debugger;
        }
        return open.apply(this, arguments);
    };
})();

断住后向上跟栈:

跟到 index.5fd0992e.js 文件中,点击左下角 { } 对其进行格式化操作,ctrl + f 局部搜索 _signature,寻到其定义生成的位置,有六个结果,在第 13263 行打下断点调试分析:

n 即为加密后的 _signature 值,向上找参数 n 的定义位置,在第 13262 行:

var n = I(F.getUri(e), e);
  • e:Object 对象
  • F.getUri(e):传入一些链接,如热搜词、推荐新闻等

所以需要跟进到 I 函数中,其定义在第 13128 行,第 13141 行是个三目表达式:

(null === (n = window.byted_acrawler) || void 0 === n || null === (a = n.sign) || void 0 === a ? void 0 : a.call(n, o)) || ""

在该行打下断点调试分析: 

a.call(n, o) 即 _signature 的值,n 为对象,c 为参数,是一个链接,所以跟进到 a 中:

在 acrawler.js 文件的第 213 行,同样进行格式化操作,这个文件就是头条的 jsvmp,这里通过补环境解决,将整段 js 文件扣下来,保存到 node 环境下运行:

补环境推荐:Web technology for developers | MDN

报错显示 referrer 未定义:

document.referrer:获取前一页面的URL地址,在控制台打印一下相关内容然后补进去;

var document = {
    referrer: "https://www.toutiao.com/",
}

或者直接引入 jsdom 库:jsdom - npm

// 安装
npm i jsdom -g
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
var document = dom.window.document;

报错显示 window 未定义:

// node 环境下为 global,浏览器环境下为 this
window = global;

报错显示 sign 属性未定义:

该 js 文件最后有如下部分,进行了环境检测,将红框部分删掉即可:

报错显示 href 未定义:

控制台打印后补上:

var location = {
    href: "https://www.toutiao.com/?wid=1657855126033",
}

报错显示 userAgent 未定义:

var navigator = {
    userAgent: "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
}

报错显示 length 未定义:

经调试,length 检测的是 location.protocol 属性,即当前 url 的协议部分,将其补上即可:

var location = {
    href: "https://www.toutiao.com/?wid=1657855126033",
    protocol: "https:",
}

以上环境补充完毕,成功获取到加密数据:

注意:url 不要填错了,否则会得不到数据

会发现得到的结果比我们之前看到的 _signature 参数的值要短上许多,经过测试也能成功获取到数据,没有影响,原来的 _signature 参数长是因为加入了 cookie 校验,加上如下代码即可,需要使用到 jsdom 库:

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
window = global;
var document = dom.window.document;
var params = {
    location:{
        href: "https://www.toutiao.com/?wid=1657855126033",
        protocol: "https:",
    },
    navigator:{
        userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
    },
};

Object.assign(window, params);


function setCookie(name, value, seconds) {
    seconds = seconds || 0;
    var expires = "";
    if (seconds != 0 ) {
    expires = "; expires=" + (new Date((new Date).getTime() + 18E5)).toGMTString() + "; path=/;";
    }
    document.cookie = name + "=" + escape(value) + expires + "; path=/";
}

cookies = "__ac_nonce=063313d37006f1a60b85d; __ac_signature=_02B4Z6wo00f01.CCQXQAAIDCJ0RsDRVvDHfwokXAAJ8bce; tt_webid=7147561357562660360; local_city_cache=%E6%AD%A6%E6%B1%89; csrftoken=116ac569546e65b98b3e6b1dd0489a97; ttcid=76c0e549d78e474ea9ae750a76cffd6216; s_v_web_id=verify_l8icmxyc_x8LU4OkA_w4tw_4uNo_96iI_H6cI4tjj0FTA; _tea_utm_cache_24=undefined; ttwid=1%7C-l6HuqfxspAYL7U-ypbDWFDV9IYiTyl4V046gr_qs9c%7C1664171529%7Cab8aad75e3dc5f2a3eb2b0551828f577dcff3ebedf23f06ec1141ceb27945092; MONITOR_WEB_ID=17890c79-d9c7-4dc5-b9d6-8b589425a66c; tt_scid=ajUodQQe3i2zo1fSD.Gn2MbGg2z9jD6ARI0pRExxhEFdXSuPtLsYpjiHN9oqECUB12c6; msToken=dVl_W1W6M6Gd7oZ-8N8ELKYOEZWFnZteIh1eiaHnU8I3eNLxczIKeoEBMKxQ5iIOhXLJJvOK6exnqRNwM9K0tS4Hul9aACaVux3GEPFgc_U=";

// 格式化 cookies ---> json
for(let cookie of cookies.split(";")){
    tmp = cookie.split("=");
    setCookie(tmp[0], tmp[1], 1800);
}

以下为长度对比:

与抓包到的  _signature 参数对比,长度一样:

运行结果:

猜你喜欢

转载自blog.csdn.net/Yy_Rose/article/details/127052052