2024最新版JavaScript逆向爬虫教程-------基础篇之无限debugger的原理与绕过

目录

  • 一、无限debugger的原理与绕过
    • 1.1 案例介绍
    • 1.2 实现原理
    • 1.3 绕过debugger方法
      • 1.3.1 禁用所有断点
      • 1.3.2 禁用局部断点
      • 1.3.3 替换文件
      • 1.3.4 函数置空与hook
  • 二、补充
    • 2.1 改写JavaScript文件
    • 2.2 浏览器开发者工具中出现的VM开头的JS文件是什么?
  • 三、实战

一、无限debugger的原理与绕过

debugger 是 JavaScript 中定义的一个专门用于断点调试的关键字,只要遇到它,JavaScript 的执行便会在此处中断,进入调试模式。有了 debugger 这个关键字,我们就可以非常方便地对 JavaScript 代码进行调试,比如使用 JavaScript Hook 时,我们可以加入 debugger 关键字,使其在关键的位置停下来,以便查找逆向突破口。但有时候 debugger 会被网站开发者利用,使其成为阻挠我们正常调试的拦路虎。本节中,我们介绍一个案例来绕过无限 debugger。

1.1 案例介绍

案例介绍:我们先看一个案例,网址是 http://shanzhi.spbeen.com/,打开这个网站,一般操作和之前的网站没有什么不同。但是,一旦我们打开开发者工具,就发现它立即进入了断点模式,如下图所示:

在这里插入图片描述
我们既没有设置任何断点,也没有执行任何额外的脚本,它就直接进入了断点模式。这时候我们可以点击 Resume script execution(恢复脚本执行)按钮,尝试跳过这个断点继续执行,如下图所示:

请添加图片描述
然而不管我们点击多少次按钮,它仍然一次次地进入断点模式,无限循环下去,我们称这样的情况为无限 debugger。怎么办呢?似乎无法正常添加断点调试了,有什么解决办法吗? 办法当然是有的,本节中我们就来总结一下无限 debugger 的应对方案,在后面部分实战的案例中我们也会遇到无限 debugger。

1.2 实现原理

首先要做的是找到无限 debugger 的源头,上面的案例通过堆栈回溯,查看 debugger 是如何生成的,如下图所示:
在这里插入图片描述
继续往上进行追溯,如下图所示:
在这里插入图片描述
这时点击左下角的格式化按钮:

setInterval(()=>{(function(a) {return (function(a) {return (Function('Function(arguments[0]+"' + a + '")()'))})(a)})('bugger')('de', 0, 0, (0,0));
}
, 1000);

利用 Function 产生 debugger,然后通过 setInterval 循环,每秒执行1次产生 debugger 语句的操作。当然,还有很多类似的实现,比如无限 for 循环、无限 while 循环、无限递归调用等,它们都可以实现这样的效果,原理大同小异。ps:从某种意义上来说,无限 debugger 不会真正的死循环(只不过这个执行次数多到我们本身靠手点难以接受罢了),而是有规律得执行逻辑,一般用定时器。 无限 debugger 产生小结:

  1. 一定会先产生 debugger 关键字,产生 debugger 关键字,可以是明文也可以混淆。

    // ① 明文 直接书写完整的 debugger
    debugger;
    // ② 可以混淆(可轻度混淆) 即eval配合 debugger
    eval('debug' + 'ger;')
    // ③ 可以重度混淆
    // 结合constructor,debugger,call,apply,action 等关键字进行混淆,增加调试的困难
    Function('debugger').call()
    Function('debugger').apply()
    Function('debugger').bind()
    Function.constructor('debugger').call('action')
    funObj.constructor('debugger').call('action')
    (function(){return !![];}['constructor']('debugger')['call']('action'))
    eval('(function (){}["constructor"]("debugger")["call"]("action"));')
    //总结:这些debugger方法,是实现debugger的基础,可以理解为是三元素。基于三种元素,可以形成多种多样的玩法
    
  2. 结合循环,循环的方式可以是:while/for 循环、包含 debugger 的函数调用自身、方法间的循环调用、计时器(setInterval)

1.3 绕过debugger方法

因为 debugger 其实就是对应的一个断点,它相当于用代码显示地声明了一个断点,要解除它,我们只需要禁用这个断点就好了。

1.3.1 禁用所有断点

全局禁用开关位于 Sources 面板的右上角,叫做 Deactivate breakpoints,如下图所示:
在这里插入图片描述
点击它,该按钮会被激活,变成蓝色,如下图所示:
在这里插入图片描述
这个时候我们再重新点击一下 Resume script execution(恢复脚本执行)按钮,跳过当前断点,页面就不会再进入到无限 debugger 的状态了。但是这种全局禁用其实并不是一个好的方案,因为禁用之后我们也无法在其他位置增加断点进行调试了,所有的断点都失效了!

ps: 解决无限 debugger 名词解释应该为在没有 debugger 干扰的情况下调试,而不是放弃所有的 debugger 调试(也就是说我们自己的调试还得能正常使用),所以此种方式基本不用。

1.3.2 禁用局部断点

取消刚才的 Deactivate breakpoints 模式,页面会重新进入无限 debugger 模式,尝试使用另一种方法来跳过这个无限 debugger。在 debugger 语句所在的行的行号上单击鼠标右键,此时会出现一个快捷菜单,如下图所示:
在这里插入图片描述
这里有一个 Never pause here 选项,意思是从不在此处暂停。选择这个选项,于是页面变成如下图所示的样子:

在这里插入图片描述
当前断点显示为橙色,并且断点前面多了一个 ? 符号,同时 Breakpoints 也出现了刚才添加的断点位置,这时再次点击 Resume script execution(恢复脚本执行)按钮,就可以发现我们不会再进入无限 debugger 模式了。当然,我们也可以选择另外一个选项 Add conditional breakpoint,如下图所示:

这个模式更加高级,我们可以设置进入断点的条件,比如在调试过程中,期望某个变量的值大于某个具体的值的时候才停下来。但在本案例中,由于这里是无限循环,我们没有什么具体的变量可以作为判定依据,因此可以直接写一个简单的表达式来控制。选择 Add conditional breakpoint 选项,直接填入 false 然后回车即可,如下图所示:

在这里插入图片描述

1.3.3 替换文件

利用 Overrides 面板我们可以将远程的 JavaScript 文件替换成本地的 JavaScript 文件,这里我们依然可以使用这个方法来对文件进行替换,替换成什么呢?很简单,我们只需要在新的文件里把 debugger 这个关键字删除。我们将当前的 JavaScript 文件复制到文本编辑器中,删除或者直接注释掉 debugger 这个关键字,修改如下:

setInterval(()=>{(function(a) {return (function(a) {return (Function('Function(arguments[0]+"' + a + '")()'))})(a)}// 直接把参数置空,当然这里也可以把整个文件替换掉(没啥业务逻辑),或者去掉setInterval等都可以)('')('', 0, 0, (0,0));
}
, 1000);

打开 Sources 面板下的 Overrides 面板,将修改后的完整 JavaScript 文件复制进去。替换完成之后,重新刷新网页,这时候发现不会进入无限 debugger 模式了。如果该操作不熟悉,可以参照下面的 3.1 改写JavaScript文件。

1.3.4 函数置空与hook

ps:一定要在 debugger 进入之前。

无限 debugger 产生的原因是定时器造成的,所以我们可以重写这个函数,使无限 debugger 失效:

// 这里是业务代码和setInterval无关,所以直接置空即可
setInterval = function(){} //定时器置空,置空之后上面的无限debugger消失
function xxx(){} //执行函数置空 xxx

② hook:不同的情况书写不同的 hook 代码,即 hook 不同的函数即可,这里我只以 setInterval 为例,其他类似。

_setInterval = setInterval
setInterval = function (a,b) {if(a.toString().indexOf('debugger') == -1){return function(){}}else{_setInterval(a,b)}
}Function.prototype.toString = function () {return `function ${this.name}() { [native code] }`
}

小结:

  1. 优先尝试禁用局部断点,即 Never pause here (最方便快捷,但是最卡–深有体会,也最容出问题)

  2. 次优先尝试重写调用函数,缺陷:容易破坏业务逻辑,导致控制流变化。如:

    Function = function(){}
    setInterval = function(){}
    
  3. 文件替换。缺陷:操作稍微有一点点的麻烦,对动态情况的支持不太好,也可能会改变控制流走向

  4. 万一以上三个都难受了怎么办? 别逆向了,反调试都那么难了,那加密不得难上天啊。放弃吧,嗷~

二、补充

2.1 改写JavaScript文件

我们知道,一个网页里面的 JavaScript 是从对应服务器上下载下来并在浏览器执行的。有时候,我们可能想要在调试的过程中对 JavaScript 做一些更改,比如说有以下需求:

  1. 发现 JavaScript 文件中包含很多阻挠调试的代码或者无效代码、干扰代码,想要将其删除(如上面的无限 debugger)。
  2. 调试到某处,想要加一行 console.log 输出一些内容,以便观察某个变量或方法在页面加载过程中的调用情况。在某些情况下,这种方法比打断点调试更方便。
  3. 调试过程遇到某个局部变量或方法,想要把它赋值给 window 对象以便全局可以访问或调用。
  4. 在调试的时候,得到的某个变量中可能包含一些关键的结果,想要加一些逻辑将这些结果转发到对应的目标服务器。

这时候我们可以试着在 Sources 面板中对 JavaScript 进行更改,但这种更改并不能长久生效,一旦刷新页面,更改就全都没有了。比如我们在 JavaScript 文件中写入一行 JavaScript 代码,然后保存,如下图所示:

在这里插入图片描述

注意:点击了左下角的格式化按钮后,不能向格式化的文件中添加内容。

这时候我们可以发现 JavaScript 文件名左侧上出现了一个警告标志,提示我们做的更改是不会保存的。这时候重新刷新一下页面,再看一下更改的这个文件,如下图所示:

有什么方法可以修改呢?其实有一些浏览器插件可以实现,比如:ReRes。在插件中,我们可以添加自定义的 JavaScript 文件,并配置 URL 映射规则,这样浏览器在加载某个在线 JavaScript 文件的时候就可以将内容替换成自定义的 JavaScript 文件了。另外,还有一些代理服务器也可以实现,比如 Charles、Fiddler,借助它们可以加载 JavaScript 文件时修改对应 URL 的响应内容,以实现对 JavaScript 文件的修改。其实浏览器的开发者工具已经原生支持这个功能了,即浏览器的 Overrides 功能,它在 Sources 面板左侧,如下图所示:

我们可以在 Overrides 面板上选定一个本地的文件夹,用于保存需要更改的 JavaScript 文件,下面来实际操作一下。切到 Overrides 面板,点击 + 按钮,如下图所示:

这时候浏览器会提示我们选择一个本地文件夹,用于存储要替换的 JavaScript 文件。这里我选定了一个新建的文件夹:FunddbOverrides,注意这时候可能会遇到下图所示的提示,如果没有问题,直接点击 允许 即可。

在这里插入图片描述

这时,在 Overrides 面板下就多了 FunddbOverrides 文件夹,用于存储所有我们想要更改的 JavaScript 文件,如下图所示:

我们可以看到,现在所在的 JavaScript 选项卡是 app.d0a16ab3b7972174cc88.js:formatted,代码已经被格式化了。因为格式化后的代码是无法直接在浏览器中修改的,所以为了方便,我把格式化后的文件复制到了 Notepad++ 中,然后把 window.eval 这行代码注释了,如下图所示:

在这里插入图片描述

接着把修改后的内容替换到原来的 JavaScript 文件中。这里要注意,要切换到 app.d0a16ab3b7972174cc88.js 文件才能修改,直接替换 JavaScript 文件的所有内容即可,如下图所示:

在这里插入图片描述

替换完毕之后 ctrl + s 保存,这时候再切换回 Overrides 面板,就可以发现成功生成了新的 JavaScript 文件,它用于替换原有的 JavaScript 文件,如下图所示:

在这里插入图片描述

替换完成之后,重新刷新网页,正如我们所料,这时候不会再进入无限 debugger 模式了,证明改写 JavaScript 成功!而且刷新页面也不会丢失了,除了注释掉干扰代码外,在一些场景下,我们还可以增加一些 JavaScript 逻辑,比如直接将某个变量的结果通过 API 发送到远程服务器,并通过服务器将数据保存下来,也就完成了直接拦截 Ajax请求并保存数据的过程了,修改 JavaScript 文件有很多用途,此方案可以为我们进行 JavaScript 逆向带来极大便利。

2.2 浏览器开发者工具中出现的VM开头的JS文件是什么?

在 Chrome 的开发者工具中,你可能会看到一些以 VM 开头的 JavaScript 文件(如 VM1057)。
在这里插入图片描述
VM 表示的是 Virtual Machine(虚拟机),这些文件通常表示由浏览器生成和执行的虚拟机脚本环境中的临时脚本。这些脚本并不是项目源代码的一部分,也不是实际存在的物理文件,它们在浏览器的内存中创建并执行。 比如说,当你在调试一个网页时,如果在某些动态生成并执行的 JS 代码上设定了断点,Chrome 调试器会在一个以 VM 开头的文件中显示这些代码,例如 VM1057。这个 VM 文件的存在只是为了调试目的,它并不存在于服务器端,也不会被存储在本地,而是存在于浏览器内存中。一般情况下,这类文件的出现是因为浏览器对 JavaScript 代码的处理方式,如动态编译或者 JavaScript 堆栈跟踪。出现的原因:

  1. 动态执行的 JavaScript 代码。比如通过 eval 函数或者 new Function 方法,Chrome 浏览器会创建一个 VM 文件来展示这段临时执行的代码。比如某个网页因为反爬虫,动态生成了 debugger,这些断点并没有直接写在服务器上的原始 JavaScript 文件中,而是在某些 JavaScript 代码的执行过程中被生成,并因此触发 debugger。这些代码也会在执行时被浏览器视为临时的 VM 脚本,并在执行到 debugger 时暂停执行,从而造成所谓的 无限 debugger 循环
  2. 来自执行栈的代码。有时候,当 JavaScript 引擎处理异步操作(例如 Promise、setTimeout 等)中的错误时,错误堆栈可能包含到 VM 脚本的引用,这是因为内部错误回调函数是在虚拟环境中执行的。

三、实战

F12 打开调试工具,打上 script 断点,如下图所示:
在这里插入图片描述
接着一直单击 Resume script execution(恢复脚本执行)按钮,直到左边 Page 下方 top 中,出现我们要分析网站的域名为止(因为有时候会受浏览器等插件的影响,不是在我们要分析的网站下断住的),如下图所示:

接着放开 script 断点,再次点击 Resume script execution(恢复脚本执行)按钮,跳到如下位置:

过这个 debugger 很简单了,使用改写 JavaScript 文件的方式即可,在 1.3.3 替换文件 已经详细讲解过,这里就不再进行赘述。替换完成之后,我们重新刷新一下网页,又出现一个 debugger,如下图所示:

优先使用禁用局部断点的方式,即鼠标放在 debugger 行号处,单击鼠标右键,选择 Never pause here,发现是可以过掉的,这里也可以采用其他方式,在产生 debugger 之前,选择堆栈 K 的时机执行如下代码:

// 因为这里产生debugger的逻辑比较复杂,我就不去分析了,写起来很累
_Function = Function
Function.prototype.constructor = function () {if (arguments[0].indexOf('debugger') !== -1) {return _Function('')}return _Function(arguments[0])
}

我这里为了简便就直接采用禁用局部断点的方式了,接着单击 Resume script execution(恢复脚本执行)按钮,发现又出现了新的 debugger,如下图所示:



发现这里在调用 document 的 appendChild 方法,目的是向 n.head 中添加元素,控制台看一下小 o,如下所示:
在这里插入图片描述
到这里我们就知道此处产生 debugger 的原因了,就是使用 appendChild 方法往页面动态插入 script 标签,然后 script 标签对中包含 debugger 关键字,故此处采取的方案,直接将 appendChild 方法重写即可,重写代码如下:

let oldAppendChild = Node.prototype.appendChild
Node.prototype.appendChild = function () {if (arguments[0].innerHTML && arguments[0].innerHTML.indexOf('debugger') !== -1) {arguments[0].innerHTML = ''}return oldAppendChild.apply(this, arguments)
}

在 debugger 出现之前(即在堆栈处于 b 或者 le 时都可以,或者更早之前都行)执行此代码,发现是可以过掉 debugger 的,然后调试到了如下位置:
在这里插入图片描述
这个就很简单了,写死的 debugger + 固定的循环次数,也没有相关的业务逻辑代码,故直接使用文件替换的方式直接替换即可,我是将 for 循环整体进行了替换,替换之后,同样,重新刷新一下网页,发现又到了 appendChild 那里,采取上面重写 appendChild 方法的方案即可,接着单击 Resume script execution(恢复脚本执行)按钮,进入到一个新的 debugger 调试,如下:

优先使用禁用局部断点的方式,即鼠标放在 debugger 行号处,单击鼠标右键,选择 Never pause here,如下图所示:

发现是可以过掉的,这里也可以采用其他方式,追溯到 debugger 产生的源头:


可以看到此处的 debugger 为 eval 结合 debugger 关键字实现,并且观察该代码中无业务逻辑代码,故直接重写 eval 函数即可,重写代码如下:

old_eval = eval
eval = function () {if (arguments[0].indexOf('debugger') !== -1) {return function () {}} else {return old_eval(arguments[0])}
}Function.prototype.toString = function () {return `function ${this.name}() { [native code] }`
}

至此此题目的所有涉及到的 debugger 调试我们都已经过掉,可以正常地进行接口分析了。

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

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

相关文章

一篇文章掌握所有国债期货的基本交易策略介绍

国债期货是一种基本的利率衍生品,根据交易者交易目的不同,可以将期货交易行为分为三类:套期保值、套利交易和投机交易。套期保值是投资者为了避免现有或将来预期的投资组合价值受市场利率变动的影响,而在国债期货市场上采取抵消性…

2023年30米分辨率土地利用遥感监测数据

改革开放以来,中国经济的快速发展对土地利用模式产生了深刻的影响。同时,中国又具有复杂的自然环境背景和广阔的陆地面积,其土地利用变化不仅对国家发展,也对全球环境变化产生了深刻的影响。为了恢复和重建我国土地利用变化的现代…

六、Redis五种常用数据结构-zset

zset是Redis的有序集合数据类型,但是其和set一样是不能重复的。但是相比于set其又是有序的。set的每个数据都有一个double类型的分数,zset正是根据这个分数来进行数据间的排序从小到大。有序集合中的元素是唯一的,但是分数(score)是可以重复的…

LeetCode416:分割等和子集

题目描述 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 解题思想 [1,5,11,5] 和为22,其中一半为 11。如果能寻找到若干数的和为11则成立可以抽象为一个0-1背包问题:容…

浮点数的由来及运算解析

数学是自然科学的皇后,计算机的设计初衷是科学计算。计算机的最基本功能是需要存储整数、实数,及对整数和实数进行算术四则运算。 但是在计算机从业者的眼中,我们知道的数学相关的基本数据类型通常是整型、浮点型、布尔型。整型又分为int8&a…

给centos机器打个样格式化挂载磁盘(新机器)

文章目录 一、先安装lvm2二、观察磁盘三、磁盘分区四、建PV五、建VG六、创建LV七、在LV上创建文件系统八、挂载到/home(1)临时挂载(2)永久挂载 九、最后reboot一下 一、先安装lvm2 yum install lvm2二、观察磁盘 三、磁盘分区 四…

Springboot整合 Spring Cloud Alibaba Seata

1.事务简介 事务是访问并可能更新数据库中各种数据项的一个程序执行单元。在关系型数据库中,一个事务由一组sql语句组成。事务具有 原子性,一致性,隔离性,持久性四个属性(ACID)。 原子性:事务是一个不可分割的工作单位…

ThreadLocal 源码详解

概述 ThreadLocal是一个java提供的本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作…

美国政府首次发布《国家网络安全态势报告》

报告提到,不断演变的关键基础设施风险、勒索软件、供应链利用、商业间谍软件和AI是主要趋势;国家网络总监办公室同时公布了第二版《国家网络安全战略实施计划》,新增了31项倡议。 前情回顾美国深化网络安全战略 美国发布国家网络安全战略实施…

快团团怎么做帮卖团长/供货大团长(如何从小白到优质团长)?

一名小白想要成长为快团团的优质团长,可以遵循以下步骤和策略: 了解平台与注册成为团长: 首先,熟悉快团团平台的操作流程和规则。快团团是一个基于微信的小程序,专注于社区团购业务。通过微信扫描团长资源二维码或在快…

【爬虫基础1.1课】——requests模块上

目录索引 requests模块的作用:实例引入: 特殊情况:锦囊1:锦囊2: 这一个栏目,我会给出我从零开始学习爬虫的全过程。感兴趣的小伙伴可以关注一波,用于复习和新学都是不错的选择。 那么废话不多说&#xff0c…

​​​​【收录 Hello 算法】5.2 队列

目录 5.2 队列 5.2.1 队列常用操作 5.2.2 队列实现 1. 基于链表的实现 2. 基于数组的实现 5.2.3 队列典型应用 5.2 队列 队列(queue)是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即…

利用香港多IP服务器进行大数据分析的潜在优势?

利用香港多IP服务器进行大数据分析的潜在优势? 在当今数据驱动的时代,大数据分析已经成为企业获取竞争优势的不二选择。而香港作为一个拥有世界级通信基础设施的城市,提供了理想的环境来部署多IP服务器,从而为大数据分析提供了独特的优势。…

2024中国(重庆)人工智能展览会8月举办

2024中国(重庆)人工智能展览会8月举办 邀请函 主办单位: 中国航空学会 重庆市南岸区人民政府 招商执行单位: 重庆港华展览有限公司 【报名I59交易会 2351交易会 9466】 展会背景: 2024中国航空科普大会暨第八届全国青少年无人机大赛在…

Spring Framework-简介

Spring Framework Java Spring是一个开源的Java应用框架,它的主要目的是简化企业级应用开发的复杂性。Spring框架为开发者提供了许多基础功能,使得开发者能够更专注于业务逻辑的实现,而不是底层的细节。 主要特点和功能: 控制反…

数据库脚本编写规范(SQL编写规范)

编写本文档的目的是保证在开发过程中产出高效、格式统一、易阅读、易维护的SQL代码。 1 编写目 2 SQL书写规范 3 SQL编写原则 软件开发全文档获取:点我获取

全域运营是割韭菜吗?看完再下结论!

随着流量时代的到来,各大公私域平台中的流量争夺战日益激烈,商家和品牌实现流量变现的难度值也不断提高,运营人员的压力也逐渐增大。在此背景下,全域运营的兴起或许是一个契机,能够将所有人从内卷的状态中解救出来。而…

网络安全之OSPF进阶

该文针对OSPF进行一个全面的认识。建议了解OSPF的基础后进行本文的一个阅读能较好理解本文。 OSPF基础的内容请查看:网络安全之动态路由OSPF基础-CSDN博客 OSPF中更新方式中的触发更新30分钟的链路状态刷新。是因为其算法决定的,距离矢量型协议是边算边…

力扣刷题 day1

移动零 283. 移动零 - 力扣(LeetCode) 看到这道题,是否第一个想法就是双指针,下面我们就来使用双指针来完成这道题. 图: 代码: java: // 移动 0 双指针public void moveZeroes(int[] nums) {for (int current 0, dest -1; …

前端框架 Vue 主要用来做什么的?

Vue.js 是一个流行的前端框架,主要用于构建交互式的用户界面。它的设计目标是通过简单的 API 提供高效的数据驱动视图层。Vue 具有响应式数据绑定和组件化的特性,使得开发者可以轻松地构建复杂的单页面应用 (SPA) 和动态网页。 1. 数据驱动视图 Vue 的…