question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

This is not really useful in any way in it's current form (String obfuscation).

See original GitHub issue

In it’s current form, the string obfuscation is pretty useless. I was lead to this after reversing a js program and seeing this inside the package.json.

How to use: Simply open the script in a node.js environment with a seperate console window. Paste this code inside it and replace ‘scriptinquestion.js’ with the js file you are running:


fs.readFile('scriptinquestion.js', 'utf8', (err, data) => {
    let str = data.substring(data.indexOf("_0x"));
    while (str) {
        for (let start = 0; start < 15; start++) {
            if (str[start] === '=') {
                for (let eol = 0; eol < 15; eol++) {
                    if (str[start + eol] === ',' || str[start + eol] === ';') {
                        let first = str.substring(0, start - 1);
                        let second = str.substring(start + 2, start + eol);

                        let callStr = str.substring(0, start + eol + 1);

                        console.log("first:" + first + ", second:" + second + " from: " + callStr);

                        if (second.startsWith("_0x")) {
                            remaps.push({from: first, to: second});
                        }

                        break;
                    }
                }
            }
        }

        let strIndex = str.substring(10).indexOf("_0x");
        if (strIndex !== -1) {
            str = str.substring(strIndex + 10);
        } else {
            break;
        }
    }

    str = data.substring(data.indexOf("_0x"));

    for (let index in remaps) {
        let remap = remaps[index];
        console.log("remap " + remap.from + "," + remap.to);
    }

    let newString = data;

    while (str) {
        for (let openBracket = 0; openBracket < 10; openBracket++) {
            if (str[openBracket] === '(') {
                for (let closeBracket = 0; closeBracket < 8; closeBracket++) {
                    if (str[openBracket + closeBracket] === ')') {
                        let callStr = str.substring(0, openBracket + closeBracket + 1);
                        let originalCallStr = callStr;

                        // chained obfuscation.
                        for (let i = 0; i < 3; i++) {
                            for (let index in remaps) {
                                let remap = remaps[index];
                                callStr = callStr.replace(remap.from, remap.to);
                            }
                        }

                        if (callStr.includes("!")) break;

                        let result = eval(callStr);

                        console.log("found call:" + callStr + " eval: " + result);

                        newString = newString.replace(originalCallStr, "'" + result + "'");

                        break;
                    }
                }
            }
        }
        let strIndex = str.substring(10).indexOf("_0x");
        if (strIndex !== -1) {
            str = str.substring(strIndex + 10);
        } else {
            console.log(newString);
            fs.writeFile("out.js", newString, function (err) {
                process.exit(-1);
            });
            return;
        }
    }
    process.exit(-1);
})

How does this work? It simply evaluates inside the scripts context (after you run it), finds strings in sourcecode and simply evaluates them in-place. There (should) be virtually no way to block eval effectively.

How would I defend against this attack? I am not am not knowledgeable in javascript, however, I would include a rolling number to encrypt and decrypt array offsets at function start and end with a call stack analysis, I would first execute the script and analyze which functions call what by adding start and end loggers to each function which would generate something like this. a() calls b() calls c() that means that it would have to call a() deobfuscation and then b() deobfuscation for the string offsets in order to be able to eval the strings inside function c(). This would make it extremely time-consuming as you would have to use a debugger, which this obfuscator already provides protection against. While it would still be possible to add in logstart and logend to just run the program to generate your own call stack, it could still be improved in a way which would trigger other protections unlike the one I specified above.

Closing notes: I do understand that javascript has it’s limits, however, currently this offers near zero protection. This is just an example, you can apply the same concept to remove opaque predicates(dead code), string splitting and what-not.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:21 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
sanex3339commented, Jul 12, 2021

Yeah, I see. eval within the obfuscated code context can show original values, but for this cases, I added more variants with

  stringArrayWrappersCount: 1,
  stringArrayWrappersChainedCalls: true,
  stringArrayWrappersParametersMaxCount: 2,
  stringArrayWrappersType: 'variable',

options.

Right now your example is very code-specific, it won’t work with many cases of obfuscated code (chained calls, mangled generator).

Please try to adapt your code for this:

var R = [
    '2UMhYCy',
    '6BELvZn',
    '880870EQpTQx',
    'Hello\x20World!',
    '1291816wjtjst',
    '955419bgVbuD',
    '3554872eQPmKA',
    'log',
    '1TyirKR',
    '820514hhyTRn',
    '194869hEPSJu',
    '1YaWnoo',
    '530177wNNdlJ'
];
(function (x, z) {
    var l = function (x, z, X, c, Z) {
        return B(Z - 0x39a, z);
    };
    var d = function (x, z, X, c, Z) {
        return B(Z - 0x39a, z);
    };
    while (!![]) {
        try {
            var X = -parseInt(l(0x4b0, 0x4b1, 0x4ae, 0x4b6, 0x4b0)) * parseInt(l(0x4b3, 0x4ae, 0x4ab, 0x4a9, 0x4ac)) + parseInt(d(0x4b1, 0x4ad, 0x4aa, 0x4b0, 0x4ab)) * parseInt(l(0x4a6, 0x4ad, 0x4ac, 0x4b0, 0x4ad)) + parseInt(l(0x4b0, 0x4b0, 0x4ba, 0x4b7, 0x4b7)) * -parseInt(l(0x4af, 0x4b5, 0x4bb, 0x4b5, 0x4b4)) + -parseInt(d(0x4b6, 0x4b0, 0x4aa, 0x4ad, 0x4af)) * parseInt(d(0x4aa, 0x4b1, 0x4aa, 0x4ad, 0x4ae)) + parseInt(l(0x4b4, 0x4ac, 0x4ae, 0x4ae, 0x4b1)) + -parseInt(d(0x4b8, 0x4b6, 0x4b5, 0x4b4, 0x4b3)) + parseInt(l(0x4ba, 0x4b1, 0x4b2, 0x4b2, 0x4b5));
            if (X === z) {
                break;
            } else {
                x['push'](x['shift']());
            }
        } catch (c) {
            x['push'](x['shift']());
        }
    }
}(R, 0xbe4bd));
function hi() {
    var n = function (x, z, X, c, Z) {
        return B(x - -0x172, X);
    };
    var i = function (x, z, X, c, Z) {
        return B(x - -0x172, X);
    };
    console[n(-0x56, -0x5a, -0x50, -0x5a, -0x5c)](i(-0x5a, -0x5d, -0x57, -0x53, -0x5e));
}
function B(x, z) {
    B = function (X, c) {
        X = X - 0x111;
        var l = R[X];
        return l;
    };
    return B(x, z);
}
hi();

I want to see, how this code will look like, to collect more information.

But in general, I have to think about how to prevent code evaluation within the obfuscated code context. For example, we can break eval (with a new option, for example). We can break both: external evaluation and evaluation within the same obfuscated context. The main problem is to do this in a sneaky way.

0reactions
sanex3339commented, Feb 14, 2022

Closed. Btw, stringArrayCallsTransform option was added.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Methods for de-obfuscating javascript that uses string ...
The way I can think of doing this, is to evaluate all the string concatenations so they are turned into a readable equivalent....
Read more >
How to Obfuscate Strings in Rust the Easy Way Using the ...
While string obfuscation isn't a new concept, the Rust programming language makes it very easy to implement via a crate called litcrypt.
Read more >
Who Moved My Code? An Anatomy of Code Obfuscation - InfoQ
Obfuscating strings is a good way to save you the use of expensive and complex obfuscation tools on one hand and make your...
Read more >
Automatically Extracting Obfuscated Strings from Malware ...
Since strings.exe looks for a sequence of printable characters, it is unable to identify the human readable string because the characters are ...
Read more >
SoK: Use of Cryptography in Malware Obfuscation
We discuss few of the prominent techniques from cryptography used in malware obfuscation, but which do not evade detection under our model in...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found