今天彻底解决无限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__])

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

相关推荐
李少兄8 分钟前
简单讲讲 SVG:前端开发中的矢量图形
前端·svg
前端小万9 分钟前
告别 CJS 库加载兼容坑
前端·前端工程化
恋猫de小郭9 分钟前
Flutter 3.38.1 之后,因为某些框架低级错误导致提交 Store 被拒
android·前端·flutter
JarvanMo13 分钟前
Flutter 需要 Hooks 吗?
前端
光影少年23 分钟前
前端如何虚拟列表优化?
前端·react native·react.js
Moment25 分钟前
一杯茶时间带你基于 Yjs 和 reactflow 构建协同流程图编辑器 😍😍😍
前端·后端·面试
invicinble42 分钟前
对于前端数据的生命周期的认识
前端
PieroPc1 小时前
用FastAPI 后端 和 HTML/CSS/JavaScript 前端写一个博客系统 例
前端·html·fastapi
hunter14501 小时前
2026.1.4 html简单制作
java·前端·笔记·html
鹏程十八少1 小时前
Android 深入剖析Android内存泄漏:ViewPager2与Fragment的生命周期陷阱
android·前端·app