备份: 完整开发状态(含反混淆脚本和临时文件)
This commit is contained in:
127
node_modules/obfuscator-io-deobfuscator/.prettierignore
generated
vendored
Normal file
127
node_modules/obfuscator-io-deobfuscator/.prettierignore
generated
vendored
Normal 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/**
|
||||
9
node_modules/obfuscator-io-deobfuscator/.prettierrc.json
generated
vendored
Normal file
9
node_modules/obfuscator-io-deobfuscator/.prettierrc.json
generated
vendored
Normal 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
201
node_modules/obfuscator-io-deobfuscator/LICENSE
generated
vendored
Normal 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
28
node_modules/obfuscator-io-deobfuscator/README.md
generated
vendored
Normal 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
|
||||
22
node_modules/obfuscator-io-deobfuscator/node_modules/commander/LICENSE
generated
vendored
Normal file
22
node_modules/obfuscator-io-deobfuscator/node_modules/commander/LICENSE
generated
vendored
Normal 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.
|
||||
1157
node_modules/obfuscator-io-deobfuscator/node_modules/commander/Readme.md
generated
vendored
Normal file
1157
node_modules/obfuscator-io-deobfuscator/node_modules/commander/Readme.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
node_modules/obfuscator-io-deobfuscator/node_modules/commander/esm.mjs
generated
vendored
Normal file
16
node_modules/obfuscator-io-deobfuscator/node_modules/commander/esm.mjs
generated
vendored
Normal 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;
|
||||
24
node_modules/obfuscator-io-deobfuscator/node_modules/commander/index.js
generated
vendored
Normal file
24
node_modules/obfuscator-io-deobfuscator/node_modules/commander/index.js
generated
vendored
Normal 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
|
||||
149
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/argument.js
generated
vendored
Normal file
149
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/argument.js
generated
vendored
Normal 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;
|
||||
2509
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/command.js
generated
vendored
Normal file
2509
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/command.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
39
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/error.js
generated
vendored
Normal file
39
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/error.js
generated
vendored
Normal 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;
|
||||
520
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/help.js
generated
vendored
Normal file
520
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/help.js
generated
vendored
Normal 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;
|
||||
330
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/option.js
generated
vendored
Normal file
330
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/option.js
generated
vendored
Normal 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;
|
||||
101
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/suggestSimilar.js
generated
vendored
Normal file
101
node_modules/obfuscator-io-deobfuscator/node_modules/commander/lib/suggestSimilar.js
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
const maxDistance = 3;
|
||||
|
||||
function editDistance(a, b) {
|
||||
// https://en.wikipedia.org/wiki/Damerau–Levenshtein_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;
|
||||
16
node_modules/obfuscator-io-deobfuscator/node_modules/commander/package-support.json
generated
vendored
Normal file
16
node_modules/obfuscator-io-deobfuscator/node_modules/commander/package-support.json
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "*",
|
||||
"target": {
|
||||
"node": "supported"
|
||||
},
|
||||
"response": {
|
||||
"type": "time-permitting"
|
||||
},
|
||||
"backing": {
|
||||
"npm-funding": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
84
node_modules/obfuscator-io-deobfuscator/node_modules/commander/package.json
generated
vendored
Normal file
84
node_modules/obfuscator-io-deobfuscator/node_modules/commander/package.json
generated
vendored
Normal 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
|
||||
}
|
||||
3
node_modules/obfuscator-io-deobfuscator/node_modules/commander/typings/esm.d.mts
generated
vendored
Normal file
3
node_modules/obfuscator-io-deobfuscator/node_modules/commander/typings/esm.d.mts
generated
vendored
Normal 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';
|
||||
969
node_modules/obfuscator-io-deobfuscator/node_modules/commander/typings/index.d.ts
generated
vendored
Normal file
969
node_modules/obfuscator-io-deobfuscator/node_modules/commander/typings/index.d.ts
generated
vendored
Normal 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
33
node_modules/obfuscator-io-deobfuscator/package.json
generated
vendored
Normal 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
30
node_modules/obfuscator-io-deobfuscator/src/cli.ts
generated
vendored
Normal 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();
|
||||
118
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/deobfuscator.ts
generated
vendored
Normal file
118
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/deobfuscator.ts
generated
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
64
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/declaration.ts
generated
vendored
Normal file
64
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/declaration.ts
generated
vendored
Normal 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))
|
||||
);
|
||||
}
|
||||
12
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/expression.ts
generated
vendored
Normal file
12
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/expression.ts
generated
vendored
Normal 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);
|
||||
}
|
||||
32
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/misc.ts
generated
vendored
Normal file
32
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/misc.ts
generated
vendored
Normal 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;
|
||||
};
|
||||
54
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/base64StringDecoder.ts
generated
vendored
Normal file
54
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/base64StringDecoder.ts
generated
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
27
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/basicStringDecoder.ts
generated
vendored
Normal file
27
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/basicStringDecoder.ts
generated
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
86
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/rc4StringDecoder.ts
generated
vendored
Normal file
86
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/rc4StringDecoder.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
39
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/stringDecoder.ts
generated
vendored
Normal file
39
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/decoders/stringDecoder.ts
generated
vendored
Normal 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'
|
||||
}
|
||||
258
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/rotation/rotation.ts
generated
vendored
Normal file
258
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/rotation/rotation.ts
generated
vendored
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
29
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/util/util.ts
generated
vendored
Normal file
29
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/strings/util/util.ts
generated
vendored
Normal 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);
|
||||
}
|
||||
192
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/variable.ts
generated
vendored
Normal file
192
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/helpers/variable.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
326
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/antiTamper/antiTamperRemover.ts
generated
vendored
Normal file
326
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/antiTamper/antiTamperRemover.ts
generated
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
64
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/config.ts
generated
vendored
Normal file
64
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/config.ts
generated
vendored
Normal 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
|
||||
}
|
||||
};
|
||||
190
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/controlFlow/controlFlowRecoverer.ts
generated
vendored
Normal file
190
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/controlFlow/controlFlowRecoverer.ts
generated
vendored
Normal 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)
|
||||
);
|
||||
};
|
||||
92
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/controlFlow/deadBranchRemover.ts
generated
vendored
Normal file
92
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/controlFlow/deadBranchRemover.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
171
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/controlFlow/sequenceSplitter.ts
generated
vendored
Normal file
171
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/controlFlow/sequenceSplitter.ts
generated
vendored
Normal 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';
|
||||
}
|
||||
}
|
||||
314
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/expressions/expressionSimplifier.ts
generated
vendored
Normal file
314
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/expressions/expressionSimplifier.ts
generated
vendored
Normal 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 =
|
||||
| '=='
|
||||
| '!='
|
||||
| '==='
|
||||
| '!=='
|
||||
| '<'
|
||||
| '<='
|
||||
| '>'
|
||||
| '>='
|
||||
| '<<'
|
||||
| '>>'
|
||||
| '>>>'
|
||||
| '+'
|
||||
| '-'
|
||||
| '*'
|
||||
| '/'
|
||||
| '%'
|
||||
| '**'
|
||||
| '|'
|
||||
| '^'
|
||||
| '&';
|
||||
186
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/objects/objectPacker.ts
generated
vendored
Normal file
186
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/objects/objectPacker.ts
generated
vendored
Normal 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;
|
||||
};
|
||||
81
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/objects/objectSimplifier.ts
generated
vendored
Normal file
81
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/objects/objectSimplifier.ts
generated
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
153
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/objects/proxyObject.ts
generated
vendored
Normal file
153
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/objects/proxyObject.ts
generated
vendored
Normal 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))
|
||||
);
|
||||
}
|
||||
}
|
||||
51
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/properties/propertySimplifier.ts
generated
vendored
Normal file
51
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/properties/propertySimplifier.ts
generated
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
159
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/proxyFunctions/proxyFunction.ts
generated
vendored
Normal file
159
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/proxyFunctions/proxyFunction.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/proxyFunctions/proxyFunctionInliner.ts
generated
vendored
Normal file
47
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/proxyFunctions/proxyFunctionInliner.ts
generated
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
630
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/strings/stringRevealer.ts
generated
vendored
Normal file
630
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/strings/stringRevealer.ts
generated
vendored
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
61
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/transformation.ts
generated
vendored
Normal file
61
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/transformation.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
67
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/variables/constantPropagator.ts
generated
vendored
Normal file
67
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/variables/constantPropagator.ts
generated
vendored
Normal 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);
|
||||
};
|
||||
95
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/variables/reassignmentRemover.ts
generated
vendored
Normal file
95
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/variables/reassignmentRemover.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/variables/unusedVariableRemover.ts
generated
vendored
Normal file
87
node_modules/obfuscator-io-deobfuscator/src/deobfuscator/transformations/variables/unusedVariableRemover.ts
generated
vendored
Normal 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
18
node_modules/obfuscator-io-deobfuscator/src/index.ts
generated
vendored
Normal 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
11
node_modules/obfuscator-io-deobfuscator/src/test.ts
generated
vendored
Normal 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);
|
||||
18
node_modules/obfuscator-io-deobfuscator/src/webpackEntry.ts
generated
vendored
Normal file
18
node_modules/obfuscator-io-deobfuscator/src/webpackEntry.ts
generated
vendored
Normal 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
110
node_modules/obfuscator-io-deobfuscator/tsconfig.json
generated
vendored
Normal 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. */
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user