DEV Community

WangLiwen
WangLiwen

Posted on

JavaScript Magic Tricks: Encryption and Anti-Debugging

JavaScript obfuscation has been used by many JavaScript developers because it is really useful and practical, and can be used to protect code from being analyzed, copied, or stolen.

However, obfuscated JavaScript may still be analyzed or debugged by others. To counteract this, this article shares an anti-debugging technique.

Goal

To make function names unmodifiable, and if modified, the code will not run.

Technical Principle

Encrypt JavaScript using a reversible algorithm. When publishing or releasing code, only provide the "ciphertext" part.The running code contains a decryption function, but the key is implicitly passed and difficult to discover. Additionally, the decryption function can be encrypted again to make analysis even more difficult.

Demo Example

Here is an example code:

function get_copyright(){
    var domain = "jshaman.com";
    var from_year = 2017;
    var copyright = "(c)" + from_year + "-" + (new Date).getFullYear() + "," + domain;
    console.log(copyright);
}
console.log(get_copyright());
Enter fullscreen mode Exit fullscreen mode

First, use the JShaman JavaScript Obfuscator for simple obfuscation, enabling only "function name encryption" to obtain random function names. The resulting code is as follows:

function _0x31aca() {
  var domain = "jshaman.com";
  var from_year = 2017;
  var copyright = "(c)" + from_year + "-" + new Date().getFullYear() + "," + domain;
  console.log(copyright);
}
console.log(_0x31aca());

Enter fullscreen mode Exit fullscreen mode

Function name changed from original get_copyright to _0x31aca.
When analyzing and debugging, analysts often rename meaningless function names to meaningful names to make it easier to understand.What we want to do in this article is to prohibit renaming, so if the function name is modified, the function will not execute.

Continue with the following lines of code that are included in the function.

var domain = "jshaman.com";
var from_year = 2017;
var copyright = "(c)" + from_year + "-" + new Date().getFullYear() + "," + domain;
console.log(copyright);
Enter fullscreen mode Exit fullscreen mode

Using the xor algorithm, it is converted into encrypted characters:

function random_key(key, i) {
    return key.charCodeAt( Math.floor(i % key.length) );
}
function enxor(data, key) {
    return data.split("").map(function(c,i,a){
        return data.charCodeAt(i)^random_key(key,i);
    }).join(",");
}
function dexor(data, key) {
    return data.split(",").map(function(c,i,a){
        return String.fromCharCode(c^random_key(key,i));
    }).join("");
}

var js_code=`
var domain = "jshaman.com";
var from_year = 2017;
var copyright = "(c)" + from_year + "-" + new Date().getFullYear() + "," + domain;
console.log(copyright);
`;

console.log(enxor(js_code,"_0x31aca"));

Enter fullscreen mode Exit fullscreen mode

Execute output:

Image description

Note that the second parameter passed to the encryption function is "_0x31aca", which is the name of the function where the code is located. Note: This value will not explicitly appear during decryption.

The previous 0x31aca function code has been transformed into the following form:

function _0x31aca(){
    var decode_js_code = dexor("85,70,25,65,17,5,12,12,62,89,22,19,12,65,65,11,44,88,25,94,80,15,77,2,48,93,90,8,59,23,2,19,127,86,10,92,92,62,26,4,62,66,88,14,17,83,83,80,104,11,114,69,80,19,67,2,48,64,1,65,88,6,11,21,127,13,88,17,25,2,74,67,127,27,88,85,67,14,14,62,38,85,25,65,17,74,67,67,114,18,88,24,17,15,6,22,127,116,25,71,84,73,74,79,56,85,12,117,68,13,15,56,58,81,10,27,24,65,72,65,125,28,90,19,26,65,7,14,50,81,17,93,10,107,0,14,49,67,23,95,84,79,15,14,56,24,27,92,65,24,17,8,56,88,12,26,10,107", arguments.callee.name);
    eval(decode_js_code);  
}
console.log(_0x31aca());

Enter fullscreen mode Exit fullscreen mode

Full source

function random_key(key, i) {
    return key.charCodeAt( Math.floor(i % key.length) );
}
function dexor(data, key) {
    return data.split(",").map(function(c,i,a){
        return String.fromCharCode(c^random_key(key,i));
    }).join("");
}
function _0x31aca(){
    var decode_js_code = dexor("85,70,25,65,17,5,12,12,62,89,22,19,12,65,65,11,44,88,25,94,80,15,77,2,48,93,90,8,59,23,2,19,127,86,10,92,92,62,26,4,62,66,88,14,17,83,83,80,104,11,114,69,80,19,67,2,48,64,1,65,88,6,11,21,127,13,88,17,25,2,74,67,127,27,88,85,67,14,14,62,38,85,25,65,17,74,67,67,114,18,88,24,17,15,6,22,127,116,25,71,84,73,74,79,56,85,12,117,68,13,15,56,58,81,10,27,24,65,72,65,125,28,90,19,26,65,7,14,50,81,17,93,10,107,0,14,49,67,23,95,84,79,15,14,56,24,27,92,65,24,17,8,56,88,12,26,10,107", arguments.callee.name);
    eval(decode_js_code);  
}
console.log(_0x31aca());
Enter fullscreen mode Exit fullscreen mode

Execute output:

Image description

The output result is the same as before the code modification.
The meaning of this code is to decrypt and execute with eval, which achieves the same functional effect as the original code.

The ingenious part is that when decrypting, the parameter used during encryption, "_0x31aca", is not passed in!
If you rename the function, for example to abc, the execution will fail.

Image description

This is because the decryption implicitly uses arguments.callee.name, which is the name of the calling function.

When the function name is _0xag and matches the key parameter passed during encryption, the correct code characters can be decrypted and executed using eval. However, if the function name is changed to abc, the passed characters will be interpreted as abc, making it impossible to decrypt the original code correctly, and therefore cannot be executed using eval.

Full source

function random_key(key, i) {
    return key.charCodeAt( Math.floor(i % key.length) );
}
function enxor(data, key) {
    return data.split("").map(function(c,i,a){
        return data.charCodeAt(i)^random_key(key,i);
    }).join(",");
}
function dexor(data, key) {
    return data.split(",").map(function(c,i,a){
        return String.fromCharCode(c^random_key(key,i));
    }).join("");
}

var js_code=`
var domain = "jshaman.com";
var from_year = 2017;
var copyright = "(c)" + from_year + "-" + new Date().getFullYear() + "," + domain;
console.log(copyright);
`;

console.log(enxor(js_code,"_0x31aca"));

function _0x31aca(){
    var decode_js_code = dexor("85,70,25,65,17,5,12,12,62,89,22,19,12,65,65,11,44,88,25,94,80,15,77,2,48,93,90,8,59,23,2,19,127,86,10,92,92,62,26,4,62,66,88,14,17,83,83,80,104,11,114,69,80,19,67,2,48,64,1,65,88,6,11,21,127,13,88,17,25,2,74,67,127,27,88,85,67,14,14,62,38,85,25,65,17,74,67,67,114,18,88,24,17,15,6,22,127,116,25,71,84,73,74,79,56,85,12,117,68,13,15,56,58,81,10,27,24,65,72,65,125,28,90,19,26,65,7,14,50,81,17,93,10,107,0,14,49,67,23,95,84,79,15,14,56,24,27,92,65,24,17,8,56,88,12,26,10,107", arguments.callee.name);
    eval(decode_js_code);  
}
console.log(_0x31aca());

Enter fullscreen mode Exit fullscreen mode

Top comments (0)