Defeating String Obfuscation in Obfuscated NodeJS Malware using AST
In recent years, there has been a notable increase in infostealers written in Node.js. These are typically built using leaked stealer source code or open-source stealer project. The source code is often modified or simply rebranded, with additional layers of obfuscation applied. The obfuscated code is then packaged in various formats: as an Electron application, which is further wrapped using packers or installers like NSIS or Wix Installer, or as a standalone Node.js executable or Deno executable. According to detections on VirusTotal, it appears that these types of malware samples are able to evade detection by most antivirus software.
In this blog, we will examine a sample referenced in this tweet: https://x.com/dez_/status/1887161549040627942
Sample:
md5: 919664c73a5f3ad383a13008f926e705
sha256: bc65ffb0e321c91eda2f4cee97f3975eea82400991eb7a7b6d745d5800290bf2
VT: https://www.virustotal.com/gui/file/bc65ffb0e321c91eda2f4cee97f3975eea82400991eb7a7b6d745d5800290bf2
Table of Contents
Reversing the sample
After conducting a basic triage of the sample, it is apparent that the file is an MSI archive containing an Electron application. Within this archive, there would be app.asar file which has been renamed. By inspecting the file table of the MSI archive, we can locate the renamed app.asar. Once identified, we can extract all the files from the MSI archive using either 7zip or the command msiexec /a <filename> /qb TARGETDIR=<dir>
from the command line.
The app.asar file contains Node.js source code, which can be extracted with 7zip (using the Asar plugin) or by running the command npx asar extract
from the terminal.
In this particular case, there is a file named obfuscated.js, which contains heavily obfuscated Node.js code.
We can use this script that reverses the string obfuscation used in this sample, making the code easier to analyze and understand. Link to the script: https://gist.github.com/nhegde610/ca265e4f09b60fe51e89ba6a75af1db5
Note: Please run the script in a sandbox environment.
After executing the script, a file named deobfuscated.js will be generated, revealing the following information:
- The sample is Nebula Stealer.
- C2 server: 149.56.76.26.
- The sample makes requests to the following endpoints: /heartbeat and /api/clients/
<username>
/commands. - It uses remote debugging to steal cookies from Chromium-based browsers.
Some snippets of the deobfuscated code
Code snippet which shows that the sample identifies itself as Nebula Stealer
const _Hf2EDn = {};
_Hf2EDn["passwords"] = 0;
_Hf2EDn["cookies"] = 0;
_Hf2EDn["autofills"] = 0;
_Hf2EDn["wallets"] = 0;
_Hf2EDn["telegram"] = false;
const _FvzwGl = _Hf2EDn,
_iZjOJA = {
["ram"]: _FccfJvr["totalmem"](),
["version"]: _FccfJvr["version"](),
["uptime"]: _FccfJvr["uptime"],
["homedir"]: _FccfJvr["homedir"](),
["hostname"]: _FccfJvr["hostname"](),
["userInfo"]: _FccfJvr["userInfo"]()["username"],
["type"]: _FccfJvr["type"](),
["arch"]: _FccfJvr["arch"](),
["release"]: _FccfJvr["release"](),
["roaming"]: process["env"]["APPDATA"],
["local"]: process["env"]["LOCALAPPDATA"],
["temp"]: process["env"]["TEMP"],
["countCore"]: process["env"]["NUMBER_OF_PROCESSORS"],
["sysDrive"]: process["env"]["SystemDrive"],
["fileLoc"]: process["cwd"](),
["randomUUID"]: _eKmBKTG["randomBytes"](16)["toString"]("hex"),
["start"]: _vSVcaG3("bHnt18")["now"](),
["debug"]: false,
["copyright"]: "<================[ Nebula Stealer ]================>\n\n",
["url"]: null
};
Code Snippet with imports, c2 url and list of process that it checks for
const _qg1okn = require("fs"),
_OpcgYw = require("path"),
_ZWkEwm = require("axios"),
_Clrqnh = require("axios"),
_FccfJvr = require("os"),
_Wf = require("form-data"),
_H_m0LS = require("adm-zip"),
{
["execSync"]: _kTdBQ,
["exec"]: _nQrzu6g
} = require("child_process"),
_eKmBKTG = require("crypto"),
_C1iMbv = require("sqlite3"),
{
["Dpapi"]: _KWhK3mn
} = require("@primno/dpapi"),
_yX0U1b = require("child_process"),
_gwEgJW = require("socket.io-client"),
_rz2d55I = require("archiver"),
_fuAP = require("node-telegram-bot-api"),
_jG60T = require("ws"),
_VyAGCVQ = "7216535671:AAGRj3nWy1Ogk19Kdr-aBjcWpRGPPCFRVcM",
_k7S5y = "6426005755",
_AGZCNo = "4622698218",
_P971C = new _fuAP(_VyAGCVQ, {
["polling"]: false
}),
_bIkihAc = _FccfJvr["userInfo"]()["username"],
_Kzx8YH = _FccfJvr["userInfo"]()["username"],
_BoWajkL = "http://149.56.76.26",
_j5Nk4EZ = ["watcher.exe", "mitmdump.exe", "mitmproxy.exe", "mitmweb.exe", "Insomnia.exe", "HTTP Toolkit.exe", "Charles.exe", "Postman.exe", "BurpSuiteCommunity.exe", "Fiddler Everywhere.exe", "Fiddler.WebUi.exe", "processhacker.exe", "HTTPDebuggerUI.exe", "HTTPDebuggerSvc.exe", "HTTPDebuggerPro.exe", "x64dbg.exe", "Ida.exe", "Ida64.exe", "Progress Telerik Fiddler Web Debugger.exe", "HTTP Debugger Pro.exe", "Fiddler.exe", "KsDumperClient.exe", "KsDumper.exe", "FolderChangesView.exe", "BinaryNinja.exe", "Cheat Engine 6.8.exe", "Cheat Engine 6.9.exe", "Cheat Engine 7.0.exe", "Cheat Engine 7.1.exe", "Cheat Engine 7.2.exe", "OllyDbg.exe", "Wireshark.exe"];
if you want to understand how to use the script for different samples, then please read the rest of the blog
Defeating String Obfuscation using Abstract Syntax Tree (AST) Parser
To address string obfuscation, it’s essential to read and modify the code while ensuring that the resulting code remains syntactically correct. This can be achieved using an Abstract Syntax Tree (AST) parser. For this task, I’ve chosen the Babel library . While there are other AST parsers available, I opted for Babel due to the availability of several high-quality tutorials that explain how to use it. You can find all the tutorials and blogs that helped in writing this script in the references section.
Abstract Syntax Tree (AST) Parser
An AST Parser would read the given file/code and represent it in form of a tree. One can use the following to visualize it:
- AST Explorer: https://astexplorer.net/
- AST Visualizer: https://adorableredpanda.github.io/ast-visualization/
Let’s use an example:
function test(a){
var b = 5
b = a + 5
return b
}
test(6)
We can create an AST using Babel which can be visualized as shown below:

All the nodes which are drawn as circles are BabelTypes. These nodes often contain certain properties that links to other nodes.
All the code is encapsulated under Program
node. It has a property body
that contains all the code statements.
Let’s take an statement b = a + 5
, it is parsed as expression statement containing Assignment expression which contains a Binary Expression consisting of an identifier and a numeric literal.
The sooner you get used to looking at the code in this manner, the easier it gets to understand AST.
You can also refer to documentation on Babel types to understand more: https://babeljs.io/docs/babel-types
Traversing AST
In the Fig.1, the link between nodes is a straight line whereas the link between node and a property is a dotted line. This is because while traversing the tree, we would not be able to directly access them. We would have to visit the node containing that property.
For example, consider the node VariableDeclaration
, it has a property declarations
which contains another node VariableDeclarator
. Let’s say we are at the node VariableDeclarator
and would like to access the property declarations
. From the Fig.1, it seems that we just need to go one step up in order to access it but that’s not how it works. When we go step up, we wouldn’t be at the property declarations
, we would be at the node VariableDeclaration
. Then from the node VariableDeclaration
, we can access the property declarations
While traversing AST, we are going from 1 node to another linked node.
Now, it doesn’t mean that in order to traverse AST, we must start from the top. We can specify which type of node we are targeting and traverse from it. For example, if we want to access Return of a Function, then we would use the following code:
const accessReturnStatement = {
ReturnStatement(path){
// insert code to do stuff after accessing it
}
}
traverse(ast, accessReturnStatement)
When dealing with a large codebase, we need to be careful while traversing and modifying it. The above code would access all the ReturnStatement present in the codebase. Now, we probably don’t want to modify all the return statement.
So, in order to avoid this disaster, we would need to add certain conditions that apply to the specific type of return statement that we want to target. The conditions could be something like:
- Function containing return statement should not have another statement in it.
- The value return by the statement should be an identifier
const accessReturnStatement = {
ReturnStatement(path){
// insert code to do stuff after accessing it
if(!t.isIdentifier(path.node.argument)) return
if(!t.isBlockStatement(path.parentPath)) return
if (path.parentPath.body.length != 1) return
}
}
traverse(ast, accessReturnStatement)
path
is the object that is used for traversal. We can go down the tree by using path.node
and then access various properties of the node through which other nodes which are linked to it. To go up the tree, we use path.parentPath
which would allow us to access the parent node. We can chain it to access the parent of the parent node : path.parentPath.parentPath
In the above code snippet, we check if the argument of the return statement is an identifier and also check the number of statements by accessing the parent of the return statement which should be a block statement and then checking if the number of statement in it is 1.
Modifiying AST
Once we are able to do AST traversal, we might want to modify certain nodes. We have a few methods available for it. Refer Babel Plugin Handbook for more details: https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md
Methods such as path.replaceWith() , path.replaceWithMultiple() path.remove() and path.replaceWithSourceString() can be used to modifying AST. For example, let’s say we wanted to remove return statement of function containing only a return statement with an identifier, we could use the following snippet:
const accessReturnStatement = {
ReturnStatement(path){
// insert code to do stuff after accessing it
if(!t.isIdentifier(path.node.argument)) return
if(!t.isBlockStatement(path.parentPath)) return
if(!t.isFunctionDeclaration(path.parentPath.parentPath)) return
if (path.parentPath.body.length != 1) return
path.remove()
}
}
traverse(ast, accessReturnStatement)
Let’s say, instead of removing the return statement, we want to replace it with a numeric literal value: “5”, we would use the following snippet:
const accessReturnStatement = {
ReturnStatement(path){
// insert code to do stuff after accessing it
if(!t.isIdentifier(path.node.argument)) return
if(!t.isBlockStatement(path.parentPath)) return
if(!t.isFunctionDeclaration(path.parentPath.parentPath)) return
if (path.parentPath.body.length != 1) return
path.replaceWith(t.numericLiteral(5))
}
}
traverse(ast, accessReturnStatement)
I would mention that all above modifications shown above are not syntactically correct and are illegal. We are using them to show the capabilities of the Babel library. More Information about the methods can be found here: https://www.trickster.dev/post/javascript-ast-manipulation-with-babel-ast-modification-apis/
Creating the script
We are using the following template to write the script which is based on the tutorials in https://steakenthusiast.github.io/archives/ :
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generate = require("@babel/generator").default;
const beautify = require("js-beautify");
const { readFileSync, writeFileSync } = require("fs");
const vm = require("vm");
const table = require("table").table;
function deobfuscate_code(filename) {
var codeFromFile = readFileSync(filename, "utf-8");
var ast = parser.parse(codeFromFile); //creating ast parser
// insert transformation logic
//invoke the functions
writeBeautifiedFile("deobfuscated.js", ast);
}
function writeBeautifiedFile(filename, ast) {
let deobfCode = generate(ast, { comments: false }).code;
deobfCode = beautify(deobfCode, {
indent_size: 2,
space_in_empty_paren: true,
});
writeCodeToFile(deobfCode, filename);
}
function writeCodeToFile(code, filename) {
let outputPath = filename;
try {
writeFileSync(outputPath, code);
console.log(`Wrote file to ${outputPath}`);
} catch (error) {
console.log("Error writing to a file", error);
}
}
deobfuscate_code("obfuscated.js"); // update the filename
In this script, We will be doing the following:
- Rename all the function and variable names
- Extract certain parts of the code in order to execute and replace the value back in the script
- Replace references to an array.
- Perform some cosmetic changes such as concatenating strings and decoding hexadecimal strings
Renaming all the function and variable names
In Node.js malware, variable names are often renamed in ways that can create scope confusion during analysisYou can read about an example and a technique for dealing with it here: https://www.trickster.dev/post/javascript-ast-manipulation-with-babel-untangling-scope-confusion/
Instead of dealing with scope, we have decided to take a brute-force approach. The algorithm follows these steps:
- Traverse and access
name
property of identifier node, storing them in an array - Traverse all the identifier again. if a name matches one found in the array, then the identifier is renamed, and the name is removed from the array.
Caveat: This approach is slower and its performance depends on the size of the code.
Code Snippet:
function renameIdentifier(ast) {
var identifier_name = [];
const getNames = {
Identifier(path) {
if (!path.node.name) return;
identifier_name.push(path.node.name);
}
}
const fixVariableDup = {
Identifier(path) {
if (!path.node.name) return;
if (!identifier_name.includes(path.node.name)) return;
let idx = identifier_name.findIndex((p) => p == path.node.name);
if (idx < 0) return;
identifier_name.splice(idx, 1);
var idName = path.node.name;
let newName = path.scope.generateUidIdentifier(idName);
path.scope.rename(idName, newName.name);
},
};
traverse(ast, getNames);
traverse(ast, fixVariableDup);
return ast;
}
When it comes to renaming variable and function names, obfuscators sometimes introduce special characters, such as zero-width characters. In these cases, it’s advisable to replace all zero-width characters with a corresponding ASCII character which makes the code easier to read and understand.
Extracting certain parts of the code
Once we have renamed all the variable and function names, we can move on to addressing string obfuscation.
Typically, in Node.js malware, after obfuscation, we might encounter a pattern like this:
const abc = xyz(1)
var ghj = fnd(hv[0])
Here, all the strings have been replaced with calls to routines that decode or decrypt the strings, which are usually stored in an array. To obtain readable strings, we would only need to execute these routines. However, because the code is obfuscated, we don’t have a clear understanding of the various sub-functions and variables used by these routines. Manually figuring this out would be a tedious task. Therefore, in this blog, we take a more general approach.
The first step is to determine the number of statements in the obfuscated code and figure out which statement corresponds to which line of code. To achieve this, we can use the following snippet:
// write statement and their number to a file
function writeCode(ast) {
const writeFileAsTable = {
Program(path) {
var body_arr = path.get("body");
var total_statements = Object.keys(body_arr).length;
var data = [];
for (let key = 0; key < total_statements; key++) {
let bodyKey = "body." + key;
let row = [
key + "",
generate(path.get(bodyKey).node).code.slice(0, 100) + "....",
];
data.push(row);
}
let outputPath = "file_body_key.txt";
try {
writeFileSync(outputPath, table(data));
console.log(`Wrote file to ${outputPath}`);
} catch (error) {
console.log("Error writing file", error);
}
},
};
traverse(ast, writeFileAsTable);
}
In above snippet, we traverse the code from the Program
node accessing all the statements and their corresponding number. We then create a table out of it and save it to a file. The output would look like this : https://gist.github.com/nhegde610/82373c95975c30c145011ea642715566
Once we have this output, the next task is to determine which statements or parts of the code we need to extract. This is where things get tricky. While we don’t know exactly what the original script looked like, we do have a rough idea of its structure. If you’re familiar with Node.js-based infostealer code, it typically follows this structure:
< list of imports such as var fs = require('fs')>
< bunch of functions declarations and variables >
< call expression invoking one of the functions which executes the rest or calling all of the function >
However, after the code is obfuscated, it might look like this:
< bunch of variable declarations>
< bunch of functions and expression statements >
< list of imports which are obfuscated as var ghfj = require(chsj(1))>
< bunch of functions and call expression >
< call expression invoking one of the functions just like the original code >
A couple of important points to note:
- The position of the call expression that invokes the malicious function remains unchanged.
- the list of imports is grouped together,similar to the original code.
These points can help us identify which parts of the code to extract and execute in such a way that we avoid triggering the malicious function. However, it is always best practice to execute untrusted code in a sandbox environment.
Picking up the parts of the code.
To select the relevant parts of the code, we simply use the statement numbers corresponding to the output. We should avoid statements that contain the use of require
, process
, path
, etc., as these are typically not executable in Node’s VM context. Additionally, we need to skip over call expressions or try
statements that appear after the import statements, as they are likely responsible for invoking the malicious code.
Now we have the following value which we use to extract parts of the code:
“0-112,114-117,122,124-146,149-160,162-217,220-255,257-261”
For the ease of mentioning the numbers,we have added the range as an input.
Code Snippet:
function extractCodeForExec(ast, argumentlist) {
var extractCode = "";
function convertStatemenetNum(argumentlist) {
var statementNum = argumentlist.split(",");
for (let i = 0; i < statementNum.length; i++) {
if (!statementNum[i].includes("-")) continue;
var ranges = statementNum[i].split("-");
if (ranges.length > 2) throw new Error("Invalid Input Found!");
if (Number(ranges[0]) > Number(ranges[1]))
throw new Error("Invalid Input Found!");
var range_list = Array.from(
{ length: Number(ranges[1]) - Number(ranges[0]) + 1 },
(_, index) => Number(ranges[0]) + index
);
statementNum[i] = range_list + "";
}
statementNum = statementNum + "";
var statementNum_list = statementNum.split(",");
for (let i = 0; i < statementNum_list.length; i++) {
statementNum_list[i] = Number(statementNum_list[i]);
}
return statementNum_list;
}
const extractCodeStr = {
Program(path) {
var body_arr = path.get("body");
var total_statements = Object.keys(body_arr).length;
var ranges = convertStatemenetNum(argumentlist);
for (let key = 0; key < total_statements; key++) {
let bodyKey = "body." + key;
if (ranges.includes(key)) {
extractCode =
extractCode + "\n" + generate(path.get(bodyKey).node).code;
}
}
},
};
traverse(ast, extractCodeStr);
return extractCode;
}
..........................
var codeToExecute = extractCodeForExec(ast,"0-112,114-117,122,124-146,149-160,162-217,220-255,257-261");
Executing the parts of the code and replacing the call
We are using the same idea that is explained here:https://steakenthusiast.github.io/2022/05/22/Deobfuscating-Javascript-via-AST-Manipulation-Various-String-Concealing-Techniques/#Example-4-String-Encryption
Code Snippet:
function deobfuscateStringObfuscation(ast, codeToExecute) {
var vmContext = vm.createContext();
const execprogram = {
Program(path) {
vm.runInContext(codeToExecute, vmContext);
},
};
function check_arguments(call_argument) {
if (t.isNumericLiteral(call_argument)) return true;
if (t.isMemberExpression(call_argument)) {
if (!t.isIdentifier(call_argument.object)) return false;
if (!t.isNumericLiteral(call_argument.property)) return false;
return true;
}
return false;
}
const mapFunc = {
CallExpression(path) {
const node = path.node;
var expressionCode = generate(node).code;
if (node.arguments.length != 1) return;
if (!check_arguments(node.arguments[0])) return;
var value;
try {
value = vm.runInContext(expressionCode, vmContext, { timeout: 25 });
console.log({ expressionCode, value });
if (typeof value == "string" || typeof value == "number") {
path.replaceWith(t.valueToNode(value));
}
if (typeof value == "boolean") {
path.replaceWith(t.booleanLiteral(value));
}
} catch (error) {
console.log({ expressionCode, error });
}
},
};
traverse(ast, execprogram);
traverse(ast, mapFunc);
return ast;
}
------------
ast = deobfuscateStringObfuscation(ast, codeToExecute);
Here, we perform the following steps:
- Execute the code in a Node.js VM context.
- Identify the call expressions that match patterns like
asda(1)
orasdf(gfdg[1])
. - Once such a call expression is found, run it again in the same Node.js VM context.
- If the output is a string, number, or boolean value, replace the call expression with the output.
While executing the code snippet, errors may be thrown because we are not running the complete code alongside the call expressions. As long as we are getting output for the call expression that we want to replace, we can ignore the errors. To mitigate further issues, a timeout is added during execution.
Unmapping strings mapped to an array
In the obfuscated code, we would see a string concealing technique where all the strings are mapped to an array. An explanation and a technique to deal with it is described here:https://steakenthusiast.github.io/2022/05/22/Deobfuscating-Javascript-via-AST-Manipulation-Various-String-Concealing-Techniques/#Example-2-String-Array-Map-Obfuscation
We are taking a different approach which involves the use of vm context.
First, we identify array declarations that are not modified in the code and extract them. Next, we wrap the reference to an array in a call expression to a function that simply returns the value passed to it:
function xyz(a){
return a
}
xyz(< array reference >)
By executing this code snippet along with the extracted code in the VM context, we retrieve the value mapped to the array reference. Finally, by replacing the array reference with its corresponding value, we are able to “unmap” it.
Full Code Snippet:
function replaceMemExpFromArray(ast) {
var funcExtrCode = "";
const ArrayNameList = []
const func_extractcode = {
VariableDeclaration(path){
if(!t.isProgram(path.parent)) return
if (path.node.declarations.length != 1) return
if (!t.isArrayExpression(path.node.declarations[0].init)) return
if (path.node.declarations[0].init.elements.length == 0) return
const ArrayName = path.node.declarations[0].id.name
const binding = path.scope.getBinding(ArrayName);
if(!binding) return
if(!binding.constant) return
ArrayNameList.push(ArrayName)
funcExtrCode = funcExtrCode + "\n" + "const " + generate(binding.path.node).code
}
}
const func_mapfunc = {
MemberExpression(path) {
var value;
if (!t.isIdentifier(path.node.object)) return;
if (!ArrayNameList.includes(path.node.object.name.toString())) return
if (!t.isNumericLiteral(path.node.property)) return;
var funcToAdd = "function xyz(a) {return a}" + "\n";
var expToExecute = "xyz(" + generate(path.node).code + ")";
var codexec = funcExtrCode + "\n" + funcToAdd + expToExecute;
var contextformemexp = vm.createContext();
try {
value = vm.runInContext(codexec, contextformemexp);
console.log({ expToExecute, value });
if (typeof value == "string" || typeof value == "number") {
path.replaceWith(t.valueToNode(value));
}
if (typeof value == "boolean") {
path.replaceWith(t.booleanLiteral(value));
}
} catch (error) {
console.log({ expToExecute, error });
return;
}
},
};
traverse(ast, func_extractcode);
traverse(ast, func_mapfunc);
return ast;
}
Cosmetic Fixes
Finally, we apply a few cosmetic fixes to the code to improve readability.
Decoding Hexadecimal Characters
Some strings are hexadecimal-encoded, which can be easily decoded using an AST parser. Refer to the tutorial that explains how it is done:https://steakenthusiast.github.io/2022/05/22/Deobfuscating-Javascript-via-AST-Manipulation-Various-String-Concealing-Techniques/#Example-1-Hexadecimal-x2F-Unicode-Escape-Sequence-Representations
Concat strings
We are using the code explained in this tutorial without any modifications. Link to the tutorial :https://steakenthusiast.github.io/2022/05/28/Deobfuscating-Javascript-via-AST-Manipulation-Constant-Folding/
Additional Thoughts
- Is this script foolproof? Can it work against any obfuscated NodeJS malware?
No, it is not foolproof. Malware authors may add tamper protection routines, OS checks, or checks for code beautification, all of which can render the script useless. If you encounter such routines, they should either be patched or removed entirely.
- In this script, we figure out certain values before extracting parts of the script. Do those values remain the same? Can the extraction be automated ?
No, the values do not remain consistent. They may change when the malware is updated or if different settings are used in the obfuscator. As for automating the extraction, it’s possible, and I encourage readers to explore this option further.
- The output of the code still contains obfuscated code, using techniques like control flow flattening. Why not deobfuscate it as well?
Defeating control flow flattening is not easy. It’s a complex process that requires significant effort. For an idea of how to approach it, you can refer to this blog:https://nerodesu017.github.io/posts/2023-12-01-antibots-part-8 . In our case, the technique used is even more intricate, requiring additional effort to retrieve the original code. However, once the strings are deobfuscated, we can generally understand what the code does. An analyst can easily run the code in a debugger and analyze its flow.
- Can this script be ported to some other library?
Yes, it can. The script can likely be ported to other libraries, such as esprime/escodegen.
References
The following blogs and videos have been immensely helpful in understanding the Babel AST Parser and how to use it for deobfuscation:
- Babel Handbook - a must read for beginners: https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md
- Trickster Dev’s javascript Blog series: https://www.trickster.dev/tags/javascript/
- API Documentation of Babel: https://npmdoc.github.io/node-npmdoc-babel-core/build/apidoc.html
- Step-by-Step guide for writing Deobfuscation Script for an obfuscated code: https://blog.confiant.com/exploring-scamclub-payloads-via-deobfuscation-using-abstract-syntax-trees-65ef7f412537
- Explanation of Babel libraries: https://segmentfault.com/a/1190000041765775/en?decode__1660=Wq0xyD0QY7qmqq0534%2B2DIrxmha2tqtDn7oD
- A Fantastic Tutorials on Deobfuscation. I have borrowed some code from this series: https://steakenthusiast.github.io/archives/
- Another Awesome Series on Deobfuscation. It also tackles control flow deobfuscation: https://nerodesu017.github.io/
- Course Explaining Weird Parts of Javascript: https://www.udemy.com/course/understand-javascript/
- Youtube Playlist with Walkthroughs of Defeating Various Javascript Obfuscation:https://www.youtube.com/watch?v=xkoFgPEfFLk&list=PLoZcQiQmH1DX6BEZdTJ6DifssHt9A8zaH