某漫画网站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.mzph.cn/bicheng/67907.shtml

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

相关文章

ffmpeg aac s16 encode_audio.c

用ffmpeg库时&#xff0c;用代码对pcm内容采用aac编码进行压缩&#xff0c;出现如下错误。 [aac 000002bc5edc6e40] Format aac detected only with low score of 1, misdetection possible! [aac 000002bc5edc8140] Error decoding AAC frame header. [aac 000002bc5edc81…

深度学习的原理和应用

一、深度学习的原理 深度学习是机器学习领域的一个重要分支&#xff0c;其原理基于多层神经网络结构和优化算法。以下是深度学习的核心原理&#xff1a; 多层神经网络结构&#xff1a;深度学习模型通常由多层神经元组成&#xff0c;这些神经元通过权重和偏置相互连接。输入数据…

mv指令详解

&#x1f3dd;️专栏&#xff1a;计算机操作系统 &#x1f305;主页&#xff1a;猫咪-9527-CSDN博客 “欲穷千里目&#xff0c;更上一层楼。会当凌绝顶&#xff0c;一览众山小。” 目录 基本语法 主要功能 常用选项详解 1. 移动文件或目录 2. 重命名文件或目录 3. -i&am…

5 分布式ID

这里讲一个比较常用的分布式防重复的ID生成策略&#xff0c;雪花算法 一个用户体量比较大的分布式系统必然伴随着分表分库&#xff0c;分机房部署&#xff0c;单体的部署方式肯定是承载不了这么大的体量。 雪花算法的结构说明 如下图所示: 雪花算法组成 从上图我们可以看…

怎么实现Redis的高可用?

大家好&#xff0c;我是锋哥。今天分享关于【怎么实现Redis的高可用&#xff1f;】面试题。希望对大家有帮助&#xff1b; 怎么实现Redis的高可用&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 为了实现 Redis 的高可用性&#xff0c;我们需要保证在发…

牛客网刷题 ——C语言初阶(6指针)——BC106 上三角矩阵判定

1. 题目描述——BC106 上三角矩阵判定 牛客网OJ题链接 描述 KiKi想知道一个n阶方矩是否为上三角矩阵&#xff0c;请帮他编程判定。上三角矩阵即主对角线以下的元素都为0的矩阵&#xff0c;主对角线为从矩阵的左上角至右下角的连线。 示例 输入&#xff1a; 3 1 2 3 0 4 5 0 0…

H266/VVC 帧内预测中 ISP 技术

帧内子划分 ISP ISP 技术是在 JVET-2002-v3 提案中详细介绍其原理&#xff0c;在 VTM8 中完整展示算法。ISP是线基内预测&#xff08;LIP&#xff09;模式的更新版本&#xff0c;它改善了原始方法在编码增益和复杂度之间的权衡&#xff0c;ISP 算法的核心原理就是利用较近的像…

了解npm:JavaScript包管理工具

在JavaScript的生态系统中&#xff0c;npm&#xff08;Node Package Manager&#xff09;无疑是一个举足轻重的存在。它不仅是Node.js的包管理器&#xff0c;更是前端开发不可或缺的一部分&#xff0c;为开发者提供了丰富的包资源、便捷的包管理以及强大的社区支持。本文将深入…

CNN Test Data

由于数据量过大&#xff0c;打不开了 搞一组小的吧。收工睡觉 https://download.csdn.net/download/spencer_tseng/90256048

协同过滤算法商品推荐系统|Java|SpringBoot|VUE|

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SpringBoot、Mybatis-Plus、VUE、jquery,html 5⃣️…

初学stm32 --- DMA直接存储器

目录 DMA介绍 STM32F1 DMA框图 DMA处理过程 DMA通道 DMA优先级 DMA相关寄存器介绍 F1 DMA通道x配置寄存器&#xff08;DMA_CCRx&#xff09; DMA中断状态寄存器&#xff08;DMA_ISR&#xff09; DMA中断标志清除寄存器&#xff08;DMA_IFCR&#xff09; DMA通道x传输…

Routine Load 导入问题处理指南

Routine Load 导入问题处理指南 在使用 Apache Doris 的 Routine Load 时&#xff0c;你是否曾经被各种奇奇怪怪的问题卡住&#xff1f;今天就来分享一些最常见的 Routine Load 问题&#xff0c;并提供相应的解决方案&#xff0c;让你快速应对&#xff0c;高效解决&#xff01;…

【面试题】技术场景 6、Java 生产环境 bug 排查

生产环境 bug 排查思路 分析日志&#xff1a;首先通过分析日志查看是否存在错误信息&#xff0c;利用之前讲过的 elk 及查看日志的命令缩小查找错误范围&#xff0c;方便定位问题。远程 debug 适用环境&#xff1a;一般公司正式生产环境不允许远程 debug&#xff0c;多在测试环…

牛客 《反转链表》 链表 题解

前言 太久没有练习C和Java&#xff0c;基本忘完了…还有数据结构也不太熟悉了。借此机会回顾一下相关的知识点&#xff0c;也为之后做准备吧。 题目内容 思路 要求时间复杂度为O(n)&#xff0c;那么只能遍历一次。反转的话&#xff0c;只需要将链表箭头指向换个方向就行。遍…

容器技术全面攻略:Docker的硬核玩法

文章背景 想象一下&#xff0c;一个项目终于要上线了&#xff0c;结果因为环境配置不一致&#xff0c;测试服务器一切正常&#xff0c;生产环境却宕机了。这是开发者噩梦的开始&#xff0c;也是Docker救世主角色的登场&#xff01;Docker的出现颠覆了传统环境配置的方式&#…

RabbitMQ高级篇

目录 确保发送者的可靠 为什么需要确保发送者的可靠性 RabbitMQ 的发送者重连机制配置 springAMQP实现发送者确认 MQ的可靠性 为什么需要实现MQ的可靠性&#xff1f; 数据持久化 Lazy Queue 核心思想 总结RabbitMQ 如何保证消息的可靠性 持久化 Lazy Queue 消息…

微信小程序用的SSL证书有什么要求吗?

微信小程序主要建立在手机端使用&#xff0c;然而手机又涉及到各种系统及版本&#xff0c;所以对SSL证书也有要求&#xff0c;如果要小程序可以安全有效的访问需要满足以下要求&#xff1a; 1、原厂SSL证书&#xff08;原厂封&#xff09;。 2、DV单域名或者DV通配符。 3、兼…

OpenCV计算机视觉 07 图像的模块匹配

在做目标检测、图像识别时&#xff0c;我们经常用到模板匹配&#xff0c;以确定模板在输入图像中的可能位置 API函数 cv2.matchTemplate(image, templ, method, resultNone, maskNone) 参数含义&#xff1a; image&#xff1a;待搜索图像 templ&#xff1a;模板图像 method&…

uniapp开发u-icon图标不显示问题

uniapp开发图标用u-icon不显示&#xff0c;换成uv-icon就可以了 插件市场从这里下载&#xff1a;uv-ui 破釜沉舟之兼容vue32、app、h5、小程序等多端&#xff0c;灵活导入&#xff0c;利剑出击 - DCloud 插件市场 组件库看这个&#xff1a;介绍 | 我的资料管理-uv-ui 是全面兼…

ELK的搭建

ELK elk&#xff1a;elasticsearch logstatsh kibana统一日志收集系统 elasticsearch&#xff1a;分布式的全文索引引擎点非关系型数据库,存储所有的日志信息&#xff0c;主和从&#xff0c;最少需要2台 logstatsh&#xff1a;动态的从各种指定的数据源&#xff0c;获取数据…