JavaScript逆向技巧总结
前面我们已经学习了不少 JavaScript 逆向相关的知识,包括浏览器调试、Hook、AST、无限 debugger 的绕过以及 JavaScript 的模拟调用等,这些知识点都比较松散,有时候大家学完了可能觉得没有形成一个知识体系,或者说没有一个常规 “套路” 来应对一些 JavaScript 逆向的处理流程。
本节中,我们就对前面的知识点做一个串联和总结,总结出 JavaScript 逆向过程中常用的一个流程,这个流程适用于大多数 JavaScript 逆向过程。大家熟练运用之后,可以在不同情况下运用不同的技巧来进行 JavaScript 逆向操作。
总的来说,JavaScript 逆向可以分为三大部分:寻找入口、调试分析和模拟执行。下面我们来分别介绍。
-
寻找入口:这是非常关键的一步,逆向在大部分情况下就是找一些加密参数到底是怎么来的比如一个请求中
token、sign等参数到底是在哪里构造的,这个关键逻辑可能写在某个关键的方法里面或者隐藏在某个关键变量里面。一个网站加载了很多 JavaScript 文件,那么怎么从这么多 JavaScript 代码里面找到关键的位置,那就是一个关键问题。这就是寻找入口。 -
调试分析:找到入口之后,比如说我们可以定位到某个参数可能是在某个方法里面执行的了 ,那么里面的逻辑究竟是怎样的,里面调用了多少加密算法,经过了多少变量赋值和转换等,这些我们需要先把整体思路搞清楚,以便于我们后面进行模拟调用或者逻辑改写。在这个过程中,我们主要借助于浏览器的调试工具进行断点调试分析或者借助于一些反混淆工具进行代码的反混淆等。
-
模拟执行:经过调试分析之后,我们差不多已经搞清楚整个逻辑了,但我们的最终目的还是写爬虫,怎么爬到数据才是根本,因此这里就需要对整个加密过程进行逻辑复写或者模拟执行,以把整个加密流程模拟出来,比如输入是一些已知变量,调用之后我们就可以拿到一些
token内容,再用这个token来进行数据爬取即可。
本节中,我们就来对以上内容进行梳理。
寻找入口
首先,我们来看下怎么寻找入口,其中包括查看请求、搜索参数、分析发起调用、断点、Hook 等操作,下面我们来分别介绍一下。
查看请求
一般来说,我们都是先分析想要的数据到底是从哪里来的。比如说对于示例网站 https://spa6.scrape.center/ ,我们可以看到首页有一条条数据,如 “霸王别姬”、“这个杀手不太冷” 等,这些数据肯定是某个请求返回的,那它究竟是从哪个请求里面来的呢?我们可以先尝试搜索下。
打开浏览器开发者工具,打开 Network 面板,然后点击搜索按钮,比如这里我们就搜索 “霸王别姬” 这四个字,如图 11-119 所示。
图 11-119 搜索 “霸王别姬”
此时可以看到对应的搜索结果,点击搜索到的结果,我们就可以定位到对应的响应结果的位置, 如图 11-120 所示。
图 11-120 定位到对应的响应结果的位置
找到对应的响应之后,我们也就可以顺便找到是哪个请求发起的了,如图 11-121 所示。
图 11-121 对应请求
比如,这单我们就顺利找到想要的数据所对应的请求位置了,可以看到这是一个 GET 请求,同时还有一个 token 参数,我们可以在后面继续分析。
一般来说,我们可以通过这种方法来尝试寻找最初的突破口。如果这个请求带有加密参数,就顺着继续找下这个参数究竟是在哪里生成的。如果这个请求对应的参数甚至都没有什么加密参数,那么这个请求都可以直接模拟爬取了。
搜索参数
在上一步中,我们找到了最初的突破口,也就是关键请求是怎么发起的,带有什么加密参数。比如,在上面的例子中,我们发现这里有一个关键的加密参数 token,那这又是怎么构造出来的呢?
一种简单有效的方法就是直接进行全局搜索。一般来说,参数名大多数情况下就是一个普通的字符串,比如这里就叫作 token,那么这个字符串肯定隐藏在某个 JavaScript 文件里面,我们可以尝试进行搜索,也可以加冒号、空格、引号等来配合搜索。因为一般来说这个参数通常会配合一些符号一起出现,比如说我们可以搜索 token、token:、token :、"token": 等。
在哪里搜索呢?我们可以直接利用浏览器调试面板的搜索功能,如图11-122所示。
图 11-122 浏览器调试面板的搜索功能
这是一个资源搜索的入口,比如可以搜索下载下来的 JavaScript 文件的内容,这里我们输入 token 来进行搜索,结果如图11-123所示。
图 11-123 搜索结果
这样我们就可以找到一些关键的位置点了,一共五个结果,结果不多,我们可以进一步点击并定位到对应的 JavaScript 文件中,然后进一步进行分析。
分析发起调用
上述的搜索是其中一种查找入口的方式,这是从源码级别上直接查找。当然,我们也可以通过其他的思路来查找入口,比如可以查看发起调用的流程,怎么查看呢?
可以直接从 Network 请求里面的 Initiator 查看当前请求构造的相关逻辑,如图 11-124 所示。
图 11-124 从 Initiator 查看当前请求构造的相关逻辑
把光标对应到 Initiator 这一列,就会出现发起这个请求都经过了哪些调用,也就是调用发起方的一步步热行流程,如图 11-125 所示。
右侧显示了一步步调用对应的源码的位置,我们可以顺次点进去找到对应的位置,比如这里第 8 层调用里面有一个 onFetchData 方法。点击右侧的代码位置,就可以找到一些相关逻辑,如图 11-126 所示。
图11-125 调用发起方的一步步执行流程
图11-126 相关逻辑
这里可以看到一些 token 相关的逻辑调用过程了。
断点
另外,我们还可以通过一些断点来进行入口的查找,比如 XHR 断点、DOM 断点、事件断点等。我们可以在开发者工具 Sources 面板里面添加设置,比如这里我们就添加了 XHR 断点和全局 Load 事件断点,如图 11-127 所示。
图 11-127 添加 XHR 断点和全局 Load 事件断点
这样网页就可以在整个网页加载之后和发起 Ajax 请求的时候停下来,进入断点调试模式。也就是说,通过浏览器强大的断点调试功能,我们也可以找到对应的入口。
调试分析
找到对应的入口位置之后,接下来我们就需要进行调试分析了。在这个步骤中,我们通常需要进行一些格式化、断点调试、反混淆等操作来辅助整个流程的分析。
格式化
格式化这个流程是非常重要的,它可以大大增强代码的可读性,一般来说很多 JavaScript 代码都是经过打包和压缩的。多数情况下,我们可以使用 Sources 面板下 JavaScript 窗口左下角的格式化按钮对代码进行格式化,如图11-128所示。
图 11-128 格式化按钮
另外,有一些网站的 HTML 和 JavaScript 是混杂在一起的,比如 https://spa8.scrape.center/ ,如图11-129 所示。
图 11-129 HTML 和 JavaScript 混杂在一起
可以看到,这里 JavaScript 代码被压缩成一行,并且放在 script 节点里面,这时候我们就需要手动复制出来,然后用一些格式化工具进行格式化。可以搜索 JavaScript Beautifier 相关工具,比如 https://beautifier.io/ ,然后把 JavaScript 代码粘贴进去,此时即可看到格式化之后的 JavaScript 代码,如图 11-130 所示。
图 11-130 格式化之后的 JavaScript 代码
另外,我们还可以选择一些格式化选项,比如缩进、换行等。
断点调试
代码格式化之后,我们就可以进入正式的调试流程了,基本操调试的代码漆加断点,同时在对应的面板里面观察对应变量的值。
如图 11-131 所示,这里我们在第 169 行添加断点,然后逐行运行对应的代码,这时代码页面就会出现对应变量的值,同时我们也可以在 Watch 面板上监听关注的变量。
图11-131 在第 169 行添加断点
通过这样的方式。我们就可以对整个代码的执行流程有一个大致的了解。关于断点调试的使用,可以参考 11.2 节和后续实战章节。
反混淆
在某些情况下,我们还有可能遇到一些混淆方式,比如控制流扁平化、数组移位等。对于一些特殊的混淆,我们可以尝试使用 AST 技术来对代码进行还原。
比如说,案例 https://antispider10.scrape.center/ 就使用控制流扁平化的方式对代码进行混淆,如图 11-132 所示。
图 11-132 使用控制流扁平化的方式对代码进行混淆
可以看到,这里有一个 while 循环,循环内通过一些判断条件执行某些逻辑,有的逻辑放在了 if 区块,有的逻辑放在了 else 区块,还有的逻辑放在了 catch 区块,这就导致我们无法一下子了解这几个区块的真正执行顺序。
对于此类混淆,为了更好地还原其真实执行逻辑,我们可以尝试使用 AST 进行还原,具体的实现流程可以参考 11.9 节的内容。
模拟执行
经过一系列调试,现在我们已经可以厘清其中的逻辑了,接下来就是一些调用执行的过程了。在前面的章节中,我们已经讲过一些案例执行的流程了。
Python 改写或模拟执行
由于 Python 简单易用,同时也能够模拟调用执行 JavaScript。如果整体逻辑不复杂的话,我们可以尝试使用 Python 来把整个加密流程完整实现一遍。如果整体流程相对复杂,我们可以尝试使用 Python 来模拟调用 JavaScript 的执行。具体的内容可以参考11.5节。