某漫画网站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 scryptdef 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.rhkb.cn/news/505610.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

netplan apply报错No module named ‘netifaces‘

Ubuntu 20.04.5 LTS \n \l&#xff0c;ctrlaltf2切换字符登录f1切换图形 处理办法&#xff1a; root登录执行 rootnode37:/disk1/Qwen2.5-72B-Instruct-GPTQ-Int4# cat /etc/netplan/01-network-manager-all.yaml # Let NetworkManager manage all devices on this system …

LabVIEW 系统诊断

LabVIEW 系统诊断是指通过各种工具和方法检测、评估、分析和解决 LabVIEW 程序和硬件系统中可能存在的故障和性能问题。系统诊断不仅涵盖软件层面的调试与优化&#xff0c;还包括硬件交互、数据传输、实时性能等方面的检查和分析。一个成功的系统诊断能够显著提升LabVIEW应用程…

【Docker】docker compose 安装 Redis Stack

注&#xff1a;整理不易&#xff0c;请不要吝啬你的赞和收藏。 前文 Redis Stack 什么是&#xff1f; 简单来说&#xff0c;Redis Stack 是增强版的 Redis &#xff0c;它在传统的 Redis 数据库基础上增加了一些高级功能和模块&#xff0c;以支持更多的使用场景和需求。Redis…

慧集通(DataLinkX)iPaaS集成平台-数据流程之流程透明化调试功能简介

在线运行流程 查看运行状态 流程第一次执行状态显示 流程第二次执行状态显示&#xff08;由于订单已同步到七星ERP中&#xff0c;由于还是这些订单所以第二次同步时就报错了&#xff09; 点击查看节点组件的详细入参与出参信息 U8C销售订单读取组件执行时详情 入参-查询条件…

数据集-目标检测系列- 电话 测数据集 call_phone >> DataBall

数据集-目标检测系列- 电话 测数据集 call DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” 贵在坚持&#xff01; …

【PPTist】公式编辑、插入音视频、添加动画

一、插入公式 点击公式的时候 latexEditorVisible 会变成 true src/views/Editor/CanvasTool/index.vue <Modalv-model:visible"latexEditorVisible" :width"880" ><LaTeXEditor close"latexEditorVisible false"update"data &…

istio-proxy oom问题排查步骤

1. 查看cluster数量 cluster数量太多会导致istio-proxy占用比较大的内存&#xff0c;此时需检查是否dr资源的host设置有配置为* 2. 查看链路数据采样率 若采样率设置过高&#xff0c;在压测时需要很大的内存来维护链路数据。可以调低采样率或增大istio-proxy内存。 检查iop中…

【数据库】四、数据库管理与维护

文章目录 四、数据库管理与维护1 安全性管理2 事务概述3 并发控制4 备份与恢复管理 四、数据库管理与维护 1 安全性管理 安全性管理是指保护数据库&#xff0c;以避免非法用户进行窃取数据、篡改数据、删除数据和破坏数据库结构等操作 三个级别认证&#xff1a; 服务器级别…

rhcsa练习(3)

1 、创建文件命令练习&#xff1a; &#xff08; 1 &#xff09; 在 / 目录下创建一个临时目录 test &#xff1b; mkdir /test &#xff08; 2 &#xff09;在临时目录 test 下创建五个文件&#xff0c;文件名分别为 passwd &#xff0c; group &#xff0c; bashrc &#x…

如何设计一个注册中心?以Zookeeper为例

这是小卷对分布式系统架构学习的第8篇文章&#xff0c;在写第2篇文章已经讲过服务发现了&#xff0c;现在就从组件工作原理入手&#xff0c;讲讲注册中心 以下是面试题&#xff1a; 某团面试官&#xff1a;你来说说怎么设计一个注册中心&#xff1f; 我&#xff1a;注册中心嘛&…

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

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

Cognitive architecture 又是个什么东东?

自Langchain&#xff1a; https://blog.langchain.dev/what-is-a-cognitive-architecture/ https://en.wikipedia.org/wiki/Cognitive_architecture 定义 A cognitive architecture refers to both a theory about the structure of the human mind and to a computational…

js代理模式

允许在不改变原始对象的情况下&#xff0c;通过代理对象来访问原始对象。代理对象可以在访问原始对象之前或之后&#xff0c;添加一些额外的逻辑或功能。 科学上网过程 一般情况下,在访问国外的网站,会显示无法访问 因为在dns解析过程,这些ip被禁止解析,所以显示无法访问 引…

多目标优化算法之一:基于分解的方法

在多目标优化算法中,“基于分解的方法”通常指的是将多目标优化问题(MOP)分解为多个单目标优化子问题,并同时优化这些子问题。这种方法的核心思想是通过引入权重向量或参考点,将多目标问题转化为多个标量优化问题,每个子问题都关注于原始问题的一个特定方面或视角。这样可…

【面试题】技术场景 4、负责项目时遇到的棘手问题及解决方法

工作经验一年以上程序员必问问题 面试题概述 问题为在负责项目时遇到的棘手问题及解决方法&#xff0c;主要考察开发经验与技术水平&#xff0c;回答不佳会影响面试印象。提供四个回答方向&#xff0c;准备其中一个方向即可。 1、设计模式应用方向 以登录为例&#xff0c;未…

uniapp 微信小程序内嵌h5实时通信

描述&#xff1a; 小程序webview内嵌的h5需要向小程序实时发送消息&#xff0c;有人说postMessage可以实现&#xff0c;所以试验一下&#xff0c;结果是实现不了实时&#xff0c;只能在特定时机后退、组件销毁、分享时小程序才能接收到信息&#xff08;小程序为了安全等考虑做了…

matlab编写分段Hermite插值多项式

文章目录 原理使用分段Hermite插值多项式原因公式第一类的两个插值积函数第二类的两个插值积函数 例题法一法二 代码分段 Hermite 插值的思路&#xff1a;分段 Hermite 插值多项式的构造&#xff1a;MATLAB 实现代码&#xff1a;结果如图&#xff1a;注归一化变量的作用&#x…

新时期下k8s 网络插件calico 安装

1、k8s master节点初始化完毕以后一直处于notreadey状态&#xff0c;一直怀疑是安装有问题或者是初始化有问题&#xff08;当然&#xff0c;如果真有问题要先解决这些问题&#xff09;&#xff0c;经过不断探索才发现是网络插件没有安装导致的&#xff0c;根据建议安装calico插…

《解锁图像的语言密码:Image Caption 开源神经网络项目全解析》

《解锁图像的语言密码&#xff1a;Image Caption 开源项目全解析》 一、开篇&#xff1a;AI 看图说话时代来临二、走进 Image Caption 开源世界三、核心技术拆解&#xff1a;AI 如何学会看图说话&#xff08;一&#xff09;深度学习双雄&#xff1a;CNN 与 RNN&#xff08;二&a…

【Maui】动态菜单实现(绑定数据视图)

前言 .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架&#xff0c;用于使用 C# 和 XAML 创建本机移动和桌面应用。 使用 .NET MAUI&#xff0c;可从单个共享代码库开发可在 Android、iOS、macOS 和 Windows 上运行的应用。 .NET MAUI 是一款开放源代码应用&#xff0c;是 X…