js debugger各种方法的演示
做了个js debugger各种方法的演示,供绕过debugger学习用
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Debugger 策略与绕过演练台</title>
<style>
:root {
--primary:#2563eb;
--danger:#ef4444;
--bg:#0b1220;
--card:#0f172a;
--muted:#94a3b8;
--text:#e2e8f0;
--accent:#22c55e;
}
*{box-sizing:border-box}
body{
margin:0;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
background: radial-gradient(1200px 600px at 10% -10%, #172554 0%, #0b1220 45%) , var(--bg);
color:var(--text);
line-height:1.6;
}
header{
max-width:1080px;margin:32px auto 8px;padding:0 16px;text-align:center;
}
h1{margin:0 0 8px 0;font-weight:800;letter-spacing:.2px}
.sub{color:var(--muted);max-width:800px;margin:0 auto 20px}
.wrap{max-width:1080px;margin:0 auto;padding:0 16px 32px}
.card{
background:linear-gradient(180deg, rgba(148,163,184,.1), transparent 30%) , var(--card);
border:1px solid rgba(148,163,184,.15);
border-radius:16px;
padding:18px;
margin-bottom:16px;
box-shadow:0 10px 30px rgba(2,6,23,.4);
}
.grid{
display:grid;
grid-template-columns: repeat(2, minmax(0,1fr));
gap:12px;
}
@media (min-width:860px){
.grid{grid-template-columns: repeat(3, minmax(0,1fr));}
}
.row{
display:flex;gap:8px;align-items:center;justify-content:flex-start;flex-wrap:wrap;
padding:10px;border:1px dashed rgba(148,163,184,.2);border-radius:12px;background:rgba(30,41,59,.35)
}
.row h3{
margin:0 8px 0 0;font-size:15px;font-weight:700;white-space:nowrap
}
button{
padding:9px 12px;font-size:14px;border:none;border-radius:10px;color:white;cursor:pointer;
background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(0,0,0,.08)), var(--primary);
transition:filter .2s, transform .02s;
}
button.small{padding:8px 10px;font-size:13px;background:#334155}
button:hover{filter:brightness(1.08)}
button:active{transform:translateY(1px)}
button.red{background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(0,0,0,.08)), var(--danger)}
.log{
height:200px;overflow:auto;background:rgba(15,23,42,.7);border-radius:12px;border:1px solid rgba(148,163,184,.15);
padding:10px;font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size:13px;
}
.log p{margin:4px 0}
.hint{color:var(--muted);font-size:13px;margin:-6px 0 8px}
/* Modal */
.modal-backdrop{
position:fixed;inset:0;background:rgba(2,6,23,.6);backdrop-filter: blur(3px);
display:none;align-items:center;justify-content:center;padding:16px;z-index:40;
}
.modal{
max-width:820px;width:100%;
background:var(--card);border:1px solid rgba(148,163,184,.2);border-radius:14px;
box-shadow:0 20px 60px rgba(2,6,23,.6);
overflow:hidden;
}
.modal header{
padding:14px 16px 0;margin:0;text-align:left;
}
.modal h2{margin:0 0 4px 0;font-size:18px}
.modal .content{
padding:4px 16px 16px;max-height:60vh;overflow:auto;color:#dbeafe;
}
.modal .content ul{padding-left:18px;margin:6px 0 0 0}
.modal .content li{margin:4px 0}
.modal .footer{
padding:12px 16px;border-top:1px solid rgba(148,163,184,.2);display:flex;justify-content:flex-end;gap:8px
}
.badge{
display:inline-block;padding:2px 8px;border-radius:999px;font-size:12px;color:white;background:#0ea5e9;margin-left:6px
}
</style>
</head>
<body>
<header>
<h1>Debugger 策略与绕过演练台</h1>
<p class="sub">点击任意触发按钮开启对应的 <code>debugger</code> 策略;旁边的“绕过提示”会给出常见规避方案与工具路径。适合在 DevTools 中练习反调试绕过。</p>
</header>
<div class="wrap">
<section class="card">
<div class="hint">▶ 建议开启 DevTools 的 <em>Pause on debugger</em>,并试试条件断点、覆盖脚本、CSP 调整等。</div>
<div class="grid" id="btnGrid">
<!-- 按钮行通过 JS 注入,见下方 config -->
</div>
<div style="display:flex;gap:8px;margin-top:12px;flex-wrap:wrap">
<button id="stopAllBtn" class="red">停止所有触发</button>
<button id="clearLogBtn" class="small">清空日志</button>
</div>
</section>
<section class="card">
<h3 style="margin:0 0 8px 0">日志</h3>
<div class="log" id="logContainer"><p>日志输出区域...</p></div>
</section>
</div>
<!-- Modal -->
<div class="modal-backdrop" id="modal">
<div class="modal">
<header>
<h2 id="modalTitle">绕过提示</h2>
</header>
<div class="content" id="modalContent">
<!-- 动态填充 -->
</div>
<div class="footer">
<button id="copyTipsBtn" class="small">复制内容</button>
<button id="closeModalBtn">关闭</button>
</div>
</div>
</div>
<script>
/* ========= 日志 / 公共 ========= */
function log(message){
const box = document.getElementById('logContainer');
const p = document.createElement('p');
p.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
box.appendChild(p);
box.scrollTop = box.scrollHeight;
}
window.log = log; // 供 new Function / Worker 使用
// 可清理资源的引用
const timers = {
explicit:null, eval:null, fn:null, ctor:null, mixed:null,
raf:null, timeout:null, promise:false, observer:null, getterInt:null, idle:null, worker:null,
evThrottler:{ last:0, handler:null }
};
function jitter(base=1200, spread=600){
return base - spread/2 + Math.random()*spread;
}
/* ========= 触发策略配置(名称、触发器、绕过指南) ========= */
const strategies = [
{
key:'explicit',
name:'显式 setInterval',
trigger(){
if (timers.explicit) return log('显式已在运行');
log('启动:显式 setInterval');
function tick(){ debugger; log('显式 debugger 触发'); }
timers.explicit = setInterval(tick, 1000);
},
tips:`
<ul>
<li>在 DevTools 勾选「从不在异常处暂停」/关闭 Pause on debugger。</li>
<li>临时把 <code>debugger</code> 改成空语句:在 Sources 里覆盖脚本(Override/Local Overrides)。</li>
<li>在运行时重写 <code>window.Function.prototype.constructor</code>/<code>debugger</code> 触发点(若可控)。</li>
<li>全局禁用定时器:把 <code>setInterval</code> 指向空函数(慎用,影响面大)。</li>
</ul>`
},
{
key:'eval',
name:'eval 调用',
trigger(){
if (timers.eval) return log('eval 已在运行');
log('启动:eval 调用');
timers.eval = setInterval(()=>{
try { eval("debugger; window.log('eval debugger 触发')"); }
catch(e){ debugger; log('eval 被 CSP 限制,降级显式触发'); }
}, 1000);
},
tips:`
<ul>
<li>站点有 CSP:在 Response Header 放宽 <code>script-src 'unsafe-eval'</code> 或调整 DevTools 的 Overrides 以去除限制(仅在可控环境调试)。</li>
<li>直接将 <code>eval</code> 指向安全实现:<code>window.eval = ()=>{}</code>(可能影响站点其他逻辑)。</li>
<li>改为条件断点:只在特定堆栈/URL 时暂停。</li>
</ul>`
},
{
key:'fn',
name:'new Function',
trigger(){
if (timers.fn) return log('Function 已在运行');
log('启动:new Function');
timers.fn = setInterval(()=>{
try { new Function("debugger; window.log('Function debugger 触发')")(); }
catch(e){ debugger; log('new Function 被 CSP 限制,降级显式触发'); }
}, 1000);
},
tips:`
<ul>
<li>CSP 下常被拦截:同 <code>eval</code> 绕过。</li>
<li>在模块脚本中,它无法捕获局部变量;将依赖挂到 <code>window</code>。</li>
<li>可用 DevTools「黑盒」脚本避免进入该文件。</li>
</ul>`
},
{
key:'ctor',
name:'Function.prototype.constructor',
trigger(){
if (timers.ctor) return log('Constructor 已在运行');
log('启动:Function.prototype.constructor');
timers.ctor = setInterval(()=>{
try { (function(){}).constructor("debugger; window.log('Constructor debugger 触发')")(); }
catch(e){ debugger; log('constructor 被 CSP 限制,降级显式触发'); }
}, 1000);
},
tips:`
<ul>
<li>与 <code>new Function</code> 等价,亦受 CSP 约束。</li>
<li>可以在运行时替换 <code>Function.prototype.constructor</code> 返回的实现。</li>
</ul>`
},
{
key:'mixed',
name:'混合随机',
trigger(){
if (timers.mixed) return log('混合已在运行');
log('启动:混合随机(含降级、抖动)');
function once(){
const arr = [
()=>{ debugger; log('显式 触发'); },
()=>{ try{ eval("debugger; window.log('eval 触发')"); }catch{ debugger; log('eval 降级'); } },
()=>{ try{ new Function("debugger; window.log('Function 触发')")(); }catch{ debugger; log('Function 降级'); } },
()=>{ try{ (function(){}).constructor("debugger; window.log('Ctor 触发')")(); }catch{ debugger; log('Ctor 降级'); } }
];
arr[Math.floor(Math.random()*arr.length)]();
}
(function loop(){
once();
timers.mixed = setTimeout(loop, jitter());
})();
},
tips:`
<ul>
<li>随机调度+多路径:用条件断点限制只在感兴趣的路径暂停。</li>
<li>在 Overrides 中批量删改所有 <code>debugger;</code>(正则查找)。</li>
</ul>`
},
{
key:'raf',
name:'requestAnimationFrame',
trigger(){
if (timers.raf) return log('rAF 已在运行');
log('启动:requestAnimationFrame 循环');
const loop=()=>{ debugger; log('rAF 触发'); timers.raf = requestAnimationFrame(loop); };
timers.raf = requestAnimationFrame(loop);
},
tips:`
<ul>
<li>后台标签页 rAF 会暂停:切后台临时规避。</li>
<li>DevTools「黑盒脚本」+ 移除 <code>debugger</code>。</li>
</ul>`
},
{
key:'timeout',
name:'setTimeout 递归(抖动)',
trigger(){
if (timers.timeout) return log('Timeout 已在运行');
log('启动:setTimeout 递归(抖动)');
const run = ()=>{ debugger; log('Timeout 触发'); timers.timeout = setTimeout(run, jitter()); };
timers.timeout = setTimeout(run, 600);
},
tips:`
<ul>
<li>重写 <code>setTimeout</code>,拦截特定回调。</li>
<li>条件断点过滤调用栈/脚本 URL。</li>
</ul>`
},
{
key:'promise',
name:'Promise/Microtask',
trigger(){
if (timers.promise) return log('Promise 已在运行');
log('启动:Promise/Microtask');
timers.promise = true;
(function schedule(){
if (!timers.promise) return;
Promise.resolve().then(()=>{ debugger; log('Promise 微任务触发'); })
.finally(()=> setTimeout(schedule, 800));
})();
},
tips:`
<ul>
<li>微任务频繁:在 DevTools 里使用「Async 堆栈」辅助溯源。</li>
<li>禁用/替换 <code>Promise</code> 的 then 包装(风险大,不推荐生产)。</li>
</ul>`
},
{
key:'mo',
name:'MutationObserver',
trigger(){
if (timers.observer) return log('MO 已在运行');
log('启动:MutationObserver');
const target = document.getElementById('logContainer');
const mo = new MutationObserver(()=>{ debugger; log('MO 触发'); });
mo.observe(target, {childList:true, characterData:true, subtree:true});
timers.observer = mo;
let i=0;
(function poke(){
if (!timers.observer) return;
const p=document.createElement('p'); p.textContent='MO 心跳 '+(++i); target.appendChild(p);
setTimeout(poke, 1500);
})();
},
tips:`
<ul>
<li>临时断开观察者:覆盖 <code>MutationObserver.prototype.observe</code>/<code>disconnect</code>。</li>
<li>避免 DOM 变更(若可控数据源)。</li>
</ul>`
},
{
key:'getter',
name:'Getter/Proxy 访问',
trigger(){
if (timers.getterInt) return log('Getter 已在运行');
log('启动:Getter/Proxy');
const carrier = {};
Object.defineProperty(carrier,'x',{get(){ debugger; log('Getter 触发'); return 1; }});
timers.getterInt = setInterval(()=>{ void carrier.x; }, 1000);
},
tips:`
<ul>
<li>用 <code>Object.defineProperty</code> 重新定义该属性,无副作用 getter。</li>
<li>若为 Proxy,可替换 <code>Proxy</code> 构造/handler。</li>
</ul>`
},
{
key:'idle',
name:'requestIdleCallback',
trigger(){
if (timers.idle) return log('Idle 已在运行');
if (!('requestIdleCallback' in window)){
log('不支持 requestIdleCallback,降级 setTimeout');
timers.idle = setTimeout(function tick(){ debugger; log('Idle 降级触发'); timers.idle=setTimeout(tick,1200); }, 800);
return;
}
log('启动:requestIdleCallback');
const loop=()=>{ timers.idle = requestIdleCallback(()=>{ debugger; log('Idle 触发'); loop(); }, {timeout:2000}); };
loop();
},
tips:`
<ul>
<li>空闲触发:让页面保持忙碌(动画/网络)以推迟触发。</li>
<li>替换 <code>requestIdleCallback</code> 为 no-op(仅测试用)。</li>
</ul>`
},
{
key:'worker',
name:'Web Worker',
trigger(){
if (timers.worker) return log('Worker 已在运行');
log('启动:Web Worker(需 DevTools 开启 Worker 源码调试)');
try{
const blob=new Blob([`
self.onmessage=()=>{};
setInterval(()=>{ debugger; self.postMessage('tick'); }, 1000);
`],{type:'application/javascript'});
const url=URL.createObjectURL(blob);
const w=new Worker(url);
w.onmessage = e => { /* 占位 */ };
timers.worker=w;
}catch(e){
log('创建 Worker 失败(可能被 CSP 的 worker-src / blob 禁止):'+e);
}
},
tips:`
<ul>
<li>Sources 面板展开「Workers」,单独禁用暂停或黑盒 worker 脚本。</li>
<li>CSP 严格下可禁用 blob/worker-src,阻止加载(仅自测环境)。</li>
</ul>`
},
{
key:'event',
name:'事件监听(mousemove/keydown)',
trigger(){
if (timers.evThrottler.handler) return log('事件监听已在运行');
log('启动:事件监听(1s 节流)');
const throttled = (e)=>{
const now=Date.now(); if (now - timers.evThrottler.last < 1000) return;
timers.evThrottler.last = now; debugger; log(`事件 ${e.type} 触发`);
};
timers.evThrottler.handler = throttled;
window.addEventListener('mousemove', throttled, {passive:true});
window.addEventListener('keydown', throttled, {passive:true});
},
tips:`
<ul>
<li>避免触发相关事件(或在控制台移除监听)。</li>
<li>运行时用 <code>getEventListeners(window)</code>(Chrome)查找并 <code>removeEventListener</code>。</li>
</ul>`
}
];
/* ========= 注入按钮行 ========= */
const grid = document.getElementById('btnGrid');
strategies.forEach(s=>{
const row = document.createElement('div');
row.className = 'row';
row.innerHTML = `
<h3>${s.name}</h3>
<button data-key="${s.key}">触发</button>
<button class="small" data-tip="${s.key}">绕过提示</button>
<span class="badge">ID: ${s.key}</span>
`;
grid.appendChild(row);
});
grid.addEventListener('click',(e)=>{
const triggerKey = e.target.getAttribute('data-key');
const tipKey = e.target.getAttribute('data-tip');
if (triggerKey){
const s = strategies.find(x=>x.key===triggerKey);
if (s) s.trigger();
} else if (tipKey){
const s = strategies.find(x=>x.key===tipKey);
if (s) openTips(s.name, s.tips);
}
});
/* ========= 停止/清理 ========= */
document.getElementById('stopAllBtn').addEventListener('click', ()=>{
// interval 类
['explicit','eval','fn','ctor','getterInt'].forEach(k=>{
if (timers[k]){ clearInterval(timers[k]); timers[k]=null; }
});
// timeout / mixed
if (timers.timeout){ clearTimeout(timers.timeout); timers.timeout=null; }
if (timers.mixed){ clearTimeout(timers.mixed); timers.mixed=null; }
// rAF
if (timers.raf){ cancelAnimationFrame(timers.raf); timers.raf=null; }
// Promise 循环标志
timers.promise = false;
// MO
if (timers.observer){ timers.observer.disconnect(); timers.observer=null; }
// idle
if (timers.idle){
if ('cancelIdleCallback' in window) cancelIdleCallback(timers.idle);
else clearTimeout(timers.idle);
timers.idle=null;
}
// Worker
if (timers.worker){ timers.worker.terminate(); timers.worker=null; }
// 事件监听
if (timers.evThrottler.handler){
window.removeEventListener('mousemove', timers.evThrottler.handler);
window.removeEventListener('keydown', timers.evThrottler.handler);
timers.evThrottler.handler=null; timers.evThrottler.last=0;
}
log('已停止所有触发');
});
document.getElementById('clearLogBtn').addEventListener('click', ()=>{
const box=document.getElementById('logContainer');
box.innerHTML = '<p>日志输出区域...</p>';
});
/* ========= Modal(绕过提示) ========= */
const modal = document.getElementById('modal');
const modalTitle = document.getElementById('modalTitle');
const modalContent = document.getElementById('modalContent');
const closeModalBtn = document.getElementById('closeModalBtn');
const copyTipsBtn = document.getElementById('copyTipsBtn');
function openTips(title, html){
modalTitle.textContent = `绕过提示 · ${title}`;
modalContent.innerHTML = html + `
<hr style="border-color:rgba(148,163,184,.2)">
<p style="margin:6px 0 0 0;color:#93c5fd;font-size:13px">
通用思路:条件断点(过滤堆栈/URL/调用参数) · 覆盖脚本移除 <code>debugger</code> · 黑盒脚本 · 临时替换 API(<code>eval</code>/<code>Function</code>/<code>setInterval</code>/Observers 等) · 关闭 Pause on debugger。
</p>`;
modal.style.display = 'flex';
}
function closeTips(){ modal.style.display = 'none'; }
closeModalBtn.addEventListener('click', closeTips);
modal.addEventListener('click',(e)=>{ if (e.target===modal) closeTips(); });
copyTipsBtn.addEventListener('click', async ()=>{
const tmp = modalContent.innerText;
try{
await navigator.clipboard.writeText(tmp);
log('已复制绕过提示到剪贴板');
}catch{
log('复制失败:可能缺少权限');
}
});
</script>
</body>
</html>