今天彻底解决无限debugger问题

一、背景

这几天准备做个工具,涉及到一些公开数据收集工作,没想到过程曲折超过了想象。其中一个必须解决的问题是无限debugger绕过。本来的原则有困难就躲开,绕过算了,不过这里太有用了,所以对这块进行了一点点研究,试试掏它老窝。

二、无限debugger它到底长啥样

1、直接加到代码里

javascript 复制代码
debugger
eval("debugger")

这种太简单了,估计没多少网站在用了。啥?还有人用,这人好善良。

2、无限循环
  • while 循环
  • for 循环
  • 计时器

for和while循环就是方法体里加debugger。JS中的定时器是setInterval ,参数:第一个参数是要定时执行的代码,第二个参数是时间。下面的代码就是利用定时器来实现debugger操作:

javascript 复制代码
setInterval(function () {
         debugger;
    }, 1000
)

这种稍微有点难度,不过还好。

3、下面上难度:混淆

比较隐蔽的方法时混淆调用debugger的代码,如下面的几种方式

javascript 复制代码
Function("debugger;").call()
Function("debugger;").apply()
或者
variable = Function("debugger;")
variable();

xxx.constructor("debugger").call("action")

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

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

混淆过后代码可能长这样,文件名可能一样,或者每次都不一样,代码也可能每次请求都不一样。我遇到的是文件名和代码都动态,zhenima!!

less 复制代码
  if (('' + _0x6dfb46 / _0x6
                 dfb46)[_0xaed838(-0x340, -0x201, -0x2ba, -0x1b8, -0x84)] !== 0x1 || _0x6dfb46 % 0x14 === 0x0) {
                (function() {
                    return !![];
                }
                )['constructor'](_0x43a3d0(0x4ae, 0x34e, 0x44e, 0x433, "\u0059\u0045\u0073\u002a") + _0xaed838(-0x1f8, -0x31f, -0x24a, -0x413, -0x2b2))["\u0063\u0061\u006c\u006c"](_0x2826e3(-0xc5, -0x1b, -0x4b, -0x96, 0x5));
            } else {
                (function() {
                    return ![];
                }
                )["\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072"](_0x6582e1['LeKcb'] + _0x43a3d0(0x436, 0x3c3, 0x470, 0x56c, "\u006b\u005a\u0047\u0046"))['apply'](_0x51784e(0x47d, 0x45b, 0x35c, "\u0075\u0043\u006f\u0021", 0x3f7));
            }

三、解决方案

1、最简单:如果不需要其他断点,可以直接全局绕过

2、简单点:使用一律不在此处暂停

3、稍微简单点:添加条件断点

在 JS 代码 debugger 行数位置,鼠标右键添加 条件断点,其中条件 设为 false

4、稍微麻烦点:hook函数

在控制台中注入即可,以下是几种常用的hook函数

  • hook构造函数
javascript 复制代码
Function.prototype._constructor = Function.prototype.constructor;
Function.prototype.constructor = function () {
  if (arguments.toString().includes("debugger")) {
    return null;
  }
  return Function.prototype._constructor.apply(this, arguments);
};
  • hook方法

    javascript 复制代码
    F_ = Function
    Function = function(s){
        if (s!=='debugger'){return F_(s)}
    }
  • hook eval

    javascript 复制代码
    eval_ = eval;
    //下面为了过瑞数的 eval.toString 检测
    eval = function (a) {
      if (a == "debugger") {
        return "";
      } else {
        return eval_(a);
      }
    };
    
    
    eval_back = eval
    eval = function (args) {
        if (args.includes('debugger')) {
            return null
        } else {
            return eval_back(args);
        }
    }
  • Hook setInterval 函数

javascript 复制代码
setInterval_back = setInterval
setInterval = function(a,b){
    if(a.toString().includes('debugger')){
      return null;
    }
    return setInterval_back(a, b);
}
  • hook html

    javascript 复制代码
    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)
    }

5、稍微难点:文件替换的方式

如果以上还不行,则直接改写js文件

  • 使用chrome 自带的方式,改写js文件,右键 Override content,然后保存,
  • 稍微难和麻烦:动态文件替换

如果以上还不行,比如文件是动态的,这时候可使用插件或者Fiddler或者charles动态替换文件。先把js下载下来,然后根据规则替换

到这就结束了?不是,这些网上都可以找到,我遇到的这个网站大大滴狡猾,js文件名是接口返回的,每次请求都不一样,而且是混淆过,hook不到debugger代码,不同用途的文件名都是类似md5的名字,没有规则,那怎么解决呢

四、掏它老窝:终极方案

尝试多种方法后,我认为只能编码解决了,自定义的编码几乎可以覆盖以后的任何困难场景。基本思路就是代理所有请求,拿到接口请求数据,解析出文件规则,然后代理js请求,找到debugger的代码特征,根据特征规则静态或者动态替换返回值。

  • 第一步、网页代理

    浏览器可以配置,host也可以直接配置,为了使用方便,我直接下载了SwitchyOmega插件

* 第二步、编码抓到所有请求

ini 复制代码
    这里使用mitmproxy(<https://mitmproxy.org/)插件,我使用的是mac,安装命令是>

         # 安装命令行
         brew install mitmproxy
         # 安装python包,用于编码
         pip install -i https://pypi.tuna.tsinghua.edu.cn/simple mitmproxy 

    抓包所有请求,示例代码如下:

        def response(flow: http.HTTPFlow):
            if "captxxx/config?" in flow.request.pretty_url:
                response_text = flow.response.text
                print("test:" + response_text)
                res = process(response_text)
                modified_response_text = process_resp(response_text)
                flow.response.text = modified_response_text


        if __name__ == "__main__":
            from mitmproxy.tools.main import mitmdump
            mitmdump(['-s', __file__])

    def response(flow: http.HTTPFlow),用于处理请求,可以根据url区分,既可以改请求,也可以改返回值。

    python代码启动之后安装证书

彻底解决无限debugger问题

一、背景

这几天准备做个工具,涉及到一些公开数据收集工作,没想到过程曲折超过了想象。其中一个必须解决的问题是无限debugger绕过。本来的原则有困难就躲开,绕过算了,不过这里太有用了,所以对这块进行了一点点研究,掏了它的老窝。

二、无限debugger它到底长啥样

1、直接加到代码里

javascript 复制代码
debugger
eval("debugger")

这种太简单了,估计没多少网站在用了。啥?还有人用,这人好善良。

2、无限循环
  • while 循环
  • for 循环
  • 计时器

for和while循环就是方法体里加debugger。JS中的定时器是setInterval ,参数:第一个参数是要定时执行的代码,第二个参数是时间。下面的代码就是利用定时器来实现debugger操作:

javascript 复制代码
setInterval(function () {
         debugger;
    }, 1000
)

这种稍微有点难度,不过还好。

3、下面上难度:混淆

比较隐蔽的方法时混淆调用debugger的代码,如下面的几种方式

javascript 复制代码
Function("debugger;").call()
Function("debugger;").apply()
或者
variable = Function("debugger;")
variable();

xxx.constructor("debugger").call("action")

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

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

混淆过后代码可能长这样,文件名可能一样,或者每次都不一样,代码也可能每次请求都不一样。我遇到的是文件名和代码都动态,zhenima!!

less 复制代码
  if (('' + _0x6dfb46 / _0x6
                 dfb46)[_0xaed838(-0x340, -0x201, -0x2ba, -0x1b8, -0x84)] !== 0x1 || _0x6dfb46 % 0x14 === 0x0) {
                (function() {
                    return !![];
                }
                )['constructor'](_0x43a3d0(0x4ae, 0x34e, 0x44e, 0x433, "\u0059\u0045\u0073\u002a") + _0xaed838(-0x1f8, -0x31f, -0x24a, -0x413, -0x2b2))["\u0063\u0061\u006c\u006c"](_0x2826e3(-0xc5, -0x1b, -0x4b, -0x96, 0x5));
            } else {
                (function() {
                    return ![];
                }
                )["\u0063\u006f\u006e\u0073\u0074\u0072\u0075\u0063\u0074\u006f\u0072"](_0x6582e1['LeKcb'] + _0x43a3d0(0x436, 0x3c3, 0x470, 0x56c, "\u006b\u005a\u0047\u0046"))['apply'](_0x51784e(0x47d, 0x45b, 0x35c, "\u0075\u0043\u006f\u0021", 0x3f7));
            }

三、解决方案

1、最简单:如果不需要其他断点,可以直接全局绕过

2、简单点:使用一律不在此处暂停

3、稍微简单点:添加条件断点

在 JS 代码 debugger 行数位置,鼠标右键添加 条件断点,其中条件 设为 false

4、稍微麻烦点:hook函数

在控制台中注入即可,以下是几种常用的hook函数

  • hook构造函数
javascript 复制代码
Function.prototype._constructor = Function.prototype.constructor;
Function.prototype.constructor = function () {
  if (arguments.toString().includes("debugger")) {
    return null;
  }
  return Function.prototype._constructor.apply(this, arguments);
};
  • hook方法

    javascript 复制代码
    F_ = Function
    Function = function(s){
        if (s!=='debugger'){return F_(s)}
    }
  • hook eval

    javascript 复制代码
    eval_ = eval;
    //下面为了过瑞数的 eval.toString 检测
    eval = function (a) {
      if (a == "debugger") {
        return "";
      } else {
        return eval_(a);
      }
    };
    
    
    eval_back = eval
    eval = function (args) {
        if (args.includes('debugger')) {
            return null
        } else {
            return eval_back(args);
        }
    }
  • Hook setInterval 函数

javascript 复制代码
setInterval_back = setInterval
setInterval = function(a,b){
    if(a.toString().includes('debugger')){
      return null;
    }
    return setInterval_back(a, b);
}
  • hook html

    javascript 复制代码
    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)
    }

5、稍微难点:文件替换的方式

如果以上还不行,则直接改写js文件

  • 使用chrome 自带的方式,改写js文件,右键 Override content,然后保存,

    ![截屏2025-03-23 22.04.30](/Users/shulongliu/Documents/文章/wuxiandebugger/截屏2025-03-23 22.04.30.png)

  • 稍微难和麻烦:动态文件替换

如果以上还不行,比如文件是动态的,这时候可使用插件或者Fiddler或者charles动态替换文件。先把js下载下来,然后根据规则替换

![截屏2025-03-23 22.01.33](/Users/shulongliu/Documents/文章/wuxiandebugger/截屏2025-03-23 22.01.33.png)

到这就结束了?不是,这些网上都可以找到,我遇到的这个网站大大滴狡猾,js文件名是接口返回的,每次请求都不一样,而且是混淆过,hook不到debugger代码,不同用途的文件名都是类似md5的名字,没有规则,那怎么解决呢

四、掏它老窝:终极方案

尝试多种方法后,我认为只能编码解决了,自定义的编码几乎可以覆盖以后的任何困难场景。基本思路就是代理所有请求,拿到接口请求数据,解析出文件规则,然后代理js请求,找到debugger的代码特征,根据特征规则静态或者动态替换返回值。

  • 第一步、网页代理

    浏览器可以配置,host也可以直接配置,为了使用方便,我直接下载了SwitchyOmega插件

  • 第二步、编码抓到所有请求

    这里使用mitmproxy(mitmproxy.org/)插件,我使用的是ma...

    bash 复制代码
         # 安装命令行
         brew install mitmproxy
         # 安装python包,用于编码
         pip install -i https://pypi.tuna.tsinghua.edu.cn/simple mitmproxy 

    抓包所有请求,示例代码如下:

    ini 复制代码
        def response(flow: http.HTTPFlow):
            if "captxxx/config?" in flow.request.pretty_url:
                response_text = flow.response.text
                print("test:" + response_text)
                res = process(response_text)
                modified_response_text = process_resp(response_text)
                flow.response.text = modified_response_text
    
    
        if __name__ == "__main__":
            from mitmproxy.tools.main import mitmdump
            mitmdump(['-s', __file__])

    def response(flow: http.HTTPFlow),用于处理请求,可以根据url区分,既可以改请求,也可以改返回值。python代码启动之后安装证书

  • 记录并解析接口返回值

    ini 复制代码
    def response(flow: http.HTTPFlow):
        if "http://xxxxxconfig.json" in flow.request.pretty_url:
            response_text = flow.response.text
            print("test:" + response_text)
            # 保存配置文件
            save_file('config_orgin.json', response_text)
            # 解析配置文件
            parser_save(response_text)
            modified_response_text = response_text
            flow.response.text = modified_response_text
            
     def parser_save(response_text):
        content = re.sub(r'^[^(]*', '', response_text)
        content = content.replace("(", '')
        content = content.replace(");", '')
        # print('##before load' + content)
        content_json = json.loads(content)
        # print('##after load' + json.dumps(content_json))
        server_host = content_json["result"]['data']["cdn_server"]
        js1_path = content_json["result"]['data']["js1_path"]
        js2_path = content_json["result"]['data']["js2_path"]
        js1_all = "https://" + server_host + "/" + js1_path + "?v=v3"
        js2_all = "https://" + server_host + "/" + js2_path + "?v=v3"
        map = {
            js1_all: 'js1',
            js2_all: 'js2'
        }
        with open('js_all.json', 'w') as file:
            json.dump(map, file)
  • 根据规则替换js文件

    由于不同文件的js代码不同,规则也不一样,然后没种文件特征一样,比如下面对功能1代码的动态替换操作

    python 复制代码
        if "https://cdnxxx.js" in flow.request.pretty_url:
            response_text = flow.response.text
            print("#####json resp url:" + flow.request.pretty_url)
            modified_response_text = process_js_file(flow.request.pretty_url, response_text)
            flow.response.text = modified_response_text
            
    # 是否在配置文件中,有则根据规则替换掉debugger代码
    def process_js_file(url, content):
        print("#####url:" + url)
        with open('js_all.json', 'r') as file:
            map = json.load(file)
        if url in map:
            print("###包含" + url)
            file_name = map[url] + '.js'
            with open(file_name, 'r') as file:
                return file.read()
        return content
        
    def replace_string(start_str, end_str, s):
    
        try:
            # 查找第一个起始位置
            start_idx = s.find(start_str)
            if start_idx == -1:
                return s
    
            # 查找最后一个结束位置(从起始位置之后开始)
            end_idx = s.rfind(end_str, start_idx + len(start_str))
            if end_idx == -1:
                return s
    
            # 计算有效截取范围
            end_len = len(end_str)
            return s[:start_idx]  + "console.log("'debugeer'")" + s[end_idx + end_len:]
    
        except IndexError:
            return s
    
    def test_clean(content):
        res = replace_string("(function(){return!![];})['constructor'](", "));", content)
        res = replace_string("(function(){return![];})['constructor'](_", "]);", res)
        return res

    最后成功跳过所有debugger代码

这样,利用代理机制+编码修改,解决了文件名、代码都是动态的问题。实际上能编码的话,可以模拟浏览器的所有请求,模拟网站的规则修改所有代码。

完整代码如下:

python 复制代码
from mitmproxy import http
import json
import os
import re

def save_file(filename, content):
    with open(filename, "w") as file:
        file.write(content)

def process_js_file(url, content):
    print("#####url:" + url)
    with open('js_all.json', 'r') as file:
        map = json.load(file)
    if url in map:
        print("###包含" + url)
        return test_clean(content)
    return content

def replace_string(start_str, end_str, s):

    try:
        # 查找第一个起始位置
        start_idx = s.find(start_str)
        if start_idx == -1:
            return s

        # 查找最后一个结束位置(从起始位置之后开始)
        end_idx = s.rfind(end_str, start_idx + len(start_str))
        if end_idx == -1:
            return s

        # 计算有效截取范围
        end_len = len(end_str)
        return s[:start_idx]  + "console.log("'debugeer'")" + s[end_idx + end_len:]

    except IndexError:
        return s

def test_clean(content):
    res = replace_string("(function(){return!![];})['constructor'](", "));", content)
    res = replace_string("(function(){return![];})['constructor'](_", "]);", res)
    return res

def parser_save(response_text):
    content = re.sub(r'^[^(]*', '', response_text)
    content = content.replace("(", '')
    content = content.replace(");", '')
    # print('##before load' + content)
    content_json = json.loads(content)
    # print('##after load' + json.dumps(content_json))
    server_host = content_json["result"]['data']["cdn_server"]
    js1_path = content_json["result"]['data']["js1_path"]
    js2_path = content_json["result"]['data']["js2_path"]
    js1_all = "https://" + server_host + "/" + js1_path + "?v=v3"
    js2_all = "https://" + server_host + "/" + js2_path + "?v=v3"
    map = {
        js1_all: 'js1',
        js2_all: 'js2'
    }
    print('###save' + json.dumps(map))
    with open('js_all.json', 'w') as file:
        json.dump(map, file)

def response(flow: http.HTTPFlow):
    if "http://xxxxxconfig.json" in flow.request.pretty_url:
        response_text = flow.response.text
        print("test:" + response_text)
        # 保存配置文件
        save_file('config_orgin.json', response_text)
        # 解析配置文件
        parser_save(response_text)
        modified_response_text = response_text
        flow.response.text = modified_response_text

    if "https://xxx/scripts/" in flow.request.pretty_url:
        response_text = flow.response.text
        print("#####json resp url:" + flow.request.pretty_url)
        modified_response_text = process_js_file(flow.request.pretty_url, response_text)
        flow.response.text = modified_response_text


if __name__ == "__main__":
    from mitmproxy.tools.main import mitmdump
    mitmdump(['-s', __file__])

本人公众号大鱼七成饱,历史文章会在上面同步

相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax