Evading JavaScript Anti-Debugging Mechanisms
2 min read

Evading JavaScript Anti-Debugging Mechanisms

Debuggers are wonderful tools that allow developers to pause the execution of code and analyze what's going on in the code at that point in time. Developers use it all the time to debug problems within their code

A simple example of Chrome's debugger (https://javascript.info/debugging-chrome)

Those who reverse-engineer can use debuggers for the very same reason. Since obfuscated code will often mangle variable and function names, debugging can provide valuable insight into what a function does. Companies that provide client-side protection know this and so they come up with ways to prevent people from debugging protected code.

JScrambler's Debugger loop on supremenewyork.com 

If you try opening your console on Supreme's website you'll find yourself stuck in a debugger loop. This makes it incredibly annoying to reverse engineer the file. You can disable breakpoints and no longer be stuck in this loop but it means you can no longer use the debugger tool to your advantage.

The Obvious Approach

Scripts such as Anti Anti-debugger on Greasyfork attempt to bypass debugger traps by overriding the caller's function body, removing the debugger statements, and eval()ing the new function. This is a smart idea but sadly doesn't work on JScrambler protected scripts because the debugger traps are evaluated in a separate context.

It's also not as simple as removing the debugger; calls in the code because they utilize integrity checks and hide debugger calls inside of encrypted eval functions.

The Less Obvious Approach

The approach I and my buddy Jordin ended up using is to rename the debugger keyword completely. The debugger can't stop on the debugger keyword if it was named banana instead right? To do this we used our modified version of Firefox to rename the keyword and here's how you can do it too (If you want to learn to build your own Firefox check out our post Going Above The Obfuscator):

--- a/js/src/frontend/ReservedWords.h
+++ b/js/src/frontend/ReservedWords.h
@@ -20,7 +20,7 @@
   MACRO(catch, catch_, TokenKind::Catch)                \
   MACRO(const, const_, TokenKind::Const)                \
   MACRO(continue, continue_, TokenKind::Continue)       \
-  MACRO(debugger, debugger, TokenKind::Debugger)        \
+  MACRO(ticket_debugger, debugger, TokenKind::Debugger) \
   MACRO(default, default_, TokenKind::Default)          \
   MACRO(delete, delete_, TokenKind::Delete)             \
   MACRO(do, do_, TokenKind::Do)                         \
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -107,7 +107,7 @@
   MACRO_(currencySign, currencySign, "currencySign")  \
   MACRO_(day, day, "day")                             \
   MACRO_(dayPeriod, dayPeriod, "dayPeriod")           \
-  MACRO_(debugger, debugger, "debugger")              \
+  MACRO_(ticket_debugger, debugger, "ticket_debugger")\
   MACRO_(decimal, decimal, "decimal")                 \

Finding this was a bit complex since they use annoying macros to define keyword and property names but with a bit of work (mainly done by Jordin).

Let's see what happens now when we hook a function like Array.slice and call our ticket_debugger keyword.

Breakpoint hit on our ticket_debugger keyword

It works!

We were able to change the keyword from debugger to ticket_debugger and get it to pause on our breakpoints instead of theirs. This makes reverse engineering their code 100x easier. We can easily walk their call stack and debug all the things.

Have fun!