Up until now, I’ve been working on JavaScript related anti-debugging techniques. But when I searched online, I found that there were not many articles on the subject, and if there were any, they were very incomplete. So in this article, I’m going to give you a summary of JavaScript anti-debugging techniques. It’s worth noting that some of these methods are already widely used by cybercriminals in malware.
With JavaScript, you only need to spend a little time debugging and analyzing, and you can understand the functional logic of the JavaScript code snippet. What we’re going to talk about can make it difficult for anyone who wants to analyze your JavaScript code. But our technique isn’t about obfuscation, it’s about making active debugging of code difficult.
The technical methods to be introduced in this paper are as follows:
1. Detect unknown execution environment (our code only wants to be executed in the browser);
2. Check debugging tools, such as DevTools.
3. Code integrity control;
4. Flow integrity control;
5. Reverse simulation;
In short, if we detect an “abnormal” condition, the flow of the program will change and jump to a fake code block and “hide” the real functional code.
1. Function redefinition
This is one of the most basic and common techniques for undebugging code. In JavaScript, we can redefine the functions used to gather information. For example, the console.log() function can be used to collect information about functions and variables and display it in the console. If we redefine this function, we can modify its behavior and hide specific information or display bogus information.
We can run this function directly in DevTools to see what it does:
`console.log(``"HelloWorld"` `); ` `var` `fake =` `function` ` () {}; ` `window[``'console'` `] [` `'log'``]= fake; ` `console.log(``"Youcan't see me!"` `); `Copy the code
After running, we should see:
VM48:1 Hello World
You will notice that the second message is not displayed because we have redefined the function to “disable” its original functionality. But we can also make it display fake information. Like this:
`console.log(``"Normalfunction"` `); ` `//First we save a reference to the original console.logfunction`
`var` `original = window[``'console'` `] [` `'log'` `]; ` `//Next we create our fakefunction`
`//Basicly we check the argument and if match we call original function with otherparam.`
`// If there is no match pass the argument to the original function`
`var` `fake =` `function``(argument) {`
`if` `(argument ===` `"Ka0labs"``) {`
`original(``"Spoofed!"` `); ` `} ` `else` `{` `original(argument); ` `}` `}` `// We redefine now console.log as our fakefunction`
`window[``'console'` `] [` `'log'``]= fake; ` `//Then we call console.log with any argument` `console.log(``"Thisis unaltered"` `); ` `//Now we should see other textin console different to "Ka0labs"`
`console.log(``"Ka0labs"` `); ` `//Aaaand everything still OK` `console.log(``"Byebye!"` `); `Copy the code
If all goes well:
Normal function
VM117:11 This is unaltered
VM117:9 Spoofed!
VM117:11 Bye bye!
In fact, in order to control how the code executes, we can also change the functionality of the function in more intelligent ways. For example, we could build a code snippet based on the above code and redefine the eval function. We can pass JavaScript code to the eval function, which will then be evaluated and executed. If we redefined this function, we could run different code:
`//Just a normal eval`
`eval(` `"console.log('1337')"` `); ` `//Now we repat the process... ` `var` `original =eval; ` `var` `fake =` `function``(argument) {` `// If the code to be evaluated contains1337... ` `if` `(argument.indexOf(``"1337"` `)! = = 1) {` ` / /... we just execute a different code` `original(``"for (i = 0; i < 10; i++) { console.log(i); }"` `); ` `} ` `else` `{` `original(argument); ` ` `}} ` `eval= fake; ` `eval(` `"console.log('Weshould see this... ')"` `); ` `//Now we should see the execution of afor loop instead of what is expected`
`eval(` `"console.log('Too1337 for you! ')"` `); `Copy the code
The running results are as follows:
1337 VM146:1We should see this… VM147:10 VM147:11 VM147:12 VM147:13 VM147:14 VM147:15 VM147:16 VM147:17 VM147:18 VM147:19
As mentioned earlier, while this method is very clever, it is also very basic and common, so it is easy to detect.
Second, the breakpoint
To help us understand what code does, JavaScript debugging tools such as DevTools can prevent script code from executing by setting breakpoints, which are fundamental to code debugging.
If you’ve studied debuggers or x86 architectures, you’re probably familiar with the 0xCC directive. In JavaScript, we have a similar instruction called the Debugger. After we declare the debugger function in the code, the script will stop running at the debugger instruction. Such as:
`console.log(``"Seeme!"` `); ` `debugger; ` `console.log(``"Seeme!"` `); `Copy the code
Many commercial products define an infinite loop of debugger instructions in their code, but some browsers block it and some don’t. The main purpose of this approach is to annoy people who want to debug your code, because an infinite loop means that the code keeps popping up asking if you want to continue running the script:
setTimeout(function(){while (true) {eval(“debugger”)
3. Time difference
This is a time-based anti-debugging technique borrowed from traditional anti-reverse techniques. When executed in a tool environment such as DevTools, scripts run very slowly (and for a long time), so we can determine if the script is currently being debugged based on the running time. For example, we can measure the run time between two set points in the code and use this value as a reference. If the run time exceeds this value, the script is currently running in the debugger.
The demo code is as follows:
`set Interval(``function``(){` `var` `startTime = performance.now(), check,diff; ` `for` `(check = 0; check < 1000; check++){` `console.log(check); ` `console.clear(); ` `}` `diff = performance.now() - startTime; ` `if` `(diff > 200){`
`alert(``"Debugger detected!"` `); ` `} ` `}, 500); `Copy the code
4. DevTools Detection (Chrome)
This technique leverages the ID attribute in div elements, which the browser automatically tries to retrieve when a div element is sent to the console (such as console.log(div)). If the code calls the getter method after console.log, the console is currently running.
The simple proof-of-concept code is as follows:
`let div = document.createElement(``'div'` `); ` `let loop = setInterval(() => {` `console.log(div); ` `console.clear(); ` `}); ` `Object.defineProperty(div,``"id"``, {get: () => {` `clearInterval(loop); ` `alert(``"Dev Tools detected!"` `); ` `}}); `Copy the code
Implicit flow integrity control
When we try to de-obfuscate code, we first try to rename some function or variable, but in JavaScript we can detect if the function name has been changed, or we can get the original name or call order directly from the stack trace.
Arguments.callee.caller can help us create a stack trace to store previously executed functions.
`function` `getCallStack() {`
`var` `stack =` `"#"``, total = 0, fn =arguments.callee; ` `while` `( (fn = fn.caller) ) {`
`stack = stack +` `""` `+fn.name; ` `total++` `}` `return` `stack`
`}`
`function` `test1() {` `console.log(getCallStack()); ` `} ` `function` `test2() {`
`test(1); ` `} ` `function` `test3() {`
`test(2); ` `} ` `function` `test4() {`
`test(3); ` `} ` `test(4); `Copy the code
Note: The more obfuscated the source code, the better this technique works.
Proxy object
Proxy objects are one of the most useful tools in JavaScript today. They help you learn about other objects in your code, including modifying their behavior and triggering object activity in a particular context. For example, we could create a dia object and track every document.createelemen call, then record the relevant information:
`const handler = {` `// Our hook to keep the track`
`apply:` `function` `(target, thisArg, args){`
`console.log(``"Intercepted a call tocreateElement with args: "` `+ args); ` `return` `target.apply(thisArg, args)`
`}`
`}`
`document.createElement=` `new` `Proxy(document.createElement, handler)` `// Create our proxy object withour hook ready to intercept`
`document.createElement(``'div'` `); `Copy the code
Next, we can record related parameters and information in the console:
VM64:3 Intercepted a call to createElement with args: div
We can use this information and debug code by intercepting specific functions, but the main purpose of this article is to introduce anti-debugging techniques, so how do we detect if the “other side” is using proxy objects? It’s a cat-and-mouse game, for example, we could use the same code snippet and try to call toString and catch the exception:
`//Call a "virgin"createElement:` `try` `{` `document.createElement.toString(); ` `}``catch``(e){` `console.log(``"I saw your proxy!"` `); ` ` `}Copy the code
The information is as follows:
`"function createElement() { [native code] }"`
Copy the code
But when we use the proxy:
`//Then apply the hook`
`consthandler = {`
`apply:` `function` `(target, thisArg, args){`
`console.log(``"Intercepted a call tocreateElement with args: "` `+ args); ` `return` `target.apply(thisArg, args)` `}` `}` `document.createElement=` `new` `Proxy(document.createElement, handler); ` `//Callour not-so-virgin-after-that-party createElement` `try` `{` `document.createElement.toString(); ` `}``catch``(e) {` `console.log(``"I saw your proxy!"` `); ` ` `}Copy the code
Yes, we can indeed detect agents:
VM391:13 I saw your proxy!
We can also add the toString method:
`const handler = {`
`apply:` `function` `(target, thisArg, args){`
`console.log(``"Intercepted a call tocreateElement with args: "` `+ args); ` `return` `target.apply(thisArg, args)` `}` `}` `document.createElement=` `new` `Proxy(document.createElement, handler); ` `document.createElement= Function.prototype.toString.bind(document.createElement); ` `//Add toString` `//Callour not-so-virgin-after-that-party createElement` `try` `{` `document.createElement.toString(); ` `}``catch``(e) {` `console.log(``"I saw your proxy!"` `); ` ` `}Copy the code
Now we can’t detect it:
`"function createElement() { [native code] }"`
Copy the code
As I said, it’s a game of cat and mouse. This time I recommend a communication circle, which summarizes mobile application website development, CSS, HTML, Webpack, Vue node Angular and interview resources. Students interested in web development technology, welcome to join: 582735936, no matter you are small white or Daniel I welcome, and Daniel organized a set of efficient learning routes and tutorials to share with you for free, while updating video materials every day. In the end, I wish you all success as soon as possible, get satisfactory offer, fast promotion and salary increase, and walk on the peak of life.