├── .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 | Version 5 | License 6 | PRs Welcome 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 | picture 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 | --------------------------------------------------------------------------------