Affichage des articles dont le libellé est 2k12. Afficher tous les articles
Affichage des articles dont le libellé est 2k12. Afficher tous les articles

lundi 2 juillet 2012

NDH 2k12 - SETIHZV Analysis (1500 pts)

Voici le writeup d'une épreuve de Stegano (avec un peu de crypto aussi) qui se distingue des autres de part son originalité.

Description

HZV antennas just caught an intergallactic troll.

On récupère le fichier trololo.txt qui contient la chaîne de caractères suivante :

 

 Analyse

On pourrait penser en premier lieu que les O représentent des 0 et les L des 1 et qu'on se trouve en présence de la représentation binaire d'un long texte. Néanmoins, on se rend vite compte que les premiers octets ne donne rien de probant en terme de code ascii.
On regarde alors plus en détails la description de l'épreuve, il est question de SETI et de message intergalactique.
SETI signifie Search for Extra-Terrestrial Intelligence. Il s'agit de détecter des signaux provenant de l'espace, on peut donc orienter nos recherches dans cette direction car il semblerait que le fichier trololo.txt contiennent un message de ce type.

On finit par tomber sur un article wikipedia au sujet du message d'Arecibo : il s'agit d'un message radio émit vers l'espace formé de 1679 nombres binaires et contenant des informations sur la Terre et l'humanité.

Ce message doit être mis sous la forme d'un rectangle de 23 sur 73 pour que les différentes parties qu'il contient soient visibles.
On peut donc faire une analogie avec notre message et essayer de faire apparaître des informations en modifiant la disposition des caractères.

Réorganisation du message

Le message contient 3360 caractères qui ne sont quasiment que des O ou des L : les autres caractères étant le T et le R en début de message. Ces derniers ne représentent d’ailleurs probablement pas d’information dans le message lui-même.

On constate en effet qu’en retirant ses deux caractères on obtient un total de 3358 caractères qui est divisible par 23 comme l’était le nombre de bits envoyés dans le message d’Arecibo.

Pour plus de clarté dans la distinction entre O et L, on peut remplacer ces derniers respectivement par . et M.

Voici ce qu’on peut voir en réorganisant le message sous la forme d’un rectangle de 23 sur 146 :


Cool ! On sait qu'on est sur la bonne voie. On trouve également les lettres HZV à la fin et entre les 2 on a 3 motifs différents qu'il va falloir décoder.


Décodage du premier motif


Si on revient à notre fameux message d'Arecibo, on voit que l'une des premières choses intégrée dans son contenu est une représentation des nombres de 1 à 10 :

1 2 3 4 5 6 7 8  9  10
----------------------
0 0 0 1 1 1 1 00 00 00
0 1 1 0 0 1 1 00 00 10
1 0 1 0 1 0 1 01 11 01
X X X X X X X X  X  X 

Chaque nombre est codé sur 3 bits sauf 8, 9 et 10 qui le sont sur 6 et des marqueurs sont présents au-dessous pour montrer la position de chaque nombre.

On retrouve bien cette disposition dans notre motif et on remarque que les nombres sont rangés par couple, on obtient la suite de nombres suivante en décodant chaque séquence de 3 bits : 41 45 53 32 35 36 43 42 43.

Il s'agit de la représentation hexadécimale des codes ascii de neuf caractères qui forment la chaîne : AES256CBC.

On connaît donc l'algorithme de chiffrement utilisé pour les données qu'on récupérera dans les autres motifs.

Décodage du deuxième motif


Le deuxième motif semble relativement proche du premier sauf qu'on peut remarquer la présence de certains bits à 1 collés à la représentation d'un nombre sur 3 bits (voir les encadrés rouges). Pas de panique ! Il s'agit simplement de la représentation d'un nombre sur 6 bits comme dans le message d'Arecibo pour les nombres 8, 9 et 10.

Dans ce motif, on peut donc distinguer 19 couples de nombres qui formeront 19 octets représentés en hexadécimal. On va coder les nombres de 0 à 7 sur 3 bits et les nombres de 8 à 15 (F) sur 6 bits.

On obtient la séquence suivante :
21 24 28 23 77 45 70 61 57 4F 69 4F 70 33 34 63 33 3D 2F

On peut supposer qu'il s'agit de la clé utilisée par l'AES pour crypter les données contenues dans le dernier motif.

Voici la clé obtenue après transposition en caractères ascii : !$(#wEpaWOiOp34c3=/

Décodage du troisième motif


La répartition des M et des . fait penser à la représentation binaire d'une chaîne de caractères. Voyons ce qu'on obtient en remplaçant les M par des 1 et les . par des 0 :


Ceci est la représentation binaire de la chaîne suivante:
U2FsdGVkX1+Xf+XkdKdwY/tXQaQ2FGzjl83fNPsmcM0DxgTdp0aDUEoMK81j9dlr

Il s'agit du codage en base64 des données chiffrées par l'AES, la chaîne de caractères  U2FsdGVkX1+ placée au début est caractéristique de l'utilisation d'un salt, son décodage donne : Salted__

On a maintenant tous les éléments pour récupérer le flag : l'algorithme de chiffrement utilisé, la clé et les données chiffrées.

Déchiffrement du flag

Un petit coup d'openssl sur tout ça devrait permettre d'avoir le flag.
On tape la commande suivante :

echo U2FsdGVkX1+Xf+XkdKdwY/tXQaQ2FGzjl83fNPsmcM0DxgTdp0aDUEoMK81j9dlr | openssl enc -a -d -salt aes-256-cbc -k !$(#wEpaWOiOp34c3=/

ça marche pas !!!!!!!!!!  On a pourtant tout bien récupéré et décodé, est-ce qu'on aurait oublié un caractère dans la clé ?
D'ailleurs elle est un peu étrange cette clé, on pourrait lire We pawn in peace (wEpaWNiNp34c3) si on remplaçait les O par des N. Ceci revient à remplacer 4F par 4E dans la représentation hexadécimale de la clé (peut être une erreur dans le challenge lors de la création du deuxième motif).

Finalement, on tente la commande :

echo U2FsdGVkX1+Xf+XkdKdwY/tXQaQ2FGzjl83fNPsmcM0DxgTdp0aDUEoMK81j9dlr | openssl enc -a -d -salt aes-256-cbc -k !$(#wEpaWNiNp34c3=/

Et on obtient : un_______gros_______flag

On peut valider avec grand plaisir cette belle épreuve du wargame NDH 2k12 avec le md5 du flag.
 

Liens

dimanche 1 juillet 2012

NDH 2k12 – Workerz (1500 pt)


  
La « nuit du hack » édition 2012 a eu lieu la semaine dernière et comme d’habitude il y a eu un challenge public … ah non comme il ne marchait pas il a été reporté à ce week-end ...
C’est donc parti pour une série de write-ups sur ces challenges. On commence par « Workerz » dans la catégorie WebApp à 1500 pt.

1.       What

On atterrit sur une jolie animation en JavaScript avec un champ pour un mot de passe. Dans le source on trouve une série de .js dont ceux-ci :
1.       Main.js qui lance une App
2.       App.js qui initialise l’animation ainsi que 3 checks !
Il n’y a pas besoin de d’aller voir ce qu’il se passe dans la classes Kode ou Worker, on peut directement aller voir les checkX.js

var App = function () {
   
var check1 = new Worker('js/check1.js'),
        check2
= new Worker('js/check2.js'),
        check3
= new Worker('js/check3.js');
    this.
music = new buzz.sound('audio/robd_chateau', {
        formats
: [ "mp3" ]
   
});
    this.
k = new Kode(check1, check2, check3);
    this.
k.initialize();
    this.
initialize();
};

2.       Check1.js
On trouve une fonction qui sera appelée à chaque fois que le passe change et qui renvoi un booleén pour savoir si c’est bon ou pas.

;(function () {
   
var messageHandler = function (e) {
       
var c,
            p
= e.data;
        if
(((p.charCodeAt(0) * 42) - 38 ===
             (Math.pow(68, 2) / 4) + 0xC0 )                  &&
             
(p.charCodeAt(1) === p.charCodeAt(0) + 0x3A)    &&
             
((p.charCodeAt(2) + 0x10) === Math.sqrt(0x961)) &&
             
(Math.pow(p.charCodeAt(3), 3) / 25 ===
           
 (Math.pow(p.charCodeAt(2), 3) * 2) + 0x186B)    &&
             
(Math.sqrt(p.charCodeAt(4) - 2) ===
           
 (p.charCodeAt(2) * 0x61) - ((0xB00B5 / 3) -
           
 (Math.pow(p.charCodeAt(3), 2) * 6) - (0xBABE * 3)))) {
                 self.
postMessage('1');
       
} else {
             self.
postMessage('0');
       
}
   
};
    self.
addEventListener('message', messageHandler, false);
    postMessage
('Loaded');
})();

On remarque que les caractères utilisés du passe sont seulement le 0, 1, 2, 3, 4, donc le début uniquement. Nettoyons un peu tout ça :

((p.charCodeAt(0) * 42) - 38 === (Math.pow(68, 2) / 4) + 0xC0)
On peut simplifier ça en p.charCodeAt(0)=== 33


(p.charCodeAt(1) === p.charCodeAt(0) + 0x3A)
Cela devient : p.charCodeAt(1)=== 91

((p.charCodeAt(2) + 0x10) === Math.sqrt(0x961))
En : p.charCodeAt(2)=== 33

(Math.pow(p.charCodeAt(3), 3) / 25 ===
(Math.pow(p.charCodeAt(2), 3) * 2) + 0x186B)
Avec une petite racine cubique à la fin (^1/3), on obtient : p.charCodeAt(2)=== 125

(Math.sqrt(p.charCodeAt(4) - 2) === (p.charCodeAt(2) * 0x61) -
((0xB00B5 / 3) - (Math.pow(p.charCodeAt(3), 2) * 6) -
(0xBABE * 3))))
Du calcul encore, on obtient rapidement : p.charCodeAt(4) === 102

On a plus qu’à convertir ces codes ascii en caractères : ![!}f


3.       Check2.js
Maintenant on a du code Python qui sera exécuté dans sa VM (p.js). On peut extraire le code et jouer avec le vrai interpréteur Python.

;(function () {
    self.
console = {
        log
: function () {}
   
};
    self.prompt
= function () {};
    importScripts
('p.js');
    Python.
initialize(null, function(chr) {
        if
(chr !== null) {
            postMessage
(String.fromCharCode(chr));
       
}
   
});

   
var messageHandler = function (e) {
        code
= "def c(p = '" + e.data + "'):\n" +
           
"    if (ord(p[5]) == ((1337 % ord('=')) + 7)):\n" +
           
"        if (ord(p[6]) == (((42 * ord(p[5])) % 13) + 45)):\n" +
           
"            if (ord(p[7]) == ((0xc0ffee % ord(p[6])) + 57)):\n" +
           
"                if (ord(p[8]) == ((0xbadf00d & ord(p[7])) + ord(p[6]) + 50)):\n" +
           
"                    return('1')\n" +
           
"    return('0')\n" +
           
"import sys; sys.stdout.write(c())\n";
        Python.eval
(code);
   
};
    addEventListener
('message', messageHandler, true);
    postMessage
('Loaded');
})();

Voilà le code extrait :
def c(p = ''):
   
if (ord(p[5]) == ((1337 % ord('=')) + 7)):
       
if (ord(p[6]) == (((42 * ord(p[5])) % 13) + 45)):
           
if (ord(p[7]) == ((0xc0ffee % ord(p[6])) + 57)):
               
if (ord(p[8]) == ((0xbadf00d & ord(p[7])) +
                   
ord(p[6]) + 50)):
                   
return('1')
   
return('0')

On utilise la même méthode que plus haut ou  même un petit brute force vu qu’il n’y a que 4
caractères en jeu.
 On obtient ces 4 caractères : ?4;o

4.       Check3.js
Le dernier check est encore exécuté dans une vm LLVM codé en JavaScript qui répond au petit nom d’Emscripten (https://github.com/kripken/emscripten)
Dans le fichier on trouve uniquement un code qui alloue 17 octets, écrit le passe dedans et exécute la fonction _check3 définit dans check3_vm.js.
Voilà le code entier de la fonction _check3 :

function _check3($x) {
 
;
 
var __label__;
  __label__
= 2;
  while
(1) switch(__label__) {
    case
2:
     
var $1;
     
var $2;
     
var $p;
      $
2=$x;
      $p
=0;
     
var $3=_malloc(6);
      $p
=$3;
     
var $4=$2;
     
var $5=(($4+13)|0);
     
var $6=HEAP8[($5)];
     
var $7=$p;
     
var $8=(($7)|0);
      HEAP8
[($8)]=$6;
     
var $9=$2;
     
var $10=(($9+15)|0);
     
var $11=HEAP8[($10)];
     
var $12=$p;
     
var $13=(($12+1)|0);
      HEAP8
[($13)]=$11;
     
var $14=$2;
     
var $15=(($14+11)|0);
     
var $16=HEAP8[($15)];
     
var $17=$p;
     
var $18=(($17+2)|0);
      HEAP8
[($18)]=$16;
     
var $19=$2;
     
var $20=(($19+14)|0);
     
var $21=HEAP8[($20)];
     
var $22=$p;
     
var $23=(($22+3)|0);
      HEAP8
[($23)]=$21;
     
var $24=$2;
     
var $25=(($24+12)|0);
     
var $26=HEAP8[($25)];
     
var $27=$p;
     
var $28=(($27+4)|0);
      HEAP8
[($28)]=$26;
     
var $29=$2;
     
var $30=(($29+10)|0);
     
var $31=HEAP8[($30)];
     
var $32=$p;
     
var $33=(($32+5)|0);
      HEAP8
[($33)]=$31;
     
var $34=$p;
     
var $35=(($34+5)|0);
     
var $36=HEAP8[($35)];
     
var $37=(($36 << 24) >> 24);
     
var $38=_fmod($37, 42);
     
var $39=(($38)&-1);
     
var $40=(($39)|0)==7;
      if
($40) { __label__ = 3; break; } else { __label__ = 9; break; }
    case
3:
     
var $42=$p;
     
var $43=(($42+3)|0);
     
var $44=HEAP8[($43)];
     
var $45=(($44 << 24) >> 24);
     
var $46=(($45)|0)==42;
      if
($46) { __label__ = 4; break; } else { __label__ = 9; break; }
    case
4:
     
var $48=$p;
     
var $49=(($48)|0);
     
var $50=HEAP8[($49)];
     
var $51=(($50 << 24) >> 24);
     
var $52=((($51)+(3))|0);
     
var $53=(($52)|0);
     
var $54=_sqrt($53);
     
var $55=(($54)&-1);
     
var $56=$p;
     
var $57=(($56+4)|0);
     
var $58=HEAP8[($57)];
     
var $59=(($58 << 24) >> 24);
     
var $60=((($59)-(30))|0);
     
var $61=(($55)|0)==(($60)|0);
      if
($61) { __label__ = 5; break; } else { __label__ = 9; break; }
    case
5:
     
var $63=$p;
     
var $64=(($63+2)|0);
     
var $65=HEAP8[($64)];
     
var $66=(($65 << 24) >> 24);
     
var $67=_ldexp($66, 3);
     
var $68=(($67)&-1);
     
var $69=((($68)|0))%(3);
     
var $70=((($69)+(47))|0);
     
var $71=$p;
     
var $72=(($71+5)|0);
     
var $73=HEAP8[($72)];
     
var $74=(($73 << 24) >> 24);
     
var $75=(($70)|0)==(($74)|0);
      if
($75) { __label__ = 6; break; } else { __label__ = 9; break; }
    case
6:
     
var $77=$p;
     
var $78=(($77+4)|0);
     
var $79=HEAP8[($78)];
     
var $80=(($79 << 24) >> 24);
     
var $81=((($80)+(800))|0);
     
var $82=$p;
     
var $83=(($82+2)|0);
     
var $84=HEAP8[($83)];
     
var $85=(($84 << 24) >> 24);
     
var $86=((((($85)|0))/(2))&-1);
     
var $87=(($86)|0);
     
var $88=_llvm_pow_f64($87, 2);
     
var $89=(($88)&-1);
     
var $90=(($81)|0)==(($89)|0);
      if
($90) { __label__ = 7; break; } else { __label__ = 9; break; }
   
case 7:
     
var $92=$p;
     
var $93=(($92+1)|0);
     
var $94=HEAP8[($93)];
     
var $95=(($94 << 24) >> 24);
     
var $96=((($95)*(666))|0);
     
var $97=$96 >> 9;
     
var $98=(($97)|0)==57;
      if
($98) { __label__ = 8; break; } else { __label__ = 9; break; }
    case
8:
      $
1=1;
      __label__
= 10; break;
    case
9:
      $
1=0;
      __label__
= 10; break;
    case
10:
     
var $102=$1;
     
;
      return $
102;
   
default: assert(0, "bad label: " + __label__);
 
}
}

On là on se dit : WTF ?! Mais en fait ça n’est pas si compliqué que ça :

Au niveau structure, c’est un automate et on part de l’état 2. Ensuite on effectue le check de cet
état et si c’est bon, on passe au suivant. Si on tombe dans l’état 9 c’est foutu.
L’état 10 sert juste à renvoyer le résultat du check. En paramètre, on a $x qui est l’adresse où
est stocké notre mot de passe. Pour accéder à la valeur contenu à cette adresse,
on utilise HEAP8[ADRR].
Les opérations du style (($4+13)|0) ne font qu’ajouter (13) à $4, le |0 comme
le &-1 ne servent à rien dans ce cas. L’adresse du mot de passe est aussi copiée dans $2.
a.       Case 2
On crée un pointeur $p, on alloue 6 octet et on met l’adresse de ces octets dans $p.
Ensuite on trouvera beaucoup de suite d’instructions de ce style :

var $4=$2;
   
var $5=(($4+13)|0);
   
var $6=HEAP8[($5)];

Elle ne font que charger une adresse ($2) ajouter une valeur à cette adresse (13) puis récupère la valeur à cette adresse (HEAP8[($5)]). C’est équivalent à : pass[13] en C par exemple.
Celle valeur est mis ensuite dans le premier octet de $p :

var $7=$p;
   
var $8=(($7)|0);
   HEAP8
[($8)]=$6;
Ces opérations sont équivalentes à p[0] = pass[13].
Jusqu’à la ligne : var $34=$p; ce n’est que de la copie des caractères
10, 11, 12, 13, 14, 15 du mot de passe dans le buffer $p. Pour être plus précis :

p[0] = password[13]
   p[1] = password[15]
      p[2] = password[11]
      p[3] = password[14]
      p[4] = password[12]
      p[5] = password[10]

On remarque que le caractère 9 n’est pas utilisé, on peut donc mettre ce que l’on veut !
Ensuite nous avons notre premier check, on récupère p[5] c’est-à-dire passe[10], on garde que les 8 derniers bits ($37=(($36 << 24) >> 24)) , on prend le reste de la division avec 42 (_fmod($37, 42)) et on test si c’est bien égale à 7 ($40=(($39)|0)==7). Si ça n’est pas égale, on va dans l’état 9.
On a donc : fmod(pass[10], 42) == 7

b.      Case 3
On récupère p[3] c’est-à-dire passe[14] et on test si il est bien égale à 42 ($46=(($45)|0)==42).
On obtient ce test : pass[14] == 42

c.       Case 4
On récupère passe[13], on ajoute 3 ($52=((($51)+(3))|0)) puis on fait la racine carré ($54=_sqrt($53)). Et ceci doit être égale à pass[12] – 30. (Je passe les étapes …)
Test : sqrt(pass [13] + 3) == pass[12] - 30

d.      Case 5
Test : pass[10] == (ldexp(pass[11], 3) % 3) + 47

e.      Case 6
En regardant dans le code de la vm, llvm_pow_f64 est en fait Math.pow de JavaScript.
Test : pass[12] + 800 == pow(pass[11] / 2, 2)

f.        Case 7
Test : (pass[15] * 666) >> 9 == 57

On se retrouve donc avec une série de tests pour 5 caractères. Certains se jetteront sur le
brute force mais on peut aussi regarder de plus près !

Le test Case2 laisse que 2 valeurs possibles : 1 et [. Mais testant avec Case5 on voit que
seul « 1 » est bon pour pass[10]. « [» ne donne aucun caractère valide pour pass[11].
Ce qui nous fait pour l’instant : ![!}f?4;oA1 comme début de passe (J’ai pris A pour pass[9])

Case 7 nous dis que le caractère 15 ne peut être que « , ». Case 3 … que
le caractère 14 est « * ».

Pour le reste, pass[12] et pass[13] dépendent de pass[11], il suffit de brute forcer pass[11]
pour voir ce que ça donne :

Première solution : pass[11] = 16, pass[12] = 32 et pass[13] = 1
Deuxième solution : pass[11] = 112, pass[12] = 32 et pass[13] = 1
Ça m’étonnerait que le mot de passe contienne le caractère ascii 1 …

Il reste une dernière solution : pass[11] = 58, pass[12] = 41 et pass[13] = 118
Ce qui est plus acceptable et qui donne les lettres : :)v

Et voilà ! On a tout et cela donne : ![!}f?4;oA1:)v*,

5.       Résultat
On obtient cette image ainsi que le flag : !}$|==FLAG==|#[?%