尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

JavaScript 逆向爬虫技巧总结!(番外篇)

JavaScript 逆向爬虫技巧总结!(番外篇) 一、寻找入口1.1 查看请求1.2 搜索参数1.3 分析发起调用1.4 断点1.5 Hook1.6 其他二、调试分析2.1 格式化2.2 断点调试2.3 反混淆三、模拟执行3.1 Python 改写和模拟执行3.2 JS 模拟执行 API3.3 浏览器模拟执行四、JavaScript 逆向爬取实战4.1 列表页4.2 详细页总的来说JavaScript 逆向可以分为三大部分寻找入口调试分析和模拟执行。寻找入口 这是非常关键的一步逆向在大部分情况下就是找一些加密参数到底是怎么来的比如请求中的 token sign 等参数到底在哪里构造的 这个关键逻辑可能写在某个关键的方法里面或者隐藏在某个关键的变量里面。一个网站加载了很多 JS 文件那么怎么从这么多 JS 代码里面找到关键的位置那就是一个关键的问题这就是寻找入口调试分析 找到入口后比如我们可以定位到某个参数可能是在某个方法里面执行了的那么里面的逻辑究竟是怎样的里面调用了多少加密算法经过了多少变量赋值和转换等这些需要我们先把整体思路整理清楚以便于我们后面进行模拟调用或者逻辑改写。在这个过程中我们主要借助于浏览器的调试工具进行断点调试分析或者借助一些反混淆工具进行代码的反混淆模拟执行 经过调试分析之后我们差不多已经搞清楚整个逻辑了但我们最终目的还是写爬虫怎么爬取到数据才是根本因此这里就需要对整个加密过程进行逻辑复写或者模拟执行以把整个加密流程模拟出来比如输入一些已知变量调用之后我们就可以拿到一些 token 内容再用这个 token 进行数据爬取即可————————————————一、寻找入口1.1 查看请求一般来说我们都是先分析想要的数据到底从哪里来的数据肯定是某个请求返回的可以打开浏览器开发者工具打开 Network 面板然后点击搜索按钮去搜索我们可以看到对应的搜索结果点击搜索到的结果我们可以定位到响应结果的位置找到对应的响应后就会知道是哪个请求发起的了————————————————1.2 搜索参数一般来说请求带有加密参数常见有 sign 或者 token要构造请求首先需要获取加密参数最简单有效的方法就是通过全局搜索参数名大多数情况下就是一个普通的字符串如 token 我们可以通过搜索 tokentoken:token :token 等等通过一步步分析定位对应的 JavaScript 文件中通过断点来判断是否正确————————————————1.3 分析发起调用上述搜索参数是一种查找入口的方式这是从源码级别上直接查找但是当搜索得到的结果过多时效果自然就会很差我们可以通过分析发起请求来获取逻辑在请求框中有一个 Initiator在这里可以查看当前请求构造的相关逻辑这个请求都经过了哪些调用也就是调用发起方的一步步执行流程通过单机右侧 内容可以直接定位到相关位置源码————————————————1.4 断点另外我们还可以通过断点进行入口查找比如 XHR 断点 DOM 断点事件断点等。我们可以在开发者工具中的 Sources 面板里面添加设置————————————————1.5 HookHook 也是一个非常常用的查找入口的功能。有时候一些代码搜索或者断点并能很有效的找到对应的入口位置这时候就可以使用 Hook 了比如说我们可以对一些常用的加密和编码算法常用的转换操作都进行一些 Hook 比如说 Base64编码 Cookie 的赋值 JSON 的序列化等比较方便的 Hook 方式就是通过 TamporMonkey 这个插件实现使用它我们不仅可以方便的自定义脚本执行时间点也可以引入一些额外的脚本辅助 Hook 进行代码编写需要篡改猴油猴Home | Tampermonkey1.6 其他以上便是一些常见的分析入口的方法当然还有很多其他方法比如使用 Pyppeteer, PlayWright 里面内置的 API 实现那一些数据拦截和过滤功能 也可以使用一些抓包软件对一些请求进行拦截和分析还可以使用一些第三方工具或浏览器来辅助分析二、调试分析2.1 格式化一般来说许多的 JavaScript 代码都是经过打包和压缩的多数情况下我们可以使用 Sources 面板下 JavaScript 窗口左下角的格式化按钮对代码进行格式化除此之外有一些网站的 HTML 和 JavaScript 是混杂在一起的我们可以使用 JavaScript Beautifier 相关工具Online JavaScript beautifier 可以得到格式化后的代码————————————————2.2 断点调试代码格式化后我们就进入了正式的调试流程基本操作是给想要调试的代码添加断点同时在对应的面板里观察对应变量的值————————————————三、模拟执行经过一系列的调试现在我们已经可以理清其中的逻辑了 接下来就是一些调用执行的过程了。3.1 Python 改写和模拟执行由于 Python 简单易用同时也能够模拟调用执行 JS 如果整体逻辑不复杂的话我们可以尝试使用 Python 来把整个加密流程完整的实现一遍。如果整体流程相对复杂我们可以尝试使用 Python 来模拟调用 JS 来执行————————————————3.2 JS 模拟执行 API由于整个逻辑是 JS 实现的使用 Python 来执行 JS难免会有一些不太方便的地方。而 Node.js 天生就有对 JS 的支持。为了更通用的实现 JS 的模拟调用 我们可以用 express 来模拟调用 JS 同时将其暴露成一个 API 从而可以实现跨语言调用————————————————3.3 浏览器模拟执行由于整个逻辑是运行在浏览器里面的我们当然也可以将浏览器当做整个执行环境。比如使用 Selenium PlayWright 等来尝试执行一些 JS 代码得到一些返回结果————————————————四、JavaScript 逆向爬取实战目标网站Scrape | Movie目标获取列表页和详细页信息4.1 列表页列表页请求构造中有三个参数只有 token 需要获取通过请求的发起程序 Initiator 可以观察得到在调用栈的 onFetchData 中获得的通过分析断点分析关键代码如图所示import hashlib import time import requests import base64 def get_token(): timestamp str(int(time.time())) str1 f/api/movie,{timestamp}.encode(utf-8) #哈希结果计算 str2 (hashlib.sha1(str1).hexdigest() f,{timestamp}).encode(utf-8) #base64 第二层加密 final base64.b64encode(str2).decode(utf-8) return final def get_page(offset): url fhttps://spa6.scrape.center/api/movie/?limit10offset{offset}token{get_token()} resp requests.get(url) return resp.json() if __name__ __main__: resp get_page(10) print(resp)4.2 详细页详细页有两个加密一个是 url加密一个是 token 加密在这里我们先解决 url 加密在这里详细页进行了 url 加密通过查看页面源码可以看到在没有点击之前详细页链接的 href 里面就带有加密后的 url 了因此这个加密请求是在 Ajax 请求完成之后生成了而且可以肯定的是由 JavaScript 生成的在这里可以通过勾选 Sources 面板中 事件侦听器断点中的 XHR 中的 readystatechange 来获取 Ajax 得到响应时的事件删除其他断点刷新页面通过一步步调试进而发现请求的构造但是这样会非常非常繁琐我们可以通过发现规律的方式来快速找到目标代码通过观察加密后的 url 可以发现加密部分结尾基本都有 可以推测采用 Base64 进行编码而使用 Base64 编码基本会使用到 window.btoa 方法所以我们可以使用 Hook 的方式定位代码// UserScript // name HookBase64 // namespace asdsadasd // version 2024-08-15 // description try to take over the world! // author zhouyi // match https://spa6.scrape.center/ // grant none // /UserScript (function() { use strict; // Your code here... function hook(object, attr){ var func object[attr]; object[attr] function(){ console.log(hooked, object, attr); var ret func.apply(object, arguments); debugger; return ret; } } hook(window, btoa) })();Hook后刷新页面可以发现定点通过观察作用域的值可以发现第一个并不是目标的加密 url其通过一个固定字符串搭配 123 等序号的方式构造一个字符串然后利用字符串去 Base64 编码获取最后的字符串固定代码为 ef34#teuq0btua#(-57w1q5o5--j98xygimlyfxs*-!i-0-mb序号在列表页获取到的 json 数据之中第二个便是 token 的构造观察发现除了 /api/movie 被替换成 /api/movie/{加密后的url部分} 外其他并无变化import hashlib import time import requests import base64 def get_token(encrypt_url): timestamp str(int(time.time())) str1 f/api/movie/{encrypt_url},{timestamp}.encode(utf-8) str2 (hashlib.sha1(str1).hexdigest() f,{timestamp}).encode(utf-8) final base64.b64encode(str2).decode(utf-8) return final def get_url(id): encrypt_url (ef34#teuq0btua#(-57w1q5o5--j98xygimlyfxs*-!i-0-mb str(id)).encode(utf-8) encrypt_url base64.b64encode(encrypt_url).decode(utf-8) url fhttps://spa6.scrape.center/api/movie/{encrypt_url}/?token{get_token(encrypt_url)} resp requests.get(url) return resp if __name__ __main__: resp get_url(1) print(resp.json())————————————————
返回列表