js逆向-控制台呼出检测
前言
控制台呼出检测是我们JS逆向工作中经常遇到的防护手段
控制台呼出检测全面解析
一、检测原理与基础机制
控制台呼出检测的核心原理是基于浏览器行为差异和环境特征变化。当开发者工具打开时,浏览器会表现出多种可检测的特征变化。
1.1 视口尺寸检测原理
当控制台打开时,浏览器可视区域(viewport)的尺寸会发生变化,这是最直接的检测方式:
js
class ViewportDetector {
constructor() {
this.originalWidth = window.visualViewport.width;
this.originalHeight = window.visualViewport.height;
this.detectionEnabled = false;
}
startDetection() {
this.detectionEnabled = true;
setInterval(() => {
const currentWidth = window.visualViewport.width;
const currentHeight = window.visualViewport.height;
if (currentWidth < this.originalWidth ||
currentHeight < this.originalHeight) {
this.onConsoleDetected('viewport_change');
}
}, 500);
}
onConsoleDetected(reason) {
console.log(`控制台检测通过: ${reason}`);
// 执行防护动作,如刷新页面、跳转或警告
window.location.reload();
}
}1.2 控制台API监控
js
网站可以重写console对象的方法来检测控制台调用:
const consoleMonitor = (function() {
const originalConsole = {...console};
const callStack = [];
// 重写所有console方法
['log', 'error', 'warn', 'info', 'debug'].forEach(method => {
console[method] = function(...args) {
// 记录调用信息
callStack.push({
method,
arguments: args,
timestamp: Date.now(),
stack: new Error().stack
});
// 执行原始操作
originalConsole[method].apply(console, args);
// 检测到控制台活动
if (callStack.length > 10) { // 阈值检测
triggerProtection('console_flooding');
}
};
});
return {callStack, originalConsole};
})();二、高级检测技术
2.1 性能时序检测
利用debugger语句执行时的时间差异进行检测:
js
class PerformanceDetector {
constructor() {
this.threshold = 100; // 毫秒阈值
}
async checkDebuggerPerformance() {
const startTime = performance.now();
// 创建debugger检测点
try {
// 方法1: 直接debugger
debugger;
} catch (e) {}
const endTime = performance.now();
const executionTime = endTime - startTime;
if (executionTime > this.threshold) {
this.onDebuggerDetected(executionTime);
return true;
}
return false;
}
async advancedCheck() {
// 多次检测提高准确性
const results = [];
for (let i = 0; i < 5; i++) {
results.push(await this.checkDebuggerPerformance());
}
const detectedCount = results.filter(Boolean).length;
return detectedCount >= 3; // 5次中3次检测到即为真
}
onDebuggerDetected(executionTime) {
console.warn(`检测到调试器,执行时间: ${executionTime}ms`);
// 防护逻辑
}
}2.2 异步调试器检测 更高级的检测使用异步和动态方法:
js
class AdvancedDebuggerDetector {
constructor() {
this.uniqueIdentifier = 0;
this.detectionMethods = [];
}
// 动态debugger生成
createDynamicDebugger() {
const uniqueId = this.uniqueIdentifier++;
const debuggerCode = `debugger;/*# sourceURL=dynamic-debugger-${uniqueId}.js*/`;
// 多种执行方式
const executers = [
() => Function(debuggerCode)(),
() => eval(debuggerCode),
() => (0, eval)(debuggerCode), // 间接eval
() => {
const script = document.createElement('script');
script.textContent = debuggerCode;
document.head.appendChild(script);
document.head.removeChild(script);
}
];
return executers[Math.floor(Math.random() * executers.length)];
}
// 启动综合检测
startComprehensiveDetection() {
// 定时检测
setInterval(() => {
const dynamicDebugger = this.createDynamicDebugger();
try {
dynamicDebugger();
} catch (e) {
// 异常处理
}
}, 1000);
// 性能监控
this.monitorPerformance();
}
monitorPerformance() {
let lastCheck = performance.now();
setInterval(() => {
const currentTime = performance.now();
const timeDiff = currentTime - lastCheck;
// 检测时间异常
if (timeDiff > 2000) { // 2秒间隔异常
this.onSuspiciousActivity('time_anomaly');
}
lastCheck = currentTime;
}, 100);
}
}三、DOM与事件监测
3.1 键盘事件监听
检测开发者工具快捷键:
js
class KeyboardEventMonitor {
constructor() {
this.keyHistory = [];
this.ctrlKeyPressed = false;
this.shiftKeyPressed = false;
this.initializeEventListeners();
}
initializeEventListeners() {
document.addEventListener('keydown', (event) => {
this.handleKeyDown(event);
});
document.addEventListener('keyup', (event) => {
this.handleKeyUp(event);
});
// 防止右键菜单检查
document.addEventListener('contextmenu', (event) => {
this.handleContextMenu(event);
});
}
handleKeyDown(event) {
const key = event.key.toLowerCase();
// 检测F12
if (key === 'f12' || event.keyCode === 123) {
event.preventDefault();
this.onDeveloperShortcut('f12');
return;
}
// 检测Ctrl+Shift+I
if (event.ctrlKey && event.shiftKey && key === 'i') {
event.preventDefault();
this.onDeveloperShortcut('ctrl_shift_i');
return;
}
// 检测Ctrl+Shift+J
if (event.ctrlKey && event.shiftKey && key === 'j') {
event.preventDefault();
this.onDeveloperShortcut('ctrl_shift_j');
return;
}
// 检测Ctrl+Shift+C
if (event.ctrlKey && event.shiftKey && key === 'c') {
event.preventDefault();
this.onDeveloperShortcut('ctrl_shift_c');
return;
}
}
onDeveloperShortcut(shortcut) {
console.warn(`检测到开发者工具快捷键: ${shortcut}`);
this.executeCounterMeasures();
}
executeCounterMeasures() {
// 多种应对策略
const strategies = [
() => window.location.reload(),
() => { throw new Error('Debugging not allowed'); },
() => this.startMemoryBomb(),
() => this.redirectToConfusion()
];
const randomStrategy = strategies[Math.floor(Math.random() * strategies.length)];
randomStrategy();
}
startMemoryBomb() {
// 内存压力策略
const largeArray = [];
for (let i = 0; i < 100000; i++) {
largeArray.push(new Array(1000).fill('*'));
}
}
}四、反检测与绕过技术
4.1 基础绕过方法
针对常见检测手段的应对策略:
js
class ConsoleProtectionBypass {
constructor() {
this.originalFunctions = new Map();
this.initialized = false;
}
initialize() {
if (this.initialized) return;
this.restoreConsoleMethods();
this.bypassDebuggerTraps();
this.disablePerformanceMonitoring();
this.restoreEventListeners();
this.initialized = true;
}
// 恢复console方法
restoreConsoleMethods() {
const consoleMethods = ['log', 'error', 'warn', 'info', 'debug', 'table'];
consoleMethods.forEach(method => {
if (console[method] && console[method].restore) {
console[method].restore();
}
});
}
// 绕过debugger陷阱
bypassDebuggerTraps() {
// 方法1: 重写Function构造函数
const originalFunction = Function.prototype.constructor;
Function.prototype.constructor = function(...args) {
if (args.length > 0 && typeof args[0] === 'string' &&
args[0].includes('debugger')) {
console.log('拦截debugger语句:', args[0]);
return function() {};
}
return originalFunction.apply(this, args);
};
// 方法2: 禁用特定断点
this.disableSpecificBreakpoints();
}
disableSpecificBreakpoints() {
// 使用断点管理API
if (typeof debugger !== 'undefined') {
// 动态脚本注入绕过
const script = document.createElement('script');
script.textContent = `
window.__originalDebugger = debugger;
debugger = function() { /* 空函数 */ };
`;
document.head.appendChild(script);
}
}
// 禁用性能监控
disablePerformanceMonitoring() {
// 重写performance方法
const originalNow = performance.now;
let timeOffset = 0;
performance.now = function() {
return originalNow.call(performance) - timeOffset;
};
// 随机时间偏移
setInterval(() => {
timeOffset += Math.random() * 10;
}, 1000);
}
}4.2 高级绕过技术
针对复杂检测系统的应对方案:
js
class AdvancedBypass {
constructor() {
this.hooks = new Map();
}
// 函数重写hook
hookFunction(target, methodName, replacement) {
const original = target[methodName];
const self = this;
target[methodName] = function(...args) {
// 调用前置处理
const preResult = self.beforeCall(methodName, args);
if (preResult.handled) return preResult.returnValue;
// 调用原始函数或替换函数
const result = replacement.call(this, original, ...args);
// 调用后置处理
const postResult = self.afterCall(methodName, result, args);
return postResult.returnValue;
};
this.hooks.set(methodName, {original, replacement});
}
// 代理整个对象
createProxy(target) {
const handler = {
get: (obj, prop) => {
if (prop === 'debugger') {
return function() {}; // 空函数
}
const value = obj[prop];
return typeof value === 'function' ? value.bind(obj) : value;
},
set: (obj, prop, value) => {
if (typeof value === 'function' &&
value.toString().includes('debugger')) {
console.log('拦截debugger函数设置:', prop);
return true; // 阻止设置
}
obj[prop] = value;
return true;
}
};
return new Proxy(target, handler);
}
// 反检测代码注入
injectAntiDetection() {
const styles = `
<style id="anti-detection-styles">
/* 隐藏检测元素 */
.detection-element { display: none !important; }
/* 防止尺寸检测 */
body { min-width: 100vw !important; min-height: 100vh !important; }
</style>
`;
const script = `
<script>
// 覆盖检测变量
window.__isDeveloperToolsOpen = false;
window.visualViewport = new Proxy(window.visualViewport, {
get: (target, prop) => {
if (prop === 'width') return window.innerWidth;
if (prop === 'height') return window.innerHeight;
return target[prop];
}
});
</script>
`;
document.head.insertAdjacentHTML('beforeend', styles + script);
}
}