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

news2025/1/11 17:08:41

文章目录

  • 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里面的参数也是可以的,感兴趣的自己尝试

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络分析与监控:阿里云拨测方案解密

作者&#xff1a;俞嵩(榆松) 随着互联网的蓬勃发展&#xff0c;网络和服务的稳定性已成为社会秩序中不可或缺的一部分。一旦网络和服务发生故障&#xff0c;其带来的后果将波及整个社会、企业和民众的生活质量&#xff0c;造成难以估量的损失。 2020 年 12 月&#xff1a; Ak…

STL——二叉搜索树

目录 二叉搜索树的概念 ⼆叉搜索树的性能分析 ⼆叉搜索树的插⼊ ⼆叉搜索树的查找 ⼆叉搜索树的删除 中序遍历结果为升序序列 二叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树&#xff0c;它或者是⼀棵空树&#xff0c;或者是具有以下性质的⼆叉树 • 若它的左⼦树不为空&#…

【文件I/O】UNIX文件基础

IO编程的本质是通过 API 操作 文件。 什么是 IO I - Input 输入O - Output 输出 这里的输入和输出都是站在应用&#xff08;运行中的程序&#xff09;的角度。外部特指文件。 这里的文件是泛指&#xff0c;并不是只表示存在存盘中的常规文件。还有设备、套接字、管道、链接…

VS调试MFC进入系统源代码配置

调试MFC代码有时候能进入MFC的源代码,有时候不能.之前一直没有深入研究.后面经过查资料发现每次调试必能进入源代码的配置.很简单,只需要3步. 1.打开工具->选项->调试->符号,勾选Microsoft符号服务器. 2.打开项目->属性->配置属性->常规,MFC的使用修改成&qu…

车载网络:现代汽车的数字心跳

在汽车领域&#xff0c;“智能汽车”一词毫不夸张。如今的汽车已不再是原始的机械工程&#xff0c;而是通过先进的车载网络无缝连接的精密数字生态系统。这些滚动计算机由复杂的电子控制单元(ECU)网络提供动力&#xff0c;ECU是负责管理从发动机性能到信息娱乐系统等一切事务的…

mycat介绍与操作步骤

文章目录 1.分库分表2.mycat 入门2.1 概述2.2 案例&#xff1a;水平分表1&#xff09;准备工作2&#xff09;配置3&#xff09;启动并测试 3.mycat 配置详解3.1 schema.xml3.2 rule.xml3.3 server.xml 4.mycat 分片&#xff1a;垂直拆分1&#xff09;准备工作2&#xff09;配置…

【Python】Python之Selenium基础教程+实战demo:提升你的测试+测试数据构造的效率!

这里写目录标题 什么是Selenium&#xff1f;Selenium基础用法详解环境搭建编写第一个Selenium脚本解析脚本脚本执行结果常用的元素定位方法常用的WebDriver方法等待机制 Selenium高级技巧详解页面元素操作处理弹窗和警告框截图和日志记录多窗口和多标签页操作 一个实战的小demo…

Apache XMLBeans 一个强大的 XML 数据处理框架

Apache XMLBeans 是一个用于处理 XML 数据的 Java 框架&#xff0c;它提供了一种方式将 XML Schema (XSD) 映射到 Java 类&#xff0c;从而使得开发者可以通过强类型化的 Java 对象来访问和操作 XML 文档。下面将以一个简单的案例说明如何使用 Apache XMLBeans 来解析、生成和验…

带格式 pdf 翻译

支持 openAI 接口&#xff0c;国内 deepseek 接口兼容 openAI 接口&#xff0c; deepseek api 又非常便宜 https://pdf2zh.com/ https://github.com/Byaidu/PDFMathTranslate

ubuntu22.04降级安装CUDA11.3

环境&#xff1a;主机x64的ubuntu22.04&#xff0c;原有CUDA12.1&#xff0c;但是现在需要CUDA11.3&#xff0c;本篇文章介绍步骤。 一、下载CUDA11.3的run文件 下载网址&#xff1a;https://developer.nvidia.com/cuda-11-3-1-download-archive?target_osLinux&target_…

9 异常

如果你希望在软件调试上有所突破,或者想了解如何通过异常进行反调试,或者想自己写一个调试器,那么就必须要深入了解异常,异常与调试是紧密相连的,异常是调试的基础。 异常产生后,首先是要记录异常信息(异常的类型、异常发生的位置等),然后要寻找异常的处理函数,我们…

springBoot整合ELK Windowsb版本 (elasticsearch+logstash+kibana)

springBoot整合ELK Windowsb版本 【elasticsearchlogstashkibana】 下载软件启动服务1、elasticsearch2、kibana3、logstash 集成springboot1、添加依赖2、在logback.xml添加相关配置3、修改logstash 配置4、重启logstash 最后测试 下载软件 elasticsearch 官网 https://www.…

详解Sonar与Jenkins 的集成使用!

本文阅读前提 本文假设读者熟悉Jenkins和SonarQube的基础操作。 核心实现功能 Jenkins中运行的job来调用SonarScanner&#xff0c;最后可实现测试结果与SonarQube中同步查看。 Jenkins中安装Sonar相关插件 配置Sonarqube Dashboard>Manage Jenkins>Systems 指定son…

鸿蒙面试 2025-01-10

写了鉴权工具&#xff0c;你在项目中申请了那些权限&#xff1f;&#xff08;常用权限&#xff09; 位置权限 &#xff1a; ohos.permission.LOCATION_IN_BACKGROUND&#xff1a;允许应用在后台访问位置信息。 ohos.permission.LOCATION&#xff1a;允许应用访问精确的位置信息…

php 使用simplexml_load_string转换xml数据格式失败

本文介绍如何使用php函数解析xml数据为数组。 <?php$a <xml><ToUserName><![CDATA[ww8b77afac71336111]]></ToUserName><FromUserName><![CDATA[sys]]></FromUserName><CreateTime>1736328669</CreateTime><Ms…

【多空资金博弈】综合副图指标,资金做多线,短线做多雷达,中长线共振,大资金进场会涨等技术信号

如上图&#xff0c;副图指标【多空资金博弈】&#xff0c;红线做多资金线&#xff0c;绿色线为做空资金线&#xff0c;紫色柱线为短线做多雷达信号&#xff0c;紫色圆柱叠加文字为大资金进场信号&#xff0c;堆量柱线和紫色空心柱线为底部吸筹建仓信号&#xff0c;三条横向虚线…

Win11家庭版转专业版

Win11家庭版转专业版&#xff08;亲测有效&#xff09; 第一步 【断网】输入这个密钥&#xff1a; R8NJ8-9X7PV-C7RCR-F3J9X-KQBP6 第二步 点击下一步会自动重启 第三步 【联网】输入这个密钥&#xff1a; F3NWX-VFMFC-MHYYF-BCJ3K-QV66Y 注意 两次输入密钥的地方一致 …

【云商城】高性能门户网构建

第3章 高性能门户网构建 网站门户就是首页 1.OpenResty 百万并发站点架构 ​ 1).OpenResty 特性介绍 ​ 2).搭建OpenResty ​ 3).Web站点动静分离方案剖析 2.Lua语法学习 ​ 1).Lua基本语法 3.多级缓存架构实战 ​ 1).多级缓存架构分析 用户请求网站&#xff0c;最开始…

上海亚商投顾:沪指探底回升微涨 机器人概念股午后爆发

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 市场全天探底回升&#xff0c;沪指盘中跌超1.6%&#xff0c;创业板指一度跌逾3%&#xff0c;午后集体拉升翻红…

计算机毕业设计Python机器学习农作物健康识别系统 人工智能 图像识别 机器学习 大数据毕业设计 算法

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…