Le problème consiste à retrouver une adresse mail @sstic.fr cachée dans le fichier à analyser. STAGE 1 On nous propose un fichier '.img' comme point d'entrée. La commande "file" nous indique qu'il s'agit d'une image FAT. Un fichier "inject.bin" se trouve à la racine. Une inspection rapide du filesystem n'indiquant pas la présence de fichiers cachés, on en déduit que le fichier à attaquer est celui là. "inject.bin" est le nom des payloads des rubberducky, une rapide recherche sur le net nous montre un logiciel, "ducky-decode", comportant un script Perl(oh joy!) permettant de décoder ce fichier. On se rend compte qu'il s'agit la d'une suite de touches créant un script powershell ouvrant un fichier encodé en base 64. le script suivant permet de récupérer le fichier produit à partir de la sortie de duky-decode.pl #!/usr/bin/perl use MIME::Base64; {local $/="ENTER"; @file=<>;} shift @file; shift @file; for $t (@file) { $t=~/.*SPACE$(.*?)ENTER$/ms; $tmp=$1=~s/(\n| )//rg; chomp $tmp; $tmp=decode_base64($tmp)=~s/(.)./$1/gr; $tmp=~/FromBase64String\('(.*?)'\)/; print decode_base64($1); } Le fichier produit se nomme, stage2.zip STAGE 2 il s'agit là de retrouver une clef de chiffrement AES dans un fichier .pk3 un unzip dessus fait apparaître un memo, il faut alors utiliser un logiciel, openarena par exemple pour se balader dans la map. Des tableaux contenant des morceaux de clefs sont disséminés un peu partout dans la carte. le "God Mode" d'openarena montre aussi une salle cachée dans laquelle se trouve la clef. Le fichier déchiffré est alors un ZIP. STAGE 3 On obtient un pcap nommé paint.cap et un memo.txt indiquant que la clef est dans Paint. L'utilisation de wireshark sur le fichier pcap montre une conversation entre un device unique et un hôte. A l'établissement de la session sur le hub 3.0 un device s'identifie comme étant un device 0x310c, c'est à dire une souris avec molette. Il s'en suit une conversation entre la souris et l'hôte. En inférant l'utilisation de Paint, il suffit de dessiner des lignes en suivant les positions successives de la souris. La description faite des trames sur http://wiki.osdev.org/Universal_Serial_Bus, indique que les quatre derniers octets contiennent respectivement : une valeur non nulle en cas d'appui sur un bouton, puis les déplacements relatifs (sur 7 bits) de la souris sur les deux axes et une valeur que nous n'utiliserons pas. Le script suivant ouvre ainsi la capture et affiche le résultat. #!/usr/bin/perl use Net::Pcap; use Tk; $top = MainWindow->new(); $canvas = $top->Canvas(-width => 1300, -height => 1300)->pack(); my $err; my $x=50.,$y=50.; my $pcap = pcap_open_offline("paint.cap",\$err); pcap_next($pcap, \%header) for(1..6); while($packet=pcap_next($pcap, \%header)) { @bytes=unpack "c*",$packet; $canvas->create('line',$x,$y,$x+$bytes[-3],$y+$bytes[-2]) if($draw && $bytes[-4]); $draw=$bytes[-4]; $x+=$bytes[-3]; $y+=$bytes[-2]; pcap_next($pcap, \%header); } MainLoop(); Après on récupère une clef de chiffrement comme étant le hashé blake256 de la phrase "The quick brown fox jumps over the lobster dog". STAGE 4 Après déchiffrement le fichier s'avère être une archive zip contenant une page web contenant un code javascript rendu obscure (JSencode?). Une variable nommée data semble être une donnée. Un point d'arrêt placé au début du script permet de debugguer. On se rend compte que le script tout entier est une chaîne de caractères évaluée. L'utilisation de la console javascript permet d'obtenir cette chaîne, en affichant, e.g. par un document.writeln, celle ci une fois évaluée. La chaîne de caractères est alors très modérément obscure, et quelques substitutions de chaînes de caractères permettent d'en découvrir le sens. La variables user agent est utilisée comme clef de chiffrement AES128 CBC. A partir de là, la lecture du code source de la page dans firebug fait apparaître un lien cache faisant référence à chrome://browser/content/preferences/preferences.xul. Ce fichier permettant d'ouvrir les préférences lorsqu'on utilise le navigateur firefox. Un téléchargement de tous les numéros de versions possibles de firefox ainsi qu'une consultation de la manière dont sont fabriquées les variables User-Agent, nous permettent d'envisager une attaque par brute force puisque seules quelques milliers de possibilités sont envisageables. Le script suivant les essayes toutes et essaye aussi certaines issues de versions non-officielles #!/usr/bin/perl -l use Crypt::OpenSSL::AES; use Crypt::CBC; use threads; use Thread::Queue; use threads::shared; $"=""; {open $f,"data.txt"; local $/; $data=<$f>;} $bindata=pack 'H*', $data; open $plop,'>plop'; print $plop $bindata; close $plop; my $q = Thread::Queue->new(); our $cpt :shared=0; sub workpayload { while(defined(my $all=$q->dequeue())) { print $all; $all=~/\((.{16})/; my $iv=$1; $iv=~s/./pack "C",ord($&)/eg; $all=~/(.{16})\)/; my $key=$1; $key=~s/./pack "C",ord($&)/eg; my $cipher = Crypt::CBC->new( -literal_key =>1, -keysize => 16, -iv => $iv, -key => $key, -cipher => "Crypt::OpenSSL::AES", -header => "none" ); my $decrypted=$cipher->decrypt($bindata); print substr($decrypted,0,8);; if (substr($decrypted,0,8)=~/PK/) { $all=~/\((.{16})/; $iv=($1=~s/ /_/gr); $all=~/(.{16})\)/; $key=($1=~s/ /_/gr); open my $out,">$iv_$key.zip"; print $out $decrypted; close $out; print `tput setaf 1`,"$key $iv\n",`tput sgr0` } print "$cpt $all @tmp\n" unless ($cpt++ % 100); } } push @tabthread,threads->create('workpayload') for(1..1); $|++; @security=("", "U; ","I; "); @wintok = ( "Windows NT 6.3", "Windows NT 6.2", "Windows NT 6.1", "Windows NT 6.0", "Windows NT 5.2", "Windows NT 5.1", "Windows NT 5.01", "Windows NT 5.0", "Windows NT 4.0", "Windows 98; Win 9x 4.90", "Windows 98", "Windows 95", "Windows CE"); @feature_tokens = ("",".NET CLR; ", "SV1; ", "Tablet PC; ", "Win64; IA64; ", "Win64; x64; ", "WOW64; "); @lintok=( "Linux i686", "Linux i586", "Linux i386", "Linux x86_64", "Linux amd64", "Linux i686 on x86_64", "Maemo; Linux armv7l", "Android; Mobile", "Android; Tablet", "OpenBSD i586", "OpenBSD amd64", "OpenBSD i686"); $Mactok="Macintosh; "; push @archmac,"Intel Mac OS X 10.$_" for(5..11); #push @archmac,"PPC Mac OS X 10.$_" for(4..15); #Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:35.0) Gecko/20100101 Firefox/24.0 @revision=( "rv:34.0.5", "rv:34.0", "rv:34.0b1", "rv:34.0b2", "rv:34.0b3", "rv:34.0b4", "rv:34.0b5", "rv:34.0b6", "rv:34.0b7", "rv:34.0b8", "rv:34.0b9", "rv:34.0b10", "rv:34.0b11", "rv:35.0.1-funnelcake32", "rv:35.0.1-funnelcake33", "rv:35.0.1-funnelcake34", "rv:35.0.1-funnelcake35", "rv:35.0.1-funnelcake36", "rv:35.0.1", "rv:35.0", "rv:35.0b1", "rv:35.0b2", "rv:35.0b3", "rv:35.0b4", "rv:35.0b5", "rv:35.0b6", "rv:35.0b8", "rv:36.0.1", "rv:36.0.3", "rv:36.0.4", "rv:36.0", "rv:36.0b1", "rv:36.0b2", "rv:36.0b3", "rv:36.0b4", "rv:36.0b5", "rv:36.0b6", "rv:36.0b7", "rv:36.0b8", "rv:36.0b9", "rv:36.0b10", "rv:37.0.1", "rv:37.0", "rv:37.0b1", "rv:37.0b2", "rv:37.0b3", "rv:37.0b4", "rv:37.0b5", "rv:37.0b6", "rv:37.0b7", "rv:38.0b1", "rv:38.0b2", "rv:38.0b3"); for $arc (@archmac) { for $rt (@revision) { my $all="Mozilla/5.0 ($Mactok$arc; $rt) Gecko/18.1 Firefox/18.1"; $q->enqueue($all); } } for $pt (@lintok) { for $rt (@revision) { my $all="Mozilla/5.0 ($pt; $rt) Gecko/18.1 Firefox/18.1"; $q->enqueue($all); } } for $pt (@wintok) { for $ft (@feature_tokens) { for $rt (@revision) { my $all="Mozilla/5.0 ($pt; $ft$rt) Gecko/18.1 Firefox/18.1"; $q->enqueue($all); } } } #while(<>) # { # chomp; # #$_.="x"x32; # $q->enqueue($_); # } $q->end(); $_->join() for(@tabthread); STAGE 5 Ce stage est le plus difficile. On se trouve face à un fichier binaire, nommé input.bin, et à un pdf décrivant un schéma d'interconnection entre des éléments de calculs. Le message présent dans le pdf indique "I love ST20 achitecture" ce qui laisse à penser que le code serait un programme ST20. St20disasm nous donne un listing cohérent quand on lui donne en entrée le fichier input.bin. Le fichier "input.bin" commence par un octet qui est utilisé pour fournir la taille en octets du code qui suit. Le programme est executé sur le transputer0. Le code est composé de deux unités sémantique différentes : -une partie de bootstrap qui va essentiellement distribuer leur code aux transputer1,2,3 -une partie execution qui est en fait une boucle ou on lit un octet de donnée est ou on rend un résultat calculé à partir de celui ci. Les transputers 1,2 et 3 sont utilisés pour fournir un flot d'octets. Une analyse rapide des différents segments de codes envoyés nous montre une grosse zone de donné dans le fichier input.bin, et les chaîne de caractères, laissée dans le code binaire indiquent "Decrypt"! La donné est donc vraissemblablement chiffrée et le code un système de décryptage de celle-ci. Le déchiffrement effectué par le transputeur 0 se code en Perl comme suit : ---------------- while(1) { #Lecture de l'octet suivant à dechiffrer read $mint[4],$var1,1; #On envoi la clef de chiffrement actuelle au transputer 1,2 et 3 print $mint[1] @key; print $mint[2] @key; print $mint[3] @key; #on lit ce qu'ils nous rendent #mint[5]/mint[6]/mint[7] sont les canaux d'entrée correspondant #aux transputer 1,2,3 respectivement read $mint[5],$c1,1: read $mint[6],$c2,1: read $mint[7],$c3,1: #Calcul du déchiffrement $c1=$c1^$c2^$c3; $res=$var1^($compt+2*$key[$compt]); $key[$compt]=$c1; $compt=($compt+1)%12; #retour de la donnée dechiffrée print $mint $res; } ---------------- Ce qui apparait alors dans cet algorithme de chiffrement est le fait que les premiers octets du fichier à chiffrer sont chiffrés uniquement avec la clef ! Cette clef est de longueur 96 bits donc trop pour tenter une attaque en force brute en temps court. Mais comme le nom fichier à produire est "congratulations.tar.bz2" on peut s'appuyer sur l'entête de ce fichier pour inférer des octets de clefs : -le fichier commence par BZh -il y a un chiffre, très probablement 9, -puis 0x314159265359 Ce qui fait 9 octets connus de texte clair. A partir de là, on peut tenter d'inverser le déchiffrement pour ces 9 octets. Si on occulte la mise à jour de la clef, le déchiffrement de l'octet numéro i se réduit à : clair=chiffré^(i+2*clef[i]) ce qui nous permet d'écrire le code suivant pour connaître des preimages des octets de clef : #!/usr/bin/perl use Archive::Zip; open data,"data.bin"; read data,$data,12; close data; @data=unpack 'C*',$data; @header=unpack 'C*', pack ('H*',"425A6839314159265359"); for my $i (0..11) { printf "%2c ",(($header[$i]^$data[$i])-$i)>>1&0xff } print "\n"; for $i (0..11) { printf "%.2x ",((($header[$i]^$data[$i])-$i)>>1)&0xff } print "\n"; On note maintenant qu'il reste deux possibilités pour chaques octet produit : la valeur entre 0 et 127 et celle entre 128 et 255. Il reste ainsi 2^9*9*256*256=301 989 888 possibilités pour la clef. Les codes perl simulant les transputeurs s'executant en quelques secondes il n'est pas raisonable d'envisager une attaque en force avec eux. La majeure partie du temps de calcul étant passée dans les communications entre les transputeurs (implementées comme des pipes nommés) j'ai ré-écrit le code en C en remplaçant là où celà était possible les communications redondantes par des variables d'état. Le temps calcul se trouve divisé par 100 mais reste encore trop important. A ce moment là l'entête BZIP peut encore être mise à contribution : il y a deux bitmaps pour indiquer les intervalles présent/absent et les symboles utilisés dans l'algorithme de Huffman utilisé dans la compression BZIP2. Il y a fort à parier que ces bitmaps sont presque tous pleins et donc afin de filtrer les clefs invalides de stopper le déchiffrement dès lors que moins de 50% des bits correspondant à ces bitmaps sont positionnés. A ce moment là, la vitesse d'exécution est multipliée par 1000 et le bruteforce devient tout à fait envisageable. On trouve le fichier clair en quelques minutes. Voici le code utilisé : #include #include struct { unsigned char key[12]; unsigned char trans4state; unsigned char trans5state; unsigned short trans6state1, trans6state3; unsigned char trans8state4, trans8state5[48]; unsigned char trans10state2, trans10state4[48]; unsigned char trans11_12state12[12]; int i; } context; inline unsigned char trans4() { int i; for(i=0;i<12;i++) { context.trans4state=context.key[i]+context.trans4state; } return context.trans4state; } inline unsigned char trans5() { int i; for(i=0;i<12;i++) { context.trans5state=context.key[i]^context.trans5state; } return context.trans5state; } inline unsigned char trans6() { unsigned a,b,c,d,p; if(context.trans6state3==0) { int i; for(i=0;i<12;i++) context.trans6state1=context.key[i]+context.trans6state1; context.trans6state3=1; } a = (context.trans6state1 & 0x8000) >> 0xF; b = (context.trans6state1 & 0x4000) >> 0xE; c = (a ^ b) & 0xFFFF; d = (context.trans6state1 << 1) & 0xFFFF; context.trans6state1 = (c ^ d) & 0xFFFF; p = context.trans6state1 & 0xFF; return p; } inline unsigned char trans1() { return trans4() ^ trans5() ^ trans6(); } inline unsigned char trans7() { unsigned char x=0,y=0; int i; for(i=0;i<6;i++) { x+=context.key[i]; y+=context.key[i+6]; } return x^y; } inline unsigned char trans8() { int i,j; unsigned char p,x; for(i=0;i<12;i++) context.trans8state5[context.trans8state4*12+i]=context.key[i]; context.trans8state4=(context.trans8state4+1)%4; p=0; for(j=0;j<4;j++) { x=0; for(i=0;i<12;i++) x=context.trans8state5[12*j+i]+x; p^=x; } return p; } inline unsigned char trans9() { unsigned char x = 0; int i; for(i=0;i<12;i++) x = (x ^ (context.key[i] << (i & 0x7))) & 0xFF; return x; } inline unsigned char trans2() { unsigned char a=trans7(); unsigned char b=trans8(); unsigned char c=trans9(); return a^b^c; } inline unsigned char trans10() { int i,j; unsigned char p,x; for(i=0;i<12;i++) context.trans10state4[context.trans10state2*12+i]=context.key[i]; context.trans10state2=(context.trans10state2+1)&3; x=0; for(i=0;i<4;i++) x=(context.trans10state4[i*12]+x)&0xff; return context.trans10state4[(x & 3) * 12 + (((x >> 4) % 12) & 0xFF)]; } inline unsigned short trans11_12() { unsigned char r11,r12; unsigned char y; unsigned x; int i; r11=context.key[0]^context.key[3]^context.key[7]; r12 = (context.trans11_12state12[1] ^ context.trans11_12state12[5] ^ context.trans11_12state12[9]); for(i=0;i<12;i++) context.trans11_12state12[i]=context.key[i]; y=context.trans11_12state12[(r11%12)]; x=context.key[r12%12]; return x<<8|y; } inline unsigned char trans3() { unsigned short xy=trans11_12(); unsigned char x,y,tmp=trans10(); x=xy>>8; y=xy&0xff; return tmp^x^y; } inline unsigned char trans0(char c) { unsigned char x=trans1(); unsigned char y=trans2(); unsigned char z=trans3(); unsigned char p; x=x^y^z; p=c ^ (context.i + 2 * context.key[context.i]); context.key[context.i] = x; context.i=(context.i+1)%12; return p; } void reset() { int i; context.i=0; context.key[0]='^'; context.key[1]='T'; context.key[2]=0x1b; //'/'; context.key[4]='V'; context.key[5]='|'; context.key[6]='d'; context.key[7]='}'; context.key[8]='i'; context.key[9]='v'; context.trans4state=0; context.trans5state=0; context.trans6state1=0; context.trans6state3=0; context.trans8state4=0; context.trans10state2=0; for(i=0;i<48;i++) context.trans8state5[i]=0; for(i=0;i<48;i++) context.trans10state4[i]=0; for(i=0;i<12;i++) context.trans11_12state12[i]=0; } void adj(int a) { int i; for(i=0;i<10;i++) if(a&(1<%d\n",ii); for(jj=0;jj<256;jj++) { for(kk=0;kk<256;kk++) { FILE *prout; char nom[100]; unsigned char stock[48],comp=0; reset(); context.key[3]=key3[ii]; context.key[10]=(unsigned char)jj; context.key[11]=(unsigned char)kk; adj(bf); sprintf(nom,"sol%d_%d_%d.bz2",ii,jj,kk); for(i=0;i<16;i++) stock[i]=trans0(data[i]); for(i=16;i<48;i++) { stock[i]=trans0(data[i]); if(stock[i]==0xff) comp++; } if(comp>=16) { fic=fopen(nom,"wb"); fwrite(stock,48,1,fic); for(i=48;i; @bytes=unpack "C*","@tmp"; @b=@bytes[8..@bytes-4]; print pack "C*",@b' \ congratulations.png.*.sTic >e permet de reconstituer le fichier caché qui s'avère être une archive zlib contenant une archive BZ2 contenant elle même un .tiff. Le .tiff n'a aucun tag particulier mais n'est pas compressé, un LSB est probable dans cette situation, comme le format TIFF est geré par imageMagick un script simple permet d'extraire les composantes RGB des pixels. script simple permet d'extraire les composantes RGB des pixels. On commence alors part extraire les bits de poids faible de chaque composante de l'images. Aucune des composantes RGB seule n'est une donnée valide. Dans les couleurs rouge et vert on remarque toutefois un gros champs de ff successifs. Ce qui fait penser au champ bitmap d'un .bz2, En essayant d'extraire les bits R+G dans un même fichier on obtient un fichier valide qu'on décompresse pour obtenir une archive TAR. voici un script effectuant l'extraction : #!/usr/bin/perl -l use Image::Magick; $p = new Image::Magick; $p->Read($ARGV[0]); @pixels=($p->GetPixels(height =>474 , width=>636,map => 'RGB')); while (@pixels+0) { ($r,$g,$b)=@pixels; $rstring.=($r&1); $rstring.=($g&1); $bstring.=($b&1); shift @pixels; shift @pixels; shift @pixels; } open rfile,">rrfile"; print rfile pack "B*",$rstring; close rfile; On obtient finalement un GIF, aucune information ne semble figurer dans le fichier, qui n'est composé que d'une seule image. En examinant la colormap on peut voir que beaucoup de couleurs semblent donner des teintes foncées ou noires. Une réorganisation de la palette fait ressortir le secret tant recherché!