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

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

相关推荐
IT、木易几秒前
如何在 React 项目中进行服务器端渲染(SSR),它有什么优势
前端·react.js·前端框架
风清云淡_A1 分钟前
【react18】react项目使用mock模拟后台接口
前端·react.js
知识分享小能手1 小时前
CSS3学习教程,从入门到精通,CSS3 定位布局页面知识点及案例代码(18)
前端·javascript·css·学习·html·css3·html5
Python私教1 小时前
Vue 在现代 Web 开发中的优势
前端·javascript·vue.js
fridayCodeFly1 小时前
<KeepAlive>和<keep-alive>有什么区别
前端·javascript·vue.js
hikktn1 小时前
【开源宝藏】30天学会CSS - DAY8 第八课 跳动的爱心动画
前端·css·开源
南蓝1 小时前
【node】如何用 pm2 管理 node 项目
前端
寻梦人121381 小时前
Vite管理的Vue3项目中monaco editer的使用以及组件封装
前端·javascript·vue.js·vscode
头发尚存的猿小二1 小时前
Linux--环境变量
前端·javascript·chrome
uuuuu17116442 小时前
HTML5 canvas圆形泡泡动画背景特效
前端·html·html5