<?php
#
# Generic php deobfuscator. Caveat emptor.  This will verbosely


# 
# Usage:
#    sudo useradd -m unsafe
#    sudo iptables -I OUTPUT -m owner --uid-owner unsafe -j LOG
#    sudo iptables -I OUTPUT -m owner --uid-owner unsafe -j DROP
#    sudo -u unsafe -c 'php deobfuscate.php evilscript.php < /dev/null 2>&1'
#    ps uaxw|grep ^unsafe|awk '{print $2}'|sudo xargs kill -9
#
# ie. run it as another user (unsafe) and hope that it doesn't
# hit you with a privilege escalation, or something that phones
# home by some side channel.  If you don't know what that means
# then the correct usage is this:
# 
# Usage:
#    rm deobfuscate.php
# 
if (isset($_SERVER['HTTP_HOST'])) {
    
$f=file_get_contents($_SERVER["SCRIPT_FILENAME"]);
    if (isset(
$_GET['text'])) {
        
header("Content-type: text/plain");
        echo 
$f; die();
    } 
    else {
        
header("Content-type: text/html");
        echo 
highlight_string($f); die();
    }
    die();
}

function 
UN_best($text)
{
    global 
$UN_best;
    if (
strlen($text)>40) {
        if (
substr($text,0,2)=="?>") {
            
$UN_best=substr($text,2);
        }
        else {
            
$UN_best = "<?php $text";  
        }
    }
}

$UN_symboltable="";
$UN_symbols_used=array();
# '"
function UN_symbol_as_var_callback($matches)
{
    global 
$UN_symboltable, $UN_symbols_used;
    if (
array_key_exists($matches[1],$UN_symboltable)) {
        
$r = '$'.$UN_symboltable[$matches[1]];
        
$UN_symbols_used[$matches[1]]=$matches[1];
    }
    else {
        
$r=$matches[0];
    }
    return 
$r;
}

function 
UN_symbol_as_func_callback($matches)
{
    global 
$UN_symboltable, $UN_symbols_used;
    if (
array_key_exists($matches[1],$UN_symboltable)) {
        
$r = $UN_symboltable[$matches[1]].'(';
        
$UN_symbols_used[$matches[1]]=$matches[1];
    }
    else {
        
$r=$matches[0];
    }
    return 
$r;
}

function 
UN_redundantstrings_callback($matches)
{
    global 
$UN_symbols_used;
    if (
array_key_exists($matches[1],$UN_symbols_used)) {
        return 
'';
    }
    return 
$matches[0];
}

function 
UN_redundantstrings($text)
{
    global 
$UN_symboltable, $UN_symbols_used;
    
# Find assignments of strings and build table
    
$text = preg_replace_callback('/\\$([a-zA-Z_0-9]+)\s*="([^"]+)";/', 'UN_redundantstrings_callback', $text);
    return 
$text;
}
function 
UN_symbol_table_lookup($text)
{
    
# Replace globals with the thing itself
        
$text = preg_replace('/\$\{["\']GLOBALS["\']\}\[["\']([^"\']+)["\']\]/','\$$1',$text);
    
# Replace variable lookup with the string
        
$text = preg_replace('/\$\{["\']([^"\']+)["\']\}/','\$$1',$text);
    
# Find assignments of strings and build table
    
preg_match_all('/\\$([a-zA-Z_0-9]+)\s*="([^"]+)"/', $text, $li_assignments, 0); # '
    
global $UN_symboltable;
    
$UN_symboltable = array();
    for (
$li_i=0;$li_i<count($li_assignments[1]);$li_i++) {
        
$UN_symboltable[$li_assignments[1][$li_i]]=$li_assignments[2][$li_i];
    }
    
# Find string variable dereference ${$xx}
    
$text = preg_replace_callback('/\$\{\$([a-zA-Z_0-9]+)\}/','UN_symbol_as_var_callback',$text);
    
# Find string variable dereference as function $xx(
    
$text = preg_replace_callback('/\$([a-zA-Z_0-9]+)\(/','UN_symbol_as_func_callback',$text);
    return 
$text;
}
function 
UN_longidentifier($li_text)
{
    global 
$deobf_no_indent;
    if (
$deobf_no_indent) return $li_text;
    
preg_match_all('/\$([a-zA-Z_0-9]{12,100}|[oOiIl01]{6,100}|[a-zA-Z_0-9]*__[a-zA-Z0-9_]*)/', $li_text, $li_varnames, 0);
    
$li_replacements=array('Aardvark','Accelerator','Accordion','Account','Accountant','Acknowledgment','Acoustic','Acrylic','Act','Action','Active','Activity','Actor','Actress','Adapter','Addition','Address','Adjustment','Adult','Advantage','Advertisement','Advice','Afghanistan','Africa','Aftermath','Afternoon','Aftershave','Afterthought','Age','Agenda','Agreement','Air','Airbus','Airmail','Airplane','Airport','Airship','Alarm','Albatross','Alcohol','Algebra','Algeria','Alibi','Alley','Alligator','Alloy','Almanac','Alphabet','Alto','Aluminium','Aluminum','Ambulance','America','Amount','Amusement','Anatomy','Anethesiologist','Anger','Angle','Angora','Animal','Anime','Ankle','Answer','Ant','Antarctica','Anteater','Antelope','Anthony','Anthropology','Apartment','Apology','Apparatus','Apparel','Appeal','Appendix','Apple','Appliance','Approval','April','Aquarius','Arch','Archaeology','Archeology','Archer','Architecture','Area','Argentina','Argument','Aries','Arithmetic','Arm','Armadillo','Armchair','Armenian','Army','Arrow','Art','Ash','Ashtray','Asia','Asparagus','Asphalt','Asterisk','Astronomy','Athlete','Atm','Atom','Attack','Attempt','Attention','Attic','Attraction','August','Aunt','Australia','Australian','Author','Authorisation','Authority','Authorization','Avenue','Babies','Baboon','Baby','Back','Backbone','Bacon','Badge','Badger','Bag','Bagel','Bagpipe','Bail','Bait','Baker','Bakery','Balance','Balinese','Ball','Balloon','Bamboo','Banana','Band','Bandana','Bangladesh','Bangle','Banjo','Bank','Bankbook','Banker','Bar','Barbara','Barber','Barge','Baritone','Barometer','Base','Baseball','Basement','Basin','Basket','Basketball','Bass','Bassoon','Bat','Bath','Bathroom','Bathtub','Battery','Battle','Bay','Beach','Bead','Beam','Bean','Bear','Beard','Beast','Beat','Beautician','Beauty','Beaver','Bed','Bedroom','Bee','Beech','Beef','Beer','Beet','Beetle','Beggar','Beginner','Begonia','Behavior','Belgian','Belief','Believe','Bell','Belt','Bench','Bengal','Beret','Berry','Bestseller','Betty','Bibliography','Bicycle','Bike','Bill','Billboard','Biology','Biplane','Birch','Bird','Birth','Birthday','Bit','Bite','Black','Bladder','Blade','Blanket','Blinker','Blizzard','Block','Blood','Blouse','Blow','Blowgun','Blue','Board','Boat','Bobcat','Body','Bolt','Bomb','Bomber','Bone','Bongo','Bonsai','Book','Bookcase','Booklet','Boot','Border','Botany','Bottle','Bottom','Boundary','Bow','Bowl','Bowling','Box','Boy','Bra','Brace','Bracket','Brain','Brake','Branch','Brand','Brandy','Brass','Brazil','Bread','Break','Breakfast','Breath','Brian','Brick','Bridge','British','Broccoli','Brochure','Broker','Bronze','Brother','Brotherinlaw','Brow','Brown','Brush','Bubble','Bucket','Budget','Buffer','Buffet','Bugle','Building','Bulb','Bull','Bulldozer','Bumper','Bun','Burglar','Burma','Burn','Burst','Bus','Bush','Business','Butane','Butcher','Butter','Button','Buzzard');
    
shuffle($li_replacements);  # something's wrong with syntax highlighting here ... the previous line is too long ...
    
$li_frequency = array_count_values($li_varnames[0]);
    
arsort($li_frequency,SORT_NUMERIC);
    
# fwrite(STDERR, print_r($li_frequency,TRUE));
    
foreach ($li_frequency as $li_v => $li_c) {
        if (
sizeof($li_replacements)==0 || $li_c <= 1 ) continue;
        
# echo "var $varname"; print_r($varname); echo "\n";
        
$li_fruit='$'.array_shift($li_replacements);
        
$li_text = str_replace($li_v,$li_fruit,$li_text);
        
# fwrite(STDERR, ":::==$li_v=>$li_fruit::$li_text::\n");
    
}
    
# fwrite(STDERR, ":::==RES;;$li_text;;:::\n");
    
return $li_text;
}

function 
UN_base64_decode_CB($matches)
{
    return 
"'".base64_decode($matches[1])."'";
}

function 
UN_squeak($text, $decode=TRUE)
{
    
# Print out the text with irritating cslashes decoded (which might make it invalid, but hey)
    
if ($decode) {
        
$text = stripcslashes($text);
        
# Decode irritating hex encoding
        #$text = preg_replace('{\\\\x([0-9a-fA-F][0-9a-fA-F])}e','chr(hexdec("$1"))',$text);
        # Decode irritating $GLOBAL encoding, e.g ${"GLOBALS"}["iycpqfriih"] instead of $iycpqfriih
        
$text = preg_replace("/\r\n/","\n",$text);
        
$text = preg_replace("/\r/","\n",$text);
        
# Calculate irritating mathematical constant functions
        
$text = preg_replace('{\b(round\s*\([-+0-9.*/ ]+\))}e','$1',$text);
        
$text = preg_replace('{\b(round\s*\([-+0-9.*/ ]+\))}e','$1',$text);
        
$text = preg_replace('{\b(round\s*\([-+0-9.*/ ]+\))}e','$1',$text);
        
$text = preg_replace('{\'([^\']*)\'\s*\.\s*\'([^\']*)\'}','\'$1$2\'',$text);
        
$text = preg_replace('{\'([^\']*)\'\s*\.\s*\'([^\']*)\'}','\'$1$2\'',$text);
        
$text = preg_replace('{\'([^\']*)\'\s*\.\s*\'([^\']*)\'}','\'$1$2\'',$text);
        
$text = preg_replace('{\'([^\']*)\'\s*\.\s*\'([^\']*)\'}','\'$1$2\'',$text);
        
$text = preg_replace('{\'([^\']*)\'\s*\.\s*\'([^\']*)\'}','\'$1$2\'',$text);
        
$text = preg_replace_callback('/base64_decode\(\'([^\']*)\'\)/','UN_base64_decode_CB',$text);
    if (
preg_match('{/\*.*?/\*.*?/\*}',$text)) {
        
$text = preg_replace('{(/\*.*?\*/)+}',' ',$text);
    }

        
$text = UN_longidentifier($text);
        
$text = UN_symbol_table_lookup($text);
        
$text = UN_symbol_table_lookup($text);
        
$text = UN_redundantstrings($text);
    
    
# Bad indentation
        
$text = preg_replace('/([;\}] *)([$a-z])/',"\$1\n\$2",$text);
        
$text = preg_replace('/([\{] *)([$a-z])/',"\$1\n\t\$2",$text);
    }
    
fwrite(STDERR,"$text");
    return 
$text;
}

# Override die() and other functions
function UN_die($reason=''){
    
UN_squeak( "\n// -- Not dying: \"$reason\" --\n");
}
function 
UN_preg_replace() {
    
# ( $pattern, $replacement, $subject , $limit=-1 , &$count ) 
    
$UN_args=func_get_args();
    
$pattern=array_shift($UN_args); 
    
$replacement=array_shift($UN_args); 
    
$subject=array_shift($UN_args); 
    
$limit=array_shift($UN_args); 
    
$limit=$limit ? $limit : -1 ;
    
$count=array_shift($UN_args); 
    if (!
preg_match('/e[A-Za-z]*$/',$pattern)) {
        
# not an eval replacement -- just do it...
        
return preg_replace($pattern,$replacement, $subject, $limit, $count);
    }
    
$pattern = preg_replace('/e([A-Za-z]*)$/','$1',$pattern);
    
$r=preg_replace($pattern,$replacement,$subject, $limit, $count);
    
UN_squeak( "\n// -- Running code in preg_replace with /e parameter --\n");
    
$r=ev41($r,'preg_replace');
    return 
$r;
}
function 
UN_callfunc(){
    
# Call a function but print out what we did
    
$UN_args=func_get_args();
    
$UN_function=array_shift($UN_args); 
    
$UN_quiet = in_array($UN_function,array('strlen','substr','ord','chr'));
    if (
$UN_function=="create_function") {
        
$UN_args[1]=UN_verbose_code($UN_args[1],'create_function-call ',$UN_args[0]);
        
# UN_squeak( "\n// -- create_function args ".print_r($UN_args)." -- \n");
    
}
    if (!
$UN_quiet) {
        
UN_squeak( "\n// -- calling $UN_function('".implode("','",$UN_args)."'); --\n");
    }
    if (
$UN_function=="preg_replace") { $UN_function="UN_preg_replace"; }
    elseif (
$UN_function=="system") { $UN_function="UN_system"; }
    elseif (
$UN_function=="shell_exec") { $UN_function="UN_shell_exec"; }
    elseif (
$UN_function=="passthru") { $UN_function="UN_passthru"; }
    elseif (
$UN_function=="include") { $UN_function="UN_include"; }
    
$UN_result = call_user_func_array($UN_function,$UN_args);
    if (!
$UN_quiet) {
        
UN_squeak( "\n// -- function $UN_function(".sizeof($UN_args)." args) returns $UN_result --\n");
    }
    
UN_best( $UN_result );
    return 
$UN_result;
}
function 
UN_passthru($parameters){ UN_squeak( "\n// -- skipping \"passthru($parameters);\" --\n"); }
function 
UN_system($parameters){ UN_squeak( "\n// -- skipping \"system($parameters);\" --\n"); }
function 
UN_shell_exec($parameters){ UN_squeak( "\n// -- skipping \"shell_exec($parameters);\" --\n"); }
# Include by eval
function UN_include($UN_includefile){
        
UN_squeak("\n// -- include file $UN_includefile --\n");
        return 
ev41("?>".file_get_contents($UN_includefile),'include'); // <?
}

function 
UN_verbose_code($UN_evalcode,$UN_context='', $UN_localvars='')
{
    global 
$allvarnames, $obfuscation, $deobf_be_verbose;
    
$obfuscation+=1;
    if (
$obfuscation>0) {
        
UN_squeak( "\n//--- begin $obfuscation $UN_context ---\n".$UN_evalcode."\n//--- end $obfuscation ---\n\n");
    
UN_best( $UN_evalcode );
    }
    else {
        
UN_squeak( "//--- begin $obfuscation $UN_context ---\n");
        
$UN_decoded = UN_squeak( $UN_evalcode, 1);
        
UN_squeak( "//--- end $obfuscation $UN_context ---\n");
    
UN_best( $UN_decoded );
    }
    
# Make anything that looks like a variable global
    
preg_match_all ('/\$([a-zA-Z_]\w*)/', $UN_evalcode, $varnames, 0);
    foreach (
$varnames[0] as $varname) {
        
# echo "var $varname"; print_r($varname); echo "\n";
    
$allvarnames[$varname]=1;
    }
    foreach (
explode(',',$UN_localvars) as $localvars) {
    unset(
$allvarnames[trim($localvars)]);
    }
    
$export_global='';
    if (
count($allvarnames)>0) {
        
$export_global .= 'global '.implode(', ',array_keys($allvarnames)).";";
    }
    
    
#$GLOBAL['UN_evalcode']=$UN_evalcode;
    #foreach ($GLOBALS as $key => $val) { global $$key; }  /* import the global namespace .. eish */
    #$excludeList = array('GLOBALS', '_FILES', '_COOKIE', '_POST', '_GET', 'excludeList');
    #$export_global="\n".
    #    '$vars_defined=array_diff(get_defined_vars(),$excludeList);'."\n".
    #    'foreach ($vars_defined as $name_11 => $value_11) { echo "$name_11 "; $GLOBALS[$name_11]=$value_11; } '."\n";
    
global $argv1;
    
$UN_evalcode = preg_replace('/\b(include|include_once)\b/', 'UN_include', $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(require|require_once)\b(.*?);/',  'UN_include($2);', $UN_evalcode );
    
$UN_evalcode = preg_replace('/(\$\$?[a-zA-Z_][a-zA-Z0-9_]*)\s*\(\)/', 'UN_callfunc("$1")', $UN_evalcode );
    
$UN_evalcode = preg_replace('/(\$\$?[a-zA-Z_][a-zA-Z0-9_]*(\[[^]]+\]|\s+)*)\(/',   'UN_callfunc($1,', $UN_evalcode );
    
$UN_evalcode = preg_replace('/\bpreg_replace\b/',  'UN_preg_replace', $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(call_user_func)\s*\(/',  'UN_callfunc(', $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(create_function)\(/',  'UN_callfunc("$1",', $UN_evalcode );
    
#$UN_evalcode = preg_replace('/\b(base64_decode)\(/',  'UN_callfunc("$1",', $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(eval)\b/', "ev41", $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(die)\b/', "UN_die", $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(system)\b/', "UN_system", $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(passthru)\b/', "UN_passthru", $UN_evalcode );
    
$UN_evalcode = preg_replace('/\b(shell_exec)\b/', "UN_shell_exec", $UN_evalcode );
    
$UN_evalcode = preg_replace('/([^$])__FILE__\b/', '$1"'.$argv1.'"', $UN_evalcode );
    
$UN_evalcode = preg_replace('/[?]><\[?]php/', '', $UN_evalcode );
    
# $UN_evalcode = preg_replace('/;/', ";\n", $UN_evalcode ); # better layout
    
$UN_evalcode = $export_global.$UN_evalcode;
    
#foreach ($allvarnames as $varname => $blah) { 
    #    $snippet = substr($GLOBAL[substr($varname,1,999)],0,32);
    #    echo $varname."=$snippet\n";
    #}
    
return $UN_evalcode;
}

# Moo
function ev41($UN_evalcode){
    global 
$deobf_be_verbose;
    
$UN_evalcode = UN_verbose_code($UN_evalcode);
    if (
$deobf_be_verbose>0) {
        
UN_squeak( "/* This is what we're really running ... */\n<?php eval('$UN_evalcode');\n", ($deobf_be_verbose>1) );
    }
    eval(
$UN_evalcode);
}

/* If we have discovered some best, print it now */
function UN_shut_down_function() {
    global 
$UN_best;
    
$error = error_get_last();
    if (
$error['type'] == 1) {
        
fwrite(STDERR, "// Fatal error caught\n");
    if (!
strpos(@$error['file'],'eval()\'d code')) {
        
print_r($error);
    }
    }
    if (
$UN_best) {
        
fwrite(STDOUT,"$UN_best");
    }
}
// Close stdin
fclose(fopen("php://stdin","r"));

$deobf_no_indent = 0;
$deobf_be_verbose = 0;
$deobf_best = '';
$UN_best='';
register_shutdown_function('UN_shut_down_function');
foreach (
$argv as $argv1) {
    
$UN_best='';
    if (
$argv1==$argv[0]) { continue; }
    if (
$argv1=="-v") { $deobf_be_verbose++; continue; }
    if (
$argv1=="-n") { $deobf_no_indent++; continue; }
    
$obfuscation=-1;
    
UN_squeak("\n// -- file ".$argv1." --\n"); 
    
// Drop some privilege, FWIW
    
$stat = stat($argv1);
    if (
$stat != FALSE) {
        @
posix_setgid($stat['uid']);
        @
posix_setuid($stat['gid']);
    }
    
$filecode=file_get_contents($argv1);  # <?
    
$filecode="?>".$filecode; # <?
    
$allvarnames=array();
    try {
        
# eval('ev41($filecode,1);');
        
ev41($filecode,1);
    }
    catch (
Exception $e) {
        
UN_squeak('// Caught exception: '. $e->getMessage(). "\n");
    }

    
// Try to indent the best one with indent
    
if ($UN_best) {
        if (! 
$deobf_no_indent && file_exists("/usr/bin/indent")) { 
            
$in = tempnam("/tmp", "in");
            
$handle = fopen($in, "w");
            
fwrite($handle, $UN_best);
            
fclose($handle);
            
$rc = shell_exec("indent < $in 2>&1 ");
            
unlink($in);
            if (
$rc) {
        
$rc=preg_replace('/< \? php/','<?php',$rc);
                
UN_squeak("// -- Output processed by indent --\n$rc\n");
            }
        }
        
fwrite(STDOUT,"$UN_best");
        
$UN_best='';
    }
    
UN_squeak("\n// -- end of file ".$argv1." --\n\n");
}
1