常见调试干扰原理及对抗

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

任何JS逆向工作的第一步都是打开浏览器对目标网页进行分析与调试,然而一部分网站为了尽可能的屏蔽爬虫访问,会在自家网站使用一些干扰开发者调试的小功能,如:无限debug、内存爆破、控制台唤起限制等。本文是作者在学习该知识过程中的笔记总结,旨在博客撰写过程中巩固知识认知,当然更希望能对在爬虫开发中遇到类似问题的工程师们提供大致思路或解决方案。


一、无限debug

无限debug是指我们在调试网页时,总是会进入到其js代码中写好的debug逻辑中,并且疯狂点击控制台的下一步或跳过按钮都无法跳出。示例如下:
无限debug示例效果

1. 无限debug的原理及实现

顾名思义,要实现无限debug,既要实现“debug”,又要让其“无限”(即上文提到的无法直接跳出)。因此无限debug的实现主要分为两点,下文分别介绍。

1.1 debug的实现

a. 关键字

直接在js代码里写入debugger关键字。这是最简单的实现方式,不涉及到字符串及函数,因此不可混淆。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    debugger;
    alert('hello word!')
</script>
<body>
</body>
</html>
b. eval()

在js代码中写入eval虚拟机,参数内传入可以实现debug的字符串。该实现方式可以结合字符串的各种操作进行混淆。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    eval("\u0064\u0065" + "\u0062\u0075" + "\u0067" + "\u0067\u0065\u0072" + "\u003b");
    alert('hello word!')
</script>
<body>
</body>
</html>

上述代码做了最基础的简单混淆,将“debugger;”转换成了unicode,并用“+”拼接,实际上eval那行的代码等同于eval(“debugger;”);

c. 函数

声明或定义各种可以实现debugger的函数,并让其执行。这种方式可以结合字符串及函数的各种操作实现较为复杂的混淆。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    // Function("debugger;").call();
    // function a(){}
    // a.constructor("debugger;").call()
    (function(){}.constructor("debugger;").call())
    alert('hello word!')
</script>
<body>
</body>
</html>

以上代码涉及到Function对象,原型链等JS知识,具体可在以下文章中了解并学习:
全面认识JavaScript的Function对象
JS 中 proto 和 prototype 存在的意义是什么?

以上介绍的三种实现debug的方式可以交叉使用,以达到更加重度的混淆的目的。

1.2 无限debug的实现

上文介绍了如何在js中实现debug,然而这种debug无论如何混淆,都是可以在浏览器的控制台直接跳过的,因此想要达到干扰js逆向的目的,必须将debug升级为“无限debug”,下文介绍实现无限debug的几种方式。

a. 死循环

该方式极其简单,但是一般不会用在真实场景中,因为死循环会将浏览器卡死。下面是示例代码,大佬们可以在浏览器打开尝试,感受一下浏览器卡死的酸爽。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    while(true){
        console.log('11111')
    }

    alert('hello word!')
</script>
<body>
</body>
</html>

实际上使用while关键字实现无限debugger,在理论上也不是完全不可行,但是while后面括号中的条件不可永远为true,必须有跳出while的条件,并且此条件是可触发的。总之:不可以死循环,否则浏览器会卡死!

b. 定时器

使用setInterval定时器结合可以实现debug的函数,非常频繁的进行debug,从而实现所谓的“无限”debug。其中setInterval后括号内,第一个参数为要执行的函数,第二个为间隔时间(单位为毫秒)。示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    function a(){
        debugger;
    }
    setInterval(a, 500)
    alert('hello word!')
</script>
<body>
</body>
</html>

2. 无限debug的绕过

以上介绍了无限debug的实现原理,下面便可以根据对其原理的理解实现对其绕过。

2.1 定时器无限debug的绕过

当明显看到代码中使用定时器进行无限debug时可采用此方法。对定时器进行hook,当其内部调用为debugger相关函数时,使其改写为跳过等操作,从而不进行原本的debug逻辑。示例代码如下:

setInterval_bak = setInterval
setInterval = function(a, b){
    if (a.toString().indexOf('debugger') == -1) {
        return setInterval_bak(a, b)
    }
    else {
        return
    }
}

代码解释:将原定时器函数进行备份→重写定时器函数(判断定时器函数内第一个参数有无debug相关操作→如无,则用备份的定时器函数原样执行→如有,则直接return,不进行任何操作,以此绕过定时器中的debug)。

2.2 函数无限debug的绕过

其实这类无限debug的绕过技巧与上文基于定时器的绕过原理相通或者说类似,与上一方法不同的是,该方法重写的不是定时器函数,而是debug的实现函数——即上一示例代码中定时器函数传入的参数“a”。这种方法适用于js代码中debug不是明显的在定时器内触发的情况。
这种情况以猿人学练习平台的第二题为例,在此题打开控制台后,会进入到无限debug当中,通过堆栈寻找到debug的实现主要是以下几行代码:

(function() {
    return !![];
}
[_$ob('0x4d')](b[_$ob('0x22')](b[_$ob('0x3a')], b[_$ob('0x23')]))[_$ob('0x55')](b[_$ob('0xd')]))

其中最后一行实际上是一个非常简单的混淆,在控制台使用鼠标选中各个混淆的字符,并将鼠标悬停,即可看到其实际值,该操作不再赘述。简单解混淆后实际代码长下面这样:

(function() {
    return !![];
}["constructor"]("debugger")["call"]("action"))

其实就是一个匿名函数,利用“constructor”属性找到原始构造函数"Function()",并用其构造一个实现debug的匿名函数,通过call方法使其执行。
可以看出这几行代码并没有定时器的影子,包括原js文件中这几行的上下文也无法直接找到,因此可以直接重写原始构造函数“Function()”。

Function.prototype.constructor_bc = Function.prototype.constructor;
Function.prototype.constructor = function(a){
    if(a.indexOf('debugger') == -1){
        alert("没有debugger")
        return Function.prototype.constructor_bc(a)
    }else{
        alert("成功绕过debugger")
        return
    }
}

可以看出这段hook代码其实跟定时器的重写差不多,都是先将原函数进行备份,然后重写过程中检查函数内部是否有debug操作,有的话不进行任何操作直接返回,没有的话就把参数传给备份函数按照原逻辑执行,这样就成功地绕过了debug操作。

2.3 简单粗暴的绕过技巧

在debugger关键字所在行,右键选择“Never pause here”。此方法只能适用于js文件中直接写入或引入文件中的debugger代码,对新生成的虚拟机中的debugger无效。

2.4 fiddler工具魔改代码

使用fiddler抓到debug所在js文件,利用fiddler的AutoResponder功能使该文件替换为已经删去debugger的本地js文件。

二、其他调试干扰手段

这里例举一些其他可能对我们的调试造成干扰的骚操作,篇幅原因对其原理不再详细赘述,在此做简单介绍并提供常规解决方案。

1. 禁止F12呼出控制台

在调试某些网页时可能出现按F12快捷键无法呼出控制台的情况,这种情况非常容易解决,通常有以下两种解决方案:
① 手动呼出控制台;
② 新建标签页,先打开控制台,再输入链接进入目标站点。

2. 调出控制台即进行跳转等

有些网站在我们调出控制台后会立即跳转至该网站首页或其他位置,针对这种情况我们可以在实现跳转等操作的函数定义后且执行前,置空或重写该函数。

3. 内存爆破

该干扰一般是通过正则检测某个函数的toSrting,此情况可以将被检测函数取消格式化,或者在检测的地方修改bool值。也有可能是通过浏览器指纹判断是否为浏览器环境,或调试时间差检测等,具体情况具体分析。

总结

此篇博客主要是作者学习记录所用,所以在博文中对各个知识点并没有非常深入的剖析讲解,随着对该部分知识的理解加深,我会回头逐渐完善改文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值