备份: 完整开发状态(含反混淆脚本和临时文件)

This commit is contained in:
ccdojox-crypto
2025-12-17 17:18:02 +08:00
parent 9e2333c90c
commit 7e9ea173a7
2872 changed files with 326818 additions and 249 deletions

127
node_modules/obfuscator-io-deobfuscator/.prettierignore generated vendored Normal file
View File

@@ -0,0 +1,127 @@
# Webpack output
webpack
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
output/script.js
output/sourceMap.out
output/sourceMap.json
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
tests/samples/**

View File

@@ -0,0 +1,9 @@
{
"printWidth": 100,
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid"
}

201
node_modules/obfuscator-io-deobfuscator/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

28
node_modules/obfuscator-io-deobfuscator/README.md generated vendored Normal file
View File

@@ -0,0 +1,28 @@
# Obfuscator.io Deobfuscator
A deobfuscator for scripts obfuscated by Obfuscator.io
## Usage
### Online
An online version is available at [obf-io.deobfuscate.io](https://obf-io.deobfuscate.io)
### CLI
Install via `npm install -g obfuscator-io-deobfuscator`
Usage: `obfuscator-io-deobfuscator <input> -o [output]`
## Features
- Recovers strings
- Removes proxy functions
- Removes and simplifies objects
- Simplifies arithmetic expressions
- Simplifies string concatenation
- Removes dead code
- Reverses control flow flattening
- Can handle most obfuscator.io forks
- Is safe (doesn't run any untrusted code/sandbox)
- Automatic config detection

View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
import commander from './index.js';
// wrapper to provide named exports for ESM.
export const {
program,
createCommand,
createArgument,
createOption,
CommanderError,
InvalidArgumentError,
InvalidOptionArgumentError, // deprecated old name
Command,
Argument,
Option,
Help,
} = commander;

View File

@@ -0,0 +1,24 @@
const { Argument } = require('./lib/argument.js');
const { Command } = require('./lib/command.js');
const { CommanderError, InvalidArgumentError } = require('./lib/error.js');
const { Help } = require('./lib/help.js');
const { Option } = require('./lib/option.js');
exports.program = new Command();
exports.createCommand = (name) => new Command(name);
exports.createOption = (flags, description) => new Option(flags, description);
exports.createArgument = (name, description) => new Argument(name, description);
/**
* Expose classes
*/
exports.Command = Command;
exports.Option = Option;
exports.Argument = Argument;
exports.Help = Help;
exports.CommanderError = CommanderError;
exports.InvalidArgumentError = InvalidArgumentError;
exports.InvalidOptionArgumentError = InvalidArgumentError; // Deprecated

View File

@@ -0,0 +1,149 @@
const { InvalidArgumentError } = require('./error.js');
class Argument {
/**
* Initialize a new command argument with the given name and description.
* The default is that the argument is required, and you can explicitly
* indicate this with <> around the name. Put [] around the name for an optional argument.
*
* @param {string} name
* @param {string} [description]
*/
constructor(name, description) {
this.description = description || '';
this.variadic = false;
this.parseArg = undefined;
this.defaultValue = undefined;
this.defaultValueDescription = undefined;
this.argChoices = undefined;
switch (name[0]) {
case '<': // e.g. <required>
this.required = true;
this._name = name.slice(1, -1);
break;
case '[': // e.g. [optional]
this.required = false;
this._name = name.slice(1, -1);
break;
default:
this.required = true;
this._name = name;
break;
}
if (this._name.length > 3 && this._name.slice(-3) === '...') {
this.variadic = true;
this._name = this._name.slice(0, -3);
}
}
/**
* Return argument name.
*
* @return {string}
*/
name() {
return this._name;
}
/**
* @package
*/
_concatValue(value, previous) {
if (previous === this.defaultValue || !Array.isArray(previous)) {
return [value];
}
return previous.concat(value);
}
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*
* @param {*} value
* @param {string} [description]
* @return {Argument}
*/
default(value, description) {
this.defaultValue = value;
this.defaultValueDescription = description;
return this;
}
/**
* Set the custom handler for processing CLI command arguments into argument values.
*
* @param {Function} [fn]
* @return {Argument}
*/
argParser(fn) {
this.parseArg = fn;
return this;
}
/**
* Only allow argument value to be one of choices.
*
* @param {string[]} values
* @return {Argument}
*/
choices(values) {
this.argChoices = values.slice();
this.parseArg = (arg, previous) => {
if (!this.argChoices.includes(arg)) {
throw new InvalidArgumentError(
`Allowed choices are ${this.argChoices.join(', ')}.`,
);
}
if (this.variadic) {
return this._concatValue(arg, previous);
}
return arg;
};
return this;
}
/**
* Make argument required.
*
* @returns {Argument}
*/
argRequired() {
this.required = true;
return this;
}
/**
* Make argument optional.
*
* @returns {Argument}
*/
argOptional() {
this.required = false;
return this;
}
}
/**
* Takes an argument and returns its human readable equivalent for help usage.
*
* @param {Argument} arg
* @return {string}
* @private
*/
function humanReadableArgName(arg) {
const nameOutput = arg.name() + (arg.variadic === true ? '...' : '');
return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
}
exports.Argument = Argument;
exports.humanReadableArgName = humanReadableArgName;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
/**
* CommanderError class
*/
class CommanderError extends Error {
/**
* Constructs the CommanderError class
* @param {number} exitCode suggested exit code which could be used with process.exit
* @param {string} code an id string representing the error
* @param {string} message human-readable description of the error
*/
constructor(exitCode, code, message) {
super(message);
// properly capture stack trace in Node.js
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.code = code;
this.exitCode = exitCode;
this.nestedError = undefined;
}
}
/**
* InvalidArgumentError class
*/
class InvalidArgumentError extends CommanderError {
/**
* Constructs the InvalidArgumentError class
* @param {string} [message] explanation of why argument is invalid
*/
constructor(message) {
super(1, 'commander.invalidArgument', message);
// properly capture stack trace in Node.js
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
}
}
exports.CommanderError = CommanderError;
exports.InvalidArgumentError = InvalidArgumentError;

View File

@@ -0,0 +1,520 @@
const { humanReadableArgName } = require('./argument.js');
/**
* TypeScript import types for JSDoc, used by Visual Studio Code IntelliSense and `npm run typescript-checkJS`
* https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types
* @typedef { import("./argument.js").Argument } Argument
* @typedef { import("./command.js").Command } Command
* @typedef { import("./option.js").Option } Option
*/
// Although this is a class, methods are static in style to allow override using subclass or just functions.
class Help {
constructor() {
this.helpWidth = undefined;
this.sortSubcommands = false;
this.sortOptions = false;
this.showGlobalOptions = false;
}
/**
* Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
*
* @param {Command} cmd
* @returns {Command[]}
*/
visibleCommands(cmd) {
const visibleCommands = cmd.commands.filter((cmd) => !cmd._hidden);
const helpCommand = cmd._getHelpCommand();
if (helpCommand && !helpCommand._hidden) {
visibleCommands.push(helpCommand);
}
if (this.sortSubcommands) {
visibleCommands.sort((a, b) => {
// @ts-ignore: because overloaded return type
return a.name().localeCompare(b.name());
});
}
return visibleCommands;
}
/**
* Compare options for sort.
*
* @param {Option} a
* @param {Option} b
* @returns {number}
*/
compareOptions(a, b) {
const getSortKey = (option) => {
// WYSIWYG for order displayed in help. Short used for comparison if present. No special handling for negated.
return option.short
? option.short.replace(/^-/, '')
: option.long.replace(/^--/, '');
};
return getSortKey(a).localeCompare(getSortKey(b));
}
/**
* Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.
*
* @param {Command} cmd
* @returns {Option[]}
*/
visibleOptions(cmd) {
const visibleOptions = cmd.options.filter((option) => !option.hidden);
// Built-in help option.
const helpOption = cmd._getHelpOption();
if (helpOption && !helpOption.hidden) {
// Automatically hide conflicting flags. Bit dubious but a historical behaviour that is convenient for single-command programs.
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
if (!removeShort && !removeLong) {
visibleOptions.push(helpOption); // no changes needed
} else if (helpOption.long && !removeLong) {
visibleOptions.push(
cmd.createOption(helpOption.long, helpOption.description),
);
} else if (helpOption.short && !removeShort) {
visibleOptions.push(
cmd.createOption(helpOption.short, helpOption.description),
);
}
}
if (this.sortOptions) {
visibleOptions.sort(this.compareOptions);
}
return visibleOptions;
}
/**
* Get an array of the visible global options. (Not including help.)
*
* @param {Command} cmd
* @returns {Option[]}
*/
visibleGlobalOptions(cmd) {
if (!this.showGlobalOptions) return [];
const globalOptions = [];
for (
let ancestorCmd = cmd.parent;
ancestorCmd;
ancestorCmd = ancestorCmd.parent
) {
const visibleOptions = ancestorCmd.options.filter(
(option) => !option.hidden,
);
globalOptions.push(...visibleOptions);
}
if (this.sortOptions) {
globalOptions.sort(this.compareOptions);
}
return globalOptions;
}
/**
* Get an array of the arguments if any have a description.
*
* @param {Command} cmd
* @returns {Argument[]}
*/
visibleArguments(cmd) {
// Side effect! Apply the legacy descriptions before the arguments are displayed.
if (cmd._argsDescription) {
cmd.registeredArguments.forEach((argument) => {
argument.description =
argument.description || cmd._argsDescription[argument.name()] || '';
});
}
// If there are any arguments with a description then return all the arguments.
if (cmd.registeredArguments.find((argument) => argument.description)) {
return cmd.registeredArguments;
}
return [];
}
/**
* Get the command term to show in the list of subcommands.
*
* @param {Command} cmd
* @returns {string}
*/
subcommandTerm(cmd) {
// Legacy. Ignores custom usage string, and nested commands.
const args = cmd.registeredArguments
.map((arg) => humanReadableArgName(arg))
.join(' ');
return (
cmd._name +
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
(cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option
(args ? ' ' + args : '')
);
}
/**
* Get the option term to show in the list of options.
*
* @param {Option} option
* @returns {string}
*/
optionTerm(option) {
return option.flags;
}
/**
* Get the argument term to show in the list of arguments.
*
* @param {Argument} argument
* @returns {string}
*/
argumentTerm(argument) {
return argument.name();
}
/**
* Get the longest command term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
longestSubcommandTermLength(cmd, helper) {
return helper.visibleCommands(cmd).reduce((max, command) => {
return Math.max(max, helper.subcommandTerm(command).length);
}, 0);
}
/**
* Get the longest option term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
longestOptionTermLength(cmd, helper) {
return helper.visibleOptions(cmd).reduce((max, option) => {
return Math.max(max, helper.optionTerm(option).length);
}, 0);
}
/**
* Get the longest global option term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
longestGlobalOptionTermLength(cmd, helper) {
return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
return Math.max(max, helper.optionTerm(option).length);
}, 0);
}
/**
* Get the longest argument term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
longestArgumentTermLength(cmd, helper) {
return helper.visibleArguments(cmd).reduce((max, argument) => {
return Math.max(max, helper.argumentTerm(argument).length);
}, 0);
}
/**
* Get the command usage to be displayed at the top of the built-in help.
*
* @param {Command} cmd
* @returns {string}
*/
commandUsage(cmd) {
// Usage
let cmdName = cmd._name;
if (cmd._aliases[0]) {
cmdName = cmdName + '|' + cmd._aliases[0];
}
let ancestorCmdNames = '';
for (
let ancestorCmd = cmd.parent;
ancestorCmd;
ancestorCmd = ancestorCmd.parent
) {
ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames;
}
return ancestorCmdNames + cmdName + ' ' + cmd.usage();
}
/**
* Get the description for the command.
*
* @param {Command} cmd
* @returns {string}
*/
commandDescription(cmd) {
// @ts-ignore: because overloaded return type
return cmd.description();
}
/**
* Get the subcommand summary to show in the list of subcommands.
* (Fallback to description for backwards compatibility.)
*
* @param {Command} cmd
* @returns {string}
*/
subcommandDescription(cmd) {
// @ts-ignore: because overloaded return type
return cmd.summary() || cmd.description();
}
/**
* Get the option description to show in the list of options.
*
* @param {Option} option
* @return {string}
*/
optionDescription(option) {
const extraInfo = [];
if (option.argChoices) {
extraInfo.push(
// use stringify to match the display of the default value
`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,
);
}
if (option.defaultValue !== undefined) {
// default for boolean and negated more for programmer than end user,
// but show true/false for boolean option as may be for hand-rolled env or config processing.
const showDefault =
option.required ||
option.optional ||
(option.isBoolean() && typeof option.defaultValue === 'boolean');
if (showDefault) {
extraInfo.push(
`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`,
);
}
}
// preset for boolean and negated are more for programmer than end user
if (option.presetArg !== undefined && option.optional) {
extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
}
if (option.envVar !== undefined) {
extraInfo.push(`env: ${option.envVar}`);
}
if (extraInfo.length > 0) {
return `${option.description} (${extraInfo.join(', ')})`;
}
return option.description;
}
/**
* Get the argument description to show in the list of arguments.
*
* @param {Argument} argument
* @return {string}
*/
argumentDescription(argument) {
const extraInfo = [];
if (argument.argChoices) {
extraInfo.push(
// use stringify to match the display of the default value
`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,
);
}
if (argument.defaultValue !== undefined) {
extraInfo.push(
`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`,
);
}
if (extraInfo.length > 0) {
const extraDescripton = `(${extraInfo.join(', ')})`;
if (argument.description) {
return `${argument.description} ${extraDescripton}`;
}
return extraDescripton;
}
return argument.description;
}
/**
* Generate the built-in help text.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {string}
*/
formatHelp(cmd, helper) {
const termWidth = helper.padWidth(cmd, helper);
const helpWidth = helper.helpWidth || 80;
const itemIndentWidth = 2;
const itemSeparatorWidth = 2; // between term and description
function formatItem(term, description) {
if (description) {
const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
return helper.wrap(
fullText,
helpWidth - itemIndentWidth,
termWidth + itemSeparatorWidth,
);
}
return term;
}
function formatList(textArray) {
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
}
// Usage
let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
// Description
const commandDescription = helper.commandDescription(cmd);
if (commandDescription.length > 0) {
output = output.concat([
helper.wrap(commandDescription, helpWidth, 0),
'',
]);
}
// Arguments
const argumentList = helper.visibleArguments(cmd).map((argument) => {
return formatItem(
helper.argumentTerm(argument),
helper.argumentDescription(argument),
);
});
if (argumentList.length > 0) {
output = output.concat(['Arguments:', formatList(argumentList), '']);
}
// Options
const optionList = helper.visibleOptions(cmd).map((option) => {
return formatItem(
helper.optionTerm(option),
helper.optionDescription(option),
);
});
if (optionList.length > 0) {
output = output.concat(['Options:', formatList(optionList), '']);
}
if (this.showGlobalOptions) {
const globalOptionList = helper
.visibleGlobalOptions(cmd)
.map((option) => {
return formatItem(
helper.optionTerm(option),
helper.optionDescription(option),
);
});
if (globalOptionList.length > 0) {
output = output.concat([
'Global Options:',
formatList(globalOptionList),
'',
]);
}
}
// Commands
const commandList = helper.visibleCommands(cmd).map((cmd) => {
return formatItem(
helper.subcommandTerm(cmd),
helper.subcommandDescription(cmd),
);
});
if (commandList.length > 0) {
output = output.concat(['Commands:', formatList(commandList), '']);
}
return output.join('\n');
}
/**
* Calculate the pad width from the maximum term length.
*
* @param {Command} cmd
* @param {Help} helper
* @returns {number}
*/
padWidth(cmd, helper) {
return Math.max(
helper.longestOptionTermLength(cmd, helper),
helper.longestGlobalOptionTermLength(cmd, helper),
helper.longestSubcommandTermLength(cmd, helper),
helper.longestArgumentTermLength(cmd, helper),
);
}
/**
* Wrap the given string to width characters per line, with lines after the first indented.
* Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
*
* @param {string} str
* @param {number} width
* @param {number} indent
* @param {number} [minColumnWidth=40]
* @return {string}
*
*/
wrap(str, width, indent, minColumnWidth = 40) {
// Full \s characters, minus the linefeeds.
const indents =
' \\f\\t\\v\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff';
// Detect manually wrapped and indented strings by searching for line break followed by spaces.
const manualIndent = new RegExp(`[\\n][${indents}]+`);
if (str.match(manualIndent)) return str;
// Do not wrap if not enough room for a wrapped column of text (as could end up with a word per line).
const columnWidth = width - indent;
if (columnWidth < minColumnWidth) return str;
const leadingStr = str.slice(0, indent);
const columnText = str.slice(indent).replace('\r\n', '\n');
const indentString = ' '.repeat(indent);
const zeroWidthSpace = '\u200B';
const breaks = `\\s${zeroWidthSpace}`;
// Match line end (so empty lines don't collapse),
// or as much text as will fit in column, or excess text up to first break.
const regex = new RegExp(
`\n|.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`,
'g',
);
const lines = columnText.match(regex) || [];
return (
leadingStr +
lines
.map((line, i) => {
if (line === '\n') return ''; // preserve empty lines
return (i > 0 ? indentString : '') + line.trimEnd();
})
.join('\n')
);
}
}
exports.Help = Help;

View File

@@ -0,0 +1,330 @@
const { InvalidArgumentError } = require('./error.js');
class Option {
/**
* Initialize a new `Option` with the given `flags` and `description`.
*
* @param {string} flags
* @param {string} [description]
*/
constructor(flags, description) {
this.flags = flags;
this.description = description || '';
this.required = flags.includes('<'); // A value must be supplied when the option is specified.
this.optional = flags.includes('['); // A value is optional when the option is specified.
// variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
const optionFlags = splitOptionFlags(flags);
this.short = optionFlags.shortFlag;
this.long = optionFlags.longFlag;
this.negate = false;
if (this.long) {
this.negate = this.long.startsWith('--no-');
}
this.defaultValue = undefined;
this.defaultValueDescription = undefined;
this.presetArg = undefined;
this.envVar = undefined;
this.parseArg = undefined;
this.hidden = false;
this.argChoices = undefined;
this.conflictsWith = [];
this.implied = undefined;
}
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*
* @param {*} value
* @param {string} [description]
* @return {Option}
*/
default(value, description) {
this.defaultValue = value;
this.defaultValueDescription = description;
return this;
}
/**
* Preset to use when option used without option-argument, especially optional but also boolean and negated.
* The custom processing (parseArg) is called.
*
* @example
* new Option('--color').default('GREYSCALE').preset('RGB');
* new Option('--donate [amount]').preset('20').argParser(parseFloat);
*
* @param {*} arg
* @return {Option}
*/
preset(arg) {
this.presetArg = arg;
return this;
}
/**
* Add option name(s) that conflict with this option.
* An error will be displayed if conflicting options are found during parsing.
*
* @example
* new Option('--rgb').conflicts('cmyk');
* new Option('--js').conflicts(['ts', 'jsx']);
*
* @param {(string | string[])} names
* @return {Option}
*/
conflicts(names) {
this.conflictsWith = this.conflictsWith.concat(names);
return this;
}
/**
* Specify implied option values for when this option is set and the implied options are not.
*
* The custom processing (parseArg) is not called on the implied values.
*
* @example
* program
* .addOption(new Option('--log', 'write logging information to file'))
* .addOption(new Option('--trace', 'log extra details').implies({ log: 'trace.txt' }));
*
* @param {object} impliedOptionValues
* @return {Option}
*/
implies(impliedOptionValues) {
let newImplied = impliedOptionValues;
if (typeof impliedOptionValues === 'string') {
// string is not documented, but easy mistake and we can do what user probably intended.
newImplied = { [impliedOptionValues]: true };
}
this.implied = Object.assign(this.implied || {}, newImplied);
return this;
}
/**
* Set environment variable to check for option value.
*
* An environment variable is only used if when processed the current option value is
* undefined, or the source of the current value is 'default' or 'config' or 'env'.
*
* @param {string} name
* @return {Option}
*/
env(name) {
this.envVar = name;
return this;
}
/**
* Set the custom handler for processing CLI option arguments into option values.
*
* @param {Function} [fn]
* @return {Option}
*/
argParser(fn) {
this.parseArg = fn;
return this;
}
/**
* Whether the option is mandatory and must have a value after parsing.
*
* @param {boolean} [mandatory=true]
* @return {Option}
*/
makeOptionMandatory(mandatory = true) {
this.mandatory = !!mandatory;
return this;
}
/**
* Hide option in help.
*
* @param {boolean} [hide=true]
* @return {Option}
*/
hideHelp(hide = true) {
this.hidden = !!hide;
return this;
}
/**
* @package
*/
_concatValue(value, previous) {
if (previous === this.defaultValue || !Array.isArray(previous)) {
return [value];
}
return previous.concat(value);
}
/**
* Only allow option value to be one of choices.
*
* @param {string[]} values
* @return {Option}
*/
choices(values) {
this.argChoices = values.slice();
this.parseArg = (arg, previous) => {
if (!this.argChoices.includes(arg)) {
throw new InvalidArgumentError(
`Allowed choices are ${this.argChoices.join(', ')}.`,
);
}
if (this.variadic) {
return this._concatValue(arg, previous);
}
return arg;
};
return this;
}
/**
* Return option name.
*
* @return {string}
*/
name() {
if (this.long) {
return this.long.replace(/^--/, '');
}
return this.short.replace(/^-/, '');
}
/**
* Return option name, in a camelcase format that can be used
* as a object attribute key.
*
* @return {string}
*/
attributeName() {
return camelcase(this.name().replace(/^no-/, ''));
}
/**
* Check if `arg` matches the short or long flag.
*
* @param {string} arg
* @return {boolean}
* @package
*/
is(arg) {
return this.short === arg || this.long === arg;
}
/**
* Return whether a boolean option.
*
* Options are one of boolean, negated, required argument, or optional argument.
*
* @return {boolean}
* @package
*/
isBoolean() {
return !this.required && !this.optional && !this.negate;
}
}
/**
* This class is to make it easier to work with dual options, without changing the existing
* implementation. We support separate dual options for separate positive and negative options,
* like `--build` and `--no-build`, which share a single option value. This works nicely for some
* use cases, but is tricky for others where we want separate behaviours despite
* the single shared option value.
*/
class DualOptions {
/**
* @param {Option[]} options
*/
constructor(options) {
this.positiveOptions = new Map();
this.negativeOptions = new Map();
this.dualOptions = new Set();
options.forEach((option) => {
if (option.negate) {
this.negativeOptions.set(option.attributeName(), option);
} else {
this.positiveOptions.set(option.attributeName(), option);
}
});
this.negativeOptions.forEach((value, key) => {
if (this.positiveOptions.has(key)) {
this.dualOptions.add(key);
}
});
}
/**
* Did the value come from the option, and not from possible matching dual option?
*
* @param {*} value
* @param {Option} option
* @returns {boolean}
*/
valueFromOption(value, option) {
const optionKey = option.attributeName();
if (!this.dualOptions.has(optionKey)) return true;
// Use the value to deduce if (probably) came from the option.
const preset = this.negativeOptions.get(optionKey).presetArg;
const negativeValue = preset !== undefined ? preset : false;
return option.negate === (negativeValue === value);
}
}
/**
* Convert string from kebab-case to camelCase.
*
* @param {string} str
* @return {string}
* @private
*/
function camelcase(str) {
return str.split('-').reduce((str, word) => {
return str + word[0].toUpperCase() + word.slice(1);
});
}
/**
* Split the short and long flag out of something like '-m,--mixed <value>'
*
* @private
*/
function splitOptionFlags(flags) {
let shortFlag;
let longFlag;
// Use original very loose parsing to maintain backwards compatibility for now,
// which allowed for example unintended `-sw, --short-word` [sic].
const flagParts = flags.split(/[ |,]+/);
if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1]))
shortFlag = flagParts.shift();
longFlag = flagParts.shift();
// Add support for lone short flag without significantly changing parsing!
if (!shortFlag && /^-[^-]$/.test(longFlag)) {
shortFlag = longFlag;
longFlag = undefined;
}
return { shortFlag, longFlag };
}
exports.Option = Option;
exports.DualOptions = DualOptions;

View File

@@ -0,0 +1,101 @@
const maxDistance = 3;
function editDistance(a, b) {
// https://en.wikipedia.org/wiki/DamerauLevenshtein_distance
// Calculating optimal string alignment distance, no substring is edited more than once.
// (Simple implementation.)
// Quick early exit, return worst case.
if (Math.abs(a.length - b.length) > maxDistance)
return Math.max(a.length, b.length);
// distance between prefix substrings of a and b
const d = [];
// pure deletions turn a into empty string
for (let i = 0; i <= a.length; i++) {
d[i] = [i];
}
// pure insertions turn empty string into b
for (let j = 0; j <= b.length; j++) {
d[0][j] = j;
}
// fill matrix
for (let j = 1; j <= b.length; j++) {
for (let i = 1; i <= a.length; i++) {
let cost = 1;
if (a[i - 1] === b[j - 1]) {
cost = 0;
} else {
cost = 1;
}
d[i][j] = Math.min(
d[i - 1][j] + 1, // deletion
d[i][j - 1] + 1, // insertion
d[i - 1][j - 1] + cost, // substitution
);
// transposition
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
}
}
}
return d[a.length][b.length];
}
/**
* Find close matches, restricted to same number of edits.
*
* @param {string} word
* @param {string[]} candidates
* @returns {string}
*/
function suggestSimilar(word, candidates) {
if (!candidates || candidates.length === 0) return '';
// remove possible duplicates
candidates = Array.from(new Set(candidates));
const searchingOptions = word.startsWith('--');
if (searchingOptions) {
word = word.slice(2);
candidates = candidates.map((candidate) => candidate.slice(2));
}
let similar = [];
let bestDistance = maxDistance;
const minSimilarity = 0.4;
candidates.forEach((candidate) => {
if (candidate.length <= 1) return; // no one character guesses
const distance = editDistance(word, candidate);
const length = Math.max(word.length, candidate.length);
const similarity = (length - distance) / length;
if (similarity > minSimilarity) {
if (distance < bestDistance) {
// better edit distance, throw away previous worse matches
bestDistance = distance;
similar = [candidate];
} else if (distance === bestDistance) {
similar.push(candidate);
}
}
});
similar.sort((a, b) => a.localeCompare(b));
if (searchingOptions) {
similar = similar.map((candidate) => `--${candidate}`);
}
if (similar.length > 1) {
return `\n(Did you mean one of ${similar.join(', ')}?)`;
}
if (similar.length === 1) {
return `\n(Did you mean ${similar[0]}?)`;
}
return '';
}
exports.suggestSimilar = suggestSimilar;

View File

@@ -0,0 +1,16 @@
{
"versions": [
{
"version": "*",
"target": {
"node": "supported"
},
"response": {
"type": "time-permitting"
},
"backing": {
"npm-funding": true
}
}
]
}

View File

@@ -0,0 +1,84 @@
{
"name": "commander",
"version": "12.1.0",
"description": "the complete solution for node.js command-line programs",
"keywords": [
"commander",
"command",
"option",
"parser",
"cli",
"argument",
"args",
"argv"
],
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/tj/commander.js.git"
},
"scripts": {
"check": "npm run check:type && npm run check:lint && npm run check:format",
"check:format": "prettier --check .",
"check:lint": "eslint .",
"check:type": "npm run check:type:js && npm run check:type:ts",
"check:type:ts": "tsd && tsc -p tsconfig.ts.json",
"check:type:js": "tsc -p tsconfig.js.json",
"fix": "npm run fix:lint && npm run fix:format",
"fix:format": "prettier --write .",
"fix:lint": "eslint --fix .",
"test": "jest && npm run check:type:ts",
"test-all": "jest && npm run test-esm && npm run check",
"test-esm": "node ./tests/esm-imports-test.mjs"
},
"files": [
"index.js",
"lib/*.js",
"esm.mjs",
"typings/index.d.ts",
"typings/esm.d.mts",
"package-support.json"
],
"type": "commonjs",
"main": "./index.js",
"exports": {
".": {
"require": {
"types": "./typings/index.d.ts",
"default": "./index.js"
},
"import": {
"types": "./typings/esm.d.mts",
"default": "./esm.mjs"
},
"default": "./index.js"
},
"./esm.mjs": {
"types": "./typings/esm.d.mts",
"import": "./esm.mjs"
}
},
"devDependencies": {
"@eslint/js": "^8.56.0",
"@types/jest": "^29.2.4",
"@types/node": "^20.2.5",
"eslint": "^8.30.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.3.0",
"eslint-plugin-jsdoc": "^48.1.0",
"globals": "^13.24.0",
"jest": "^29.3.1",
"prettier": "^3.2.5",
"prettier-plugin-jsdoc": "^1.3.0",
"ts-jest": "^29.0.3",
"tsd": "^0.31.0",
"typescript": "^5.0.4",
"typescript-eslint": "^7.0.1"
},
"types": "typings/index.d.ts",
"engines": {
"node": ">=18"
},
"support": true
}

View File

@@ -0,0 +1,3 @@
// Just reexport the types from cjs
// This is a bit indirect. There is not an index.js, but TypeScript will look for index.d.ts for types.
export * from './index.js';

View File

@@ -0,0 +1,969 @@
// Type definitions for commander
// Original definitions by: Alan Agius <https://github.com/alan-agius4>, Marcelo Dezem <https://github.com/mdezem>, vvakame <https://github.com/vvakame>, Jules Randolph <https://github.com/sveinburne>
// Using method rather than property for method-signature-style, to document method overloads separately. Allow either.
/* eslint-disable @typescript-eslint/method-signature-style */
/* eslint-disable @typescript-eslint/no-explicit-any */
// This is a trick to encourage editor to suggest the known literals while still
// allowing any BaseType value.
// References:
// - https://github.com/microsoft/TypeScript/issues/29729
// - https://github.com/sindresorhus/type-fest/blob/main/source/literal-union.d.ts
// - https://github.com/sindresorhus/type-fest/blob/main/source/primitive.d.ts
type LiteralUnion<LiteralType, BaseType extends string | number> =
| LiteralType
| (BaseType & Record<never, never>);
export class CommanderError extends Error {
code: string;
exitCode: number;
message: string;
nestedError?: string;
/**
* Constructs the CommanderError class
* @param exitCode - suggested exit code which could be used with process.exit
* @param code - an id string representing the error
* @param message - human-readable description of the error
*/
constructor(exitCode: number, code: string, message: string);
}
export class InvalidArgumentError extends CommanderError {
/**
* Constructs the InvalidArgumentError class
* @param message - explanation of why argument is invalid
*/
constructor(message: string);
}
export { InvalidArgumentError as InvalidOptionArgumentError }; // deprecated old name
export interface ErrorOptions {
// optional parameter for error()
/** an id string representing the error */
code?: string;
/** suggested exit code which could be used with process.exit */
exitCode?: number;
}
export class Argument {
description: string;
required: boolean;
variadic: boolean;
defaultValue?: any;
defaultValueDescription?: string;
argChoices?: string[];
/**
* Initialize a new command argument with the given name and description.
* The default is that the argument is required, and you can explicitly
* indicate this with <> around the name. Put [] around the name for an optional argument.
*/
constructor(arg: string, description?: string);
/**
* Return argument name.
*/
name(): string;
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*/
default(value: unknown, description?: string): this;
/**
* Set the custom handler for processing CLI command arguments into argument values.
*/
argParser<T>(fn: (value: string, previous: T) => T): this;
/**
* Only allow argument value to be one of choices.
*/
choices(values: readonly string[]): this;
/**
* Make argument required.
*/
argRequired(): this;
/**
* Make argument optional.
*/
argOptional(): this;
}
export class Option {
flags: string;
description: string;
required: boolean; // A value must be supplied when the option is specified.
optional: boolean; // A value is optional when the option is specified.
variadic: boolean;
mandatory: boolean; // The option must have a value after parsing, which usually means it must be specified on command line.
short?: string;
long?: string;
negate: boolean;
defaultValue?: any;
defaultValueDescription?: string;
presetArg?: unknown;
envVar?: string;
parseArg?: <T>(value: string, previous: T) => T;
hidden: boolean;
argChoices?: string[];
constructor(flags: string, description?: string);
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*/
default(value: unknown, description?: string): this;
/**
* Preset to use when option used without option-argument, especially optional but also boolean and negated.
* The custom processing (parseArg) is called.
*
* @example
* ```ts
* new Option('--color').default('GREYSCALE').preset('RGB');
* new Option('--donate [amount]').preset('20').argParser(parseFloat);
* ```
*/
preset(arg: unknown): this;
/**
* Add option name(s) that conflict with this option.
* An error will be displayed if conflicting options are found during parsing.
*
* @example
* ```ts
* new Option('--rgb').conflicts('cmyk');
* new Option('--js').conflicts(['ts', 'jsx']);
* ```
*/
conflicts(names: string | string[]): this;
/**
* Specify implied option values for when this option is set and the implied options are not.
*
* The custom processing (parseArg) is not called on the implied values.
*
* @example
* program
* .addOption(new Option('--log', 'write logging information to file'))
* .addOption(new Option('--trace', 'log extra details').implies({ log: 'trace.txt' }));
*/
implies(optionValues: OptionValues): this;
/**
* Set environment variable to check for option value.
*
* An environment variables is only used if when processed the current option value is
* undefined, or the source of the current value is 'default' or 'config' or 'env'.
*/
env(name: string): this;
/**
* Set the custom handler for processing CLI option arguments into option values.
*/
argParser<T>(fn: (value: string, previous: T) => T): this;
/**
* Whether the option is mandatory and must have a value after parsing.
*/
makeOptionMandatory(mandatory?: boolean): this;
/**
* Hide option in help.
*/
hideHelp(hide?: boolean): this;
/**
* Only allow option value to be one of choices.
*/
choices(values: readonly string[]): this;
/**
* Return option name.
*/
name(): string;
/**
* Return option name, in a camelcase format that can be used
* as a object attribute key.
*/
attributeName(): string;
/**
* Return whether a boolean option.
*
* Options are one of boolean, negated, required argument, or optional argument.
*/
isBoolean(): boolean;
}
export class Help {
/** output helpWidth, long lines are wrapped to fit */
helpWidth?: number;
sortSubcommands: boolean;
sortOptions: boolean;
showGlobalOptions: boolean;
constructor();
/** Get the command term to show in the list of subcommands. */
subcommandTerm(cmd: Command): string;
/** Get the command summary to show in the list of subcommands. */
subcommandDescription(cmd: Command): string;
/** Get the option term to show in the list of options. */
optionTerm(option: Option): string;
/** Get the option description to show in the list of options. */
optionDescription(option: Option): string;
/** Get the argument term to show in the list of arguments. */
argumentTerm(argument: Argument): string;
/** Get the argument description to show in the list of arguments. */
argumentDescription(argument: Argument): string;
/** Get the command usage to be displayed at the top of the built-in help. */
commandUsage(cmd: Command): string;
/** Get the description for the command. */
commandDescription(cmd: Command): string;
/** Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one. */
visibleCommands(cmd: Command): Command[];
/** Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one. */
visibleOptions(cmd: Command): Option[];
/** Get an array of the visible global options. (Not including help.) */
visibleGlobalOptions(cmd: Command): Option[];
/** Get an array of the arguments which have descriptions. */
visibleArguments(cmd: Command): Argument[];
/** Get the longest command term length. */
longestSubcommandTermLength(cmd: Command, helper: Help): number;
/** Get the longest option term length. */
longestOptionTermLength(cmd: Command, helper: Help): number;
/** Get the longest global option term length. */
longestGlobalOptionTermLength(cmd: Command, helper: Help): number;
/** Get the longest argument term length. */
longestArgumentTermLength(cmd: Command, helper: Help): number;
/** Calculate the pad width from the maximum term length. */
padWidth(cmd: Command, helper: Help): number;
/**
* Wrap the given string to width characters per line, with lines after the first indented.
* Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
*/
wrap(
str: string,
width: number,
indent: number,
minColumnWidth?: number,
): string;
/** Generate the built-in help text. */
formatHelp(cmd: Command, helper: Help): string;
}
export type HelpConfiguration = Partial<Help>;
export interface ParseOptions {
from: 'node' | 'electron' | 'user';
}
export interface HelpContext {
// optional parameter for .help() and .outputHelp()
error: boolean;
}
export interface AddHelpTextContext {
// passed to text function used with .addHelpText()
error: boolean;
command: Command;
}
export interface OutputConfiguration {
writeOut?(str: string): void;
writeErr?(str: string): void;
getOutHelpWidth?(): number;
getErrHelpWidth?(): number;
outputError?(str: string, write: (str: string) => void): void;
}
export type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll';
export type HookEvent = 'preSubcommand' | 'preAction' | 'postAction';
// The source is a string so author can define their own too.
export type OptionValueSource =
| LiteralUnion<'default' | 'config' | 'env' | 'cli' | 'implied', string>
| undefined;
export type OptionValues = Record<string, any>;
export class Command {
args: string[];
processedArgs: any[];
readonly commands: readonly Command[];
readonly options: readonly Option[];
readonly registeredArguments: readonly Argument[];
parent: Command | null;
constructor(name?: string);
/**
* Set the program version to `str`.
*
* This method auto-registers the "-V, --version" flag
* which will print the version number when passed.
*
* You can optionally supply the flags and description to override the defaults.
*/
version(str: string, flags?: string, description?: string): this;
/**
* Get the program version.
*/
version(): string | undefined;
/**
* Define a command, implemented using an action handler.
*
* @remarks
* The command description is supplied using `.description`, not as a parameter to `.command`.
*
* @example
* ```ts
* program
* .command('clone <source> [destination]')
* .description('clone a repository into a newly created directory')
* .action((source, destination) => {
* console.log('clone command called');
* });
* ```
*
* @param nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
* @param opts - configuration options
* @returns new command
*/
command(
nameAndArgs: string,
opts?: CommandOptions,
): ReturnType<this['createCommand']>;
/**
* Define a command, implemented in a separate executable file.
*
* @remarks
* The command description is supplied as the second parameter to `.command`.
*
* @example
* ```ts
* program
* .command('start <service>', 'start named service')
* .command('stop [service]', 'stop named service, or all if no name supplied');
* ```
*
* @param nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
* @param description - description of executable command
* @param opts - configuration options
* @returns `this` command for chaining
*/
command(
nameAndArgs: string,
description: string,
opts?: ExecutableCommandOptions,
): this;
/**
* Factory routine to create a new unattached command.
*
* See .command() for creating an attached subcommand, which uses this routine to
* create the command. You can override createCommand to customise subcommands.
*/
createCommand(name?: string): Command;
/**
* Add a prepared subcommand.
*
* See .command() for creating an attached subcommand which inherits settings from its parent.
*
* @returns `this` command for chaining
*/
addCommand(cmd: Command, opts?: CommandOptions): this;
/**
* Factory routine to create a new unattached argument.
*
* See .argument() for creating an attached argument, which uses this routine to
* create the argument. You can override createArgument to return a custom argument.
*/
createArgument(name: string, description?: string): Argument;
/**
* Define argument syntax for command.
*
* The default is that the argument is required, and you can explicitly
* indicate this with <> around the name. Put [] around the name for an optional argument.
*
* @example
* ```
* program.argument('<input-file>');
* program.argument('[output-file]');
* ```
*
* @returns `this` command for chaining
*/
argument<T>(
flags: string,
description: string,
fn: (value: string, previous: T) => T,
defaultValue?: T,
): this;
argument(name: string, description?: string, defaultValue?: unknown): this;
/**
* Define argument syntax for command, adding a prepared argument.
*
* @returns `this` command for chaining
*/
addArgument(arg: Argument): this;
/**
* Define argument syntax for command, adding multiple at once (without descriptions).
*
* See also .argument().
*
* @example
* ```
* program.arguments('<cmd> [env]');
* ```
*
* @returns `this` command for chaining
*/
arguments(names: string): this;
/**
* Customise or override default help command. By default a help command is automatically added if your command has subcommands.
*
* @example
* ```ts
* program.helpCommand('help [cmd]');
* program.helpCommand('help [cmd]', 'show help');
* program.helpCommand(false); // suppress default help command
* program.helpCommand(true); // add help command even if no subcommands
* ```
*/
helpCommand(nameAndArgs: string, description?: string): this;
helpCommand(enable: boolean): this;
/**
* Add prepared custom help command.
*/
addHelpCommand(cmd: Command): this;
/** @deprecated since v12, instead use helpCommand */
addHelpCommand(nameAndArgs: string, description?: string): this;
/** @deprecated since v12, instead use helpCommand */
addHelpCommand(enable?: boolean): this;
/**
* Add hook for life cycle event.
*/
hook(
event: HookEvent,
listener: (
thisCommand: Command,
actionCommand: Command,
) => void | Promise<void>,
): this;
/**
* Register callback to use as replacement for calling process.exit.
*/
exitOverride(callback?: (err: CommanderError) => never | void): this;
/**
* Display error message and exit (or call exitOverride).
*/
error(message: string, errorOptions?: ErrorOptions): never;
/**
* You can customise the help with a subclass of Help by overriding createHelp,
* or by overriding Help properties using configureHelp().
*/
createHelp(): Help;
/**
* You can customise the help by overriding Help properties using configureHelp(),
* or with a subclass of Help by overriding createHelp().
*/
configureHelp(configuration: HelpConfiguration): this;
/** Get configuration */
configureHelp(): HelpConfiguration;
/**
* The default output goes to stdout and stderr. You can customise this for special
* applications. You can also customise the display of errors by overriding outputError.
*
* The configuration properties are all functions:
* ```
* // functions to change where being written, stdout and stderr
* writeOut(str)
* writeErr(str)
* // matching functions to specify width for wrapping help
* getOutHelpWidth()
* getErrHelpWidth()
* // functions based on what is being written out
* outputError(str, write) // used for displaying errors, and not used for displaying help
* ```
*/
configureOutput(configuration: OutputConfiguration): this;
/** Get configuration */
configureOutput(): OutputConfiguration;
/**
* Copy settings that are useful to have in common across root command and subcommands.
*
* (Used internally when adding a command using `.command()` so subcommands inherit parent settings.)
*/
copyInheritedSettings(sourceCommand: Command): this;
/**
* Display the help or a custom message after an error occurs.
*/
showHelpAfterError(displayHelp?: boolean | string): this;
/**
* Display suggestion of similar commands for unknown commands, or options for unknown options.
*/
showSuggestionAfterError(displaySuggestion?: boolean): this;
/**
* Register callback `fn` for the command.
*
* @example
* ```
* program
* .command('serve')
* .description('start service')
* .action(function() {
* // do work here
* });
* ```
*
* @returns `this` command for chaining
*/
action(fn: (...args: any[]) => void | Promise<void>): this;
/**
* Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both.
*
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. A required
* option-argument is indicated by `<>` and an optional option-argument by `[]`.
*
* See the README for more details, and see also addOption() and requiredOption().
*
* @example
*
* ```js
* program
* .option('-p, --pepper', 'add pepper')
* .option('-p, --pizza-type <TYPE>', 'type of pizza') // required option-argument
* .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
* .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
* ```
*
* @returns `this` command for chaining
*/
option(
flags: string,
description?: string,
defaultValue?: string | boolean | string[],
): this;
option<T>(
flags: string,
description: string,
parseArg: (value: string, previous: T) => T,
defaultValue?: T,
): this;
/** @deprecated since v7, instead use choices or a custom function */
option(
flags: string,
description: string,
regexp: RegExp,
defaultValue?: string | boolean | string[],
): this;
/**
* Define a required option, which must have a value after parsing. This usually means
* the option must be specified on the command line. (Otherwise the same as .option().)
*
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
*/
requiredOption(
flags: string,
description?: string,
defaultValue?: string | boolean | string[],
): this;
requiredOption<T>(
flags: string,
description: string,
parseArg: (value: string, previous: T) => T,
defaultValue?: T,
): this;
/** @deprecated since v7, instead use choices or a custom function */
requiredOption(
flags: string,
description: string,
regexp: RegExp,
defaultValue?: string | boolean | string[],
): this;
/**
* Factory routine to create a new unattached option.
*
* See .option() for creating an attached option, which uses this routine to
* create the option. You can override createOption to return a custom option.
*/
createOption(flags: string, description?: string): Option;
/**
* Add a prepared Option.
*
* See .option() and .requiredOption() for creating and attaching an option in a single call.
*/
addOption(option: Option): this;
/**
* Whether to store option values as properties on command object,
* or store separately (specify false). In both cases the option values can be accessed using .opts().
*
* @returns `this` command for chaining
*/
storeOptionsAsProperties<T extends OptionValues>(): this & T;
storeOptionsAsProperties<T extends OptionValues>(
storeAsProperties: true,
): this & T;
storeOptionsAsProperties(storeAsProperties?: boolean): this;
/**
* Retrieve option value.
*/
getOptionValue(key: string): any;
/**
* Store option value.
*/
setOptionValue(key: string, value: unknown): this;
/**
* Store option value and where the value came from.
*/
setOptionValueWithSource(
key: string,
value: unknown,
source: OptionValueSource,
): this;
/**
* Get source of option value.
*/
getOptionValueSource(key: string): OptionValueSource | undefined;
/**
* Get source of option value. See also .optsWithGlobals().
*/
getOptionValueSourceWithGlobals(key: string): OptionValueSource | undefined;
/**
* Alter parsing of short flags with optional values.
*
* @example
* ```
* // for `.option('-f,--flag [value]'):
* .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour
* .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
* ```
*
* @returns `this` command for chaining
*/
combineFlagAndOptionalValue(combine?: boolean): this;
/**
* Allow unknown options on the command line.
*
* @returns `this` command for chaining
*/
allowUnknownOption(allowUnknown?: boolean): this;
/**
* Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
*
* @returns `this` command for chaining
*/
allowExcessArguments(allowExcess?: boolean): this;
/**
* Enable positional options. Positional means global options are specified before subcommands which lets
* subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
*
* The default behaviour is non-positional and global options may appear anywhere on the command line.
*
* @returns `this` command for chaining
*/
enablePositionalOptions(positional?: boolean): this;
/**
* Pass through options that come after command-arguments rather than treat them as command-options,
* so actual command-options come before command-arguments. Turning this on for a subcommand requires
* positional options to have been enabled on the program (parent commands).
*
* The default behaviour is non-positional and options may appear before or after command-arguments.
*
* @returns `this` command for chaining
*/
passThroughOptions(passThrough?: boolean): this;
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* Use parseAsync instead of parse if any of your action handlers are async.
*
* Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
*
* Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
* - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
* - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
* - `'user'`: just user arguments
*
* @example
* ```
* program.parse(); // parse process.argv and auto-detect electron and special node flags
* program.parse(process.argv); // assume argv[0] is app and argv[1] is script
* program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
* ```
*
* @returns `this` command for chaining
*/
parse(argv?: readonly string[], parseOptions?: ParseOptions): this;
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
*
* Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
* - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
* - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
* - `'user'`: just user arguments
*
* @example
* ```
* await program.parseAsync(); // parse process.argv and auto-detect electron and special node flags
* await program.parseAsync(process.argv); // assume argv[0] is app and argv[1] is script
* await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
* ```
*
* @returns Promise
*/
parseAsync(
argv?: readonly string[],
parseOptions?: ParseOptions,
): Promise<this>;
/**
* Parse options from `argv` removing known options,
* and return argv split into operands and unknown arguments.
*
* argv => operands, unknown
* --known kkk op => [op], []
* op --known kkk => [op], []
* sub --unknown uuu op => [sub], [--unknown uuu op]
* sub -- --unknown uuu op => [sub --unknown uuu op], []
*/
parseOptions(argv: string[]): ParseOptionsResult;
/**
* Return an object containing local option values as key-value pairs
*/
opts<T extends OptionValues>(): T;
/**
* Return an object containing merged local and global option values as key-value pairs.
*/
optsWithGlobals<T extends OptionValues>(): T;
/**
* Set the description.
*
* @returns `this` command for chaining
*/
description(str: string): this;
/** @deprecated since v8, instead use .argument to add command argument with description */
description(str: string, argsDescription: Record<string, string>): this;
/**
* Get the description.
*/
description(): string;
/**
* Set the summary. Used when listed as subcommand of parent.
*
* @returns `this` command for chaining
*/
summary(str: string): this;
/**
* Get the summary.
*/
summary(): string;
/**
* Set an alias for the command.
*
* You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
*
* @returns `this` command for chaining
*/
alias(alias: string): this;
/**
* Get alias for the command.
*/
alias(): string;
/**
* Set aliases for the command.
*
* Only the first alias is shown in the auto-generated help.
*
* @returns `this` command for chaining
*/
aliases(aliases: readonly string[]): this;
/**
* Get aliases for the command.
*/
aliases(): string[];
/**
* Set the command usage.
*
* @returns `this` command for chaining
*/
usage(str: string): this;
/**
* Get the command usage.
*/
usage(): string;
/**
* Set the name of the command.
*
* @returns `this` command for chaining
*/
name(str: string): this;
/**
* Get the name of the command.
*/
name(): string;
/**
* Set the name of the command from script filename, such as process.argv[1],
* or require.main.filename, or __filename.
*
* (Used internally and public although not documented in README.)
*
* @example
* ```ts
* program.nameFromFilename(require.main.filename);
* ```
*
* @returns `this` command for chaining
*/
nameFromFilename(filename: string): this;
/**
* Set the directory for searching for executable subcommands of this command.
*
* @example
* ```ts
* program.executableDir(__dirname);
* // or
* program.executableDir('subcommands');
* ```
*
* @returns `this` command for chaining
*/
executableDir(path: string): this;
/**
* Get the executable search directory.
*/
executableDir(): string | null;
/**
* Output help information for this command.
*
* Outputs built-in help, and custom text added using `.addHelpText()`.
*
*/
outputHelp(context?: HelpContext): void;
/** @deprecated since v7 */
outputHelp(cb?: (str: string) => string): void;
/**
* Return command help documentation.
*/
helpInformation(context?: HelpContext): string;
/**
* You can pass in flags and a description to override the help
* flags and help description for your command. Pass in false
* to disable the built-in help option.
*/
helpOption(flags?: string | boolean, description?: string): this;
/**
* Supply your own option to use for the built-in help option.
* This is an alternative to using helpOption() to customise the flags and description etc.
*/
addHelpOption(option: Option): this;
/**
* Output help information and exit.
*
* Outputs built-in help, and custom text added using `.addHelpText()`.
*/
help(context?: HelpContext): never;
/** @deprecated since v7 */
help(cb?: (str: string) => string): never;
/**
* Add additional text to be displayed with the built-in help.
*
* Position is 'before' or 'after' to affect just this command,
* and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
*/
addHelpText(position: AddHelpTextPosition, text: string): this;
addHelpText(
position: AddHelpTextPosition,
text: (context: AddHelpTextContext) => string,
): this;
/**
* Add a listener (callback) for when events occur. (Implemented using EventEmitter.)
*/
on(event: string | symbol, listener: (...args: any[]) => void): this;
}
export interface CommandOptions {
hidden?: boolean;
isDefault?: boolean;
/** @deprecated since v7, replaced by hidden */
noHelp?: boolean;
}
export interface ExecutableCommandOptions extends CommandOptions {
executableFile?: string;
}
export interface ParseOptionsResult {
operands: string[];
unknown: string[];
}
export function createCommand(name?: string): Command;
export function createOption(flags: string, description?: string): Option;
export function createArgument(name: string, description?: string): Argument;
export const program: Command;

33
node_modules/obfuscator-io-deobfuscator/package.json generated vendored Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "obfuscator-io-deobfuscator",
"version": "1.0.6",
"description": "A deobfuscator for scripts obfuscated by Obfuscator.io",
"main": "dist/index.js",
"bin": "dist/cli.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "tsc && node dist/test.js",
"prepare": "tsc"
},
"author": "Ben",
"license": "ISC",
"dependencies": {
"@babel/generator": "^7.22.3",
"@babel/parser": "^7.22.4",
"@babel/traverse": "^7.22.4",
"@babel/types": "^7.22.4",
"@codemod/matchers": "^1.7.1",
"@types/node": "^20.2.5",
"commander": "^12.0.0"
},
"devDependencies": {
"@types/babel__core": "^7.20.1",
"assert": "^2.1.0",
"path-browserify": "^1.0.1",
"terser-webpack-plugin": "^5.3.9",
"ts-loader": "^9.4.4",
"typescript": "^5.1.3",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
}
}

30
node_modules/obfuscator-io-deobfuscator/src/cli.ts generated vendored Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env node
import { parse } from '@babel/parser';
import { program } from 'commander';
import fs from 'fs';
import { Deobfuscator } from './deobfuscator/deobfuscator';
import { defaultConfig } from './deobfuscator/transformations/config';
const pkg = require('../package.json');
program
.name(pkg.name)
.description(pkg.description)
.version(pkg.version)
.usage('<input_path> -o [output_path]')
.argument('<input_path>', 'file to deobfuscate')
.option('-o, --output [output_path]', 'output file path', 'deobfuscated.js')
.option('-s, --silent', 'emit nothing to stdout')
.action((input, options) => {
const source = fs.readFileSync(input).toString();
const ast = parse(source, { sourceType: 'unambiguous' });
const deobfuscator = new Deobfuscator(ast, { ...defaultConfig, silent: !!options.silent });
const output = deobfuscator.execute();
fs.writeFileSync(options.output, output);
if (!options.silent) {
console.log(`Wrote deobfuscated file to ${options.output}`);
}
});
program.parse();

View File

@@ -0,0 +1,118 @@
import generate from '@babel/generator';
import * as t from '@babel/types';
import traverse from '@babel/traverse';
import { TransformationType } from './transformations/transformation';
import { Config, defaultConfig } from './transformations/config';
import { ObjectSimplifier } from './transformations/objects/objectSimplifier';
import { ProxyFunctionInliner } from './transformations/proxyFunctions/proxyFunctionInliner';
import { UnusedVariableRemover } from './transformations/variables/unusedVariableRemover';
import { ConstantPropgator } from './transformations/variables/constantPropagator';
import { ReassignmentRemover } from './transformations/variables/reassignmentRemover';
import { StringRevealer } from './transformations/strings/stringRevealer';
import { DeadBranchRemover } from './transformations/controlFlow/deadBranchRemover';
import { SequenceSplitter } from './transformations/controlFlow/sequenceSplitter';
import { PropertySimplifier } from './transformations/properties/propertySimplifier';
import { ExpressionSimplifier } from './transformations/expressions/expressionSimplifier';
import { ControlFlowRecoverer } from './transformations/controlFlow/controlFlowRecoverer';
import { ObjectPacker } from './transformations/objects/objectPacker';
import { AntiTamperRemover } from './transformations/antiTamper/antiTamperRemover';
export class Deobfuscator {
private readonly ast: t.File;
private readonly config: Config;
private readonly transformationTypes: TransformationType[] = [
UnusedVariableRemover,
ConstantPropgator,
ReassignmentRemover,
DeadBranchRemover,
ObjectPacker,
ProxyFunctionInliner,
ExpressionSimplifier,
SequenceSplitter,
ControlFlowRecoverer,
PropertySimplifier,
AntiTamperRemover,
ObjectSimplifier,
StringRevealer
];
private static readonly MAX_ITERATIONS = 50;
/**
* Creates a new deobfuscator.
* @param ast The AST.
* @param config The config (optional).
*/
constructor(ast: t.File, config: Config = defaultConfig) {
this.ast = ast;
this.config = config;
}
/**
* Executes the deobfuscator.
* @returns The simplified code.
*/
public execute(): string {
let types = this.transformationTypes.filter(t => this.config[t.properties.key].isEnabled);
let i = 0;
while (i < Deobfuscator.MAX_ITERATIONS) {
let isModified = false;
if (!this.config.silent) {
console.log(`\n[${new Date().toISOString()}]: Starting pass ${i + 1}`);
}
for (const type of types) {
const transformationConfig = this.config[type.properties.key];
const transformation = new type(this.ast, transformationConfig);
if (!this.config.silent) {
console.log(
`[${new Date().toISOString()}]: Executing ${
transformation.constructor.name
}`
);
}
let modified = false;
try {
modified = transformation.execute(
console.log.bind(console, `[${transformation.constructor.name}]:`)
);
} catch (err) {
console.error(err);
}
if (modified) {
isModified = true;
}
if (!this.config.silent) {
console.log(
`[${new Date().toISOString()}]: Executed ${
transformation.constructor.name
}, modified ${modified}`
);
}
if (type.properties.rebuildScopeTree) {
this.clearCache();
}
}
i++;
if (!isModified) {
break;
}
}
return generate(this.ast, { jsescOption: { minimal: true } }).code;
}
/**
* Clears the traversal cache to force the scoping to be handled
* again on the next traverse.
*/
private clearCache(): void {
(traverse as any).cache.clear();
}
}

View File

@@ -0,0 +1,64 @@
import * as t from '@babel/types';
import { isTypeFunction } from './variable';
export type DeclarationOrAssignmentStatement<T extends t.LVal, V extends t.Expression> =
| (t.VariableDeclaration & {
declarations: [t.VariableDeclarator & { id: T; init: V }];
})
| (t.ExpressionStatement & { expression: t.AssignmentExpression & { left: T; right: V } });
/**
* Checks whether a node is a variable declaration or assignment expression
* within an expression statement that is initialising a variable that
* satisfies the provided constraints.
* @param node The AST node.
* @param isId The function that determines whether the variable being declared matches.
* @param isValue The function that determines whether the value the variable is initialised to matches.
* @returns Whether.
*/
export function isDeclarationOrAssignmentStatement<T extends t.LVal, V extends t.Expression>(
node: t.Node,
isId: isTypeFunction<T> | ((node: t.Node) => boolean),
isValue: isTypeFunction<V> | ((node: t.Node) => boolean)
): node is DeclarationOrAssignmentStatement<T, V> {
return (
(t.isVariableDeclaration(node) &&
node.declarations.length == 1 &&
isId(node.declarations[0].id) &&
node.declarations[0].init &&
isValue(node.declarations[0].init)) ||
(t.isExpressionStatement(node) &&
t.isAssignmentExpression(node.expression) &&
isId(node.expression.left) &&
isValue(node.expression.right))
);
}
/**
* Checks whether a node is a variable declaration or assignment expression
* that is initialising a variable that satisfies the provided constraints.
* @param node The AST node.
* @param isId The function that determines whether the variable being declared matches.
* @param isValue The function that determines whether the value the variable is initialised to matches.
* @returns Whether.
*/
export type DeclarationOrAssignmentExpression<T extends t.LVal, V extends t.Expression> =
| (t.VariableDeclaration & {
declarations: [t.VariableDeclarator & { id: T; init: V }];
})
| (t.AssignmentExpression & { left: T; right: V });
export function isDeclarationOrAssignmentExpression<T extends t.LVal, V extends t.Expression>(
node: t.Node,
isId: isTypeFunction<T> | ((node: t.Node) => boolean),
isValue: isTypeFunction<V> | ((node: t.Node) => boolean)
): node is DeclarationOrAssignmentExpression<T, V> {
return (
(t.isVariableDeclaration(node) &&
node.declarations.length == 1 &&
isId(node.declarations[0].id) &&
node.declarations[0].init &&
isValue(node.declarations[0].init)) ||
(t.isAssignmentExpression(node) && isId(node.left) && isValue(node.right))
);
}

View File

@@ -0,0 +1,12 @@
import * as t from '@babel/types';
/**
* Returns whether a node is a unary expression that represents a negative number.
* @param node The AST node.
* @returns Whether.
*/
export function isNegativeNumericLiteral(
node: t.Node
): node is t.UnaryExpression & { operator: '-'; argument: t.NumericLiteral } {
return t.isUnaryExpression(node) && node.operator == '-' && t.isNumericLiteral(node.argument);
}

View File

@@ -0,0 +1,32 @@
import generate from '@babel/generator';
import * as t from '@babel/types';
import {parseExpression} from '@babel/parser';
/**
* Copies an expression.
* @param expression The expression.
* @returns The copy.
*/
export const copyExpression = (expression: t.Expression): t.Expression => {
return parseExpression(generate(expression).code);
};
/**
* Sets a property on an object.
* @param obj The object.
* @param property The property key.
* @param value The value.
*/
export const setProperty = (obj: any, property: string, value: any): void => {
(obj as Record<string, any>).property = value;
};
/**
* Gets the value of a property on an object.
* @param obj The object.
* @param property The property key.
* @returns
*/
export const getProperty = (obj: any, property: string): any => {
return (obj as Record<string, any>).property;
};

View File

@@ -0,0 +1,54 @@
import { base64Transform } from '../util/util';
import { DecoderType, StringDecoder } from './stringDecoder';
export class Base64StringDecoder extends StringDecoder {
private readonly stringCache: Map<string, string>;
/**
* Creates a new base 64 string decoder.
* @param stringArray The string array.
* @param indexOffset The offset used when accessing elements by index.
*/
constructor(stringArray: string[], indexOffset: number) {
super(stringArray, indexOffset);
this.stringCache = new Map();
}
/**
* Returns the type of the decoder.
*/
public get type(): DecoderType {
return DecoderType.BASE_64;
}
/**
* Decodes a string.
* @param index The index.
* @returns The string.
*/
public getString(index: number): string {
const cacheKey = index + this.stringArray[0];
if (this.stringCache.has(cacheKey)) {
return this.stringCache.get(cacheKey) as string;
}
const encoded = this.stringArray[index + this.indexOffset];
const str = base64Transform(encoded);
this.stringCache.set(cacheKey, str);
return str;
}
/**
* Decodes a string for the rotate string call.
* @param index The index.
* @returns THe string.
*/
public getStringForRotation(index: number): string {
if (this.isFirstCall) {
this.isFirstCall = false;
throw new Error();
}
return this.getString(index);
}
}

View File

@@ -0,0 +1,27 @@
import { DecoderType, StringDecoder } from './stringDecoder';
export class BasicStringDecoder extends StringDecoder {
/**
* Returns the type of the decoder.
*/
public get type(): DecoderType {
return DecoderType.BASIC;
}
/**
* Decodes a string.
* @param index The index.
*/
public getString(index: number): string {
return this.stringArray[index + this.indexOffset];
}
/**
* Decodes a string for the rotate string call.
* @param index The index.
* @returns THe string.
*/
public getStringForRotation(index: number): string {
return this.getString(index);
}
}

View File

@@ -0,0 +1,86 @@
import { base64Transform } from '../util/util';
import { DecoderType, StringDecoder } from './stringDecoder';
export class Rc4StringDecoder extends StringDecoder {
private readonly stringCache: Map<string, string>;
/**
* Creates a new RC4 string decoder.
* @param stringArray The string array.
* @param indexOffset The offset used when accessing elements by index.
*/
constructor(stringArray: string[], indexOffset: number) {
super(stringArray, indexOffset);
this.stringCache = new Map();
}
/**
* Returns the type of the decoder.
*/
public get type(): DecoderType {
return DecoderType.RC4;
}
/**
* Decodes a string.
* @param index The index.
*/
public getString(index: number, key: string): string {
const cacheKey = index + this.stringArray[0];
if (this.stringCache.has(cacheKey)) {
return this.stringCache.get(cacheKey) as string;
}
const encoded = this.stringArray[index + this.indexOffset];
const str = this.rc4Decode(encoded, key);
this.stringCache.set(cacheKey, str);
return str;
}
/**
* Decodes a string for the rotate string call.
* @param index The index.
* @returns THe string.
*/
public getStringForRotation(index: number, key: string): string {
if (this.isFirstCall) {
this.isFirstCall = false;
throw new Error();
}
return this.getString(index, key);
}
/**
* Decodes a string encoded with RC4.
* @param str The RC4 encoded string.
* @param key The key.
* @returns The decoded string.
*/
private rc4Decode(str: string, key: string): string {
const s = [];
let j = 0;
let decoded = '';
str = base64Transform(str);
for (var i = 0; i < 256; i++) {
s[i] = i;
}
for (var i = 0; i < 256; i++) {
j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
[s[i], s[j]] = [s[j], s[i]];
}
i = 0;
j = 0;
for (let y = 0; y < str.length; y++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
[s[i], s[j]] = [s[j], s[i]];
decoded += String.fromCharCode(str.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]);
}
return decoded;
}
}

View File

@@ -0,0 +1,39 @@
export abstract class StringDecoder {
protected readonly stringArray: string[];
protected readonly indexOffset: number;
protected isFirstCall: boolean;
/**
* Creates a new string decoder.
* @param stringArray The string array.
* @param indexOffset The offset used when accessing elements by index.
*/
constructor(stringArray: string[], indexOffset: number) {
this.stringArray = stringArray;
this.indexOffset = indexOffset;
this.isFirstCall = true;
}
/**
* Returns the type of the decoder.
*/
public abstract get type(): DecoderType;
/**
* Decodes a string.
* @param args The arguments of the decode call.
*/
public abstract getString(...args: (number | string)[]): string;
/**
* Decodes a string for the string rotation call.
* @param args The arguments of the decode call.
*/
public abstract getStringForRotation(...args: (number | string)[]): string;
}
export enum DecoderType {
BASIC = 'BASIC',
BASE_64 = 'BASE_64',
RC4 = 'RC4'
}

View File

@@ -0,0 +1,258 @@
import * as t from '@babel/types';
import { StringDecoder } from '../decoders/stringDecoder';
import { isNegativeNumericLiteral } from '../../expression';
type BinaryOperator = '+' | '-' | '*' | '/' | '%';
const binaryOperatorSet = new Set(['+', '-', '*', '/', '%']);
type UnaryOperator = '-';
const unaryOperatorSet = new Set(['-']);
const operationSet = new Set([
'CallExpression',
'UnaryExpression',
'BinaryExpression',
'NumericLiteral'
]);
type CallOperation = {
type: 'CallOperation';
decoder: StringDecoder;
args: (number | string)[];
};
type UnaryOperation = {
type: 'UnaryOperation';
operator: UnaryOperator;
argument: Operation;
};
type BinaryOperation = {
type: 'BinaryOperation';
operator: BinaryOperator;
left: Operation;
right: Operation;
};
type Operation = CallOperation | UnaryOperation | BinaryOperation | t.NumericLiteral;
/**
* Parses an operation.
* @param expression The expression.
* @param decoderMap The string decoder map.
* @returns The operation.
*/
function parseOperation(
expression: t.BinaryExpression | t.UnaryExpression | t.CallExpression | t.NumericLiteral,
decoderMap: Map<string, StringDecoder>
): Operation {
switch (expression.type) {
case 'CallExpression':
return parseCallOperation(expression, decoderMap);
case 'UnaryExpression':
return parseUnaryOperation(expression, decoderMap);
case 'BinaryExpression':
return parseBinaryOperation(expression, decoderMap);
case 'NumericLiteral':
return expression;
}
}
/**
* Parses a call operation.
* @param expression The call expression.
* @param decoderMap The string decoder map.
* @returns The call operation.
*/
function parseCallOperation(
expression: t.CallExpression,
decoderMap: Map<string, StringDecoder>
): CallOperation {
if (
!t.isIdentifier(expression.callee) ||
expression.callee.name != 'parseInt' ||
expression.arguments.length != 1 ||
!t.isCallExpression(expression.arguments[0])
) {
throw new Error('Unsupported string call operation');
}
const stringCall = expression.arguments[0];
if (
!t.isIdentifier(stringCall.callee) ||
!stringCall.arguments.every(
e => t.isNumericLiteral(e) || isNegativeNumericLiteral(e) || t.isStringLiteral(e)
)
) {
throw new Error('Unsupported string call operation');
}
const args = stringCall.arguments.map(e =>
t.isNumericLiteral(e) || t.isStringLiteral(e) ? e.value : -(e as any).argument.value
);
const name = stringCall.callee.name;
if (!decoderMap.has(name)) {
throw new Error(`Unknown string decoder ${name}`);
}
const decoder = decoderMap.get(name) as StringDecoder;
return {
type: 'CallOperation',
decoder,
args
};
}
/**
* Parses a unary operation.
* @param expression The unary expression.
* @param decoderMap The string decoder map.
* @returns The unary operation.
*/
function parseUnaryOperation(
expression: t.UnaryExpression,
decoderMap: Map<string, StringDecoder>
): UnaryOperation {
if (!unaryOperatorSet.has(expression.operator)) {
throw new Error(`Unsupported unary operator ${expression.operator}`);
} else if (!operationSet.has(expression.argument.type)) {
throw new Error(`Unsupported string rotation operation type ${expression.argument.type}`);
}
const argument = parseOperation(expression.argument as any, decoderMap);
return {
type: 'UnaryOperation',
operator: expression.operator as UnaryOperator,
argument
};
}
/**
* Parses a binary operation.
* @param expression The binary expression.
* @param decoderMap The string decoder map.
* @returns The binary operation.
*/
function parseBinaryOperation(
expression: t.BinaryExpression,
decoderMap: Map<string, StringDecoder>
): BinaryOperation {
if (!binaryOperatorSet.has(expression.operator)) {
throw new Error(`Unsupported binary operator ${expression.operator}`);
} else if (!operationSet.has(expression.left.type)) {
throw new Error(`Unsupported string rotation operation type ${expression.left.type}`);
} else if (!operationSet.has(expression.right.type)) {
throw new Error(`Unsupported string rotation operation type ${expression.right.type}`);
}
const left = parseOperation(expression.left as any, decoderMap);
const right = parseOperation(expression.right as any, decoderMap);
return {
type: 'BinaryOperation',
operator: expression.operator as BinaryOperator,
left,
right
};
}
/**
* Applies an operation.
* @param operation The operation.
* @returns The result.
*/
function applyOperation(operation: Operation): any {
switch (operation.type) {
case 'CallOperation':
return applyCall(operation);
case 'UnaryOperation':
return applyUnaryOperation(operation);
case 'BinaryOperation':
return applyBinaryOperation(operation);
case 'NumericLiteral':
return operation.value;
}
}
/**
* Applies a call of a string decoder.
* @param call The call.
* @returns The result.
*/
function applyCall(call: CallOperation): any {
return parseInt(
(call.decoder.getStringForRotation as (...args: (number | string)[]) => string)(
...call.args
)
);
}
/**
* Applies a unary operation.
* @param operation The unary operation.
* @returns The result.
*/
function applyUnaryOperation(operation: UnaryOperation): any {
const argument = applyOperation(operation.argument);
switch (operation.operator) {
case '-':
return -argument;
}
}
/**
* Applies a binary operation.
* @param operation The binary operation.
* @returns The result.
*/
function applyBinaryOperation(operation: BinaryOperation): any {
const left = applyOperation(operation.left);
const right = applyOperation(operation.right);
switch (operation.operator) {
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
return left / right;
case '%':
return left % right;
}
}
/**
* Rotates the string array.
* @param expression The expression containing the string array calls.
* @param decoderMap The string decoder map.
* @param stopValue The value to stop at.
*/
export function rotateStringArray(
array: string[],
expression: t.BinaryExpression,
decoderMap: Map<string, StringDecoder>,
stopValue: number
): void {
const operation = parseOperation(expression, decoderMap);
let i = 0;
while (true) {
try {
const value = applyOperation(operation);
if (value == stopValue) {
break;
} else {
array.push(array.shift()!);
}
} catch (err) {
array.push(array.shift()!);
}
// avoid entering infinite loops
if (i++ > 1e5) {
throw new Error('Max number of string rotation iterations reached');
}
}
}

View File

@@ -0,0 +1,29 @@
const BASE_64_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';
/**
* Base 64 transforms a string.
* @param str The string.
* @returns The transformed string.
*/
export function base64Transform(str: string): string {
let a = '';
let c = 0;
let d = 0;
let e;
for (let i = 0; (e = str.charAt(i++)); ) {
e = BASE_64_ALPHABET.indexOf(e);
if (e != -1) {
d = c % 4 ? d * 64 + e : e;
if (c++ % 4) {
a += String.fromCharCode(255 & (d >> ((-2 * c) & 6)));
}
}
}
const encoded = a
.split('')
.map(c => `%${c.charCodeAt(0).toString(16).padStart(2, '0')}`)
.join('');
return decodeURIComponent(encoded);
}

View File

@@ -0,0 +1,192 @@
import { Binding, NodePath } from '@babel/traverse';
import * as t from '@babel/types';
export abstract class ConstantVariable<T extends t.Node> {
public readonly name: string;
public readonly binding: Binding;
public readonly expression: T;
/**
* Creates a new constant variable.
* @param name The name of the variable.
* @param binding The binding.
* @param expression The value the variable holds.
*/
constructor(name: string, binding: Binding, expression: T) {
this.name = name;
this.binding = binding;
this.expression = expression;
}
/**
* Removes the variable and any declarations.
*/
public abstract remove(): void;
}
export class ConstantDeclarationVariable<T extends t.Node> extends ConstantVariable<T> {
private readonly declaratorPath: NodePath<t.Node>;
/**
* Creates a new constant variable that is declared and initialised immediately.
* @param declaratorPath The path of the variable declarator.
* @param name The name of the variable.
* @param binding The binding.
* @param expression The value the variable holds.
*/
constructor(declaratorPath: NodePath<t.Node>, name: string, binding: Binding, expression: T) {
super(name, binding, expression);
this.declaratorPath = declaratorPath;
}
/**
* Removes the variable.
*/
public remove(): void {
this.declaratorPath.remove();
}
}
export class ConstantAssignmentVariable<T extends t.Node> extends ConstantVariable<T> {
private readonly declaratorPath: NodePath<t.Node>;
private readonly assignmentPath: NodePath<t.AssignmentExpression>;
/**
* Creates a new constant variable that is declared with no value then assigned to later.
* @param declaratorPath The path of the variable declarator.
* @param assignmentPath The path of the assignment to the variable.
* @param name The name of the variable.
* @param binding The binding.
* @param expression The value the variable holds.
*/
constructor(
declaratorPath: NodePath<t.Node>,
assignmentPath: NodePath<t.AssignmentExpression>,
name: string,
binding: Binding,
expression: T
) {
super(name, binding, expression);
this.declaratorPath = declaratorPath;
this.assignmentPath = assignmentPath;
}
/**
* Removes the variable.
*/
public remove(): void {
this.declaratorPath.remove();
// only safe to remove an assignment if the parent doesn't rely on it
if (
this.assignmentPath.parentPath &&
this.assignmentPath.parentPath.isExpressionStatement()
) {
this.assignmentPath.remove();
} else {
this.assignmentPath.replaceWith(this.expression);
}
}
}
export type isTypeFunction<T extends t.Node> = (node: t.Node) => node is T;
/**
* Checks whether a node is initialising a 'constant' variable and returns the variable if so.
* @param path The path.
* @param isType The function that determines whether the expression is of the desired type.
* @returns The constant variable or undefined.
*/
export function findConstantVariable<T extends t.Node>(
path: NodePath,
isType: isTypeFunction<T>,
canBeFunction: boolean = false
): ConstantVariable<T> | undefined {
if (
path.isVariableDeclarator() &&
t.isIdentifier(path.node.id) &&
path.node.init != undefined &&
isType(path.node.init)
) {
const name = path.node.id.name;
const binding = path.scope.getBinding(name);
return binding && isConstantBinding(path, binding)
? new ConstantDeclarationVariable<T>(path, name, binding, path.node.init)
: undefined;
}
// essentially same as declarator but allows function declarations
else if (
canBeFunction &&
path.isFunctionDeclaration() &&
t.isIdentifier(path.node.id) &&
isType(path.node)
) {
const name = path.node.id.name;
const binding = path.scope.getBinding(name);
return binding && isConstantBinding(path, binding)
? new ConstantDeclarationVariable<T>(path, name, binding, path.node)
: undefined;
} else if (
path.isAssignmentExpression() &&
path.node.operator == '=' &&
t.isIdentifier(path.node.left) &&
isType(path.node.right)
) {
const name = path.node.left.name;
const binding = path.scope.getBinding(name);
return binding && isConstantAssignedBinding(path, binding)
? new ConstantAssignmentVariable(binding.path, path, name, binding, path.node.right)
: undefined;
}
return undefined;
}
/**
* Returns whether a binding is constant for our purposes. Babel views
* 'var' declarations within loops as non constants so this acts as a fix
* for that.
* @param path The path.
* @param binding The binding.
* @returns Whether.
*/
function isConstantBinding(path: NodePath, binding: Binding): boolean {
return (
binding.constant ||
(binding.constantViolations.length == 1 &&
path.node == binding.path.node &&
path.node == binding.constantViolations[0].node)
);
}
/**
* Returns whether a binding with a single assignment expression (separate
* to the declaration) can be treated as constant.
* @param path The path.
* @param binding The binding.
* @returns Whether.
*/
function isConstantAssignedBinding(
path: NodePath<t.AssignmentExpression>,
binding: Binding
): boolean {
if (
((binding.path.isVariableDeclarator() && binding.path.node.init == undefined) ||
binding.path.parentKey === 'params') && // either variable declarator with no initialiser or parameter of function
binding.constantViolations.length === 1 &&
binding.constantViolations[0].node === path.node
) {
const declarationParent = binding.path.isVariableDeclarator()
? (binding.path.getStatementParent() as NodePath<t.Statement>).parent
: (binding.path.parent as t.Function).body;
const parent = path.findParent(
p => p.isStatement() || p.isConditionalExpression() || p.isLogicalExpression()
);
if (!parent || !parent.isStatement() || parent.parent !== declarationParent) {
return false;
}
return true;
} else {
return false;
}
}

View File

@@ -0,0 +1,326 @@
import traverse, { NodePath } from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import * as m from '@codemod/matchers';
export class AntiTamperRemover extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'antiTamperRemoval',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
enter(path) {
/*
Matches the function which is used to wrap around calls of the self
defending, debug protection and console output disabling functions.
var _0x34a66a = (function () {
var _0x634fc3 = true;
return function (_0x446108, _0x8e5201) {
var _0x3cb39f = _0x634fc3
? function () {
if (_0x8e5201) {
var _0x104088 = _0x8e5201.apply(_0x446108, arguments);
_0x8e5201 = null;
return _0x104088;
}
}
: function () {};
_0x634fc3 = false;
return _0x3cb39f;
};
})();
*/
const wrapperName = m.capture(m.identifier());
const functionWrapper = m.variableDeclaration('var', [
m.variableDeclarator(
wrapperName,
m.callExpression(
m.functionExpression(
null,
[],
m.blockStatement([
m.variableDeclaration('var', [
m.variableDeclarator(m.identifier(), m.booleanLiteral(true))
]),
m.returnStatement(
m.functionExpression(
null,
[m.identifier(), m.identifier()],
m.blockStatement([
m.variableDeclaration('var', [
m.variableDeclarator(
m.identifier(),
m.conditionalExpression(
m.identifier(),
m.functionExpression(
null,
[],
m.blockStatement()
),
m.functionExpression(
null,
[],
m.blockStatement([])
)
)
)
]),
m.expressionStatement(
m.assignmentExpression(
'=',
m.identifier(),
m.booleanLiteral(false)
)
),
m.returnStatement(m.identifier())
])
)
)
])
),
[]
)
)
]);
/*
Matches self defending calls
var _0x37696c = _0x351e96(this, function () {
return _0x37696c.toString().search("(((.+)+)+)+$").toString().constructor(_0x37696c).search("(((.+)+)+)+$");
});
*/
const selfDefendingName = m.capture(m.identifier());
const selfDefendingCall = m.variableDeclaration('var', [
m.variableDeclarator(
selfDefendingName,
m.callExpression(m.identifier(), [
m.thisExpression(),
m.functionExpression(
null,
[],
m.blockStatement([m.returnStatement(m.callExpression())])
)
])
)
]);
/*
Matches debug protection
_0x248aac(this, function () {
var _0x1459a4 = new RegExp('function *\\( *\\)');
var _0x3fc097 = new RegExp('\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)', 'i');
var _0x22eedd = _0x3668ff('init');
if (!_0x1459a4.test(_0x22eedd + 'chain') || !_0x3fc097.test(_0x22eedd + 'input')) {
_0x22eedd('0');
} else {
_0x3668ff();
}
})();
We also remove the actual debug protection function. In this case this is `_0x3668ff`.
function _0x3668ff(_0x3357cd) {
function _0x5454fe(_0x11ef79) {
if (typeof _0x11ef79 === 'string') {
return function (_0x57f8a0) {}.constructor('while (true) {}').apply('counter');
} else if (('' + _0x11ef79 / _0x11ef79).length !== 0x1 || _0x11ef79 % 0x14 === 0x0) {
(function () {
return true;
})
.constructor('debugger')
.call('action');
} else {
(function () {
return false;
})
.constructor('debugger')
.apply('stateObject');
}
_0x5454fe(++_0x11ef79);
}
try {
if (_0x3357cd) {
return _0x5454fe;
} else {
_0x5454fe(0x0);
}
} catch (_0x1ffbd3) {}
}
*/
const debugProtectionName = m.capture(m.identifier());
const debugProtectionCall = m.expressionStatement(
m.callExpression(
m.callExpression(m.identifier(), [
m.thisExpression(),
m.functionExpression(
null,
[],
m.blockStatement([
m.variableDeclaration('var', [
m.variableDeclarator(
m.identifier(),
m.newExpression(m.identifier('RegExp'))
)
]),
m.variableDeclaration('var', [
m.variableDeclarator(
m.identifier(),
m.newExpression(m.identifier('RegExp'))
)
]),
m.variableDeclaration('var', [
m.variableDeclarator(
m.identifier(),
m.callExpression(debugProtectionName)
)
]),
m.ifStatement(
m.logicalExpression(),
m.blockStatement([
m.expressionStatement(
m.callExpression(m.identifier(), [
m.stringLiteral('0')
])
)
]),
m.blockStatement([
m.expressionStatement(
m.callExpression(m.identifier(), [])
)
])
)
])
)
]),
[]
)
);
/*
Matches console output disabling.
var _0x47a7a6 = _0x5ec4cc(this, function () {
var _0x3fa604;
try {
var _0x13dd7b = Function("return (function() {}.constructor(\"return this\")( ));");
_0x3fa604 = _0x13dd7b();
} catch (_0x425a7f) {
_0x3fa604 = window;
}
var _0x391b61 = _0x3fa604.console = _0x3fa604.console || {};
var _0x3911f6 = ["log", 'warn', "info", "error", "exception", 'table', "trace"];
for (var _0x3080b9 = 0x0; _0x3080b9 < _0x3911f6.length; _0x3080b9++) {
var _0x423cc4 = _0x5ec4cc.constructor.prototype.bind(_0x5ec4cc);
var _0x48665b = _0x3911f6[_0x3080b9];
var _0x57f828 = _0x391b61[_0x48665b] || _0x423cc4;
_0x423cc4.__proto__ = _0x5ec4cc.bind(_0x5ec4cc);
_0x423cc4.toString = _0x57f828.toString.bind(_0x57f828);
_0x391b61[_0x48665b] = _0x423cc4;
}
});
*/
const consoleOutputName = m.capture(m.identifier());
const consoleOutputCall = m.variableDeclaration('var', [
m.variableDeclarator(
consoleOutputName,
m.callExpression(m.identifier(), [
m.thisExpression(),
m.functionExpression(
null,
[],
m.blockStatement([
m.variableDeclaration('var', [
m.variableDeclarator(m.identifier(), null)
]),
m.tryStatement(),
m.variableDeclaration(),
m.variableDeclaration('var', [
m.variableDeclarator(m.identifier(), m.arrayExpression())
]),
m.forStatement()
])
)
])
)
]);
let failedReplacement = false;
if (functionWrapper.match(path.node)) {
const binding = path.scope.getBinding(wrapperName.current!.name);
if (binding) {
for (const reference of binding.referencePaths) {
const parent = reference.getStatementParent() as NodePath;
if (selfDefendingCall.match(parent.node)) {
const selfDefendingBinding = parent.scope.getBinding(
selfDefendingName.current!.name
);
if (selfDefendingBinding) {
for (const selfDefendingReference of selfDefendingBinding.referencePaths) {
selfDefendingReference.getStatementParent()?.remove();
self.setChanged();
}
}
if (!parent.removed) {
parent.remove();
self.setChanged();
}
} else if (debugProtectionCall.match(parent.node)) {
// remove actual anti-debug function
const antiDebugBinding = parent.scope.getBinding(
debugProtectionName.current!.name
);
if (antiDebugBinding) {
antiDebugBinding.path.remove();
self.setChanged();
}
// remove IIFE around call
parent
.parentPath!.getStatementParent()
?.getStatementParent()
?.remove();
} else if (consoleOutputCall.match(parent.node)) {
const consoleOutputBinding = parent.scope.getBinding(
consoleOutputName.current!.name
);
if (consoleOutputBinding) {
for (const consoleOutputReference of consoleOutputBinding.referencePaths) {
consoleOutputReference.getStatementParent()!.remove();
self.setChanged();
}
}
} else {
// ignore references that are within the console output function
const possibleParent = parent
.getFunctionParent()
?.getStatementParent();
if (
possibleParent &&
consoleOutputCall.match(possibleParent.node)
) {
continue;
}
log('Unknown reference to generic self defending function wrapper');
failedReplacement = true;
}
}
}
if (!failedReplacement) {
path.remove();
self.setChanged();
}
}
}
});
return this.hasChanged();
}
}

View File

@@ -0,0 +1,64 @@
import { TransformationConfig } from './transformation';
export type TransformationKey =
| 'objectSimplification'
| 'objectPacking'
| 'proxyFunctionInlining'
| 'stringRevealing'
| 'expressionSimplification'
| 'constantPropagation'
| 'reassignmentRemoval'
| 'sequenceSplitting'
| 'controlFlowRecovery'
| 'deadBranchRemoval'
| 'antiTamperRemoval'
| 'unusedVariableRemoval'
| 'propertySimplification';
export type Config = { [key in TransformationKey]: TransformationConfig } & {
silent?: boolean;
};
export const defaultConfig: Config = {
silent: false,
objectSimplification: {
isEnabled: true,
unsafeReplace: true
},
objectPacking: {
isEnabled: true
},
proxyFunctionInlining: {
isEnabled: true
},
stringRevealing: {
isEnabled: true
},
expressionSimplification: {
isEnabled: true
},
constantPropagation: {
isEnabled: true
},
reassignmentRemoval: {
isEnabled: true
},
sequenceSplitting: {
isEnabled: true
},
controlFlowRecovery: {
isEnabled: true
},
deadBranchRemoval: {
isEnabled: true
},
antiTamperRemoval: {
isEnabled: true
},
unusedVariableRemoval: {
isEnabled: true
},
propertySimplification: {
isEnabled: true
}
};

View File

@@ -0,0 +1,190 @@
import * as t from '@babel/types';
import traverse from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import { findConstantVariable } from '../../helpers/variable';
import { DeclarationOrAssignmentExpression, isDeclarationOrAssignmentExpression } from '../../helpers/declaration';
export class ControlFlowRecoverer extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'controlFlowRecovery',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
enter(path) {
const stateVariable = findConstantVariable<StateArrayExpression>(path, isStateArrayExpression);
if (!stateVariable) {
return;
}
const states = stateVariable.expression.callee.object.value.split(
stateVariable.expression.arguments[0].value
);
const statementPath = path.getStatementParent();
if (!statementPath) {
return;
}
let nextPath = statementPath.getNextSibling();
let initialValue: number;
if (isFlatteningForLoop(nextPath.node, stateVariable.name)) {
initialValue = t.isAssignmentExpression(nextPath.node.init)
? nextPath.node.init.right.value
: nextPath.node.init.declarations[0].init.value;
} else if (isDeclarationOrAssignmentExpression(nextPath.node, t.isIdentifier, t.isNumericLiteral)) {
const counterName = t.isAssignmentExpression(nextPath.node)
? nextPath.node.left.name
: nextPath.node.declarations[0].id.name;
initialValue = t.isAssignmentExpression(nextPath.node)
? nextPath.node.right.value
: nextPath.node.declarations[0].init.value;
nextPath = nextPath.getNextSibling();
if (!isFlatteningWhileLoop(nextPath.node, stateVariable.name, counterName)) {
return;
}
} else {
return;
}
const cases = nextPath.node.body.body[0].cases;
const casesMap = new Map<string, t.Statement[]>(
cases.map(c => [(c.test as t.StringLiteral).value, c.consequent])
);
const statements = [];
for (let i = initialValue; ; i++) {
const state = states[i];
if (!casesMap.has(state)) {
break;
}
const blockStatements = casesMap.get(state) as t.Statement[];
statements.push(...blockStatements.filter(s => !t.isContinueStatement(s)));
if (
blockStatements.length > 0 &&
t.isReturnStatement(blockStatements[blockStatements.length - 1])
) {
break;
}
}
path.remove();
nextPath.replaceWithMultiple(statements);
self.setChanged();
}
});
return this.hasChanged();
}
}
type StateArrayExpression = t.CallExpression & {
callee: t.MemberExpression & { object: t.StringLiteral };
arguments: { [0]: t.StringLiteral };
};
/**
* Returns whether a node is a state array expression.
* @param node The node.
* @returns Whether.
*/
const isStateArrayExpression = (node: t.Node): node is StateArrayExpression => {
return (
t.isCallExpression(node) &&
t.isMemberExpression(node.callee) &&
t.isStringLiteral(node.callee.object) &&
((t.isStringLiteral(node.callee.property) && node.callee.property.value == 'split') ||
(t.isIdentifier(node.callee.property) && node.callee.property.name == 'split')) &&
node.arguments.length == 1 &&
t.isStringLiteral(node.arguments[0])
);
};
type FlatteningLoopBody = t.BlockStatement & {
body: {
[0]: t.SwitchStatement & {
discriminant: t.MemberExpression & {
object: t.Identifier;
property: t.UpdateExpression & { argument: t.Identifier };
};
cases: (t.SwitchCase & { test: t.StringLiteral })[];
};
[1]: t.BreakStatement;
};
};
/**
* Returns whether a node is the body of a control flow flattening loop.
* @param node The AST node.
* @param statesName The name of the variable containing the states.
* @param counterName The name of the block counter variable.
* @returns Whether.
*/
const isFlatteningLoopBody = (node: t.Node, statesName: string, counterName: string): node is FlatteningLoopBody => {
return (
t.isBlockStatement(node) &&
node.body.length == 2 &&
t.isBreakStatement(node.body[1]) &&
t.isSwitchStatement(node.body[0]) &&
t.isMemberExpression(node.body[0].discriminant) &&
t.isIdentifier(node.body[0].discriminant.object) &&
node.body[0].discriminant.object.name == statesName &&
t.isUpdateExpression(node.body[0].discriminant.property) &&
t.isIdentifier(node.body[0].discriminant.property.argument) &&
node.body[0].discriminant.property.argument.name == counterName &&
node.body[0].cases.every(c => c.test && t.isStringLiteral(c.test))
);
};
type FlatteningForLoop = t.ForStatement & {
init: DeclarationOrAssignmentExpression<t.Identifier, t.NumericLiteral>;
body: FlatteningLoopBody;
};
/**
* Returns whether a node is a control flow flattening for loop.
* @param node The node.
* @param statesName The name of the variable containing the states.
* @returns Whether.
*/
const isFlatteningForLoop = (node: t.Node, statesName: string): node is FlatteningForLoop => {
return (
t.isForStatement(node) &&
node.init != undefined &&
isDeclarationOrAssignmentExpression(node.init, t.isIdentifier, t.isNumericLiteral) &&
isFlatteningLoopBody(
node.body,
statesName,
t.isAssignmentExpression(node.init) ? node.init.left.name : node.init.declarations[0].id.name
)
);
};
type FlatteningWhileLoop = t.WhileStatement & {
test: t.BooleanLiteral;
body: FlatteningLoopBody;
};
/**
* Returns whether a node is a control flow flattening while loop.
* @param node The node.
* @param statesName The name of the variable containing the states.
* @returns Whether.
*/
const isFlatteningWhileLoop = (node: t.Node, statesName: string, counterName: string): node is FlatteningWhileLoop => {
return (
t.isWhileStatement(node) &&
t.isBooleanLiteral(node.test) &&
node.test.value == true &&
isFlatteningLoopBody(node.body, statesName, counterName)
);
};

View File

@@ -0,0 +1,92 @@
import * as t from '@babel/types';
import traverse from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
export class DeadBranchRemover extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'deadBranchRemoval',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
IfStatement(path) {
if (self.isSemiLiteral(path.node.test)) {
if (self.isTruthy(path.node.test)) {
const statements = t.isBlockStatement(path.node.consequent)
? path.node.consequent.body
: [path.node.consequent];
path.replaceWithMultiple(statements);
self.setChanged();
} else {
if (path.node.alternate) {
if (t.isBlockStatement(path.node.alternate)) {
path.replaceWithMultiple(path.node.alternate.body);
} else {
path.replaceWith(path.node.alternate);
}
} else {
path.remove();
}
self.setChanged();
}
}
},
ConditionalExpression(path) {
// simplify expressions in form (true ? ... : ...)
if (self.isSemiLiteral(path.node.test)) {
const replacement = self.isTruthy(path.node.test) ? path.node.consequent : path.node.alternate;
path.replaceWith(replacement);
self.setChanged();
}
// simplify expressions in form (a ? true : false)
else if (t.isBooleanLiteral(path.node.consequent) && t.isBooleanLiteral(path.node.alternate)) {
const consequent = path.node.consequent.value;
const alternate = path.node.alternate.value;
let replacement: t.Node;
if (consequent && !alternate) {
replacement = t.unaryExpression('!', t.unaryExpression('!', path.node.test));
} else if (!consequent && alternate) {
replacement = t.unaryExpression('!', path.node.test);
} else if (consequent && alternate) {
replacement = t.sequenceExpression([path.node.test, t.booleanLiteral(true)]);
} else {
replacement = t.sequenceExpression([path.node.test, t.booleanLiteral(false)]);
}
path.replaceWith(replacement);
self.setChanged();
}
}
});
return this.hasChanged();
}
/**
* Returns whether a node is a literal or can be treated as one in the context of a test expression.
* @param node The node.
* @returns Whether.
*/
private isSemiLiteral(node: t.Node): node is t.Literal | t.ArrayExpression | t.ObjectExpression {
return t.isLiteral(node) || t.isArrayExpression(node) || t.isObjectExpression(node);
}
/**
* Returns whether a literal node is truthy.
* @param literal The literal node.
* @returns Whether.
*/
private isTruthy(literal: t.Literal | t.ArrayExpression | t.ObjectExpression): boolean {
return t.isBooleanLiteral(literal) || t.isNumericLiteral(literal) || t.isStringLiteral(literal)
? !!literal.value
: true;
}
}

View File

@@ -0,0 +1,171 @@
import * as t from '@babel/types';
import traverse, { NodePath } from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
export class SequenceSplitter extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'sequenceSplitting'
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
ConditionalExpression(path) {
if (path.parentPath && path.parentPath.isExpressionStatement()) {
const replacement = t.ifStatement(
path.node.test,
t.expressionStatement(path.node.consequent),
t.expressionStatement(path.node.alternate)
);
if (
path.parentPath.parentPath &&
path.parentPath.parentPath.key == 'alternate' &&
path.parentPath.parentPath.isBlockStatement() &&
path.parentPath.parentPath.node.body.length == 1
) {
path.parentPath.parentPath.replaceWith(replacement);
} else {
path.parentPath.replaceWith(replacement);
}
path.skip();
self.setChanged();
}
},
LogicalExpression(path) {
if (
(path.node.operator == '&&' || path.node.operator == '||') &&
path.parentPath &&
path.parentPath.isExpressionStatement()
) {
const test =
path.node.operator == '&&'
? path.node.left
: t.unaryExpression('!', path.node.left);
const replacement = t.ifStatement(test, t.expressionStatement(path.node.right));
if (
path.parentPath.parentPath &&
path.parentPath.parentPath.key == 'alternate' &&
path.parentPath.parentPath.isBlockStatement() &&
path.parentPath.parentPath.node.body.length == 1
) {
path.parentPath.parentPath.replaceWith(replacement);
} else {
path.parentPath.replaceWith(replacement);
}
path.skip();
self.setChanged();
}
},
['ForStatement|WhileStatement|DoWhileStatement' as any](
path: NodePath<t.ForStatement | t.WhileStatement | t.DoWhileStatement>
) {
if (!t.isBlockStatement(path.node.body)) {
path.node.body = t.blockStatement([path.node.body]);
self.setChanged();
}
},
IfStatement(path) {
if (!t.isBlockStatement(path.node.consequent)) {
path.node.consequent = t.blockStatement([path.node.consequent]);
self.setChanged();
}
if (
path.node.alternate &&
!t.isBlockStatement(path.node.alternate) &&
!t.isIfStatement(path.node.alternate)
) {
path.node.alternate = t.blockStatement([path.node.alternate]);
self.setChanged();
}
},
VariableDeclaration(path) {
if (path.node.declarations.length > 1) {
const replacements = path.node.declarations.map(d =>
t.variableDeclaration(path.node.kind, [d])
);
if (
path.parentPath &&
path.parentPath.isForStatement() &&
path.parentKey == 'init'
) {
const lastDeclaration = replacements.pop();
path.parentPath.insertBefore(replacements);
path.parentPath.node.init = lastDeclaration;
} else {
path.replaceWithMultiple(replacements);
}
self.setChanged();
}
},
SequenceExpression(path) {
const expressions = path.node.expressions;
if (expressions.length == 1) {
path.replaceWith(expressions[0]);
self.setChanged();
return;
}
let outerPath: NodePath = path;
while (!t.isStatement(outerPath.node)) {
const parent = outerPath.parentPath;
if (!parent) {
return;
}
if (
(parent.isConditionalExpression() &&
(outerPath.key == 'consequent' || outerPath.key == 'alternate')) ||
(parent.isLogicalExpression() && outerPath.key == 'right') ||
(parent.isForStatement() &&
(outerPath.key == 'test' || outerPath.key == 'update')) ||
(parent.isDoWhileStatement() && outerPath.key == 'test') ||
(parent.isArrowFunctionExpression() && outerPath.key == 'body')
) {
return;
}
outerPath = parent;
}
const lastExpression = expressions[expressions.length - 1];
if (self.isExcluded(lastExpression)) {
const firstExpressions = expressions.splice(0, expressions.length - 2);
if (firstExpressions.length > 0) {
const expressionStatements = firstExpressions.map(e =>
t.expressionStatement(e)
);
outerPath.insertBefore(expressionStatements);
self.setChanged();
}
} else {
const lastExpression = expressions.splice(expressions.length - 1, 1)[0];
const expressionStatements = expressions.map(e => t.expressionStatement(e));
outerPath.insertBefore(expressionStatements);
path.replaceWith(lastExpression);
self.setChanged();
}
}
});
return this.hasChanged();
}
/**
* Returns whether a node that is the last in a sequence expression
* is excluded from being placed on its own.
* @param node The AST node.
* @returns Whether.
*/
private isExcluded(node: t.Node): boolean {
return t.isIdentifier(node) && node.name == 'eval';
}
}

View File

@@ -0,0 +1,314 @@
import * as t from '@babel/types';
import traverse from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import { isNegativeNumericLiteral } from '../../helpers/expression';
export class ExpressionSimplifier extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'expressionSimplification'
};
private static readonly RESOLVABLE_UNARY_OPERATORS: Set<string> = new Set([
'-',
'+',
'!',
'~',
'typeof',
'void'
]);
private static readonly RESOLVABLE_BINARY_OPERATORS: Set<string> = new Set([
'==',
'!=',
'===',
'!==',
'<',
'<=',
'>',
'>=',
'<<',
'>>',
'>>>',
'+',
'-',
'*',
'/',
'%',
'**',
'|',
'^',
'&'
]);
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
['UnaryExpression|BinaryExpression'](path) {
const replacement = path.isUnaryExpression()
? self.simplifyUnaryExpression(path.node)
: self.simplifyBinaryExpression(path.node as t.BinaryExpression);
if (replacement) {
path.replaceWith(replacement);
self.setChanged();
}
}
});
return this.hasChanged();
}
/**
* Attempts to simplify an expression.
* @param expression The expression.
* @returns The expression in the simplest form possible.
*/
private simplifyExpression(expression: t.Expression): t.Expression {
if (t.isUnaryExpression(expression) || t.isBinaryExpression(expression)) {
const replacement = t.isUnaryExpression(expression)
? this.simplifyUnaryExpression(expression)
: this.simplifyBinaryExpression(expression);
return replacement || expression;
} else {
return expression;
}
}
/**
* Attempts to simplify a unary expression.
* @param expression The unary expression.
* @returns The simplified expression or undefined.
*/
private simplifyUnaryExpression(expression: t.UnaryExpression): t.Expression | undefined {
if (!ExpressionSimplifier.RESOLVABLE_UNARY_OPERATORS.has(expression.operator)) {
return undefined;
} else if (isNegativeNumericLiteral(expression)) {
return undefined; // avoid trying to simplify negative numbers
}
const argument = this.simplifyExpression(expression.argument);
if (this.isResolvableExpression(argument)) {
const argumentValue = this.getResolvableExpressionValue(argument);
const value = this.applyUnaryOperation(
expression.operator as ResolvableUnaryOperator,
argumentValue
);
return this.convertValueToExpression(value);
} else {
return undefined;
}
}
/**
* Attempts to simplify a binary expression.
* @param expression The binary expression.
* @returns The simplified expression or undefined.
*/
private simplifyBinaryExpression(expression: t.BinaryExpression): t.Expression | undefined {
if (
!t.isExpression(expression.left) ||
!ExpressionSimplifier.RESOLVABLE_BINARY_OPERATORS.has(expression.operator)
) {
return undefined;
}
const left = this.simplifyExpression(expression.left);
const right = this.simplifyExpression(expression.right);
if (this.isResolvableExpression(left) && this.isResolvableExpression(right)) {
const leftValue = this.getResolvableExpressionValue(left);
const rightValue = this.getResolvableExpressionValue(right);
const value = this.applyBinaryOperation(
expression.operator as ResolvableBinaryOperator,
leftValue,
rightValue
);
return this.convertValueToExpression(value);
} else if (expression.operator == '-' && isNegativeNumericLiteral(right)) {
// convert (- -a) to +a (as long as a is a number)
expression.right = right.argument;
expression.operator = '+';
return expression;
} else {
return undefined;
}
}
/**
* Applies a unary operation.
* @param operator The operator.
* @param argument The argument value.
* @returns The resultant value.
*/
private applyUnaryOperation(operator: ResolvableUnaryOperator, argument: any): any {
switch (operator) {
case '-':
return -argument;
case '+':
return +argument;
case '!':
return !argument;
case '~':
return ~argument;
case 'typeof':
return typeof argument;
case 'void':
return void argument;
}
}
/**
* Applies a binary operation.
* @param operator The resolvable binary operator.
* @param left The value of the left expression.
* @param right The value of the right expression.
* @returns The resultant value.
*/
private applyBinaryOperation(operator: ResolvableBinaryOperator, left: any, right: any): any {
switch (operator) {
case '==':
return left == right;
case '!=':
return left != right;
case '===':
return left === right;
case '!==':
return left !== right;
case '<':
return left < right;
case '<=':
return left <= right;
case '>':
return left > right;
case '>=':
return left >= right;
case '<<':
return left << right;
case '>>':
return left >> right;
case '>>>':
return left >>> right;
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
return left / right;
case '%':
return left % right;
case '**':
return left ** right;
case '|':
return left | right;
case '^':
return left ^ right;
case '&':
return left & right;
}
}
/**
* Gets the real value from a resolvable expression.
* @param expression The resolvable expression.
* @returns The value.
*/
private getResolvableExpressionValue(expression: ResolvableExpression): any {
switch (expression.type) {
case 'NumericLiteral':
case 'StringLiteral':
case 'BooleanLiteral':
case 'DecimalLiteral':
return expression.value;
case 'BigIntLiteral':
return BigInt(expression.value);
case 'UnaryExpression':
return -this.getResolvableExpressionValue(
expression.argument as Exclude<t.Literal, t.RegExpLiteral | t.TemplateLiteral>
);
case 'NullLiteral':
return null;
case 'Identifier':
return undefined;
case 'ArrayExpression':
return [];
case 'ObjectExpression':
return {};
}
}
/**
* Attempts to convert a value of unknown type to an expression node.
* @param value The value.
* @returns The expression or undefined.
*/
private convertValueToExpression(value: any): t.Expression | undefined {
switch (typeof value) {
case 'string':
return t.stringLiteral(value);
case 'number':
return value >= 0
? t.numericLiteral(value)
: t.unaryExpression('-', t.numericLiteral(Math.abs(value)));
case 'boolean':
return t.booleanLiteral(value);
case 'bigint':
return t.bigIntLiteral(value.toString());
case 'undefined':
return t.identifier('undefined');
default:
return undefined;
}
}
/**
* Returns whether a node is a resolvable expression that can be
* evaluated safely.
* @param node The AST node.
* @returns Whether.
*/
private isResolvableExpression(node: t.Node): node is ResolvableExpression {
return (
(t.isLiteral(node) && !t.isRegExpLiteral(node) && !t.isTemplateLiteral(node)) ||
(t.isUnaryExpression(node) && node.operator == '-' && t.isLiteral(node.argument)) ||
(t.isIdentifier(node) && node.name == 'undefined') ||
(t.isArrayExpression(node) && node.elements.length == 0) ||
(t.isObjectExpression(node) && node.properties.length == 0)
);
}
}
type ResolvableExpression =
| Exclude<t.Literal, t.RegExpLiteral | t.TemplateLiteral>
| (t.UnaryExpression & { operator: '-'; argument: t.Literal })
| (t.Identifier & { name: 'undefined' })
| (t.ArrayExpression & { elements: [] })
| (t.ObjectExpression & { properties: [] });
type ResolvableUnaryOperator = '-' | '+' | '!' | '~' | 'typeof' | 'void';
type ResolvableBinaryOperator =
| '=='
| '!='
| '==='
| '!=='
| '<'
| '<='
| '>'
| '>='
| '<<'
| '>>'
| '>>>'
| '+'
| '-'
| '*'
| '/'
| '%'
| '**'
| '|'
| '^'
| '&';

View File

@@ -0,0 +1,186 @@
import * as t from '@babel/types';
import { findConstantVariable } from '../../helpers/variable';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import traverse, { NodePath } from '@babel/traverse';
export class ObjectPacker extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'objectPacking'
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
enter(path) {
const variable = findConstantVariable<EmptyObjectExpression>(
path,
isEmptyObjectExpression
);
if (!variable) {
return;
}
const statementPath = path.getStatementParent();
if (
!statementPath ||
statementPath.parentPath == undefined ||
typeof statementPath.key != 'number'
) {
return;
}
const statements = (statementPath.parentPath.node as any)[statementPath.parentKey];
const referencePathSet = new Set(variable.binding.referencePaths);
let numRemoved = 0;
for (let i = statementPath.key + 1; i < statements.length; i++) {
const node = statements[i];
if (
t.isExpressionStatement(node) &&
self.isPropertyAssignment(node.expression, variable.name)
) {
// replace multiple properties assigned in same statement
if (self.isPropertyAssignment(node.expression.right, variable.name)) {
const properties = [node.expression.left];
let right: t.Expression = node.expression.right;
while (self.isPropertyAssignment(right, variable.name)) {
properties.push(right.left);
right = right.right;
}
// don't duplicate expressions with side effects
if (!t.isLiteral(right)) {
break;
}
for (const { property } of properties) {
const isComputed =
!t.isStringLiteral(property) &&
!t.isNumericLiteral(property) &&
!t.isIdentifier(property);
const objectProperty = t.objectProperty(
property,
right,
isComputed
);
variable.expression.properties.push(objectProperty);
self.setChanged();
numRemoved++;
}
} else {
const key = node.expression.left.property;
const isComputed =
!t.isStringLiteral(key) &&
!t.isNumericLiteral(key) &&
!t.isIdentifier(key);
// if the value contains a reference to the object itself then can't inline it
if (
self.hasSelfReference(
node.expression.right,
statementPath,
i,
referencePathSet,
log
)
) {
break;
}
const property = t.objectProperty(
key,
node.expression.right,
isComputed
);
variable.expression.properties.push(property);
self.setChanged();
numRemoved++;
}
} else {
break;
}
}
statements.splice(statementPath.key + 1, numRemoved);
}
});
return this.hasChanged();
}
/**
* Searches a value for a reference to the object itself. Inlining this value
* as an object property would be unsafe: https://github.com/ben-sb/obfuscator-io-deobfuscator/issues/39
* @param value The value of the object property.
* @param statementPath The path of the statement assigning the property.
* @param arrayIndex The index of the assigning statement within the parent statement array.
* @param referencePathSet A set of paths referencing the object being packed.
* @returns Whether the value contains a reference to the object.
*/
private hasSelfReference(
value: t.Node,
statementPath: NodePath,
arrayIndex: number,
referencePathSet: Set<NodePath>,
log: LogFunction
): boolean {
try {
const valuePath = statementPath.parentPath!.get(
`${statementPath.parentKey}.${arrayIndex}`
) as NodePath;
let hasSelfReference = false;
traverse(
value,
{
Identifier(path) {
if (referencePathSet.has(path)) {
hasSelfReference = true;
}
}
},
valuePath.scope,
undefined,
valuePath
);
return hasSelfReference;
} catch (err) {
log(`Error looking for self reference when object packing: ${err}`);
return false;
}
}
/**
* Returns whether a node is setting a property on a given object.
* @param node The AST node.
* @param objectName The name of the object.
* @returns Whether.
*/
private isPropertyAssignment(
node: t.Node,
objectName: string
): node is t.AssignmentExpression & { left: t.MemberExpression } {
return (
t.isAssignmentExpression(node) &&
t.isMemberExpression(node.left) &&
t.isIdentifier(node.left.object) &&
node.left.object.name == objectName
);
}
}
type EmptyObjectExpression = t.ObjectExpression & { properties: [] };
/**
* Returns whether a node is an empty object expression.
* @param node The node.
* @returns Whether.
*/
const isEmptyObjectExpression = (node: t.Node): node is EmptyObjectExpression => {
return t.isObjectExpression(node) && node.properties.length == 0;
};

View File

@@ -0,0 +1,81 @@
import * as t from '@babel/types';
import { LogFunction, Transformation, TransformationConfig, TransformationProperties } from '../transformation';
import traverse, { NodePath } from '@babel/traverse';
import { findConstantVariable } from '../../helpers/variable';
import { ProxyObject, ProxyObjectExpression, isProxyObjectExpression } from './proxyObject';
import { getProperty, setProperty } from '../../helpers/misc';
interface ObjectSimplificationConfig extends TransformationConfig {
unsafeReplace?: boolean;
}
export class ObjectSimplifier extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'objectSimplification',
rebuildScopeTree: true
};
private readonly config: ObjectSimplificationConfig;
/**
* Creates a new transformation.
* @param ast The AST.
* @param config The config.
*/
constructor(ast: t.File, config: ObjectSimplificationConfig) {
super(ast, config);
this.config = config;
}
/**
* Executes the transformation.
* @param log The log function.
* @returns Whether any changes were made.
*/
public execute(log: LogFunction): boolean {
const self = this;
const usages: [NodePath, ProxyObject][] = [];
let depth = 0;
traverse(this.ast, {
enter(path) {
setProperty(path, 'depth', depth++);
const variable = findConstantVariable<ProxyObjectExpression>(path, isProxyObjectExpression);
if (!variable) {
return;
}
// check if object values are modified
for (const referencePath of variable.binding.referencePaths) {
if (
referencePath.parentPath &&
referencePath.parentPath.isMemberExpression() &&
referencePath.parentPath.parentPath &&
referencePath.parentPath.parentPath.isAssignmentExpression() &&
referencePath.parentPath.key == 'left'
) {
if (!self.config.unsafeReplace) {
log(`Not replacing object ${variable.name} as it is modified`);
path.skip();
return;
}
}
}
const proxyObject = new ProxyObject(variable);
proxyObject.process();
usages.push(...proxyObject.getUsages().map(p => [p, proxyObject] as [NodePath, ProxyObject]));
}
});
// replace innermost usages first
usages.sort((a, b) => getProperty(b[0], 'depth') - getProperty(a[0], 'depth'));
for (const [path, proxyObject] of usages) {
if (proxyObject.replaceUsage(path)) {
this.setChanged();
}
}
return this.hasChanged();
}
}

View File

@@ -0,0 +1,153 @@
import * as t from '@babel/types';
import { ConstantVariable } from '../../helpers/variable';
import { copyExpression } from '../../helpers/misc';
import { ProxyFunction, isProxyFunctionExpression } from '../proxyFunctions/proxyFunction';
import { NodePath } from '@babel/traverse';
export type ProxyObjectExpression = t.ObjectExpression;
/**
* Returns whether a node is a proxy object.
* @param node The node.
* @returns Whether.
*/
export const isProxyObjectExpression = (node: t.Node): node is ProxyObjectExpression => {
return t.isObjectExpression(node) && node.properties.length > 0;
};
export class ProxyObject {
private readonly variable: ConstantVariable<ProxyObjectExpression>;
private readonly literalProperties: Map<string | number, t.Expression> = new Map();
private readonly proxyFunctionProperties: Map<string | number, ProxyFunction> = new Map();
/**
* Creates a new proxy object.
* @param variable The variable.
*/
constructor(variable: ConstantVariable<ProxyObjectExpression>) {
this.variable = variable;
}
/**
* Finds all the object's entries which can be replaced.
*/
public process(): void {
for (const property of this.variable.expression.properties) {
if (t.isObjectProperty(property) && this.isLiteralPropertyKey(property)) {
const key = t.isIdentifier(property.key) ? property.key.name : property.key.value;
if (t.isLiteral(property.value)) {
this.literalProperties.set(key, property.value);
} else if (isProxyFunctionExpression(property.value)) {
const proxyFunction = new ProxyFunction(property.value);
this.proxyFunctionProperties.set(key, proxyFunction);
}
} else if (t.isObjectMethod(property) && this.isLiteralMethodKey(property)) {
const key = t.isIdentifier(property.key) ? property.key.name : property.key.value;
if (isProxyFunctionExpression(property)) {
const proxyFunction = new ProxyFunction(property);
this.proxyFunctionProperties.set(key, proxyFunction);
}
}
}
}
/**
* Returns the usages of the object.
* @returns The usages.
*/
public getUsages(): NodePath[] {
return this.variable.binding.referencePaths;
}
/**
* Attempts to replace a usage of the object.
* @param path The path of the usage.
* @returns Whether it was replaced.
*/
public replaceUsage(path: NodePath): boolean {
const parentPath = path.parentPath;
if (
parentPath &&
parentPath.isMemberExpression() &&
this.isLiteralMemberKey(parentPath.node) &&
(!parentPath.parentPath ||
!parentPath.parentPath.isAssignmentExpression() ||
parentPath.parentKey != 'left')
) {
const key = t.isIdentifier(parentPath.node.property)
? parentPath.node.property.name
: parentPath.node.property.value;
if (this.literalProperties.has(key)) {
const value = this.literalProperties.get(key) as t.Expression;
parentPath.replaceWith(copyExpression(value));
return true;
} else if (
parentPath.parentPath &&
parentPath.parentPath.isCallExpression() &&
parentPath.key == 'callee' &&
this.proxyFunctionProperties.has(key)
) {
const proxyFunction = this.proxyFunctionProperties.get(key) as ProxyFunction;
const replacement = proxyFunction.getReplacement(
parentPath.parentPath.node.arguments
);
parentPath.parentPath.replaceWith(replacement);
return true;
}
}
return false;
}
/**
* Returns whether an object property has a literal key.
* @param property The object property.
* @returns Whether.
*/
private isLiteralPropertyKey(
property: t.ObjectProperty
): property is
| (t.ObjectProperty & { key: t.StringLiteral | t.NumericLiteral })
| (t.ObjectProperty & { computed: false; key: t.Identifier }) {
return (
t.isStringLiteral(property.key) ||
t.isNumericLiteral(property.key) ||
(!property.computed && t.isIdentifier(property.key))
);
}
/**
* Returns whether an object method has a literal key.
* @param property The object method.
* @returns Whether.
*/
private isLiteralMethodKey(
property: t.ObjectMethod
): property is
| (t.ObjectMethod & { key: t.StringLiteral | t.NumericLiteral })
| (t.ObjectMethod & { computed: false; key: t.Identifier }) {
return (
t.isStringLiteral(property.key) ||
t.isNumericLiteral(property.key) ||
(!property.computed && t.isIdentifier(property.key))
);
}
/**
* Returns whether a member expression has a literal key.
* @param member The member expression.
* @returns Whether.
*/
private isLiteralMemberKey(
member: t.MemberExpression
): member is
| (t.MemberExpression & { property: t.StringLiteral | t.NumericLiteral })
| (t.MemberExpression & { computed: false; property: t.Identifier }) {
return (
t.isStringLiteral(member.property) ||
t.isNumericLiteral(member.property) ||
(!member.computed && t.isIdentifier(member.property))
);
}
}

View File

@@ -0,0 +1,51 @@
import * as t from '@babel/types';
import traverse, { NodePath } from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
export class PropertySimplifier extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'propertySimplification'
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
MemberExpression(path) {
if (
path.node.computed &&
t.isStringLiteral(path.node.property) &&
t.isValidIdentifier(path.node.property.value)
) {
path.node.property = t.identifier(path.node.property.value);
path.node.computed = false;
self.setChanged();
}
},
['ObjectProperty|ObjectMethod' as any](
path: NodePath<t.ObjectProperty | t.ObjectMethod>
) {
if (
path.node.computed &&
t.isStringLiteral(path.node.key) &&
t.isValidIdentifier(path.node.key.value)
) {
path.node.key = t.identifier(path.node.key.value);
path.node.computed = false;
self.setChanged();
} else if (
path.node.computed &&
(t.isStringLiteral(path.node.key) || t.isNumericLiteral(path.node.key))
) {
path.node.computed = false;
}
}
});
return this.hasChanged();
}
}

View File

@@ -0,0 +1,159 @@
import * as t from '@babel/types';
import { ConstantVariable } from '../../helpers/variable';
import { copyExpression } from '../../helpers/misc';
import traverse, { NodePath } from '@babel/traverse';
export type ProxyFunctionExpression = t.Function & {
params: t.Identifier[];
body:
| (t.BlockStatement & {
body: { [0]: t.ReturnStatement & { argument: t.Expression | undefined } };
})
| t.Expression;
};
/**
* Returns whether a proxy function expression.
* @param node The node.
* @returns Whether.
*/
export const isProxyFunctionExpression = (node: t.Node): node is ProxyFunctionExpression => {
return (
t.isFunction(node) &&
node.params.every(p => t.isIdentifier(p)) &&
((t.isBlockStatement(node.body) &&
node.body.body.length == 1 &&
t.isReturnStatement(node.body.body[0]) &&
(node.body.body[0].argument == undefined ||
(t.isExpression(node.body.body[0].argument) &&
isProxyValue(node.body.body[0].argument)))) ||
(t.isArrowFunctionExpression(node) &&
t.isExpression(node.body) &&
isProxyValue(node.body)))
);
};
/**
* Returns whether a node is a valid proxy function return value.
* @param node The node.
* @returns Whether.
*/
const isProxyValue = (node: t.Node): boolean => {
if (t.isFunction(node) || t.isBlockStatement(node) || t.isSequenceExpression(node)) {
return false;
}
let isValid = true;
traverse(node, {
['SequenceExpression|BlockStatement|Function|AssignmentExpression'](path) {
isValid = false;
path.stop();
},
noScope: true
});
return isValid;
};
type Argument = t.ArgumentPlaceholder | t.JSXNamespacedName | t.SpreadElement | t.Expression;
export class ProxyFunction {
private readonly expression: ProxyFunctionExpression;
/**
* Creates a new proxy function.
* @param expression The proxy function expression.
*/
constructor(expression: ProxyFunctionExpression) {
this.expression = expression;
}
/**
* Returns the replacement for a call of the proxy function.
* @param args The arguments of the call.
* @returns The replacement expression.
*/
public getReplacement(args: Argument[]): t.Expression {
const expression = t.isExpression(this.expression.body)
? copyExpression(this.expression.body)
: this.expression.body.body[0].argument
? copyExpression(this.expression.body.body[0].argument)
: t.identifier('undefined');
this.replaceParameters(expression, args);
return expression;
}
/**
* Replaces usages of the proxy function's parameters with the concrete arguments for a given call.
* @param expression The expression.
* @param args The arguments of the call.
*/
private replaceParameters(expression: t.Expression, args: Argument[]): void {
const paramMap = new Map<string, t.Node>(
this.expression.params.map((param: t.Identifier, index: number) => [
param.name,
args[index] || t.identifier('undefined')
])
);
const pathsToReplace: [NodePath, t.Expression][] = [];
traverse(expression, {
enter(path) {
if (
t.isIdentifier(path.node) &&
// check it is a real identifier
!(
path.parentPath &&
path.parentPath.isMemberExpression() &&
path.key == 'property'
) &&
paramMap.has(path.node.name)
) {
const replacement = paramMap.get(path.node.name) as t.Expression;
pathsToReplace.push([path, replacement]);
}
},
noScope: true
});
for (const [path, replacement] of pathsToReplace) {
path.replaceWith(replacement);
}
}
}
export class ProxyFunctionVariable extends ProxyFunction {
private readonly variable: ConstantVariable<ProxyFunctionExpression>;
/**
* Creates a new proxy function variable.
* @param variable The variable.
*/
constructor(variable: ConstantVariable<ProxyFunctionExpression>) {
super(variable.expression);
this.variable = variable;
}
/**
* Returns the calls to the proxy function.
* @returns The calls to the proxy function.
*/
public getCalls(): NodePath[] {
return this.variable.binding.referencePaths;
}
/**
* Attempts to replace a call of the proxy function.
* @param path The path of the call.
* @returns Whether it was replaced.
*/
public replaceCall(path: NodePath): boolean {
if (path.parentPath && path.parentPath.isCallExpression() && path.key == 'callee') {
const expression = this.getReplacement(path.parentPath.node.arguments);
path.parentPath.replaceWith(expression);
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,47 @@
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import traverse, { NodePath } from '@babel/traverse';
import { findConstantVariable } from '../../helpers/variable';
import { ProxyFunctionExpression, ProxyFunctionVariable, isProxyFunctionExpression } from './proxyFunction';
import { getProperty, setProperty } from '../../helpers/misc';
export class ProxyFunctionInliner extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'proxyFunctionInlining',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
* @returns Whether any changes were made.
*/
public execute(log: LogFunction): boolean {
const usages: [NodePath, ProxyFunctionVariable][] = [];
let depth = 0;
traverse(this.ast, {
enter(path) {
setProperty(path, 'depth', depth++);
const variable = findConstantVariable<ProxyFunctionExpression>(path, isProxyFunctionExpression, true);
if (!variable) {
return;
}
const proxyFunction = new ProxyFunctionVariable(variable);
usages.push(
...proxyFunction.getCalls().map(p => [p, proxyFunction] as [NodePath, ProxyFunctionVariable])
);
}
});
// replace innermost proxy calls first
usages.sort((a, b) => getProperty(b[0], 'depth') - getProperty(a[0], 'depth'));
for (const [path, proxyFunction] of usages) {
if (proxyFunction.replaceCall(path)) {
this.setChanged();
}
}
return this.hasChanged();
}
}

View File

@@ -0,0 +1,630 @@
import * as t from '@babel/types';
import traverse, { NodePath } from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import {
isDeclarationOrAssignmentExpression,
isDeclarationOrAssignmentStatement
} from '../../helpers/declaration';
import { BasicStringDecoder } from '../../helpers/strings/decoders/basicStringDecoder';
import generate from '@babel/generator';
import { Rc4StringDecoder } from '../../helpers/strings/decoders/rc4StringDecoder';
import { DecoderType, StringDecoder } from '../../helpers/strings/decoders/stringDecoder';
import { Base64StringDecoder } from '../../helpers/strings/decoders/base64StringDecoder';
import { rotateStringArray } from '../../helpers/strings/rotation/rotation';
const BASE_64_WRAPPER_REGEX =
/['"]abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\+\/=['"]\.indexOf/;
const RC4_WRAPPER_REGEX =
/[a-zA-Z$_]?[a-zA-Z0-9$_]+\s?\+=\s?String\.fromCharCode\([a-zA-Z$_]?[a-zA-Z0-9$_]+\.charCodeAt\([a-zA-Z$_]?[a-zA-Z0-9$_]+\)\s?\^\s?[a-zA-Z$_]?[a-zA-Z0-9$_]+\[\([a-zA-Z$_]?[a-zA-Z0-9$_]+\[[a-zA-Z$_]?[a-zA-Z0-9$_]+\]\s?\+\s?[a-zA-Z$_]?[a-zA-Z0-9$_]+\[[a-zA-Z$_]?[a-zA-Z0-9$_]+\]\)\s?%\s?(?:256|0x100)\]\)/;
export class StringRevealer extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'stringRevealing',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
enter(path) {
if (
self.isDirectStringArrayDeclarator(path.node) ||
self.isStringArrayFunction(path.node)
) {
const isDirectArray = t.isVariableDeclarator(path.node);
let stringArray: string[];
if (t.isFunction(path.node)) {
if (t.isVariableDeclaration(path.node.body.body[0])) {
const arrayExpression = path.node.body.body[0].declarations[0]
.init as t.ArrayExpression;
stringArray = arrayExpression.elements.map(
e => (e as t.StringLiteral).value
);
} else {
const string = (path.node.body.body[0] as any).expression.right.callee
.object.value;
const separator = (path.node.body.body[0] as any).expression.right
.arguments[0].value;
stringArray = string.split(separator);
}
} else {
stringArray = path.node.init.elements.map(
e => (e as t.StringLiteral).value
);
}
const arrayName = path.node.id.name;
const binding = path.scope.getBinding(arrayName);
if (!binding) {
return;
}
const wrapperFunctionSet: Set<NodePath<t.FunctionDeclaration>> = new Set();
const stringDecoders = [];
let rotateCall: NodePath<t.ExpressionStatement> | undefined;
for (const referencePath of binding.referencePaths) {
// ignore call to function from within
if (!isDirectArray && referencePath.scope == path.scope) {
continue;
}
if (referencePath.parentKey == 'callee') {
const functionParent = referencePath.getFunctionParent();
if (!functionParent) {
log('Unknown reference to string array function');
return;
}
if (self.isBasicStringArrayWrapper(functionParent.node, arrayName)) {
const offsetExpression = (functionParent.node.body.body[1] as any)
.expression.right.body.body[0].expression.right;
const absoluteOffset = offsetExpression.right.value;
const offset =
offsetExpression.operator == '+'
? absoluteOffset
: -absoluteOffset;
const decoder = new BasicStringDecoder(stringArray, offset);
stringDecoders.push(decoder);
wrapperFunctionSet.add(
functionParent as NodePath<t.FunctionDeclaration>
);
} else if (
self.isComplexStringArrayWrapper(functionParent.node, arrayName)
) {
const offsetExpression = (functionParent.node as any).body.body[1]
.expression.right.body.body[0].expression.right;
const absoluteOffset = offsetExpression.right.value;
const offset =
offsetExpression.operator == '+'
? absoluteOffset
: -absoluteOffset;
const src = generate(functionParent.node).code;
if (BASE_64_WRAPPER_REGEX.test(src)) {
if (RC4_WRAPPER_REGEX.test(src)) {
const decoder = new Rc4StringDecoder(stringArray, offset);
stringDecoders.push(decoder);
} else {
const decoder = new Base64StringDecoder(
stringArray,
offset
);
stringDecoders.push(decoder);
}
wrapperFunctionSet.add(
functionParent as NodePath<t.FunctionDeclaration>
);
} else {
log('Unknown string array wrapper type');
return;
}
} else {
log('Unknown reference to string array function');
return;
}
} else if (
isDirectArray &&
referencePath.key == 'object' &&
referencePath.parentPath &&
referencePath.parentPath.isMemberExpression()
) {
const functionParent = referencePath.getFunctionParent();
if (!functionParent) {
log('Unknown reference to string array function');
return;
}
if (
self.isComplexDirectStringArrayWrapper(
functionParent.node,
arrayName
)
) {
const offsetStatement = (functionParent.node as any).body.body[0];
const offsetExpression = (
t.isVariableDeclaration(offsetStatement)
? offsetStatement.declarations[0].init
: (offsetStatement as any).expression.right
) as t.BinaryExpression & { right: t.NumericLiteral };
const absoluteOffset = offsetExpression.right.value;
const offset =
offsetExpression.operator == '+'
? absoluteOffset
: -absoluteOffset;
const src = generate(functionParent.node).code;
if (BASE_64_WRAPPER_REGEX.test(src)) {
if (RC4_WRAPPER_REGEX.test(src)) {
const decoder = new Rc4StringDecoder(stringArray, offset);
stringDecoders.push(decoder);
} else {
const decoder = new Base64StringDecoder(
stringArray,
offset
);
stringDecoders.push(decoder);
}
wrapperFunctionSet.add(
functionParent as NodePath<t.FunctionDeclaration>
);
} else {
log('Unknown string array wrapper type');
return;
}
} else {
log('Unknown reference to string array function');
return;
}
} else if (referencePath.parentKey == 'arguments') {
const parentPath = referencePath.parentPath as NodePath;
if (self.isRotateStringArrayCall(parentPath.node, arrayName)) {
rotateCall =
parentPath.parentPath as NodePath<t.ExpressionStatement>;
} else {
log('Unknown reference to string array function');
return;
}
} else {
log('Unknown reference to string array function');
return;
}
}
// ensure there is at least one wrapper function
if (wrapperFunctionSet.size == 0) {
log('No string wrapper functions found');
return;
}
const wrapperFunctions = Array.from(wrapperFunctionSet);
const wrapperFunctionNames = wrapperFunctions.map(w => w.node.id!.name);
const wrapperBindings = wrapperFunctions.map((w, i) =>
w.scope.getBinding(wrapperFunctionNames[i])
);
if (wrapperBindings.find(w => !w)) {
log(`Failed to find string concealer wrapper functions`);
return;
}
// perform string rotation if necessary
if (rotateCall) {
const stopValue = (rotateCall.node.expression as any).arguments[1].value;
const body = (rotateCall.node.expression as any).callee.body.body;
const loop = body[body.length - 1];
const statement = loop.body.body[0].block.body[0];
const expression: t.BinaryExpression = t.isVariableDeclaration(statement)
? statement.declarations[0].init
: (statement as any).expression.right;
const decoderMap = new Map<string, StringDecoder>(
stringDecoders.map((decoder, index) => [
wrapperFunctionNames[index],
decoder
])
);
rotateStringArray(stringArray, expression, decoderMap, stopValue);
}
let failedReplacement = false;
for (let i = 0; i < wrapperFunctions.length; i++) {
const wrapperFunction = wrapperFunctions[i];
const wrapperBinding = wrapperBindings[i];
const decoder = stringDecoders[i];
for (const referencePath of wrapperBinding!.referencePaths) {
const functionParent = referencePath.getFunctionParent();
const outerFunctionParent =
functionParent && functionParent.getFunctionParent();
const parentPath = referencePath.parentPath;
if (
(functionParent &&
(functionParent.node == wrapperFunction.node ||
(rotateCall &&
functionParent.node ==
(rotateCall.node.expression as t.CallExpression)
.callee))) ||
(outerFunctionParent &&
outerFunctionParent.node == wrapperFunction.node)
) {
continue;
} else if (
!parentPath ||
!self.isStringArrayWrapperCall(parentPath.node, decoder.type)
) {
failedReplacement = true;
} else {
try {
const args = parentPath.node.arguments.map(
a => (a as t.NumericLiteral | t.StringLiteral).value
);
const value = (
decoder.getString as (
...args: (number | string)[]
) => string
)(...args);
if (typeof value == 'string') {
parentPath.replaceWith(t.stringLiteral(value));
self.setChanged();
} else {
failedReplacement = true;
}
} catch (err) {
failedReplacement = true;
}
}
}
}
if (!failedReplacement) {
path.remove();
for (const wrapper of wrapperFunctions) {
wrapper.remove();
}
if (rotateCall) {
rotateCall.remove();
}
self.setChanged();
}
} else if (self.isEscapedStringLiteral(path.node)) {
path.node.extra = undefined;
self.setChanged();
}
}
});
return this.hasChanged();
}
/**
* Returns whether a node is directly declaring a string array.
* @param node The AST node.
* @returns Whether.
*/
private isDirectStringArrayDeclarator(node: t.Node): node is t.VariableDeclarator & {
id: t.Identifier;
init: t.ArrayExpression & { elements: t.StringLiteral[] };
} {
return (
t.isVariableDeclarator(node) &&
t.isIdentifier(node.id) &&
node.init != undefined &&
t.isArrayExpression(node.init) &&
node.init.elements.length > 0 &&
node.init.elements.every(e => t.isStringLiteral(e))
);
}
/**
* Returns whether a node is the function that splits and returns the
* string array.
* @param node The AST node.
* @returns Whether.
*/
private isStringArrayFunction(
node: t.Node
): node is t.FunctionDeclaration & { id: t.Identifier } {
return (
t.isFunctionDeclaration(node) &&
t.isBlockStatement(node.body) &&
node.body.body.length == 3 &&
isDeclarationOrAssignmentStatement(
node.body.body[0],
t.isIdentifier,
(node: t.Node) =>
(t.isArrayExpression(node) && node.elements.every(e => t.isStringLiteral(e))) || // explicit string array
(t.isCallExpression(node) && // creating string array by splitting a string
t.isMemberExpression(node.callee) &&
t.isStringLiteral(node.callee.object) &&
t.isIdentifier(node.callee.property) &&
node.callee.property.name == 'split' &&
node.arguments.length == 1 &&
t.isStringLiteral(node.arguments[0]))
) &&
isDeclarationOrAssignmentStatement(
node.body.body[1],
t.isIdentifier,
(node: t.Node) =>
t.isFunctionExpression(node) &&
t.isBlockStatement(node.body) &&
node.body.body.length == 1 &&
t.isReturnStatement(node.body.body[0]) &&
t.isIdentifier(node.body.body[0].argument)
) &&
t.isReturnStatement(node.body.body[2]) &&
t.isCallExpression(node.body.body[2].argument) &&
t.isIdentifier(node.body.body[2].argument.callee) &&
node.body.body[2].argument.arguments.length == 0
);
}
/**
* Returns whether a node is a basic string array wrapper function.
* @param node The AST node.
* @param stringArrayName The name of the string array function.
* @returns Whether.
*/
private isBasicStringArrayWrapper(
node: t.Node,
stringArrayName: string
): node is t.FunctionDeclaration {
return (
t.isFunctionDeclaration(node) &&
t.isBlockStatement(node.body) &&
node.body.body.length == 3 &&
isDeclarationOrAssignmentStatement(
node.body.body[0],
t.isIdentifier,
(node: t.Node) =>
t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.callee.name == stringArrayName &&
node.arguments.length == 0
) &&
isDeclarationOrAssignmentStatement(
node.body.body[1],
t.isIdentifier,
(node: t.Node) =>
t.isFunctionExpression(node) &&
t.isBlockStatement(node.body) &&
node.body.body.length == 3 &&
isDeclarationOrAssignmentStatement(
node.body.body[0],
t.isIdentifier,
(node: t.Node) =>
t.isBinaryExpression(node) &&
(node.operator == '-' || node.operator == '+') &&
t.isIdentifier(node.left) &&
t.isNumericLiteral(node.right)
) &&
isDeclarationOrAssignmentStatement(
node.body.body[1],
t.isIdentifier,
(node: t.Node) =>
t.isMemberExpression(node) &&
t.isIdentifier(node.object) &&
t.isIdentifier(node.property)
) &&
t.isReturnStatement(node.body.body[2]) &&
t.isIdentifier(node.body.body[2].argument)
) &&
t.isReturnStatement(node.body.body[2]) &&
t.isCallExpression(node.body.body[2].argument) &&
t.isIdentifier(node.body.body[2].argument.callee) &&
node.body.body[2].argument.arguments.length == 2 &&
t.isIdentifier(node.body.body[2].argument.arguments[0]) &&
t.isIdentifier(node.body.body[2].argument.arguments[1])
);
}
/**
* Returns whether a node is either a base 64 or RC4 string array wrapper function.
* @param node The AST node.
* @param stringArrayName The name of the string array function.
* @returns Whether.
*/
private isComplexStringArrayWrapper(
node: t.Node,
stringArrayName: string
): node is t.FunctionDeclaration {
return (
t.isFunctionDeclaration(node) &&
t.isBlockStatement(node.body) &&
node.body.body.length == 3 &&
isDeclarationOrAssignmentStatement(
node.body.body[0],
t.isIdentifier,
(node: t.Node) =>
t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.callee.name == stringArrayName &&
node.arguments.length == 0
) &&
isDeclarationOrAssignmentStatement(
node.body.body[1],
t.isIdentifier,
(node: t.Node) =>
t.isFunctionExpression(node) &&
t.isBlockStatement(node.body) &&
node.body.body.length >= 4 &&
isDeclarationOrAssignmentStatement(
node.body.body[0],
t.isIdentifier,
(node: t.Node) =>
t.isBinaryExpression(node) &&
(node.operator == '-' || node.operator == '+') &&
t.isIdentifier(node.left) &&
t.isNumericLiteral(node.right)
) &&
isDeclarationOrAssignmentStatement(
node.body.body[1],
t.isIdentifier,
(node: t.Node) =>
t.isMemberExpression(node) &&
t.isIdentifier(node.object) &&
t.isIdentifier(node.property)
) &&
t.isIfStatement(node.body.body[2]) &&
t.isIfStatement(node.body.body[node.body.body.length - 2]) &&
t.isReturnStatement(node.body.body[node.body.body.length - 1])
) &&
t.isReturnStatement(node.body.body[2]) &&
t.isCallExpression(node.body.body[2].argument) &&
t.isIdentifier(node.body.body[2].argument.callee) &&
node.body.body[2].argument.arguments.length == 2 &&
t.isIdentifier(node.body.body[2].argument.arguments[0]) &&
t.isIdentifier(node.body.body[2].argument.arguments[1])
);
}
/**
* Returns whether a node is either a base 64 or RC4 string array wrapper function,
* around a direct string array.
* @param node The AST node.
* @param stringArrayName The name of the string array.
* @returns Whether.
*/
private isComplexDirectStringArrayWrapper(
node: t.Node,
stringArrayName: string
): node is t.FunctionDeclaration {
let lastStatement: t.Statement;
return (
t.isFunctionDeclaration(node) &&
t.isBlockStatement(node.body) &&
node.body.body.length >= 6 &&
isDeclarationOrAssignmentStatement(
node.body.body[0],
t.isIdentifier,
(node: t.Node) =>
t.isBinaryExpression(node) &&
(node.operator == '-' || node.operator == '+') &&
t.isIdentifier(node.left) &&
t.isNumericLiteral(node.right)
) &&
isDeclarationOrAssignmentStatement(
node.body.body[1],
t.isIdentifier,
(node: t.Node) =>
t.isMemberExpression(node) &&
t.isIdentifier(node.object) &&
node.object.name == stringArrayName &&
t.isIdentifier(node.property)
) &&
t.isIfStatement(node.body.body[2]) &&
t.isVariableDeclaration(node.body.body[3]) &&
t.isIfStatement(node.body.body[node.body.body.length - 2]) &&
(lastStatement = node.body.body[node.body.body.length - 1]) &&
t.isReturnStatement(lastStatement) &&
!!lastStatement.argument &&
t.isIdentifier(lastStatement.argument)
);
}
/**
* Returns whether a node is a call to rotate the string array.
* @param node The AST node.
* @param stringArrayName The name of the string array function.
* @returns Whether.
*/
private isRotateStringArrayCall(
node: t.Node,
stringArrayName: string
): node is t.CallExpression & {
callee: t.FunctionExpression & { body: t.BlockStatement };
arguments: [t.Identifier, t.NumericLiteral];
} {
return (
t.isCallExpression(node) &&
node.arguments.length == 2 &&
t.isIdentifier(node.arguments[0]) &&
node.arguments[0].name == stringArrayName &&
t.isNumericLiteral(node.arguments[1]) &&
t.isFunctionExpression(node.callee) &&
t.isBlockStatement(node.callee.body) &&
((node.callee.body.body.length == 1 &&
t.isForStatement(node.callee.body.body[0]) &&
node.callee.body.body[0].init != undefined &&
isDeclarationOrAssignmentExpression(
node.callee.body.body[0].init,
t.isIdentifier,
(node: t.Node) =>
t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.arguments.length == 0
) &&
node.callee.body.body[0].test != undefined &&
t.isBooleanLiteral(node.callee.body.body[0].test) &&
node.callee.body.body[0].test.value) ||
(node.callee.body.body.length == 2 &&
isDeclarationOrAssignmentStatement(
node.callee.body.body[0],
t.isIdentifier,
(node: t.Node) =>
t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.arguments.length == 0
) &&
t.isWhileStatement(node.callee.body.body[1]) &&
t.isBooleanLiteral(node.callee.body.body[1].test) &&
node.callee.body.body[1].test.value == true) ||
(node.callee.body.body.length == 1 &&
t.isWhileStatement(node.callee.body.body[0]) &&
t.isBooleanLiteral(node.callee.body.body[0].test) &&
node.callee.body.body[0].test.value == true &&
t.isBlockStatement(node.callee.body.body[0].body) &&
node.callee.body.body[0].body.body.length == 1 &&
t.isTryStatement(node.callee.body.body[0].body.body[0])))
);
}
/**
* Returns whether a node is a call of the string array wrapper function.
* @param node The AST node.
* @param wrapperType The type of string wrapper.
* @returns Whether.
*/
private isStringArrayWrapperCall(
node: t.Node,
wrapperType: DecoderType
): node is t.CallExpression & {
callee: t.Identifier;
arguments: (t.NumericLiteral | t.StringLiteral)[];
} {
return (
t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
((wrapperType == DecoderType.RC4 &&
node.arguments.length == 2 &&
t.isNumericLiteral(node.arguments[0]) &&
t.isStringLiteral(node.arguments[1])) ||
(wrapperType != DecoderType.RC4 &&
(node.arguments.length == 1 || node.arguments.length == 2) &&
t.isNumericLiteral(node.arguments[0])))
);
}
/**
* Returns whether a node is an escaped string literal.
* @param node The AST node.
* @returns Whether.
*/
private isEscapedStringLiteral(node: t.Node): node is t.StringLiteral {
return (
t.isStringLiteral(node) &&
node.extra != undefined &&
typeof node.extra.rawValue == 'string' &&
typeof node.extra.raw == 'string' &&
node.extra.raw.replace(/["']/g, '') != node.extra.rawValue
);
}
}

View File

@@ -0,0 +1,61 @@
import * as t from '@babel/types';
import { TransformationKey } from './config';
export abstract class Transformation {
protected readonly ast: t.File;
private changed: boolean = false;
/**
* Creates a new transformation.
* @param ast The AST.
* @param config The transformation config.
*/
constructor(ast: t.File, config: TransformationConfig) {
this.ast = ast;
}
/**
* Executes the transformation.
* @param log The log function.
* @returns Whether changes were made.
*/
public abstract execute(log: LogFunction): boolean;
/**
* Returns whether the script has been modified.
* @returns Whether the script has been modified.
*/
protected hasChanged(): boolean {
return this.changed;
}
/**
* Marks that the script has been modified.
*/
protected setChanged(): void {
this.changed = true;
}
}
export type LogFunction = (...args: string[]) => void;
export interface TransformationConfig {
isEnabled: boolean;
[key: string]: boolean | undefined;
}
/**
* Static properties all transformations must have.
*/
export interface TransformationProperties {
key: TransformationKey;
rebuildScopeTree?: boolean;
}
/**
* Represents the transformation class type.
*/
export interface TransformationType {
new (ast: t.File, config: TransformationConfig): Transformation;
properties: TransformationProperties;
}

View File

@@ -0,0 +1,67 @@
import * as t from '@babel/types';
import traverse, { NodePath } from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import { ConstantAssignmentVariable, findConstantVariable } from '../../helpers/variable';
import { copyExpression } from '../../helpers/misc';
export class ConstantPropgator extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'constantPropagation',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
enter(path) {
// note that in general this is unsafe, should perform data flow analysis to handle params that are constants regardless of their runtime value
const variable = findConstantVariable<Literal>(path, isLiteral);
if (!variable) {
return;
}
// avoid propagating params that are assigned to within branches
if (variable instanceof ConstantAssignmentVariable) {
if (variable.binding.path.parentKey == 'params') {
const functionParent =
variable.binding.path.getStatementParent() as NodePath<t.Function>;
const parentPath = path.getStatementParent() as NodePath<t.Statement>;
if (parentPath.parent != functionParent.node.body) {
return;
}
}
}
for (const referencePath of variable.binding.referencePaths) {
const expression = copyExpression(variable.expression);
referencePath.replaceWith(expression);
self.setChanged();
}
variable.remove();
}
});
return this.hasChanged();
}
}
/**
* Type for literals which can be safely propagated.
* Excludes regular expressions due to https://github.com/ben-sb/obfuscator-io-deobfuscator/issues/38
*/
type Literal = Exclude<t.Literal, t.RegExpLiteral>;
/**
* Returns whether a node is a literal that can be safely propagated.
* @param node The node.
* @returns Whether.
*/
const isLiteral = (node: t.Node): node is Literal => {
return t.isLiteral(node) && !t.isRegExpLiteral(node);
};

View File

@@ -0,0 +1,95 @@
import * as t from '@babel/types';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
import { findConstantVariable } from '../../helpers/variable';
import traverse, { Binding } from '@babel/traverse';
export class ReassignmentRemover extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'reassignmentRemoval',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
enter(path) {
const variable = findConstantVariable<t.Identifier>(path, t.isIdentifier);
if (!variable || variable.name == variable.expression.name) {
return;
}
// check that the variable we would replace with isn't reassigned multiple times
const assignedBinding = path.scope.getBinding(variable.expression.name);
if (
assignedBinding &&
!assignedBinding.constant &&
!(
(assignedBinding.constantViolations.length == 1 &&
assignedBinding.path.isVariableDeclarator() &&
assignedBinding.path.node.init == undefined) ||
self.isExcludedConstantViolation(assignedBinding)
)
) {
return;
}
for (const referencePath of variable.binding.referencePaths) {
referencePath.replaceWith(t.identifier(variable.expression.name));
self.setChanged();
}
// remove any declarations of variable we are replacing
for (const declarationPath of [
...variable.binding.constantViolations,
variable.binding.path
]) {
if (declarationPath != path) {
declarationPath.remove();
}
}
if (
path.isStatement() ||
path.isVariableDeclarator() ||
(path.parentPath &&
(path.parentPath.isStatement() ||
(path.parentPath.isSequenceExpression() &&
path.node !=
path.parentPath.node.expressions[
path.parentPath.node.expressions.length - 1
])))
) {
path.remove();
} else {
// might have side effects, replace with RHS instead
path.replaceWith(variable.expression);
}
}
});
return this.hasChanged();
}
/**
* Checks whether a binding has a constant violation that reassigns a function from
* within (i.e. string decoder function), and thus should be treated as constant.
* @param assignedBinding The binding.
* @returns Whether.
*/
private isExcludedConstantViolation(assignedBinding: Binding) {
if (
assignedBinding.constantViolations.length == 1 &&
assignedBinding.path.isFunctionDeclaration()
) {
const functionParent = assignedBinding.constantViolations[0].getFunctionParent();
return functionParent && functionParent.node == assignedBinding.path.node;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,87 @@
import * as t from '@babel/types';
import traverse from '@babel/traverse';
import { LogFunction, Transformation, TransformationProperties } from '../transformation';
export class UnusedVariableRemover extends Transformation {
public static readonly properties: TransformationProperties = {
key: 'unusedVariableRemoval',
rebuildScopeTree: true
};
/**
* Executes the transformation.
* @param log The log function.
*/
public execute(log: LogFunction): boolean {
const self = this;
traverse(this.ast, {
Scope(path) {
for (const binding of Object.values(path.scope.bindings)) {
if (
!binding.referenced &&
binding.constantViolations.length == 0 &&
binding.path.key != 'handler' &&
!binding.path.isFunctionExpression() // don't remove named function expressions
) {
// ensure we don't remove variables that are exposed globally
if (
t.isProgram(binding.scope.block) &&
(binding.kind == 'var' || binding.kind == 'hoisted')
) {
return;
}
const paths =
binding.path.parentKey == 'params'
? [...binding.referencePaths, ...binding.constantViolations]
: [
binding.path,
...binding.referencePaths,
...binding.constantViolations
];
for (const path of paths) {
// skip any patterns declaring other variables
if (
path.isVariableDeclarator() &&
((t.isArrayPattern(path.node.id) &&
path.node.id.elements.length > 1) ||
(t.isObjectPattern(path.node.id) &&
path.node.id.properties.length > 1))
) {
continue;
}
if (
path.key == 'consequent' ||
path.key == 'alternate' ||
path.key == 'body'
) {
path.replaceWith(t.blockStatement([]));
} else {
// check if we are going to create an empty variable declaration (otherwise can sometimes trigger Babel build error)
const parentPath = path.parentPath;
if (
parentPath &&
parentPath.isVariableDeclaration() &&
parentPath.node.declarations.length == 1
) {
parentPath.remove();
} else {
path.remove();
}
}
if (paths.length > 0) {
self.setChanged();
}
}
}
}
}
});
return this.hasChanged();
}
}

18
node_modules/obfuscator-io-deobfuscator/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,18 @@
import { parse } from '@babel/parser';
import { Deobfuscator } from './deobfuscator/deobfuscator';
import { Config, defaultConfig } from './deobfuscator/transformations/config';
/**
* Deobfuscates a provided JS program.
* @param source The source code.
* @param config The deobfuscator configuration.
* @returns The deobfuscated code.
*/
export function deobfuscate(source: string, config: Config = defaultConfig): string {
const ast = parse(source);
const deobfuscator = new Deobfuscator(ast, config);
const output = deobfuscator.execute();
return output;
}

11
node_modules/obfuscator-io-deobfuscator/src/test.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import { parse } from '@babel/parser';
import fs from 'fs';
import { Deobfuscator } from './deobfuscator/deobfuscator';
const source = fs.readFileSync('input/source.js').toString();
const ast = parse(source, { sourceType: 'unambiguous' });
const deobfuscator = new Deobfuscator(ast);
const output = deobfuscator.execute();
fs.writeFileSync('output/output.js', output);

View File

@@ -0,0 +1,18 @@
import { parse } from '@babel/parser';
import { Deobfuscator } from './deobfuscator/deobfuscator';
import { Config } from './deobfuscator/transformations/config';
/**
* Entry point for the website to deobfuscate a script.
* @param source The script.
* @param config The config.
* @returns The deobfuscated code.
*/
export function deobfuscate(source: string, config: Config): string {
const ast = parse(source, { sourceType: 'unambiguous' });
const deobfuscator = new Deobfuscator(ast, config);
const output = deobfuscator.execute();
return output;
}

110
node_modules/obfuscator-io-deobfuscator/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,110 @@
{
"include": ["src/**/*"],
"exclude": ["node_modules"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016",
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "commonjs",
/* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
"declaration": true,
/* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist",
/* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true,
/* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true,
/* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true,
/* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}