├── .gitignore ├── LICENSE ├── README.md ├── bin └── jsobfuscate ├── gruntfile.js ├── jsObfuscate.js ├── package.json ├── tasks └── jsObfuscate.js └── test ├── bind.js ├── fail.js └── reduce.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/output 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cai Guanhao (Choi Goon-ho) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | js-obfuscator 2 | ============= 3 | 4 | Obfuscate JavaScript files via [javascriptobfuscator.com]( 5 | http://www.javascriptobfuscator.com/). 6 | 7 | **IMPORTANT**: It is possible that your code can be viewed and/or stored by 8 | javascriptobfuscator.com. Please DO NOT include any sensitive data. If you 9 | care about your code, split and uglify them before obfuscating. 10 | 11 | --- 12 | 13 | You can install it globally as a command: 14 | 15 | ``` 16 | npm install -g js-obfuscator 17 | echo "var fs = require('fs')" | jsobfuscate -o keepIndentations=false 18 | var _0x40c7=["\x66\x73"];var fs=require(_0x40c7[0]); 19 | ``` 20 | 21 | Or install it as a dependency: 22 | 23 | ``` 24 | npm install js-obfuscator --save 25 | var jsObfuscator = require('js-obfuscator'); 26 | jsObfuscator ( input [, options ] ) 27 | Returns: a Q promise. 28 | ``` 29 | 30 | Or you can use it with Grunt `~0.4.0`: 31 | 32 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out 33 | the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains 34 | how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as 35 | install and use Grunt plugins. Once you're familiar with that process, you may 36 | install this plugin with this command: 37 | 38 | ```shell 39 | npm install js-obfuscator --save-dev 40 | ``` 41 | 42 | Once the plugin has been installed, it may be enabled inside your Gruntfile 43 | with this line of JavaScript: 44 | 45 | ```js 46 | grunt.loadNpmTasks('js-obfuscator'); 47 | ``` 48 | 49 | ## Options 50 | 51 | ### keepLinefeeds 52 | Type: `Boolean` Default: `false` 53 | 54 | ### keepIndentations 55 | Type: `Boolean` Default: `false` 56 | 57 | ### encodeStrings 58 | Type: `Boolean` Default: `true` 59 | 60 | ### encodeNumbers 61 | Type: `Boolean` Default: `true` 62 | 63 | ### moveStrings 64 | Type: `Boolean` Default: `true` 65 | 66 | ### replaceNames 67 | Type: `Boolean` Default: `true` 68 | 69 | ### variableExclusions 70 | Type: `Array` Default: `[ '^_get_', '^_set_', '^_mtd_' ]` 71 | 72 | ### concurrency (for Grunt plugin only) 73 | Type: `Number` Default: `2` Range: `1 - 99` 74 | 75 | ## Command 76 | 77 | ``` 78 | $ jsobfuscate -h 79 | Usage: jsobfuscate [OPTIONS] [FILES] 80 | 81 | Obfuscate JavaScript files via javascriptobfuscator.com. 82 | Read from STDIN if no files specified. 83 | 84 | Default Options: 85 | -o keepLinefeeds=false 86 | -o keepIndentations=false 87 | -o encodeStrings=true 88 | -o encodeNumbers=true 89 | -o moveStrings=true 90 | -o replaceNames=true 91 | -o variableExclusions="['^_get_', '^_set_', '^_mtd_']" 92 | ``` 93 | 94 | ## Examples 95 | 96 | ### Grunt 97 | 98 | ```js 99 | grunt.initConfig({ 100 | jsObfuscate: { 101 | test: { 102 | options: { 103 | concurrency: 2, 104 | keepLinefeeds: false, 105 | keepIndentations: false, 106 | encodeStrings: true, 107 | encodeNumbers: true, 108 | moveStrings: true, 109 | replaceNames: true, 110 | variableExclusions: [ '^_get_', '^_set_', '^_mtd_' ] 111 | }, 112 | files: { 113 | 'dest/dest.js': [ 114 | 'src/src1.js', 115 | 'src/src2.js' 116 | ] 117 | } 118 | } 119 | } 120 | }); 121 | ``` 122 | 123 | ### Call 124 | 125 | ```js 126 | var jsObfuscator = require('js-obfuscator'); 127 | 128 | var script = 'Array.prototype.diff = function(a) {' + 129 | ' return this.filter(function(i) {return a.indexOf(i) === -1;});' + 130 | '};'; 131 | 132 | var options = { 133 | keepLinefeeds: true, 134 | keepIndentations: true, 135 | encodeStrings: true, 136 | encodeNumbers: true, 137 | moveStrings: true, 138 | replaceNames: true, 139 | variableExclusions: [ '^_get_', '^_set_', '^_mtd_' ] 140 | }; 141 | 142 | jsObfuscator(script, options).then(function(obfuscated) { 143 | console.log(obfuscated); 144 | }, function(err) { 145 | console.error(err); 146 | }); 147 | 148 | /* 149 | var _0xa3c9=["\x64\x69\x66\x66","\x70\x72\x6F\x74\x6F\x74\x79\x70\x65", 150 | "\x69\x6E\x64\x65\x78\x4F\x66","\x66\x69\x6C\x74\x65\x72"] 151 | Array[_0xa3c9[1]][_0xa3c9[0]]=function (_0x4068x1) 152 | { 153 | return this[_0xa3c9[3]](function (_0x4068x2) 154 | { 155 | return _0x4068x1[_0xa3c9[2]](_0x4068x2)===-1; 156 | } 157 | ); 158 | } 159 | ; 160 | */ 161 | ``` 162 | 163 | ## Developer 164 | 165 | * caiguanhao <caiguanhao@gmail.com> 166 | -------------------------------------------------------------------------------- /bin/jsobfuscate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var Q = require('q'); 4 | var fs = require('fs'); 5 | var charSpinner = require('char-spinner') 6 | var jsObfuscate = require('../jsObfuscate'); 7 | var $0 = require('path').basename(process.argv[1]); 8 | var args = process.argv.slice(2); 9 | var options = jsObfuscate.defaultOptions; 10 | 11 | for (var i = args.length - 1; i >= 0; i--) { 12 | switch (args[i]) { 13 | case '-h': 14 | case '--help': 15 | var l = console.log; 16 | l('Usage: ' + $0 + ' [OPTIONS] [FILES]'); 17 | l(); 18 | l('Obfuscate JavaScript files via javascriptobfuscator.com.'); 19 | l('Read from STDIN if no files specified.'); 20 | l(); 21 | l('Default Options:'); 22 | l(' -o keepLinefeeds=false'); 23 | l(' -o keepIndentations=false'); 24 | l(' -o encodeStrings=true'); 25 | l(' -o encodeNumbers=true'); 26 | l(' -o moveStrings=true'); 27 | l(' -o replaceNames=true'); 28 | l(' -o variableExclusions="[\'^_get_\', \'^_set_\', \'^_mtd_\']"'); 29 | l(); 30 | l('Notice: It is possible that your code can be viewed and/or stored by'); 31 | l('javascriptobfuscator.com. Please DO NOT include any sensitive data.'); 32 | process.exit(0); 33 | case '-o': 34 | var option = args[i + 1]; 35 | if (!option) throw 'option not provided'; 36 | if (option.indexOf('=') === -1) throw 'option should be "key=value"'; 37 | option = option.split('=', 2); 38 | var key = option[0], value; 39 | var type = Object.prototype.toString.call(options[key]); 40 | if (!options.hasOwnProperty(key)) throw 'unknown option "' + key + '"'; 41 | try { 42 | value = new Function('return ' + option[1] + ';')(); 43 | if (Object.prototype.toString.call(value) !== type) throw ''; 44 | } catch (e) { 45 | throw 'option "' + key + '" should be of type `' + type + '`'; 46 | } 47 | options[key] = value; 48 | args.splice(i, 2); 49 | break; 50 | default: 51 | if (args[i][0] === '-') throw 'unknown option "' + args[i] + '"'; 52 | } 53 | } 54 | 55 | var files = args; 56 | var content = ''; 57 | var promise; 58 | 59 | if (files.length === 0) { 60 | promise = Q.fcall(function() { 61 | var deferred = Q.defer(); 62 | process.stdin.setEncoding('utf8'); 63 | process.stdin.on('readable', function() { 64 | var chunk; 65 | while ((chunk = process.stdin.read()) !== null) { 66 | content += chunk; 67 | } 68 | }); 69 | process.stdin.on('end', function() { 70 | deferred.resolve(content); 71 | }); 72 | return deferred.promise; 73 | }); 74 | if (process.stdin.isTTY) 75 | console.error('Type or paste JavaScript code and then press Ctrl-D to ' + 76 | 'obfuscate.'); 77 | } else { 78 | promise = Q(files.map(function(src) { 79 | return fs.readFileSync(src).toString().replace(/^\#\!.*/, ''); 80 | }).join('\n;\n')); 81 | } 82 | 83 | promise.then(function(content) { 84 | try { 85 | process.stdout.clearLine(); // clear possible ^D char 86 | charSpinner(); 87 | } catch(e) {} 88 | return jsObfuscate(content, options); 89 | }).then(console.log).catch(console.error); 90 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | clean: { 5 | output: 'test/output' 6 | }, 7 | jsObfuscate: { 8 | test: { 9 | options: { 10 | concurrency: 2, 11 | keepLinefeeds: false, 12 | keepIndentations: false, 13 | encodeStrings: true, 14 | encodeNumbers: true, 15 | moveStrings: true, 16 | replaceNames: true, 17 | variableExclusions: [ 18 | '^_get_', 19 | '^_set_', 20 | '^_mtd_' 21 | ] 22 | }, 23 | files: { 24 | 'test/output/test.js': [ 25 | 'test/bind.js', 26 | 'test/reduce.js' 27 | ] 28 | } 29 | }, 30 | fail: { 31 | files: { 32 | 'test/output/fail.js': 'test/fail.js' 33 | } 34 | }, 35 | bin: { 36 | files: { 37 | 'test/output/bin.js': 'bin/jsobfuscate' 38 | } 39 | } 40 | } 41 | }); 42 | 43 | grunt.loadTasks('tasks'); 44 | 45 | grunt.loadNpmTasks('grunt-contrib-clean'); 46 | 47 | grunt.registerTask('default', [ 48 | 'clean', 49 | 'jsObfuscate' 50 | ]); 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /jsObfuscate.js: -------------------------------------------------------------------------------- 1 | var Q = require('q'); 2 | var https = require('https'); 3 | var entities = require('entities'); 4 | var querystring = require('querystring'); 5 | var htmlparser = require('htmlparser2'); 6 | 7 | function request(method, postData, reqHeaders) { 8 | var deferred = Q.defer(); 9 | reqHeaders = reqHeaders || {}; 10 | var headers = { 11 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit' + 12 | '/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36' 13 | }; 14 | for (var h in reqHeaders) { 15 | headers[h] = reqHeaders[h]; 16 | } 17 | var req = https.request({ 18 | host: 'www.javascriptobfuscator.com', 19 | port: 443, 20 | path: '/Javascript-Obfuscator.aspx', 21 | method: method || 'GET', 22 | headers: headers 23 | }, function(res) { 24 | res.setEncoding('utf8'); 25 | var body = ''; 26 | res.on('data', function(chunk) { 27 | body += chunk; 28 | }); 29 | res.on('end', function() { 30 | deferred.resolve({ 31 | headers: res.headers, 32 | body: body 33 | }); 34 | }); 35 | }); 36 | req.on('error', function (err) { 37 | deferred.reject(err); 38 | }); 39 | if (postData) { 40 | req.write(querystring.stringify(postData)); 41 | } 42 | req.end(); 43 | return deferred.promise; 44 | } 45 | 46 | function getFormData(content) { 47 | var keys = [ 48 | '__EVENTTARGET', 49 | '__EVENTARGUMENT', 50 | '__VIEWSTATE', 51 | '__EVENTVALIDATION', 52 | 'uploader1', 53 | 'Button1' 54 | ]; 55 | var formData = {}; 56 | var deferred = Q.defer(); 57 | var parser = new htmlparser.Parser({ 58 | onopentag: function(name, attribs) { 59 | if (name === 'input') { 60 | if (keys.indexOf(attribs.name) > -1) { 61 | formData[attribs.name] = attribs.value || ''; 62 | } 63 | } 64 | }, 65 | onend: function(tagname) { 66 | deferred.resolve(formData); 67 | } 68 | }); 69 | parser.write(content); 70 | parser.end(); 71 | return deferred.promise; 72 | } 73 | 74 | function getResult(content) { 75 | var getText; 76 | var deferred = Q.defer(); 77 | var parser = new htmlparser.Parser({ 78 | onopentag: function(name, attribs) { 79 | if (name === 'textarea' && attribs.name === 'ctl00$MainContent$TextBox2') { 80 | getText = true; 81 | } 82 | }, 83 | ontext: function(text) { 84 | if (getText === true) { 85 | getText = text; 86 | } 87 | }, 88 | onend: function(tagname) { 89 | deferred.resolve(getText); 90 | } 91 | }); 92 | parser.write(content); 93 | parser.end(); 94 | return deferred.promise; 95 | } 96 | 97 | function sanitizeOptions(options) { 98 | var defaults = { 99 | keepLinefeeds: false, 100 | keepIndentations: false, 101 | encodeStrings: true, 102 | encodeNumbers: true, 103 | moveStrings: true, 104 | replaceNames: true, 105 | variableExclusions: [ '^_get_', '^_set_', '^_mtd_' ] 106 | }; 107 | var toStr = Object.prototype.toString; 108 | if (toStr.call(options) !== '[object Object]') return defaults; 109 | for (var def in defaults) { 110 | if (toStr.call(defaults[def]) !== toStr.call(options[def])) continue; 111 | defaults[def] = options[def]; 112 | } 113 | return defaults; 114 | } 115 | 116 | function obfuscate(strInput, options) { 117 | options = sanitizeOptions(options); 118 | return request(). 119 | then(function(res) { 120 | var headers = {}; 121 | if (res.headers['set-cookie']) { 122 | var cookie = res.headers['set-cookie'][0]; 123 | var cookiedata = cookie.slice(0, cookie.indexOf(';')); 124 | headers['Cookie'] = cookiedata; 125 | } 126 | headers['Content-Type'] = 'application/x-www-form-urlencoded'; 127 | return getFormData(res.body).then(function(formData) { 128 | return { 129 | formData: formData, 130 | headers: headers 131 | }; 132 | }); 133 | }). 134 | then(function(bundle) { 135 | if (options.keepLinefeeds) bundle.formData['ctl00$MainContent$cbLineBR'] = 'on'; 136 | if (options.keepIndentations) bundle.formData['ctl00$MainContent$cbIndent'] = 'on'; 137 | if (options.encodeStrings) bundle.formData['ctl00$MainContent$cbEncodeStr'] = 'on'; 138 | if (options.encodeNumbers) bundle.formData['ctl00$MainContent$cbEncodeNumber'] = 'on'; 139 | if (options.moveStrings) bundle.formData['ctl00$MainContent$cbMoveStr'] = 'on'; 140 | if (options.replaceNames) bundle.formData['ctl00$MainContent$cbReplaceNames'] = 'on'; 141 | 142 | bundle.formData['UploadLib_Uploader_js'] = '1'; 143 | bundle.formData['__EVENTTARGET'] = 'ctl00$MainContent$Button1'; 144 | bundle.formData['ctl00$MainContent$TextBox1'] = strInput; 145 | bundle.formData['ctl00$MainContent$TextBox2'] = ''; 146 | bundle.formData['ctl00$MainContent$TextBox3'] = options.variableExclusions.join('\n'); 147 | 148 | return bundle; 149 | }). 150 | then(function(bundle) { 151 | return request('POST', bundle.formData, bundle.headers); 152 | }). 153 | then(function(res) { 154 | return getResult(res.body); 155 | }). 156 | then(function(result) { 157 | result = entities.decodeHTML(result).trim(); 158 | if (result.length === 0) { 159 | throw 'Empty result.'; 160 | } 161 | if (result.slice(0, 33) === 'JScriptCodeDom.CodeParseException') { 162 | throw result; 163 | } 164 | return result; 165 | }); 166 | } 167 | 168 | module.exports = obfuscate; 169 | module.exports.defaultOptions = sanitizeOptions(); 170 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-obfuscator", 3 | "version": "0.1.4", 4 | "description": "Obfuscate JavaScript files via javascriptobfuscator.com. This is also a Grunt plugin.", 5 | "main": "jsObfuscate.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/caiguanhao/js-obfuscator.git" 9 | }, 10 | "bin": { 11 | "jsobfuscate": "bin/jsobfuscate" 12 | }, 13 | "keywords": [ 14 | "JavaScript", 15 | "obfuscate", 16 | "obfuscator", 17 | "javascriptobfuscator", 18 | "grunt", 19 | "gruntplugin", 20 | "grunt-plugin" 21 | ], 22 | "author": "Cai Guanhao (Choi Goon-ho)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/caiguanhao/js-obfuscator/issues" 26 | }, 27 | "homepage": "https://github.com/caiguanhao/js-obfuscator", 28 | "dependencies": { 29 | "async": "^0.9.0", 30 | "char-spinner": "^1.0.1", 31 | "entities": "^1.1.1", 32 | "htmlparser2": "^3.7.2", 33 | "q": "^1.0.1" 34 | }, 35 | "devDependencies": { 36 | "grunt": "^0.4.5", 37 | "grunt-contrib-clean": "^0.5.0" 38 | }, 39 | "files": [ 40 | "bin", 41 | "tasks", 42 | "jsObfuscate.js", 43 | "README.md", 44 | "LICENSE" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /tasks/jsObfuscate.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var jsObfuscate = require('../jsObfuscate'); 3 | 4 | module.exports = function(grunt) { 5 | 6 | grunt.registerMultiTask( 7 | 'jsObfuscate', 8 | 'Obfuscate JavaScript files via javascriptobfuscator.com.', 9 | function() { 10 | 11 | var options = this.options(); 12 | var cc = options.concurrency; 13 | cc = /^[0-9]+$/.test(cc) && (cc > 0 && cc < 100) ? cc : 2; 14 | 15 | var queue = async.queue(function(task, callback) { 16 | var content = task.src.map(function(src) { 17 | return grunt.file.read(src).replace(/^\#\!.*/, ''); 18 | }).join('\n;\n'); 19 | jsObfuscate(content, options). 20 | then(function(obfuscated) { 21 | grunt.file.write(task.dest, obfuscated); 22 | }). 23 | then(callback). 24 | catch(callback); 25 | }, cc); 26 | 27 | queue.drain = this.async(); 28 | 29 | var files = this.files; 30 | 31 | for (var i = 0; i < files.length; i++) { 32 | queue.push(files[i], (function(current) { 33 | return function(err) { 34 | if (err) { 35 | var src = current.src.join(', '); 36 | console.error(('Fatal error occurred when processing ' + src + 37 | ':').red); 38 | grunt.fail.fatal(err); 39 | } 40 | grunt.log.ok('Obfuscated: ' + current.dest.cyan); 41 | }; 42 | })(files[i])); 43 | } 44 | 45 | }); 46 | 47 | }; 48 | -------------------------------------------------------------------------------- /test/bind.js: -------------------------------------------------------------------------------- 1 | if (!Function.prototype.bind) { 2 | Function.prototype.bind = function (oThis) { 3 | if (typeof this !== "function") { 4 | // closest thing possible to the ECMAScript 5 5 | // internal IsCallable function 6 | throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 7 | } 8 | 9 | var aArgs = Array.prototype.slice.call(arguments, 1), 10 | fToBind = this, 11 | fNOP = function () {}, 12 | fBound = function () { 13 | return fToBind.apply(this instanceof fNOP && oThis 14 | ? this 15 | : oThis, 16 | aArgs.concat(Array.prototype.slice.call(arguments))); 17 | }; 18 | 19 | fNOP.prototype = this.prototype; 20 | fBound.prototype = new fNOP(); 21 | 22 | return fBound; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /test/fail.js: -------------------------------------------------------------------------------- 1 | throw new TypeError("JScriptCodeDom.CodeParseException: 2 | String have line break , Line 1, Char 20"); 3 | -------------------------------------------------------------------------------- /test/reduce.js: -------------------------------------------------------------------------------- 1 | if ( 'function' !== typeof Array.prototype.reduce ) { 2 | Array.prototype.reduce = function( callback /*, initialValue*/ ) { 3 | 'use strict'; 4 | if ( null === this || 'undefined' === typeof this ) { 5 | throw new TypeError( 6 | 'Array.prototype.reduce called on null or undefined' ); 7 | } 8 | if ( 'function' !== typeof callback ) { 9 | throw new TypeError( callback + ' is not a function' ); 10 | } 11 | var t = Object( this ), len = t.length >>> 0, k = 0, value; 12 | if ( arguments.length >= 2 ) { 13 | value = arguments[1]; 14 | } else { 15 | while ( k < len && ! k in t ) k++; 16 | if ( k >= len ) 17 | throw new TypeError('Reduce of empty array with no initial value'); 18 | value = t[ k++ ]; 19 | } 20 | for ( ; k < len ; k++ ) { 21 | if ( k in t ) { 22 | value = callback( value, t[k], k, t ); 23 | } 24 | } 25 | return value; 26 | }; 27 | } 28 | --------------------------------------------------------------------------------