From 647e118579b6ee1fb7a0578058234d8d05721b6d Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Tue, 7 May 2019 17:19:05 +0200 Subject: [PATCH 1/4] Added BitwiseXOR resolution, works for simple variables containing 'string ^ string' ; changed debug output, now it prints to STDERR TODO: handle goto statement --- RevPHPNodeVisitor.php | 59 +++++++++++++++++++++++++++++++++++++++++-- SymbolTable.php | 4 +-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/RevPHPNodeVisitor.php b/RevPHPNodeVisitor.php index 858976c..04c9839 100644 --- a/RevPHPNodeVisitor.php +++ b/RevPHPNodeVisitor.php @@ -171,6 +171,12 @@ public function findValue(PhpParser\Node $node, &$value) { } else { } } else + if ($node instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor) { + if ($this->xorStrings($node, $value)) { + $found_value = TRUE; + } else { + } + } else if ($node instanceof PhpParser\Node\Expr\Variable) { $variable_value = null; if ($node->name instanceof PhpParser\Node) @@ -276,6 +282,10 @@ public function findName(PhpParser\Node $node, &$varname) { if ($this->concatStrings($node, $varname)) $found_string = TRUE; } else + if ($node instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor) { + if ($this->xorStrings($node, $varname)) + $found_string = TRUE; + } else if ($node instanceof PhpParser\Node\Scalar\String_) { $varname = $node->value; # XXX - ??? $found_string = TRUE; @@ -317,7 +327,7 @@ public function findName(PhpParser\Node $node, &$varname) { $varname = $node->name; $found_string = TRUE; } else { - debug_print_backtrace(); + ob_start(); debug_print_backtrace(); fwrite(STDERR, rtrim(ob_get_clean() ?: "failed to dump backtrace") . PHP_EOL); } } else if ($node instanceof PhpParser\Node\Expr\List_) { @@ -334,7 +344,7 @@ public function findName(PhpParser\Node $node, &$varname) { # Something dreadfully wrong. This is pretty much the # very last thing it could be. fwrite(STDERR, "Problem in findName(), no name, line {$node->getLine()}: ".print_r($node, TRUE)."\n"); - debug_print_backtrace(); + ob_start(); debug_print_backtrace(); fwrite(STDERR, rtrim(ob_get_clean() ?: "failed to dump backtrace") . PHP_EOL); } } #fwrite(STDERR, "leave findName, {$node->getType()}, name: \"$varname\"\n"); @@ -393,6 +403,26 @@ public function concatStrings(PhpParser\Node\Expr\BinaryOp\Concat $concat, &$con return $found_string; } + public function xorStrings(PhpParser\Node\Expr\BinaryOp\BitwiseXor $xorstring, &$xoredstring) { + $found_string = FALSE; + if ($this->findValue($xorstring->left, $leftstring) + && $this->findValue($xorstring->right, $rightstring)) { + $xoredstring = $leftstring ^ $rightstring; + $found_string = TRUE; + } else { + # Replace left or right nodes with statically-determinable values, if possible + if ($this->findValue($xorstring->left, $leftvalue)) { + if (is_string($leftvalue)) + $xorstring->left = $this->newNodeAsType($leftvalue, false); + } + if ($this->findValue($xorstring->right, $rightvalue)) { + if (is_string($rightvalue)) + $xorstring->right = $this->newNodeAsType($rightvalue, false); + } + } + return $found_string; + } + public function enterNode(PhpParser\Node $node) { if ($node instanceof PhpParser\Node\Stmt\Function_) { $this->symtbl->pushScope(); @@ -826,6 +856,31 @@ public function leaveNode(PhpParser\Node $node) { $node->left = $this->newNodeAsType($leftvalue, false); } } else + if ($node instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor) { + # Roll up any substrings. This can be computationally inefficient. + $left_is_string = false; + if ($this->findValue($node->left, $leftvalue)) { + if (is_string($leftvalue)) + $left_is_string = true; + } + if ($this->findValue($node->right, $rightvalue)) { + if (is_string($rightvalue)) { + if ($node->left instanceof PhpParser\Node\Expr\BinaryOp\BitwiseXor) { + if ($this->findValue($node->left->right, $othervalue)) { + if (is_string($othervalue)) { + $node->right = $this->newNodeAsType($othervalue ^ $rightvalue); + $node->left = $node->left->left; + } + } + } else { + $node->right = $this->newNodeAsType($rightvalue, false); + } + } + } else { + if ($left_is_string) + $node->left = $this->newNodeAsType($leftvalue, false); + } + } else if ($node instanceof PhpParser\Node\Stmt\Function_) { $this->substituteFunctionName($node); $this->symtbl->popScope(); diff --git a/SymbolTable.php b/SymbolTable.php index c4f31da..b6d592a 100644 --- a/SymbolTable.php +++ b/SymbolTable.php @@ -115,11 +115,11 @@ public function isSymbol($candidate_string) { public function globalSymbolValue($name, &$value) { $found_value = FALSE; if (is_object($name)) { - debug_print_backtrace(); + ob_start(); debug_print_backtrace(); fwrite(STDERR, rtrim(ob_get_clean() ?: "failed to dump backtrace") . PHP_EOL); } #fwrite(STDERR, "+++++\nglobalSymbolValue: ".print_r($name, TRUE)."\n-------\n"); if (!is_string($name) && !is_int($name)) { - debug_print_backtrace(); + ob_start(); debug_print_backtrace(); fwrite(STDERR, rtrim(ob_get_clean() ?: "failed to dump backtrace") . PHP_EOL); fwrite(STDERR, "Bad array name in globalSymbolValue:\n"); fwrite(STDERR, print_r($name, true)); } From 5e151677a9ee00dfd0a36ecc697c1ad32d06cc73 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 19 Jun 2026 12:53:34 +0200 Subject: [PATCH 2/4] Added gitignore for composer.lock and vendor/ --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1502b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +composer.lock From 2a259931ab860d7dae29d6eb0976ff223b1391df Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 19 Jun 2026 12:55:07 +0200 Subject: [PATCH 3/4] add bzdecompress() to functions to run --- RevPHPNodeVisitor.php | 1 + 1 file changed, 1 insertion(+) diff --git a/RevPHPNodeVisitor.php b/RevPHPNodeVisitor.php index 04c9839..fa1a5e2 100644 --- a/RevPHPNodeVisitor.php +++ b/RevPHPNodeVisitor.php @@ -43,6 +43,7 @@ public function __construct( 'base64_decode', 'gzuncompress', 'strrev', 'gzinflate', 'str_rot13', 'urldecode', 'chr', 'ord', 'strtolower', 'strtoupper', 'base64_encode', 'range', 'implode', + 'bzdecompress', ); foreach ($special_functions as $fname) { $this->decode_func_names[] = strtolower($fname); From 994663379c4433a929d2dc35555e65ebfbb6216e Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 19 Jun 2026 14:42:42 +0200 Subject: [PATCH 4/4] Added project definitions to composer.json, including restrict to php7.4, noted into Readme, typo fix: Factory --- README.md | 2 ++ composer.json | 16 ++++++++++++++++ revphp | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b6b76b..f01f793 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# This Project is tied to nikic/php-parser version 2.0 series, and so tied to PHP 7.4 + # An aid to de-obfuscating PHP malware Lots of malware that afflicts WordPress, Joomla and other PHP-based diff --git a/composer.json b/composer.json index 0cf212e..bd7f5ed 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,21 @@ { + "name": "gnanet/reverse-php-malware", + "description": "De-obfuscate and reverse engineer PHP malware, originalproject: https://github.com/bediger4000/reverse-php-malware", + "homepage": "https://github.com/gnanet/reverse-php-malware", + "type": "project", + "authors": [ + { + "name": "Bruce Ediger", + "email": "bediger8@gmail.com" + }, + { + "name": "GNa", + "email": "gna@r-us.hu" + } + ], + "minimum-stability": "stable", "require": { + "php": "^7.4", "nikic/php-parser": "^2.0" } } diff --git a/revphp b/revphp index 1e13be6..57b9996 100755 --- a/revphp +++ b/revphp @@ -6,7 +6,7 @@ include('SymbolTable.php'); include('RevPHPNodeVisitor.php'); use PhpParser\NodeTraverser; -use PhpParser\ParserFactor; +use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; $remove_comments = true;