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
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();
};
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');
})();
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)
(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))))
((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.
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');
})();
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')
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.
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)
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__);
}
}
;
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.
é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].
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.
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)];
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;
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 :
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]
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
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
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)
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
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 !
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].
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 « * ».
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 :
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==|#[?%
Joli write-up :)
RépondreSupprimerLa source du 3e check: http://hastebin.com/favinudoce.c