├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── img
└── screenshot.png
├── index.js
├── package-lock.json
└── package.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Serverless directories
108 | .serverless/
109 |
110 | # FuseBox cache
111 | .fusebox/
112 |
113 | # DynamoDB Local files
114 | .dynamodb/
115 |
116 | # TernJS port file
117 | .tern-port
118 |
119 | # Stores VSCode versions used for testing VSCode extensions
120 | .vscode-test
121 |
122 | # yarn v2
123 | .yarn/cache
124 | .yarn/unplugged
125 | .yarn/build-state.yml
126 | .yarn/install-state.gz
127 | .pnp.*
128 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Yvain Ramora
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
JSConfuser String Decryptor
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | a simple algorithm for statically decrypting the strings of a program obfuscated with JSConfuser
11 |
12 |
13 | ## Table of contents
14 | - [About](#about)
15 | - [Pictures](#pictures)
16 | - [Installation](#installation)
17 | - [Usage](#usage)
18 | - [Contribute](#contribute)
19 | - [License](#license)
20 |
21 | ## About
22 | This project is designed to facilitate malware analysis and reverse engineering. It has been developed in JavaScript with NodeJS and has been built with love
23 |
24 | ## Pictures
25 |
26 |
27 |
28 |
29 |
30 | ## Installation
31 | * Clone the project: `git clone https://github.com/0v41n/JSConfuser-String-Decryptor.git`
32 | * Go to the project directory: `cd JSConfuser-String-Decryptor`
33 | * Installing dependencies : `npm install`
34 |
35 | ## Usage:
36 | -h Display the help menu
37 | -i Specify the input file to display decrypted strings
38 | -d Decrypt a specific string
39 | -v Enable verbose mode
40 | -l Display software licensing information
41 |
42 | ## Contribute
43 | Contributions are welcome! Follow these steps to contribute to this project:
44 | 1. Fork the project.
45 | 2. Create a branch: `git checkout -b feature/NewFeature`.
46 | 3. Make the necessary changes and commit: `git commit -am 'Add new feature'`.
47 | 4. Push to branch: `git push origin feature/NewFeature`
48 | 5. Submit a pull request.
49 |
50 | ## License
51 | This project is licensed under the MIT licence. See the [LICENSE](LICENSE) file for more details.
52 |
53 | ## Contact
54 | Yvain Ramora - yvain@mailfence.com
55 |
56 |
57 | Project created with ❤️
58 |
59 |
--------------------------------------------------------------------------------
/img/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0v41n/JSConfuser-String-Decryptor/490fa346a0e44287cafbad100a3804fd32e2c579/img/screenshot.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License
3 |
4 | Copyright (c) 2023 Yvain Ramora
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | /* importing modules */
26 | const argv = require("minimist")(process.argv.slice(2));
27 | const fs = require("fs");
28 |
29 | var help = `
30 | Usage:
31 | -h Display the help menu
32 | -i Specify the input file to display decrypted strings
33 | -d Decrypt a specific string
34 | -v Enable verbose mode
35 | -l Display software licensing information
36 | `
37 |
38 | if (argv.h) {
39 | console.log(help);
40 | } else if (argv.l) {
41 | fs.readFile('LICENSE', 'utf8', (err, data) => {
42 | if (!err) {
43 | console.log(data);
44 | } else {
45 | console.log('Error:\n The LICENSE file is missing. Please ensure you have the original, unmodified package.');
46 | }
47 | })
48 | } else if (argv.i) {
49 |
50 | /* reading the input file */
51 | if (fs.existsSync(argv.i)) {
52 | fs.readFile(argv.i, "utf8", (err, data) => {
53 | if (!err) {
54 | decryptString(data);
55 | } else {
56 | console.log("Error:\n An error has occurred while reading the input file.");
57 | }
58 | })
59 | } else {
60 | console.log("Error:\n You must provide a valid input file.");
61 | }
62 | } else if (argv.d) {
63 | decrypt(ConvertEscapeSequences(String(argv.d)), undefined);
64 | } else {
65 | console.log(help);
66 | }
67 |
68 | /* function for finding strings and decrypting them */
69 | function decryptString(data) {
70 |
71 | /* function for extracting encrypted strings in the format `= [ ... ]` */
72 | function extractCryptedStrings(data) {
73 | let matchedData = data.match(/=\s*\[.*,.*\]/i);
74 | if (!matchedData) return [];
75 |
76 | return matchedData[0]
77 | .replace(/=\s*/, '')
78 | .split(/\s*,\s*/)
79 | .filter(str => str.startsWith("'") && str.endsWith("'"))
80 | .map(str => ConvertEscapeSequences(str.slice(1, -1)));
81 | }
82 |
83 | /* function to extract strings from a specific function */
84 | function extractStringsFromFunction(data) {
85 |
86 | /* regex to capture functions returning strings of characters */
87 | let functionMatch = data.match(/function\s*[0-9A-Z$_]+\s*\(\s*\)\s*{\s*return\s*'.*'\s*}/i);
88 | if (!functionMatch) return [];
89 |
90 | let stringInsideFunction = functionMatch[0].match(/'.*'/)[0].slice(1, -1);
91 | return decompress(ConvertEscapeSequences(stringInsideFunction));
92 | }
93 |
94 | /* function to extract strings corresponding to the format '[0-9A-Z]+' */
95 | function extractFormattedStrings(data) {
96 | let matchedStrings = data.match(/'(\\u[0-F]{4}|\\x[0-F]{2}|[0-9A-Z])+'/gi);
97 | return matchedStrings ? matchedStrings.map(str => ConvertEscapeSequences(str.slice(1, -1))) : [];
98 | }
99 |
100 | /* finds encrypted strings */
101 | var functionStrings = extractStringsFromFunction(data);
102 | var formattedStrings = extractFormattedStrings(data);
103 | var cryptedStrings = [...new Set(extractCryptedStrings(data).concat(functionStrings, formattedStrings))];
104 |
105 | /* calculates the level of obfuscation */
106 | var level = levelObfuscation(data);
107 |
108 | /* checks that the obfuscation is JSConfuser */
109 | if (level != 0) {
110 |
111 | /* decrypts strings based on their signatures */
112 | cryptedStrings.forEach(str => {
113 | decrypt(str, level);
114 | })
115 | } else {
116 | console.log("Error:\n The file is not obfuscated by JSConfuser.");
117 | }
118 | }
119 |
120 | /* function for decrypting a string */
121 | function decrypt(str, level) {
122 | if (str.startsWith('<~') && str.endsWith('~>')) {
123 | /*
124 | Let S be the sequence of characters in the string str
125 | For each quintuplet of characters Si, Si+1, Si+2, Si+3, Si+4, (where the indices vary in steps of 5), we define:
126 | C = 52200625 * (Si - 33) + 614125 * (Si+1 - 33) + 7225 * (Si+2 - 33) + 85 * (Si+3 - 33) + (Si+4 - 33)
127 | where Si represents the ASCII code of the i-th character in the S sequence.
128 | The following values are extracted from C:
129 | a = 255 & (C ≫ 24)
130 | b = 255 & (C ≫ 16)
131 | c = 255 & (C ≫ 8)
132 | d = 255 & (C)
133 | Each quadruplet a, b, c, d is added to the plaintext sequence where:
134 | ≫ is the right shift operation, & is the bitwise logical "AND" operation.
135 | */
136 | var plaintext = [];
137 | str = str.slice(2, -2).replace(/s/g, '').replace('z', '!!!!!');
138 | var oldStr = str;
139 | str += "uuuuu".slice(str.length % 5);
140 | for (let i = 0; i < str.length; i += 5) {
141 | var C = 52200625 * (str.charCodeAt(i) - 33) + 614125 * (str.charCodeAt(i + 1) - 33) + 7225 * (str.charCodeAt(i + 2) - 33) + 85 * (str.charCodeAt(i + 3) - 33) + (str.charCodeAt(i + 4) - 33);
142 | plaintext.push(255 & C >> 24, 255 & C >> 16, 255 & C >> 8, 255 & C);
143 | }
144 | plaintext = String.fromCharCode(...plaintext).slice(0, oldStr.length - str.length);
145 | printResult('<~' + oldStr + '~>', plaintext);
146 | } else if (str.startsWith('{') && str.endsWith('}')) {
147 | /*
148 | let {S1, S2, ..., S2n} be the sequence of numbers extracted from the string str
149 | For each pair S2i-1, S2i (where 1 ≤ i ≤ n) we define:
150 | x = S2i-1
151 | y = S2i
152 | The decryption process is given by:
153 | Pi = x ≫ 8 * (y & 7) & 255
154 | where ≫ is the right shift operation, & is the logical "AND" operation (bit by bit) and Pi is the i-th element of the plaintext sequence.
155 | The process continues for each pair until all the bits of y have been used, because after each iteration, y is shifted three positions to the right.
156 | */
157 | var plaintext = [];
158 | var oldStr = str;
159 | str = str.slice(1, -1).split(',').map(Number);
160 | for (let i = 0; i < str.length; i += 2) {
161 | var [x, y] = [str[i], str[i + 1]];
162 | while (y) {
163 | plaintext.push(x >> 8 * (y & 7) & 255);
164 | y >>= 3;
165 | }
166 | }
167 | plaintext = String.fromCharCode(...plaintext).replace(/~/g, '');
168 | printResult('{' + str + '}', plaintext);
169 | } else {
170 | var plaintext = [];
171 | if (level == 1) {
172 | plaintext = base91Decode(str)
173 | } else {
174 | /*
175 | Let S be the sequence of characters in the string str
176 | For each character Si (where the indices vary from 0 to length(S) - 1):
177 | Zi = Si - 33
178 | Update x and y as follows:
179 | Xi+1 = Xi + 5
180 | Yi+1 = (Yi ≪ 5) | Zi
181 | where ≪ is the left shift operation and ∣ is the logical "OR" operation.
182 | If, after the update, Xi+1 is equal to or greater than 8:
183 | Xi+1 = Xi - 8
184 | The decryption process is given by:
185 | Pi = ((Yi ≪ 5) | Zi) ≫ Xi+1 & 255
186 | where ≫ is the right shift operation, & is the logical "AND" operation (bit by bit) and Pi is the i-th element of the plaintext sequence.
187 | The process continues for each character.
188 | */
189 | var x = y = 0;
190 | for (let i = 0; i < str.length; i++) {
191 | var z = str.charCodeAt(i) - 33;
192 | x += 5;
193 | if (x >= 8) {
194 | x -= 8;
195 | plaintext.push((y << 5 | z) >> x & 255);
196 | }
197 | y = y << 5 | z;
198 | }
199 | }
200 | plaintext = String.fromCharCode(...plaintext);
201 | if(level == undefined) {
202 | printResult(str, plaintext);
203 | printResult(str, String.fromCharCode(...base91Decode(str)));
204 | } else {
205 | if (str.length > 2 && plaintext.length > 2) {
206 | if (entropy(plaintext) < entropy(str)) {
207 | printResult(str, plaintext);
208 | } else {
209 | printResult(entropy(plaintext) < 50 ? plaintext : null, str);
210 | }
211 | }
212 | }
213 | }
214 | }
215 |
216 | /* function to display the results */
217 | function printResult(str, plaintext) {
218 | if (argv.v) {
219 | if (str == null) {
220 | console.log(`
221 | \x1b[90m${'_'.repeat(20)}\x1b[0m
222 | \x1b[90mplaintext : \x1b[32m'${plaintext}'\x1b[0m
223 | \x1b[90mentropy : \x1b[33m${entropy(plaintext)}\x1b[0m
224 | \x1b[90m${'_'.repeat(20)}\x1b[0m\n`)
225 | } else {
226 | console.log(`
227 | \x1b[90m${'_'.repeat(20)}\x1b[0m
228 | \x1b[90mciphertext : \x1b[32m'${str}'\x1b[0m
229 | \x1b[90mentropy : \x1b[33m${entropy(str)}\x1b[0m
230 | \x1b[90mplaintext : \x1b[32m'${plaintext}'\x1b[0m
231 | \x1b[90mentropy : \x1b[33m${entropy(plaintext)}\x1b[0m
232 | \x1b[90m${'_'.repeat(20)}\x1b[0m\n`)
233 | }
234 | } else {
235 | console.log(plaintext)
236 | }
237 | }
238 |
239 | /* function for converts Unicode and Hexadecimal escape sequences into characters */
240 | function ConvertEscapeSequences(str) {
241 | var newStr = "";
242 | var chars = str.match(/(\\x[0-F]{2}|\\u[0-F]{4}|.)/gi);
243 | if (chars) {
244 | chars.forEach(char => newStr += char.length != 1 ? String.fromCharCode(parseInt(char.match(/[0-F]+/i)[0], 16)) : char);
245 | } else {
246 | newStr = str;
247 | }
248 | return newStr;
249 | }
250 |
251 | /* function for decompressing JSConfuser strings */
252 | function decompress(str) {
253 | var firstChar1 = firstChar2 = str[0];
254 | var byteMax = oldByte = 256;
255 | var uncompressedChars = [firstChar1];
256 | var decompressTable = {};
257 | for (let i = 1; i < str.length; i++) {
258 | var char = str.charCodeAt(i);
259 | if (char < byteMax) {
260 | char = str[i];
261 | } else {
262 | char = decompressTable[char] ? decompressTable[char] : firstChar1 + firstChar2;
263 | }
264 | uncompressedChars.push(char);
265 | firstChar2 = char.charAt(0);
266 | decompressTable[oldByte] = firstChar1 + firstChar2;
267 | firstChar1 = char;
268 | oldByte++;
269 | }
270 | return uncompressedChars.join('').split('1');
271 | }
272 |
273 | /* function for decoding base91 */
274 | function base91Decode(str) {
275 | var plaintext = [];
276 | var base91Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~"';
277 | var currentCharIndex = -1;
278 | var accumulator = 0;
279 | var shiftCount = 0;
280 |
281 | for (let i = 0; i < str.length; i++) {
282 | const charIndex = base91Chars.indexOf(str[i]);
283 |
284 | if (currentCharIndex < 0) {
285 | currentCharIndex = charIndex;
286 | } else {
287 | currentCharIndex += charIndex * base91Chars.length;
288 | accumulator |= currentCharIndex << shiftCount;
289 | shiftCount += (currentCharIndex & 8191) > 88 ? 13 : 14;
290 | while (shiftCount > 7) {
291 | plaintext.push(accumulator & 255);
292 | accumulator >>= 8;
293 | shiftCount -= 8;
294 | }
295 | currentCharIndex = -1;
296 | }
297 | }
298 | if (currentCharIndex > -1) {
299 | plaintext.push((accumulator | currentCharIndex << shiftCount) & 255);
300 | }
301 | return plaintext;
302 | }
303 |
304 | /* function for calculating the level of obfuscation */
305 | function levelObfuscation(data) {
306 | let functionMatch = data.match(/function\s*[0-9A-Z$_]+\s*\(\s*[0-9A-Z$_]+\s*,\s*[0-9A-Z$_]+\s*,\s*[0-9A-Z$_]+\s*,\s*[0-9A-Z$_]+\s*=\s*[0-9A-Z$_]+\s*,\s*[0-9A-Z$_]+\s*=\s*[0-9A-Z$_]+\s*\)/gi)
307 | if (!functionMatch) return 0;
308 | return functionMatch.length
309 | }
310 |
311 | /* function for calculating the entropy of a character string */
312 | function entropy(str) {
313 | /*
314 | Let S be a string of length n.
315 | Let Si be the ith character of S and ord(Si) the ASCII/Unicode value of Si.
316 | The 'distance' di between two adjacent characters Si and Si+1 is given by :
317 | di = |ord(Si) - ord(Si+1)|
318 | The total distance d is the sum of the individual distances:
319 | d = Σᵢ₌₁ⁿ-¹di
320 | The entropy E (as we have defined it) is the average of the distances:
321 | E = D / (n-1)
322 | */
323 | str = String(str)
324 | if (str.length < 2) return 0;
325 | let totalDistance = 0;
326 | for (let i = 0; i < str.length - 1; i++) {
327 | let distance = Math.abs(str.charCodeAt(i) - str.charCodeAt(i + 1));
328 | totalDistance += distance;
329 | }
330 | let averageDistance = totalDistance / (str.length - 1);
331 | return averageDistance;
332 | }
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsconfuser-string-decryptor",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "jsconfuser-string-decryptor",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "fs": "^0.0.1-security",
13 | "minimist": "^1.2.8"
14 | }
15 | },
16 | "node_modules/fs": {
17 | "version": "0.0.1-security",
18 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
19 | "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
20 | },
21 | "node_modules/minimist": {
22 | "version": "1.2.8",
23 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
24 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
25 | "funding": {
26 | "url": "https://github.com/sponsors/ljharb"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsconfuser-string-decryptor",
3 | "version": "1.0.0",
4 | "description": "a simple algorithm for statically decrypting the strings of a program obfuscated with JSConfuser",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/0v41n/JSConfuser-String-Decryptor.git"
12 | },
13 | "author": "Yvain Ramora",
14 | "license": "ISC",
15 | "bugs": {
16 | "url": "https://github.com/0v41n/JSConfuser-String-Decryptor/issues"
17 | },
18 | "homepage": "https://github.com/0v41n/JSConfuser-String-Decryptor#readme",
19 | "dependencies": {
20 | "fs": "^0.0.1-security",
21 | "minimist": "^1.2.8"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------