某漫画网站JS逆向反混淆流程分析

文章目录

  • 1. 写在前面
  • 1. 接口分析
  • 2. 反混淆分析

【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章

作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!

1. 写在前面

  前段时间有几个小伙伴咨询过关于某漫画网站的图片数据如何下载获取,看了一下觉得这个网站蛮适合初学者或者逆向分析爱好者练手的!它涉及到反调试、数据解密、JS反混淆、Cookie反爬虫、TLS指纹的检测


分析目标

aHR0cHM6Ly93d3cuY29sYW1hbmdhLmNvbS9tYW5nYS1tZjg3NDEyNy8xLzU2Lmh0bWw=

初看时有小伙伴也提出过使用自动化的方式来获取图片链接再下载,但是这个链接是临时的。自动化是可以的,但只能是等待所有服务端下发的图片内容加载完毕渲染呈现到页面后使用截图的方式来获取,如下所示:

在这里插入图片描述

1. 接口分析

打开网站准备调试分析之前是有一个反调试的,一般这种大多通过动态生成的函数或代码片段触发!然后过这种反调试的方案是很多的(还有一些大佬开源分享的绝大场景下通杀的方案)如下所示:

在这里插入图片描述

这里我们也是可以通过重写构造函数与其原型方法拦截且移除动态生成代码中反调试语句,代码如下所示:

(function () {
    'use strict';

    const OriginalFunction = Function;

    Function = function (...args) {
        handleDebuggerRemoval(args);
        logStackTrace("Function");
        return OriginalFunction(...args);
    };

    Function.prototype = OriginalFunction.prototype;

    Function.prototype.constructor = function (...args) {
        handleDebuggerRemoval(args);
        logStackTrace("Function.constructor");
        return OriginalFunction(...args);
    };

    /**
     * 移除字符串参数中的 "debugger" 语句
     * @param {Array} args - 参数数组
     */
    function handleDebuggerRemoval(args) {
        for (let i = 0; i < args.length; i++) {
            if (typeof args[i] === "string") {
                args[i] = args[i].replace(/debugger/g, "");
            }
        }
    }
    function logStackTrace(context) {
        const stackTrace = new Error().stack;
        log(`[${context}] Call Stack:`, stackTrace);

        if (DEBUG?.deb === 0) {
            debugger;
        }

        log(`[${context}] =============== End ===============`);
    }
})();

过了反调试之后,我们首先去看一下发包的情况。其实初次看的话没有明确的特征告诉我们从哪里下手,只能花点时间来各方面来分析一下,如下所示:

在这里插入图片描述

点击可发现这个接口貌似就是图片请求加载的发包(不过注意请求的是.enc.webp)大概率是经过处理的,而且在Cookies中也是添加了某些关键的字段,如下所示:

在这里插入图片描述

这里猜测在后续的请求中可能是需要携带这个Cookie参数请求的

在这里插入图片描述

这种场景下通过经验来梳理一下流程分析我们可以从网页加载的源码中来开始,它这种实时章节的加载大概率是不断的拼接后续的漫画图来获取资源的!然后在首次请求页面资源的时候肯定有基础的数据或者一些特征可以挖掘的

这里我们过掉反调试之后重方一下页面请求(请求记得过一下TLS检测)并保证Cookie请求的时候携带了__cf__bkm参数,如下所示:

在这里插入图片描述

可以看到请求的HTML内容中有一串密文(C_DATA)这个就是需要去解密的,解密后会拿到当前漫画章节中的详情信息JSON数据

2. 反混淆分析

它这个JS代码都是经过混淆的!不要硬看,浪费时间。核心逻辑基本都在custom.js、read.js文件中,先把JS拿下来反混淆静态分析一下!找到解密C_DATA的地方,混淆代码如下所示:

在这里插入图片描述

整个这块拿下来先解一下混淆,静态分析就很清晰了。处理解密C_DATA的混淆源码还原之后的JS代码如下所示:

 if (__cad.isInReadPage()) {
    let decryptedData;
    __cad.useCodeIndex = 1;

    try {
        decryptedData = window.devtools.jsd(
            "USJZOHqNw84GoMA9",
            window.devtools.jsc.enc.Base64.parse(window.C_DATA).toString(window.devtools.jsc.enc.Utf8)
        );

        if (decryptedData === '') {
            __cad.useCodeIndex = 2;
            decryptedData = window.devtools.jsd(
                "c9UPIOaql84fJIoz",
                window.devtools.jsc.enc.Base64.parse(window.C_DATA)
                    .toString(window.devtools.jsc.enc.Utf8)
            );
        }

        window.devtools.jse(decryptedData);

    } catch (error) {
        __cad.useCodeIndex = 2;
        decryptedData = window.devtools.jsd(
            "c9UPIOaql84fJIoz",
            window.devtools.jsc.enc.Base64.parse(window.C_DATA)
                .toString(window.devtools.jsc.enc.Utf8)
        );
    }

    window.devtools.jse(decryptedData);

    const decodedUrls = window.devtools.jsc.enc.Base64.parse(window.image_info.urls__direct).toString(window.devtools.jsc.enc.Utf8);
    window.__images_yy = decodedUrls.split("|SEPARATER|");

    window.__specialDisplay = 1;
    if (!window.image_info.img_type) {
        window.__specialDisplay = 0;
    }
}

直接在控制台把进行解密的JS代码执行可以看到明文的C_DATA数据,如下所示:

在这里插入图片描述

来!接下来分析一下上面还原之后的JS代码到底做了些什么。首先可以看到入口则是检测是否处于阅读页面,开始对C_DATA密文数据进行解密操作,它这个解密的逻辑基本都是一样的,先尝试使用默认的第一个密钥加B64的解码,数据钥匙解出来没有继续尝试切换使用第二个密钥!最后解密图片的URL信息并分割URL列表,最后的话是设置显示的操作

下面作者根据反混淆之后的JS代码使用Python算法来实现对C_DATA的解密操作,代码实现所示:

import base64
from loguru import logger
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Protocol.KDF import scrypt

def base64Decode(base64Str):
    return base64.b64decode(base64Str).decode('utf-8')

def aesDecrypt(encData, key):
    key_bytes = key.encode('utf-8')
    cipher = AES.new(key_bytes, AES.MODE_ECB)
    decrypted = unpad(cipher.decrypt(encData), AES.block_size)
    return decrypted.decode('utf-8')

def jsd(key, encryptedData):
    decodedData = base64Decode(encryptedData)
    encData = base64.b64decode(decodedData)
    return aesDecrypt(encData, key)

def decryptCData(c_data):
    key1 = 'USJZOHqNw84GoMA9'

    decryptedData = jsd(key1, c_data)

    logger.info(f"解密数据:{decryptedData}")

if __name__ == '__main__':
    c_data = '' # 密文数据
    decryptCData(c_data)

这里直接到浏览扣一个加密数据丢进去测试,得到运行如下所示:

在这里插入图片描述
在这里插入图片描述

通过下面混淆代码调试标记出来的的几处不难发现大致的流程

在这里插入图片描述

对混淆的JS代码简单做一下还原可以更加直观有效的帮助分析。__cad[_0x3b6833(0x591)]实则就是一个setCookieValue的操作,通过获取上面JSON数据中的enc_code2enc_code1的值来对下面Cookies中的值进行一个解密操作,如下所示:

在这里插入图片描述

接下来,针对还原后的JS代码来进行分析,代码如下所示:

let decryptedValue = window.devtools.jsd(
    _0x447fdd,
    window.devtools.jsc.enc.Base64.parse(window.mh_info.enc_code2).toString(window.devtools.jsc.enc.Utf8)
);

if (decryptedValue === '') {
    decryptedValue = window.devtools.jsd("RMjidK1Dgv0Ojuhm", window.devtools.jsc.enc.Base64.parse(window.mh_info.enc_code2).toString(window.devtools.jsc.enc.Utf8));
}

if (!decryptedValue.startsWith(mh_info.mhid + '/')) {
    decryptedValue = window.devtools.jsd("RMjidK1Dgv0Ojuhm", window.devtools.jsc.enc.Base64.parse(window.mh_info.enc_code2).toString(window.devtools.jsc.enc.Utf8));
}

let cookieOptions = { "expires": 0.005 };
__cad.cookie(_0x29107e, decryptedValue, cookieOptions);

let decryptedValue2 = window.devtools.jsd(
    _0x447fdd,
    window.devtools.jsc.enc.Base64.parse(window.mh_info.enc_code1).toString(window.devtools.jsc.enc.Utf8)
);

if (decryptedValue2 === '') {
    decryptedValue2 = window.devtools.jsd("HNoYX7fJXcM1PWAK", window.devtools.jsc.enc.Base64.parse(window.mh_info.enc_code1).toString(window.devtools.jsc.enc.Utf8));
}

// 转换解密后的值为整数
let valueAsInt = parseInt(decryptedValue2);

// 如果转换失败(NaN),再次尝试解密
if (String(valueAsInt) === "NaN") {
    decryptedValue2 = window.devtools.jsd("HNoYX7fJXcM1PWAK", window.devtools.jsc.enc.Base64.parse(window.mh_info.enc_code1).toString(window.devtools.jsc.enc.Utf8));
}

// 存储第二个cookie
let cookieOptions2 = { "expires": 0.005 };
__cad.cookie(_0x3ee2e4, decryptedValue2, cookieOptions2);

通过对上面还原后的JS代码进行静态分析可以发现,初始化的时候是给了一个密钥,然后假设解密是空的,就会使用默认的密钥进行解密!如果解密值不符合预期(不以mh_info.mhid/开头),则重试解密,enc_code1的流程差不多

在这里插入图片描述

接下来我们看一下devtools.jsd的解密算法调用,用的什么

在这里插入图片描述

这里我们根据调试以及反混淆后的JS代码还原一下对mh_info参数中的字段解密,加密算法如下所示:

const CryptoJS = require('crypto-js');

function aesDecrypt(encData, key) {
    const parsedKey = CryptoJS.enc.Utf8.parse(key);
    const decrypted = CryptoJS.AES.decrypt(encData, parsedKey, {
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.Pkcs7
    });
    return CryptoJS.enc.Utf8.stringify(decrypted);
}

function parseBase64(encodedStr) {
    return CryptoJS.enc.Base64.parse(encodedStr);
}

function decryptProcess(encCode1, encCode2, pageId, mhId) {
    const key1 = "ZsfOA40m7kWjodMH";

    const parsedEncCode2 = parseBase64(encCode2).toString(CryptoJS.enc.Utf8);
    const parsedEncCode1 = parseBase64(encCode1).toString(CryptoJS.enc.Utf8);

    let decryptedEncCode2;
    try {
        decryptedEncCode2 = aesDecrypt(parsedEncCode2, key1);
        if (!decryptedEncCode2 || !decryptedEncCode2.startsWith(`${mhId}/`)) {
            decryptedEncCode2 = aesDecrypt(parsedEncCode2, key2);
        }
    } catch (e) {
        decryptedEncCode2 = aesDecrypt(parsedEncCode2, key2);
    }

    return {
        cookie: { key: `_tkb_${pageId}`, value: decryptedEncCode2 },
    };
}

// 测试数据
const mh_info = {
    "startimg": 1,
    "enc_code1": "cDJSdkkyUFUzbVZrUXZ1S213TFBuQT09",
    "mhid": "873947",
    "enc_code2": "Q1FrNTVrRGZHZjhQM3dEdkg0cU4vYnVmTU9RWjBWdzMzYmhYSlpyKzM0QjN3cmxFSTdYV1VVWUlXRkNMVHhhNw==",
    "mhname": "捉刀人",
    "pageid": 7557687,
    "pagename": "56",
    "pageurl": "1/57.html",
    "readmode": 3,
    "maxpreload": 10,
    "defaultminline": 1,
    "domain": "img.colamanga.com",
    "manga_size": "",
    "default_price": 0,
    "price": 0,
    "use_server": "",
    "webPath": "/manga-mf874127/"
};

const result = decryptProcess(mh_info.enc_code1, mh_info.enc_code2, mh_info.pageid, mh_info.mhid);
console.log(result);

注意一下上面算法解密所使用到的AES密钥是每天都在更新的哈

在这里插入图片描述
解决完Cookie生成解密后我们来看最终的图片如何才能去下载的!从前往后分析的话已经拿到了C_DATA数据并解密,通过对解密数据中的Key成功解密获取到Cookie参数,下面就需要知道完整的图片地址,携带Cookie去请求即可,如下继续分析:

在这里插入图片描述

图片地址生存获取的JS混淆代码同样需要还原,还原如下所示:

window.getpice = function (pageIndex) {
    let imageUrl = '';

    if (!window.image_info.img_type) {
        let currentLine = window.lines[chapter_id].use_line;

        let imageIndex = parseInt(window.mh_info.startimg) + pageIndex - 1;

        let fileName = __cr.PrefixInteger(imageIndex, 4) + ".jpg";

        if (window.image_info.imgKey != undefined && window.image_info.imgKey !== '') {
            fileName = __cr.PrefixInteger(imageIndex, 4) + ".enc.webp";
        }

        let baseDomain;
        let sanitizedDomain = currentLine.replace("img.", '');
        sanitizedDomain = document.domain.replace("www.", '');

        let cookieValue = __cad.getCookieValue();
        let pageId = mh_info.pageid;
        let cookieKey = cookieValue[0] + pageId.toString();
        let encodedPath = __cad.cookie(cookieKey);

        if (encodedPath == null) {
            __cad.setCookieValue();
            encodedPath = __cad.cookie(cookieKey);
        }

        if (mh_info.use_server === '') {
            baseDomain = `//img.${sanitizedDomain}/comic/${encodeURI(encodedPath)}${fileName}`;
        } else {
            baseDomain = `//img${mh_info.use_server}.${sanitizedDomain}/comic/${encodeURI(encodedPath)}${fileName}`;
        }

        imageUrl = baseDomain;
    } else {
        let imagePath = window.__images_yy[pageIndex - 1];
        if (window.image_info.img_type === '1') {
            imageUrl = __cr.switchWebp(imagePath, window.mh_info.manga_size);
        } else {
            imageUrl = imagePath;
        }
    }

    return imageUrl;
};

先获取当前章节的线路信息再计算图片序号,根据序号生成图片文件名JPG然后替换它的主域名。其中也进行了一些Cookie的设置操作最终拿到完整图片路径

在这里插入图片描述

在这里插入图片描述

最后的图片数据则是通过AES解密二进制图片数据,然后就可以直接下载了!_0x1d85d5是密文对象,包含了加密的图片数据,解密的结果_0x5183f2则是图片的二进制数据(WordArray类型

var key = "KZTC0WwWqyeStZD2";
var _0x5183f2 = window.CryptoJS.AES.decrypt(_0x1d85d5, key, {
  'iv': window.CryptoJS.enc.Utf8.parse("0000000000000000"),
  'mode': window.CryptoJS.mode.CBC,
  'padding': window.CryptoJS.pad.Pkcs7
});

貌似不携带Cookie里面的参数也是可以的,感兴趣的自己尝试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/951965.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

124.【C语言】数据结构之快速排序的小区间优化和非递归的解决方法

目录 1.小区间优化 测试代码 运行结果 2.非递归的解决方法(重要!) 递归产生的问题 一般来说,递归改非递归有两种方法 算法分析 递归产生的二叉树 栈的示意图 先写代码框架 再填写细节部分 1.小区间优化 回顾121.【C语言】数据结构之快速排序(未优化的Hoare排序存在…

赛车微型配件订销管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 赛车微型配件行业通常具有产品多样性、需求不确定性、市场竞争激烈等特点。配件供应商需要根据市场需求及时调整产品结构和库存&#xff0c;同时要把握好供应链管理和销售渠道。传统的赛车微型配件订销管理往往依赖于人工经验和简单的数据分析&#xff0c;效率低下且容易…

Java一个简单的反弹动画练习

文章目录 说明代码详解创建窗体代码创建绘图板创建线程 运行结果完整代码 说明 做了一个小球和星型做反弹动画的窗体作为练习&#xff0c;分享给大家&#xff0c;为了方便和我一样的小白可以看的比较明白&#xff0c;所以尽量详细的标注了注释&#xff0c;希望能帮到同样在学习…

基于YOLOv8的车辆跟踪、车速计算和车辆统计应用

1、环境搭建 通过conda创建一个python≥3.8环境&#xff0c;激活环境后安装ultralytics8.2、python-opencv、shapely>2.0.0: conda create -n yolov8 python3.10 conda activate yolov8 pip install ultralytics8.2 pip install python-opencv pip install shapely>2.0…

如何提升scrapy的效率

如何提升scrapy的效率 在settings配置文件中修改CONCURRENT_REQUESTS 100 scrapy默认开启的线程数量为32个&#xff0c;这样设置可以使其线程数量为100个在运行scrapy时,会有大量的日志信息输出&#xff0c;为了减少cpu的使用率&#xff0c;可以设置log输出信息为WORNING或者…

网络安全 | 网络安全法规:GDPR、CCPA与中国网络安全法

网络安全 | 网络安全法规&#xff1a;GDPR、CCPA与中国网络安全法 一、前言二、欧盟《通用数据保护条例》&#xff08;GDPR&#xff09;2.1 背景2.2 主要内容2.3 特点2.4 实施效果与影响 三、美国《加利福尼亚州消费者隐私法案》&#xff08;CCPA&#xff09;3.1 背景3.2 主要内…

HarmonyOS(ArkUI框架介绍)

ArkUI框架介绍 ArkUI简介 基本概念 UI&#xff1a; 即用户界面。开发者可以将应用的用户界面设计为多个功能页面&#xff0c;每个页面进行单独的文件管理&#xff0c;并通过页面路由API完成页面间的调度管理如跳转、回退等操作&#xff0c;以实现应用内的功能解耦。 组件&…

EasyExcel(二)导出Excel表自动换行和样式设置

EasyExcel(一)导出Excel表列宽自适应 背景 在上一篇文章中解决导出列宽自适应,然后也解决了导出列宽不可超过255的问题。但是实际应用场景中仍然会有导出数据的长度超过列宽255。这时导出效果就会出现如下现象: 多出列宽宽度的内容会浮出来,影响后边列数据的显示。 解决…

记录一下vue2项目优化,虚拟列表vue-virtual-scroll-list处理10万条数据

文章目录 封装BrandPickerVirtual.vue组件页面使用组件属性 select下拉接口一次性返回10万条数据&#xff0c;页面卡死&#xff0c;如何优化&#xff1f;&#xff1f;这里使用 分页 虚拟列表&#xff08;vue-virtual-scroll-list&#xff09;&#xff0c;去模拟一个下拉的内容…

迅为RK3568开发板篇OpenHarmony配置HDF驱动控制LED-配置创建私有配置文件

接 下 来 新 建 vendor/hihope/rk3568/hdf_config/khdf/topeet/topeet_config.hcs 文 件 &#xff0c;topeet_config.hcs 为驱动私有配置文件&#xff0c;用来填写一些驱动的默认配置信息。HDF 框架在加载驱动时&#xff0c;会获取相应的配置信息并将其保存在 HdfDeviceObject …

nginx负载均衡-基于端口的负载均衡(一)

注意&#xff1a; (1) 做负载均衡技术至少需要三台服务器&#xff1a;一台独立的负载均衡器&#xff0c;两台web服务器做集群 一、nginx分别代理后端web1 和 web2的三台虚拟主机 1、web1&#xff08;nginx-10.0.0.7&#xff09;配置基于端口的虚拟主机 [rootOldboy extra]# …

金融项目实战 02|接口测试分析、设计以及实现

目录 ⼀、接口相关理论 二、接口测试 1、待测接口&#xff1a;投资业务 2、接口测试流程 3、设计用例理论 1️⃣设计方法 2️⃣工具 4、测试点提取 5、测试用例&#xff08;只涉及了必测的&#xff09; 1️⃣注册图⽚验证码、注册短信验证码 2️⃣注册 3️⃣登录 …

vue3使用vue3-video-play播放m3u8视频

1.安装vue3-video-play npm install vue3-video-play --save2.在组件中使用 import vue3-video-play/dist/style.css; import VideoPlay from vue3-video-play;// 视频配置项 const options reactive({src: https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8, //视频源mute…

Redis:数据类型

1. 字符串&#xff08;String&#xff09; 简介 概念&#xff1a;这是最简单的数据类型&#xff0c;可以存储字符串、整数或浮点数。特点&#xff1a;支持原子操作&#xff0c;如递增和递减数值。 示例 # 设置一个键值对 SET mykey "Hello, Redis!"# 获取该键的值…

【Web安全】SQL 注入攻击技巧详解:UNION 注入(UNION SQL Injection)

【Web安全】SQL 注入攻击技巧详解&#xff1a;UNION 注入&#xff08;UNION SQL Injection&#xff09; 引言 UNION注入是一种利用SQL的UNION操作符进行注入攻击的技术。攻击者通过合并两个或多个SELECT语句的结果集&#xff0c;可以获取数据库中未授权的数据。这种注入技术要…

移远BC28_opencpu方案_pin脚分配

先上图&#xff0c;BC28模块的pin脚如图所示&#xff1a; 下面看看GPIO的复用管脚 然后我自己整理了一份完整的pin功能列表

PHP多功能投票小程序源码

多功能投票小程序&#xff1a;全方位打造专属投票盛宴的得力助手 &#x1f389; &#x1f527; 基于先进的ThinkPHP框架与Uniapp技术深度融合&#xff0c;我们匠心独运&#xff0c;精心雕琢出一款功能全面、操作便捷的投票小程序&#xff0c;旨在为您带来前所未有的投票体验。…

ORB-SALM3配置流程及问题记录

目录 前言 一、OPB-SLAM3基本配置流程 1.下载编译Pangolin 二、ORB-SLAM3配置 1.下载源码 2.创建ROS工作空间并编译ORB-SLAM3-ROS源码 3.尝试编译 三、运行测试 一、OPB-SLAM3基本配置流程 ORB-SLAM3是一个支持视觉、视觉加惯导、混合地图的SLAM&#xff08;Simultane…

RabbitMQ介绍与使用

RabbitMQ官网 RabbitMQ 介绍 RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;基于 AMQP&#xff08;高级消息队列协议&#xff09;标准&#xff0c;使用 Erlang 编程语言构建。它是消息队列&#xff08;MQ&#xff09;的一种&#xff0c;广泛应用于分布式系统中&#x…

自然语言处理之jieba分词和TF-IDF分析

jieba分词和TF-IDF分析 目录 jieba分词和TF-IDF分析1 jieba1.1 简介1.2 终端下载1.3 基本语法 2 TF-IDF分析2.1 什么是语料库2.2 TF2.3 IDF2.4 TF-IDF2.5 函数导入2.6 方法 3 实际测试3.1 问题解析3.2 代码测试 1 jieba 1.1 简介 结巴分词&#xff08;Jieba&#xff09;是一个…