JS中实现debugger的方法
首先,我们要知道,在浏览器实现debugger的方法有哪些
- debugger关键词 ,相当于C++内联汇编的int3,在代码中嵌入这么一个关键词方便调试
- eval(‘debugger’) ,原理跟第一个一样,只不过是使用eval方法来调用。这个可操作空间比较大,字符串可以经过混淆或者拼接以后最后变成
debugger
这个关键词即可。 - Function(“debugger”), 原理跟2类似,只不过是在虚拟机里面执行匿名函数,匿名函数里有debugger的方法
这些debugger方法,是实现debugger的基础,可以理解为是三元素。基于三种元素,可以形成多种多样的玩法
无限Debugger示例
Demo1
那么接下来, 我就将上面的三元素进行组合,产生各种无限debugger
setInterval('debugger', 500)
这种是利用定时器的方法,去不断间隔的创造宏任务队列,用来干扰我们的调试 。
解决方案:
- 右键-> Never pause here
- 在定时器启动之前重写 setInterval
- AutoResponse 直接把干扰点干掉
Demo2
html页面生产时,直接自动生成几千个script标签,标签里只写一个debugger
<script>
!function (){debugger}()
</script>
<script>
!function (){debugger}()
</script><script>
!function (){debugger}()
</script><script>
!function (){debugger}()
</script><script>
!function (){debugger}()
</script><script>
如果是这种方式的话,刚刚那种解决方案就不行了。解决方案:AutoResponse 直接把干扰点干掉
Demo3
for (let i=0;i<=5000;i++)
{
Function('debugger')() // 原型链 获取Function
eval('debugger')
debugger;
}
解决方案
- Never pause here
- 干掉这个循环进入的函数
- AutoResponse 直接把干扰点干掉
Demo4
添加script标签,插入debugger
for (let i=0;i<=5000;i++)
{
cont = document.body
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.innerHTML = 'debugger';
cont.appendChild(newScript);
}
解决方案: document.createElement里面把script重写
总结
- 优先尝试 Never pause here (最方便快捷,但是最卡,也最容出问题)
- 次优先尝试重写调用函数
- 使用 AutoResponse/mapping/overrides 替换
无限Debugger实战
这个是猿人学第五十四题,大家也可以自己动手体验下。
进入到目标网站,直接打开调试器,就出现了一个反调试
通过调用栈找到上一层,可以看到这个反调试的函数
s(s(11))
ƒ eval() { [native code] }
直接在控制台敲一下s11,可以看到这个就是eval函数
s(38)+s(56)+s(62)+s(50)
'debugger'
执行一下这四个字符串,就可以看到是debugger这个字符串。
这个反调试只有一层的话,就可以直接Never pause here
就可以过掉,也可以采用HOOK的方法,重写一个eval函数,让这个地方不会断下来。
_eval = eval;
eval= function()
{
if ([arguments[0]].indexOf('debugger'))
return eval(arguments[0])
}
这样这个反调试的点就过掉了,然后我们进行一个翻页,又出现一个无限debugger,查看一下当前的堆栈
这里用循环的方式一直去添加一个o的节点,
o实际上就是一个带debugger关键字的标签,按照之前的思路,我们应该把appendChild这个函数给hook掉。
_appendChild = Node.prototype.appendChild
Node.prototype.appendChild = function()
{
if (arguments[0].innerHTML && arguments[0].innerHTML.indexOf('debugger') != -1)
{
arguments[0].innerHTML = ''
}
return _appendChild.apply(this, arguments)
}
执行一下这段代码,这个无限Debugger就过掉了。
继续运行,这里也有个无限Debugger,直接右键Never pause here解决
然后刷新一下页面,这里就会出现一个新的无限debugger,这里有一堆的debugger标签
这里用Overrides把这几百个debugger关键词替换掉。
重新刷新页面,这样就过掉了这个无限Debugger
再继续,这里又出现了一个无限Debugger,因为这个位置只有一行,不管多复杂,其实都可以右键Never pause here
搞定,也可以直接Overrides删掉,混淆的太厉害了,原理就不看了。
到这里,整个无限Debugger的案例就结束了。
无限Debugger秒破方案
我们来想这么一个问题,之前我们都是通过HOOK的方式来解决掉debugger关键字的,那么有没有一种HOOK方案,可以一劳永逸直接干掉所有无限Debugger呢?
答案当然是有,不过这个HOOK需要下到浏览器层。你自己编译一份谷歌浏览器,然后呢,在cpp层,将js层的debugger给他hook掉,改成其他的,就是把debugger这个关键字给他失效,不让他生效。
这个方法听起来,有一点费劲,基本跟重新编译一份安卓系统差不多了。既然有这么一个思路在,那有没有已经造好的可以拿来用的轮子,当然有,火狐浏览器帮我们把这个事情做了。
The Firefox Debugger now includes a new feature: an option to disable the debugger; keyword on the current page. This feature is accessible via a new checkbox in the Breakpoints side panel labeled Pause on debugger statement, located next to the existing Pause on exceptions checkbox. By default, this option is enabled, meaning that the debugger statements are active unless manually disabled.
上面这个是firefox 更新Version 121.0的一条更新日志, 更新日志写的很明白 , 取消勾选Pause on debugger statement(在调试器语句暂停) ,就可以过掉所有的反调试,自己打的断点不受影响。
我这里下载了最新版的火狐浏览器,在开发者工具里面勾选上这一项,就可以过掉所有的无限Debugger了。