-
-
Save 19h/1440858 to your computer and use it in GitHub Desktop.
Javascript Packer b64 / Hexcoding / Repack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/php | |
<?php | |
class JavaScriptPacker { | |
const IGNORE = '$1'; | |
private $_script = ''; | |
private $_encoding = 62; | |
private $_fastDecode = true; | |
private $_specialChars = false; | |
private $LITERAL_ENCODING = array('None' => 0, 'Numeric' => 10, 'Normal' => 62, 'ASCII' => 95); | |
public function __construct($_script, $_encoding = 95, $_fastDecode = true, $_specialChars = false) { | |
$this->_script = $_script . "\n"; | |
if (array_key_exists($_encoding, $this->LITERAL_ENCODING)) | |
$_encoding = $this->LITERAL_ENCODING[$_encoding]; | |
else if (array_key_exists($_encoding, array_flip($this->LITERAL_ENCODING))); | |
else | |
$_encoding = 62; | |
$this->_encoding = min((int) $_encoding, 95); | |
$this->_fastDecode = $_fastDecode; | |
$this->_specialChars = $_specialChars; | |
} | |
public function pack() { | |
$this->_addParser('_basicCompression'); | |
if ($this->_specialChars) | |
$this->_addParser('_encodeSpecialChars'); | |
if ($this->_encoding) | |
$this->_addParser('_encodeKeywords'); | |
return $this->_pack($this->_script); | |
} | |
private function _pack($script) { | |
for ($i = 0; isset($this->_parsers[$i]); $i++) { | |
$script = call_user_func(array( | |
&$this, | |
$this->_parsers[$i] | |
), $script); | |
} | |
return $script; | |
} | |
private $_parsers = array(); | |
private function _addParser($parser) { | |
$this->_parsers[] = $parser; | |
} | |
private function _basicCompression($script) { | |
$parser = new ParseMaster(); | |
$parser->escapeChar = '\\'; | |
$parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE); | |
$parser->add('/"[^"\\n\\r]*"/', self::IGNORE); | |
$parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' '); | |
$parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' '); | |
$parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); | |
$parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE); | |
if ($this->_specialChars) | |
$parser->add('/;;;[^\\n\\r]+[\\n\\r]/'); | |
$parser->add('/\\(;;\\)/', self::IGNORE); | |
$parser->add('/;+\\s*([};])/', '$2'); | |
$script = $parser->exec($script); | |
$parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3'); | |
$parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3'); | |
$parser->add('/\\s+/', ''); | |
return $parser->exec($script); | |
} | |
private function _encodeSpecialChars($script) { | |
$parser = new ParseMaster(); | |
$parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/', array( | |
'fn' => '_replace_name' | |
)); | |
$regexp = '/\\b_[A-Za-z\\d]\\w*/'; | |
$keywords = $this->_analyze($script, $regexp, '_encodePrivate'); | |
$encoded = $keywords['encoded']; | |
$parser->add($regexp, array( | |
'fn' => '_replace_encoded', | |
'data' => $encoded | |
)); | |
return $parser->exec($script); | |
} | |
private function _encodeKeywords($script) { | |
if ($this->_encoding > 62) | |
$script = $this->_escape95($script); | |
$parser = new ParseMaster(); | |
$encode = $this->_getEncoder($this->_encoding); | |
$regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/'; | |
$keywords = $this->_analyze($script, $regexp, $encode); | |
$encoded = $keywords['encoded']; | |
$parser->add($regexp, array( | |
'fn' => '_replace_encoded', | |
'data' => $encoded | |
)); | |
if (empty($script)) | |
return $script; | |
else { | |
return $this->_bootStrap($parser->exec($script), $keywords); | |
} | |
} | |
private function _analyze($script, $regexp, $encode) { | |
$all = array(); | |
preg_match_all($regexp, $script, $all); | |
$_sorted = array(); | |
$_encoded = array(); | |
$_protected = array(); | |
$all = $all[0]; | |
if (!empty($all)) { | |
$unsorted = array(); | |
$protected = array(); | |
$value = array(); | |
$this->_count = array(); | |
$i = count($all); | |
$j = 0; | |
do { | |
--$i; | |
$word = '$' . $all[$i]; | |
if (!isset($this->_count[$word])) { | |
$this->_count[$word] = 0; | |
$unsorted[$j] = $word; | |
$values[$j] = call_user_func(array( | |
&$this, | |
$encode | |
), $j); | |
$protected['$' . $values[$j]] = $j++; | |
} | |
$this->_count[$word]++; | |
} while ($i > 0); | |
$i = count($unsorted); | |
do { | |
$word = $unsorted[--$i]; | |
if (isset($protected[$word])) { | |
$_sorted[$protected[$word]] = substr($word, 1); | |
$_protected[$protected[$word]] = true; | |
$this->_count[$word] = 0; | |
} | |
} while ($i); | |
usort($unsorted, array( | |
&$this, | |
'_sortWords' | |
)); | |
$j = 0; | |
do { | |
if (!isset($_sorted[$i])) | |
$_sorted[$i] = substr($unsorted[$j++], 1); | |
$_encoded[$_sorted[$i]] = $values[$i]; | |
} while (++$i < count($unsorted)); | |
} | |
return array( | |
'sorted' => $_sorted, | |
'encoded' => $_encoded, | |
'protected' => $_protected | |
); | |
} | |
private $_count = array(); | |
private function _sortWords($match1, $match2) { | |
return $this->_count[$match2] - $this->_count[$match1]; | |
} | |
private function _bootStrap($packed, $keywords) { | |
$ENCODE = $this->_safeRegExp('$encode\\($count\\)'); | |
$packed = "'" . $this->_escape($packed) . "'"; | |
$ascii = min(count($keywords['sorted']), $this->_encoding); | |
if ($ascii == 0) | |
$ascii = 1; | |
$count = count($keywords['sorted']); | |
foreach ($keywords['protected'] as $i => $value) { | |
$keywords['sorted'][$i] = ''; | |
} | |
ksort($keywords['sorted']); | |
$keywords = "'" . implode('|', $keywords['sorted']) . "'.split('|')"; | |
$encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii); | |
$encode = $this->_getJSFunction($encode); | |
$encode = preg_replace('/_encoding/', '$ascii', $encode); | |
$encode = preg_replace('/arguments\\.callee/', '$encode', $encode); | |
$inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : ''); | |
if ($this->_fastDecode) { | |
$decode = $this->_getJSFunction('_decodeBody'); | |
if ($this->_encoding > 62) | |
$decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode); | |
elseif ($ascii < 36) | |
$decode = preg_replace($ENCODE, $inline, $decode); | |
if ($count == 0) | |
$decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1); | |
} | |
$unpack = $this->_getJSFunction('_unpack'); | |
if ($this->_fastDecode) { | |
$this->buffer = $decode; | |
$unpack = preg_replace_callback('/\\{/', array( | |
&$this, | |
'_insertFastDecode' | |
), $unpack, 1); | |
} | |
$unpack = preg_replace('/"/', "'", $unpack); | |
if ($this->_encoding > 62) { | |
$unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack); | |
} | |
if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) { | |
$this->buffer = $encode; | |
$unpack = preg_replace_callback('/\\{/', array( | |
&$this, | |
'_insertFastEncode' | |
), $unpack, 1); | |
} else { | |
$unpack = preg_replace($ENCODE, $inline, $unpack); | |
} | |
$unpackPacker = new JavaScriptPacker($unpack, 0, false, true); | |
$unpack = $unpackPacker->pack(); | |
$params = array( | |
$packed, | |
$ascii, | |
$count, | |
$keywords | |
); | |
if ($this->_fastDecode) { | |
$params[] = 0; | |
$params[] = '{}'; | |
} | |
$params = implode(',', $params); | |
return 'eval(' . $unpack . '(' . $params . "))\n"; | |
} | |
private $buffer; | |
private function _insertFastDecode($match) { | |
return '{' . $this->buffer . ';'; | |
} | |
private function _insertFastEncode($match) { | |
return '{$encode=' . $this->buffer . ';'; | |
} | |
private function _getEncoder($ascii) { | |
return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ? '_encode95' : '_encode62' : '_encode36' : '_encode10'; | |
} | |
private function _encode10($charCode) { | |
return $charCode; | |
} | |
private function _encode36($charCode) { | |
return base_convert($charCode, 10, 36); | |
} | |
private function _encode62($charCode) { | |
$res = ''; | |
if ($charCode >= $this->_encoding) { | |
$res = $this->_encode62((int) ($charCode / $this->_encoding)); | |
} | |
$charCode = $charCode % $this->_encoding; | |
if ($charCode > 35) | |
return $res . chr($charCode + 29); | |
else | |
return $res . base_convert($charCode, 10, 36); | |
} | |
private function _encode95($charCode) { | |
$res = ''; | |
if ($charCode >= $this->_encoding) | |
$res = $this->_encode95($charCode / $this->_encoding); | |
return $res . chr(($charCode % $this->_encoding) + 161); | |
} | |
private function _safeRegExp($string) { | |
return '/' . preg_replace('/\$/', '\\\$', $string) . '/'; | |
} | |
private function _encodePrivate($charCode) { | |
return "_" . $charCode; | |
} | |
private function _escape($script) { | |
return preg_replace('/([\\\\\'])/', '\\\$1', $script); | |
} | |
private function _escape95($script) { | |
return preg_replace_callback('/[\\xa1-\\xff]/', array( | |
&$this, | |
'_escape95Bis' | |
), $script); | |
} | |
private function _escape95Bis($match) { | |
return '\x' . ((string) dechex(ord($match))); | |
} | |
private function _getJSFunction($aName) { | |
if (defined('self::JSFUNCTION' . $aName)) | |
return constant('self::JSFUNCTION' . $aName); | |
else | |
return ''; | |
} | |
const JSFUNCTION_unpack = 'function($packed, $ascii, $count, $keywords, $encode, $decode) { | |
while ($count--) { | |
if ($keywords[$count]) { | |
$packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]); | |
} | |
} | |
return $packed; | |
}'; | |
const JSFUNCTION_decodeBody = ' if (!\'\'.replace(/^/, String)) { | |
// decode all the values we need | |
while ($count--) { | |
$decode[$encode($count)] = $keywords[$count] || $encode($count); | |
} | |
// global replacement function | |
$keywords = [function ($encoded) {return $decode[$encoded]}]; | |
// generic match | |
$encode = function () {return \'\\\\w+\'}; | |
// reset the loop counter - we are now doing a global replace | |
$count = 1; | |
} | |
'; | |
const JSFUNCTION_encode10 = 'function($charCode) { | |
return $charCode; | |
}'; | |
const JSFUNCTION_encode36 = 'function($charCode) { | |
return $charCode.toString(36); | |
}'; | |
const JSFUNCTION_encode62 = 'function($charCode) { | |
return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) + | |
(($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36)); | |
}'; | |
const JSFUNCTION_encode95 = 'function($charCode) { | |
return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) + | |
String.fromCharCode($charCode % _encoding + 161); | |
}'; | |
} | |
class ParseMaster { | |
public $ignoreCase = false; | |
public $escapeChar = ''; | |
const EXPRESSION = 0; | |
const REPLACEMENT = 1; | |
const LENGTH = 2; | |
private $GROUPS = '/\\(/'; | |
private $SUB_REPLACE = '/\\$\\d/'; | |
private $INDEXED = '/^\\$\\d+$/'; | |
private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/'; | |
private $ESCAPE = '/\\\./'; | |
private $QUOTE = '/\'/'; | |
private $DELETED = '/\\x01[^\\x01]*\\x01/'; | |
public function add($expression, $replacement = '') { | |
$length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string) $expression), $out); | |
if (is_string($replacement)) { | |
if (preg_match($this->SUB_REPLACE, $replacement)) { | |
if (preg_match($this->INDEXED, $replacement)) { | |
$replacement = (int) (substr($replacement, 1)) - 1; | |
} else { | |
$quote = preg_match($this->QUOTE, $this->_internalEscape($replacement)) ? '"' : "'"; | |
$replacement = array( | |
'fn' => '_backReferences', | |
'data' => array( | |
'replacement' => $replacement, | |
'length' => $length, | |
'quote' => $quote | |
) | |
); | |
} | |
} | |
} | |
if (!empty($expression)) | |
$this->_add($expression, $replacement, $length); | |
else | |
$this->_add('/^$/', $replacement, $length); | |
} | |
public function exec($string) { | |
$this->_escaped = array(); | |
$regexp = '/'; | |
foreach ($this->_patterns as $reg) { | |
$regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|'; | |
} | |
$regexp = substr($regexp, 0, -1) . '/'; | |
$regexp .= ($this->ignoreCase) ? 'i' : ''; | |
$string = $this->_escape($string, $this->escapeChar); | |
$string = preg_replace_callback($regexp, array( | |
&$this, | |
'_replacement' | |
), $string); | |
$string = $this->_unescape($string, $this->escapeChar); | |
return preg_replace($this->DELETED, '', $string); | |
} | |
public function reset() { | |
$this->_patterns = array(); | |
} | |
private $_escaped = array(); | |
private $_patterns = array(); | |
private function _add() { | |
$arguments = func_get_args(); | |
$this->_patterns[] = $arguments; | |
} | |
private function _replacement($arguments) { | |
if (empty($arguments)) | |
return ''; | |
$i = 1; | |
$j = 0; | |
while (isset($this->_patterns[$j])) { | |
$pattern = $this->_patterns[$j++]; | |
if (isset($arguments[$i]) && ($arguments[$i] != '')) { | |
$replacement = $pattern[self::REPLACEMENT]; | |
if (is_array($replacement) && isset($replacement['fn'])) { | |
if (isset($replacement['data'])) | |
$this->buffer = $replacement['data']; | |
return call_user_func(array( | |
&$this, | |
$replacement['fn'] | |
), $arguments, $i); | |
} elseif (is_int($replacement)) { | |
return $arguments[$replacement + $i]; | |
} | |
$delete = ($this->escapeChar == '' || strpos($arguments[$i], $this->escapeChar) === false) ? '' : "\x01" . $arguments[$i] . "\x01"; | |
return $delete . $replacement; | |
} else { | |
$i += $pattern[self::LENGTH]; | |
} | |
} | |
} | |
private function _backReferences($match, $offset) { | |
$replacement = $this->buffer['replacement']; | |
$quote = $this->buffer['quote']; | |
$i = $this->buffer['length']; | |
while ($i) { | |
$replacement = str_replace('$' . $i--, $match[$offset + $i], $replacement); | |
} | |
return $replacement; | |
} | |
private function _replace_name($match, $offset) { | |
$length = strlen($match[$offset + 2]); | |
$start = $length - max($length - strlen($match[$offset + 3]), 0); | |
return substr($match[$offset + 1], $start, $length) . $match[$offset + 4]; | |
} | |
private function _replace_encoded($match, $offset) { | |
return $this->buffer[$match[$offset]]; | |
} | |
private $buffer; | |
private function _escape($string, $escapeChar) { | |
if ($escapeChar) { | |
$this->buffer = $escapeChar; | |
return preg_replace_callback('/\\' . $escapeChar . '(.)' . '/', array( | |
&$this, | |
'_escapeBis' | |
), $string); | |
} else { | |
return $string; | |
} | |
} | |
private function _escapeBis($match) { | |
$this->_escaped[] = $match[1]; | |
return $this->buffer; | |
} | |
private function _unescape($string, $escapeChar) { | |
if ($escapeChar) { | |
$regexp = '/' . '\\' . $escapeChar . '/'; | |
$this->buffer = array( | |
'escapeChar' => $escapeChar, | |
'i' => 0 | |
); | |
return preg_replace_callback($regexp, array( | |
&$this, | |
'_unescapeBis' | |
), $string); | |
} else { | |
return $string; | |
} | |
} | |
private function _unescapeBis() { | |
if (isset($this->_escaped[$this->buffer['i']]) && $this->_escaped[$this->buffer['i']] != '') { | |
$temp = $this->_escaped[$this->buffer['i']]; | |
} else { | |
$temp = ''; | |
} | |
$this->buffer['i']++; | |
return $this->buffer['escapeChar'] . $temp; | |
} | |
private function _internalEscape($string) { | |
return preg_replace($this->ESCAPE, '', $string); | |
} | |
} | |
function packd($i) { for ($p = "", $x = 0; $x <= strlen($i)-1; ++$x) $p .= "\x" . array_pop(unpack("H*", $i[$x])); return $p; } | |
function prepare($i) { | |
$a = "ZXZhbCgoZnVuY3Rpb24oZCl7dmFyIGEsZSxiLGcsYyxmLGgsaixpPTAsbD0wLGs9W107aWYoZCl7ZCs9IiI7ZG8gZz0iQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLz0iLmluZGV4T2YoZC5jaGFyQXQoaSsrKSksYz0iQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLz0iLmluZGV4T2YoZC5jaGFyQXQoaSsrKSksZj0iQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLz0iLmluZGV4T2YoZC5jaGFyQXQoaSsrKSksaD0iQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLz0iLmluZGV4T2YoZC5jaGFyQXQoaSsrKSksaj1nPDwxOHxjPDwxMnxmPDw2fGgsYT1qPj4xNiYyNTUsZT1qPj44JjI1NSxiPWomMjU1LDY0PT1mP2tbbCsrXT1TdHJpbmcuZnJvbUNoYXJDb2RlKGEpOiA2ND09aD9rW2wrK109U3RyaW5nLmZyb21DaGFyQ29kZShhLGUpOmtbbCsrXT1TdHJpbmcuZnJvbUNoYXJDb2RlKGEsZSxiKTt3aGlsZShpPGQubGVuZ3RoKTthPWsuam9pbigiIik7ZT0gW107aD1mPWM9Zz1iPTA7Zm9yKGErPSIiO2I8YS5sZW5ndGg7KWM9YS5jaGFyQ29kZUF0KGIpLDEyOD5jPyhlW2crK109U3RyaW5nLmZyb21DaGFyQ29kZShjKSxiKyspOjE5MTxjJiYyMjQ+Yz8oZj1hLmNoYXJDb2RlQXQoYisxKSxlW2crK109U3RyaW5nLmZyb21DaGFyQ29kZSgoYyYzMSk8PDZ8ZiY2MyksYis9Mik6KGY9YS5jaGFyQ29kZUF0KGIrMSksaD1hLmNoYXJDb2RlQXQoYisyKSxlW2crK109U3RyaW5nLmZyb21DaGFyQ29kZSgoYyYxNSk8PDEyfChmJjYzKTw8NnxoJjYzKSxiKz0zKTthPWUuam9pbigiIil9ZWxzZSBhPWQ7cmV0dXJuIGF9KSgi"; return base64_decode($a) . packd(base64_encode($i)) . "\"));"; | |
} | |
print "\t>> "; | |
$out = ($argc == 3) ? !file_exists($argv[2]) ? $argv[2] : crc32(time()) : crc32(time())/*(!file_exists('Application')) ? 'Application' : 'Application_' . time()*/; | |
if ($argc == 1) | |
exit("Call this script with the target as argument and an output-name. If last not set, default is used. ('Application').\n"); | |
elseif ($argc > 3) | |
exit("Called with too many arguments.\n"); | |
if (!file_exists($argv[1])) | |
exit("Input file does not exist.\n"); | |
elseif ($argc == 3); | |
/*if ( !is_writable($argv[2]) ) exit("Output-file is useless and not writable.\n"); if ( !is_writable($out) ) exit("Output-file is useless and not writable.\n");*/ | |
print "\t>> Initializing.. In: {$argv[1]}, Out: {$out}.\n"; | |
print "\t>> Initializing Pakager & Packer.. "; | |
$src = $argv[1]; | |
$s = file_get_contents($src); | |
// i1 | |
//$packer = new JavaScriptPacker($s, 95, true, false); | |
//$packed = $packer->pack(); | |
//$x = $packed; | |
$packed = $s; | |
print "done.\n"; | |
print "\t>> Writing to {$out}.. "; | |
$packed = prepare($packed); | |
$packed = prepare($packed); | |
file_put_contents($out, /*"#!/usr/local/bin/node\n*/"(function(){" . $packed . "})();"); | |
print "done.\n"; | |
print "\t>> Finishing.\n"; | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment