Anti-Debugging Techniques in JavaScript: A Deep Dive

← Back to Blog

Why Anti-Debugging Matters

Obfuscation protects your code from static analysis — reading the source file. But a determined reverse engineer can bypass static analysis entirely by running your code and observing its behavior with browser DevTools. Anti-debugging techniques target this dynamic analysis approach, making it much harder to step through your code, set breakpoints, or observe runtime values.

Technique 1: Debugger Statement Loop

The simplest anti-debugging technique is inserting debugger statements in a continuous loop. When DevTools is open, the browser breaks on each debugger statement, pausing execution — making it nearly impossible to use the debugger normally.

// Basic debugger trap
setInterval(function() {
  debugger;
}, 100);

The javascript-obfuscator's "Debug Protection" option implements a more sophisticated version of this that's harder to simply click through.

Technique 2: DevTools Detection

It's possible to detect when browser DevTools is open using several browser quirks:

// Size-based detection (works in some browsers)
function isDevToolsOpen() {
  return window.outerWidth - window.innerWidth > 200 ||
         window.outerHeight - window.innerHeight > 200;
}

// Performance-based detection
function isDevToolsOpen() {
  const start = performance.now();
  debugger;
  return performance.now() - start > 100; // Debugger paused execution
}

Note: These methods are not 100% reliable across all browsers and browser versions, but they work against non-technical users.

Technique 3: Function.toString() Monitoring

Sophisticated analysis tools often override built-in functions. You can detect this:

// Detect if console.log has been tampered with
const nativeLog = console.log.toString();
if (nativeLog.indexOf('[native code]') === -1) {
  // console.log has been overridden — kill execution
  while(true) {}
}

Technique 4: Timing Attacks

Code executes much slower when being stepped through in a debugger. Timing-based checks exploit this:

function timingCheck() {
  const start = Date.now();
  // Fast computation
  for (let i = 0; i < 1000; i++) {}
  const elapsed = Date.now() - start;
  
  if (elapsed > 100) {
    // Execution is too slow — debugger is attached
    document.location = 'about:blank';
  }
}
timingCheck();

Technique 5: Anti-Eval Detection

Some reverse engineering workflows involve eval()-ing modified versions of your code. Detecting eval contexts:

// Detect if running in eval context
try {
  eval('var _check = 1');
  if (typeof _check !== 'undefined') {
    // Running in eval — suspicious
  }
} catch(e) { /* strict mode blocks eval */ }

The Debug Protection Interval

Our obfuscator's debug protection option includes an interval parameter — how often (in milliseconds) to re-trigger the debugger check. A shorter interval (e.g., 500ms) is more aggressive but may impact user experience if somehow triggered in production. 2000ms is a reasonable default.

Combining Techniques

Anti-debugging is most effective layered with obfuscation. Obfuscation forces use of dynamic analysis; anti-debugging makes dynamic analysis painful. Together, they significantly raise the bar for anyone trying to understand your code at runtime.

Enable it easily: Toggle Debug Protection in our free obfuscator to automatically apply advanced anti-debugging to your JavaScript.