0%

网页调试之 debugger 原理与绕过

网页调试之 debugger 原理与绕过

debugger 语句用于停止执行 JavaScript (以下简称 JS),并调用 (如果可用) 调试函数。

使用 debugger 语句类似于在代码中设置断点。

注意: 如果调试工具不可用,则调试语句将无法工作。

实现 debugger 功能

直接使用书写 debugger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE
html >
< html
lang = "en" >

< head >
< meta
charset = "UTF-8" >
< meta
http - equiv = "X-UA-Compatible"
content = "IE=edge" >
< title > Example
DEBUGGER < /title>
</head>

<body>
<script>
debugger;
</script>
</body>

</html>

当我们使用浏览器打开 Devtools 即执行 debugger;如下图所示

eval 配合 debugger

eval () 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。

如果参数是一个表达式,eval () 函数将执行表达式。如果参数是 Javascript 语句,eval () 将执行 Javascript 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Example DEBUGGER</title>
</head>

<body>
<script>
var a = 1;
eval("var 1 = 1;debugger")
</script>
</body>

</html>

当使用 eval 执行时,将会在虚拟机中执行,也就是说非同一作用域。

同时也由于将字符串当作表达式来执行,那么里面常常伴随着代码混淆

函数内执行 debugger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE
html >
< html
lang = "en" >

< head >
< meta
charset = "UTF-8" >
< meta
http - equiv = "X-UA-Compatible"
content = "IE=edge" >
< title > Example
DEBUGGER < /title>
</head>

<body>
<script>
(function a(){
var data = Date();
alert(data);
debugger;
}())
</script>
</body>

</html>

因为以上三种体现形式,在 debugger 上所设计的方案十分多。例如常见的无限制 debugger、配合 settimeout 延迟 debugger、代码混淆 + debugger 等等。

设置 debugger 的原理去对抗反爬,其核心原理就是如果调试工具可用,则调试语句将执行. 也就是经常一打开就跳出 debugger。

无限 debugger,其实是一种泛指的概念,无限泛指多,而非真的无限

其基于 debugger 之上,在此加入多次执行 debugger 的语句从而实现 “无限 debugger”。“反正只要 chrome
Devtools 不开 debugger 便不会执行”.(经过调试是这样的,如果不准确请自行完善哦)

debugger 绕过原理

debugger 的绕过也很简单,我个人总结共有两种大的方向。它们分别是替换、掠过。其原理都是不让 debugger 执行。个人并不推荐新手使用替换法中的方法

  • 替换法
    • JS 注入
    • 重写 (Hook)
  • 掠过法
    • Never pause here
    • 条件断点

JS 注入

实现 js 注入的方式有很多,例如 chrome Devtools 的 overrides、fiddler autoresponse、 mitmproxy、Charles 的 map
local 等等。若有兴趣自行搜索其使用方式

Never pause here

找到 debugger 前面的行号,鼠标右键点击该行号,点击 Never pause here。便会跳过此断点

条件断点

找到 debugger 前面的行号,鼠标右键点击该行号,点击 Add conditional breakpoint,直接写 false。回车即可

Deactivate breakpoints

打开这个图标如下图所示(高亮为打开)

当遇见 breakpoints 时会执行一次断点,鼠标单击如下图标

即可直接跳过 breakpoints。

小技巧:Deactivate breakpoints 可以配合 xhr、dom、Script 等断点使用,便于调试

Hook 绕过

1
2
3
function a() {
eval("debugger");
}
1
2
3
4
5
6
7
8
(function() {
var eval_cache = eval;
eval = function(obj) {
if (obj.indexof('debugger') === -1) {
eval_cache(obj);
}
}
}())

此方法有局限性,若在此函数 (在这里指函数 a) 若没有借用相关函数(eval),那么就无法使用此方法绕过

函数滞空法

当遇见断点时,回退一次堆栈。将对应函数滞空即可,例如遇见如下的 debugger

1
2
3
4
function a() {
debugger;
eval("debugger");
}

直接在控制台输入如下内容即可。

此方法有局限性,若在此函数中还参杂了关键代码,将可能无法访问或调试等

总结

Debugger 绕过其实并不难,但在调试中仅仅是一道 “开胃菜”,本节总结了 debugger 的实现方式,以及触发机制。当然也总结了几种我已知的所有绕过方案。

展望

如何 hook “变量” debugger?如果可以实现那么就可以实现反调试的 debugger “通杀”,当然目前我也有在探究此方案。在加到 hook 函数中,那么调试便可以近似于一步到位。

Powered By Valine
v1.5.2