Voici la troisième partie du récit de la piste des cartes à puce au NSEC 2013. Vous pouvez consulter les deux premières parties: partie 1 et partie 2.
J’avais donc trouvé deux drapeaux et le suivant était l’ancienne clé de chiffrement utilisée dans l’appliquette courante. C’était un bon indice: il fallait fouiller le binaire de l’ancienne appliquette fourni sur la page. J’ai téléchargé le fichier et obtenu un fichier .cap, que j’ai instinctivement ouvert dans 7-zip, par habitude avec les fichiers jar. C’était effectivement une archive zip, mais elle contenait une série d’autres fichiers .cap nommés Header.cap, Method.cap, Constant pool.cap, static fields.cap, etc. Ces fichiers n’étaient pas des zip et ne contenaient que des données binaires. Je reconnaissais les différentes composantes d’un fichier class, mais elles n’étaient pas dans le format habituel. J’ai commencé à chercher un outil de décompilation pour les appliquettes Java Card.
Après une heure d’essais et de recherches, j’ai trouvé un fil de discussion qui mentionnait que le SDK Java Card comportait un outil appelé Normalizer, capable de convertir un fichier .cap en fichier .class. J’ai aussitôt téléchargé le SDK Java Card et lancé le normalizer sur le fichier .cap initial (l’archive). Le normalizer a démarré, puis a fini par planter avec une erreur indiquant qu’une ClassRef était introuvable pour la Ref ###### (un numéro élevé dont je ne me souviens plus). J’étais de nouveau perdu. J’ai ouvert quelques fichiers .cap dans Vim en mode hexadécimal, en me concentrant sur constant pool.cap, où je m’attendais à trouver de l’information importante, mais sans rien trouver. J’ai soumis beaucoup de chaînes hexadécimales tirées de ces fichiers comme drapeaux, sans succès.
Après m’être penché sur des problèmes d’autres pistes, je suis revenu à celui-ci et j’ai décidé de prendre le chemin le plus long: j’ai décompilé l’outil normalizer, écrit en Java. Une fois l’outil décompilé, je l’ai exécuté en mode débogage pour trouver l’erreur. Il s’avère qu’au moment d’initialiser les références externes, il ajoute toutes les références externes mentionnées (les autres paquets dont votre appliquette a besoin), mais il n’ajoute pas le paquet java.lang, qui est implicite. Dès qu’une classe de java.lang est référencée, il lève donc l’exception. J’ai modifié un peu le code pour qu’il inclue le paquet java.lang, le normalizer s’est exécuté sans erreur et m’a donné un fichier AAA.class. Ça se raconte vite, mais il m’a fallu de 30 à 45 minutes pour finalement obtenir ce fichier .class.
J’ai immédiatement décompilé ce fichier class en code Java et j’ai vu qu’il chiffrait en CBC avec une clé de chiffrement rangée dans un champ statique.
package io.nsec.javacard.OldCDCApplet;
import javacard.framework.*;
import javacard.security.AESKey;
import javacard.security.KeyBuilder;
import javacardx.crypto.Cipher;
public class AAA extends Applet
{
private void method_token255_descoff88(APDU apdu)
{
byte abyte0[] = apdu.getBuffer();
int i = (short)abyte0[4];
int j = (short)(byte)apdu.setIncomingAndReceive();
if((short)i != (short)j)
ISOException.throwIt((short)26368);
if(Util.arrayCompare(field_token0_descoff24, (short)0, abyte0, (short)5, (short)4) == 0)
{
field_token3_descoff45 = -1;
return;
}
for(short word0 = (short)0; (short)word0 < 16; word0++)
{
field_token1_descoff31 = (AESKey)KeyBuilder.buildKey((byte)15, (short)128, false);
field_token1_descoff31.setKey(sfield_token255_descoff17_staticref2, (short)0);
field_token2_descoff38 = Cipher.getInstance((byte)13, false);
field_token2_descoff38.init(field_token1_descoff31, (byte)2);
field_token2_descoff38.doFinal(sfield_token255_descoff10_staticref0, (short)0, sfield_token255_descoff10_staticref0.length, abyte0, (short)0);
}
field_token3_descoff45 = (byte)(short)(field_token3_descoff45 - 1);
Util.setShort(abyte0, (short)0, field_token3_descoff45);
apdu.setOutgoingAndSend((short)0, (short)2);
ISOException.throwIt((short)25344);
}
public void process(APDU apdu)
{
byte abyte0[] = apdu.getBuffer();
if(abyte0[0] == 0)
{
if(abyte0[1] == -92)
{
Util.setShort(abyte0, (short)0, field_token3_descoff45);
apdu.setOutgoingAndSend((short)0, (short)2);
return;
}
if(abyte0[1] == 32 && abyte0[2] == 0 && abyte0[3] == 0 && abyte0[4] == 4)
method_token255_descoff88(apdu);
else
ISOException.throwIt((short)27904);
} else
{
ISOException.throwIt((short)27904);
}
}
private AAA()
{
field_token3_descoff45 = -1;
}
public static void install(byte abyte0[], short word0, byte byte0)
{
(new AAA()).register();
}
private AESKey field_token1_descoff31;
private Cipher field_token2_descoff38;
byte field_token3_descoff45;
static final byte sfield_token255_descoff10_staticref0[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
};
static final byte sfield_token255_descoff17_staticref2[] = {
-15, -90, -15, -90, -15, -90, -15, -90, -15, -90,
-15, -90, -15, -90, -15, -90
};
byte field_token0_descoff24[] = {
48, 48, 48, 48
};
}
J’ai soumis cette clé et… Bingo! J’avais trouvé le troisième drapeau.
Encore une fois, en discutant avec les organisateurs après la compétition, ils m’ont fait remarquer qu’en ouvrant simplement le fichier nommé static fields.cap dans un éditeur hexadécimal, j’aurais vu la clé. C’était vrai: je n’avais pas regardé ce fichier du tout et la clé ressortait bien, puisque le reste du fichier n’était pratiquement que des zéros. Quand on ne sait pas où chercher, il n’est toutefois pas facile de deviner quel bloc hexadécimal est la bonne clé.
Bref, j’avais mon troisième drapeau et j’étais plutôt content du résultat jusque-là.
La partie 4 arrive et racontera l’incroyable histoire du quatrième drapeau.
