Challenge SSTIC 2019 - Documentation¶
Le challenge peut être retrouvé à l’adresse suivante: http://static.sstic.org/challenge2019/challenge_SSTIC_2019-virtual_phone.tar.gz
$ tar -tzvf challenge_SSTIC_2019-virtual_phone.tar.gz
drwxr-xr-x david/david 0 2019-03-27 12:43 virtual_phone/
-rw-r--r-- david/david 622 2019-03-28 23:24 virtual_phone/README
-rw-r--r-- david/david 30897 2019-03-28 23:24 virtual_phone/rom.bin
-rw-r--r-- david/david 14737268 2019-03-28 23:24 virtual_phone/power_consumption.npz
-rw-r--r-- david/david 35879224 2019-03-28 23:24 virtual_phone/flash.bin
L’ensemble des scripts utilisés pour résoudre les étapes sont disponibles dans le dossier scripts
Etape 1¶
« Nous avons consacré du temps à rendre possible le démarrage du téléphone sécurisé dans un environnement virtualisé. Malheureusement le coffre de clef du téléphone ciblé n’a pas pu être copié. Avant de devoir restituer le téléphone, nous avons été en mesure d’enregistrer une trace de consommation de courant lors du démarrage du téléphone. » – Présentation, Challenge 2019
La présentation laisse supposer une possible attaque par analyse de consommation. L’environnement virtuel suivant nous est fourni :
$ qemu-system-aarch64 -nographic -machine virt,secure=on -cpu max -smp 1 -m 1024 -bios rom.bin -semihosting-config enable,target=native -device loader,file=./flash.bin,addr=0x04000000
##########################################
# virtual environment detected #
# QEMU 3.1+ is needed #
##########################################
NOTICE: Booting SSTIC ARM Trusted Firmware
KEYSTORE: AES Key is still encrypted, need decryption
KEYSTORE: Need RSA key to decrypt
KEYSTORE: RSA private exponent is not set, please set it in the keystore or enter hex value :
La première étape va donc consister à recupérer l’exposant privé RSA.
La trace de consommation nous est donnée en format .npz
avec un seul .npy
qui contient un tableau NumPy sérialisé.

Le déchiffrement s’effectue de ~700,000 jusqu’à ~1,489,300. On remarquera sur cette plage que 2 motifs se répètent lors du déchiffrement; suggérant l’utilisation de l’exponentiation rapide par cette implémentation de RSA. A titre d’exemple, voici une implémentation tirée du livre Applied Cryptography: Protocols, Algorithms and Source Code in C de Bruce Schneier.
unsigned long qe2(unsigned long x, unsigned long y, unsigned long n) {
unsigned long s,t,u;
int i;
s = 1; t = x; u = y;
while(u) {
if(u&1) s = (s*t)%n;
u>>=1;
t = (t*t)%n;
}
return(s);
}

En définissant un seuil de consommation pour différencier les 2 motifs, on récupère la serie de « square+multiply » et « square » utilisée et ainsi reconstruit l’exposant privé.
# etape1/1.py
trace = numpy.load('power_consumption.npz')
data = trace['arr_0'][700000:1489300]
pattern_x = 0
peak_count = 0
peak_count_array = []
for y in data:
if (y < -0.05): # peak
if (pattern_x < 500):
# same pattern
peak_count += 1
else:
# next pattern
peak_count_array.append(peak_count)
peak_count = 1
pattern_x = 0
pattern_x += 1
d = 0 # private exponent
bit_sel = 0
for peak_count in peak_count_array:
if peak_count > 26:
# square + multiply
d |= 1 << bit_sel
bit_sel += 1
print("%x" % d)
# 23d87cdf97bb95abe6273c384190c765f552ab86f6de30a8db74435c95e6e3138f54af689812d8f9359cf0f4d453a0c11ec68ce470216c09e74c8947adaf23e902415d61ddf2c0ffe459cbb40f7de42bdb7cd14093100a570e8c29819765e2d8d276f86471b52ac29aa2ce2bb72cd45006279e82bec253ae9675fe45824f6001
Bravo, envoyez le flag SSTIC{a947d6980ccf7b87cb8d7c246} à l'adresse challenge2019@sstic.org pour valider votre avancée
Etape 2¶
Pour cette deuxième étape, un script python3 get_safe1_key.py
simulant un élément sécurisé, nous est donné.
Après une phase d’initialisation, il est demandé à l’utilisateur d’entrer une séquence de 8 combinaisons de 4 boutons, représentant 4 interrupteurs à bouton poussoir. Un sha256 h
est calculé à partir des 8 sorties s1..s8
et ensuite comparé avec le sha256 00c8bb35d44dcbb2712a11799d8e1316045d64404f337f4ff653c27607f436ea
.
[...]
key = bytearray([s1,s2,s3,s4,s5,s6,s7,s8])
h = hashlib.sha256(key).hexdigest()
if "00c8bb35d44dcbb2712a11799d8e1316045d64404f337f4ff653c27607f436ea" == h:
ok("Hash ok")
info("Dérivation de la clef AES safe_01")
aes_key = hashlib.scrypt(key,salt =b"sup3r_s3cur3_k3y_d3r1v4t10n_s4lt",n=1<<0xd,r=1<<3,p=1<<1,dklen=32)
info("aes key : %s" % aes_key.hex())
info("Vous pouvez sauvegarder cette clef en utilisant /root/tools/add_key.py key")
else:
error("Mauvais hash, déchiffrement impossible")
[...]
Chacune des sorties résulte d’une série d’appels à secure_device()
.
def step1():
r = secure_device(0x35,0x27,3)
r = secure_device(0x7e,r,3)
r = secure_device(0x66,r,2)
r = secure_device(0x8,r,1)
r = secure_device(0x13,r,0)
r = secure_device(0x1f,r,1)
r = secure_device(0xa,r,2)
r = secure_device(0xd3,r,0)
r = secure_device(0xc6,r,3)
return r
[...]
input()
s1 = step1()
Le secure_device()
n’a pas été implémenté mais le circuit logique schematics.png
n’a cependant pas été supprimé.
def secure_device(a,b,op):
out = 0
"""
TODO :
- Implémentation de la communication avec le secure element
* Entrées A et B (A0 = bit de poids faible)
* Entrée OP
* Sortie Out (Out0 = bit de poids faible)
* Les boutons permettent à l'utilisateur de rentrer sa combinaison secrète pour le déchiffrement
- Supprimer les docs de conception
"""
return out

Sachant que \(2^4\) (16) combinaisons sont possibles pour chacune des 8 étapes et que ces dernières sont indépendantes les unes des autres, une récupération des 16 sorties respectives permet de définir l’ensemble des clefs possibles. L’espace de recherche se réduit ici à \(2^{4^8}\) soit \(2^{32}\) clefs. Au vu de l’utilisation de SHA-256, une première approche peut consister à bruteforcer cet espace. Il serait donc question de \(2^{32}\) sha256 calculés dans le pire des cas.
Sur mon vieux core i5 de 3ème génération (i5-3320M CPU @ 2.60GHz), un test.c
parallélisé en utilisant OpenMP
sur le SHA256()
de la bibliothèque openssl
a donné un taux de \(6.59 \cdot 10^6 \text{H/s}\). Ainsi, la génération des \(2^{32}\) sha256 peut être exhaustive en \(2^{32} \text{H} \div (6.59 \cdot 10^6 \text{H/s}) \approx 651.7\text{s}\); ce qui rend le bruteforce faisable en un temps raisonnable. Cette stratégie a donc été choisi.
En suivant le schéma schematics.png
, les principales fonctions logiques A, B, C et le circuit secure_device()
ont été implémentés (etape2/1.py) pour déterminer les 16 octets possibles de chacune des sorties i.e
[i] s1: [25, 57, 64, 71, 98, 114, 129, 137, 143, 144, 175, 191, 194, 199, 215, 223]
[i] s2: [8, 16, 32, 41, 50, 82, 144, 164, 180, 181, 209, 217, 218, 219, 243, 245]
[i] s3: [1, 28, 79, 123, 141, 159, 176, 187, 189, 191, 217, 219, 220, 221, 223, 237]
[i] s4: [0, 26, 40, 53, 56, 64, 78, 82, 83, 85, 91, 113, 156, 169, 170, 173]
[i] s5: [0, 1, 65, 71, 96, 130, 142, 169, 175, 189, 203, 212, 222, 237, 253, 255]
[i] s6: [27, 49, 61, 78, 100, 101, 157, 178, 193, 200, 207, 221, 224, 237, 238, 246]
[i] s7: [5, 10, 77, 79, 84, 96, 134, 154, 158, 169, 187, 191, 202, 221, 223, 232]
[i] s8: [7, 72, 77, 84, 120, 131, 133, 151, 170, 176, 216, 240, 243, 251, 252, 253]
Après l’écriture du bruteforce à partir de s1..s8
(etape2/2.c), les sorties utilisées à la génération du sha256 00c8bb35d44dcbb2712a11799d8e1316045d64404f337f4ff653c27607f436ea
sont retrouvées en ~2min24.
$ time ./etape2/2
key = bytearray([143, 164, 223, 169, 212, 237, 187, 240])
./etape2/2 569,02s user 0,12s system 396% cpu 2:23,67 total
>>> import hashlib
>>> key = bytearray([143, 164, 223, 169, 212, 237, 187, 240])
>>> aes_key = hashlib.scrypt(key,salt =b"sup3r_s3cur3_k3y_d3r1v4t10n_s4lt",n=1<<0xd,r=1<<3,p=1<<1,dklen=32)
>>> hashlib.scrypt(key,salt =b"sup3r_s3cur3_k3y_d3r1v4t10n_s4lt",n=1<<0xd,r=1<<3,p=1<<1,dklen=32).hex()
'5fb3a83d1fd97137076019ad6e96c6a366fb6b32618d162e00cdee9bad427a8a'
Le flag est retourné en passant la clef au script /root/tools/add_key
.
Envoyez le flag SSTIC{5fb3a83d1fd97137076019ad6e96c6a366fb6b32618d162e00cdee9bad427a8a} à l'adresse challenge2019@sstic.org pour valider votre avancée
Etape 3¶
Suite à la validation de l’étape 2, le premier conteneur safe_01
est déchiffré.
# /root/safe_01/decrypted_file
Usage : /root/safe_01/decrypted_file <flag>
Après l’ouverture du binaire dans un désassembleur, le programme initialement écrit peut globalement se résumer comme suit :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | static const char* g_str;
static const char* g_nope = "Not good\n";
static const char* g_ok = "That's the correct flag :)\n";
void _throw(char* str) {
throw(str);
}
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage : %s <flag>\n", argv[0]);
exit(1);
}
try {
_throw(argv[1]);
} catch (char* flag) {
register const char* str __asm__ ("x28");
if (strcmp("SSTIC{congolexicomatisation}", flag) == 0)
str = g_ok;
else
str = g_nope;
g_str = str;
puts(g_str);
}
return 0;
}
|
Le programme lève explicitement une exception de type char *
avec l’argument saisi par l’utilisateur. Le bloc catch
, supposé traiter l’exception, ne se comportera pas comme présenté.
Lors de la compilation d’un code c++ présentant des blocs try/catch
, la section .gcc_except_table
est créée pour permettre à la libstdc++ de trouver, entre autres, les informations concernant les blocs catch
(landing pads).
La partie concernant le précédent bloc catch
(resp. ligne 18-26) de la section .gcc_except_table
est présentée ci-dessous; partie annotée grâce au script IDA gcc_extab.py écrit et publié par Igor Skochinsky pour sa présentation à REcon « Compiler Internals: Exceptions and RTTI » en 2012.
.gcc_except_table:00000000004032CC AREA .gcc_except_table, DATA, READONLY
[...]
.gcc_except_table:00000000004032D5 DCB loc_402EB4 - 0x402E68 ; cs_start[1] = 00402EB4
.gcc_except_table:00000000004032D6 DCB 4 ; cs_len[1] = 4 (end = 00402EB8)
.gcc_except_table:00000000004032D7 DCB 0xB8, 1 ; cs_lp[1] = 00402F20
.gcc_except_table:00000000004032D9 DCB 1 ; cs_action[1] = 1 (004032E9)
[...]
L’adresse de destination du bloc catch
a ici été modifiée en post-compilation pour pointer 0x60
octets plus loin (resp. ligne 24). Initialement, l’adresse de destination du bloc catch #2 (cs_lp[1])
serait de 0x402EC0
.
Lorsque une exception est levée, la section .eh_frame
est parcourue pour gérer le processus de stack unwinding.
Le Frame Description Entry correspondant à la fonction 0x402E34
levant l’exception (resp. _throw()) est comme suit :
$ aarch64-linux-gnu-readelf --debug-dump=frames decrypted_file
Contents of the .eh_frame section:
[...]
00000090 000000000000001c 00000094 FDE cie=00000000 pc=0000000000402e34..0000000000402e68
DW_CFA_advance_loc: 1 to 0000000000402e35
DW_CFA_def_cfa_offset: 32
DW_CFA_offset: r29 (x29) at cfa-32
DW_CFA_offset: r30 (x30) at cfa-24
DW_CFA_val_expression: r28 (x28) (DW_OP_skip: -12222)
DW_CFA_nop
DW_CFA_nop
[...]
La séquence d’instructions (Call Frame Instruction) a ici été modifiée pour rediriger le flot d’exécution (i.e ligne 9) et ainsi venir interpréter un bytecode Dwarf placé dans la section .gnu.hash
en 0x403215 - 12222 + 2 = 0x400259
.
Au retour du programme Dwarf, la valeur de retour sera ensuite dépilée de la pile (Dwarf) et mise dans x28
avant de donner le contrôle au bloc catch
correspondant (i.e 0x402F20
). Il s’agira donc de la chaîne de caractères à retourner à l’utilisateur.
Il a été choisi de réutiliser le code de l’interpréteur Dwarf de la bibliothèque libgcc pour décoder le bytecode et tracer le programme. Une propagation de teinte y a été ajouté afin d’écarter le code mort et de réduire le code généré e.g branchements inconditionnels, constants unfolding, … (etape3/1.c)
A partir de ce bytecode décodé et de traces d’exécution, les structures de contrôle identifiées permettent de reconstituer en partie la logique de l’algorithme.
Le corps principal de ce dernier peut se présenter comme suit :
1 2 3 4 5 6 7 8 9 10 11 12 | def A(id_1, id_2, id_3, id_4):
for rd in range(8):
m_1, m_2 = M(id_1, id_2, K(id_3, id_4))
id_1, id_2, id_3, id_4 = id_3, id_4, m_1, m_2
t_1 = (id_1 ^ 0x65850b36e76aaed5) \
+ (id_2 ^ 0xd9c69b74a86ec613) \
+ (id_3 ^ 0xdc7564f1612e5347) \
+ (id_4 ^ 0x658302a68e8e1c24)
return t_1 == 0
|
Note
Le details des fonctions peut être retrouvé à etape3/2.py.
Les arguments id_1..id_4
correspondent aux 4 QWORD
que compose le flag saisi par l’utilisateur.
A chaque tour de boucle, 2 nouvelles valeurs m_1, m_2
sont dérivées des 4 précedents id_1, id_2, id_3, id_4
et deviendront respectivement les nouveaux id_3, id_4
.
Les précédents id_3, id_4
remplaceront id_1, id_2
.
Le corps de la boucle possède cependant une faiblesse. Après l’exécution de la boucle (ligne 6), les valeurs connues sont id_1, id_2, id_3, id_4
qui sont respectivement id_3, id_4, m_1, m_2
au dernier tour de boucle (i.e rd = 7
). Ainsi, les valeurs id_3, id_4
utilisées par K()
sont connues.
Les valeurs id_1, id_2
utilisées par M()
restent cependant inconnues. Si il s’avère que M()
est réversible alors les entrées id_1, id_2
peuvent être rétrouvées et par conséquent, les valeurs id_1, id_2, id_3, id_4
initiales à rd = 0
le seront.
def M(m_1, m_2, k):
s_1, s_2 = m_1, m_2
for rd in range(1, 16):
t_3, t_4 = J(k, rd % 16)
s_1 = t_3 ^ I(H(s_2), s_1)
s_2 = t_4 ^ L(H(s_1), s_2)
return s_1, s_2
Si I()
et L()
sont réversibles alors la fonction inverse M_inv()
de M()
peut s’écrire de la façon suivante :
def M_inv(s_1, s_2, k):
m_1, m_2 = s_1, s_2
for rd in range(15, 0, -1):
t_3, t_4 = J(k, rd % 16)
m_2 = L_inv(H(m_1), m_2 ^ t_4)
m_1 = I_inv(H(m_2), m_1 ^ t_3)
return m_1, m_2
La fonction L()
est sans perte d’information i.e
def L(l_1, l_2):
lo_l_1 = LODWORD(l_1)
hi_l_1 = HIDWORD(l_1)
lo_l_2 = LODWORD(l_2)
hi_l_2 = HIDWORD(l_2)
return (ROL((hi_l_2 ^ lo_l_1) ^ hi_l_1, 0x12) & 0xffffffff) << 0x20 | ROL(lo_l_2 ^ hi_l_1, 0x1a) & 0xffffffff
En connaissance de s = L(l_1, l_2)
et l_1
, l_2
peut ainsi être récupéré :
def L_inv(l_1, s):
lo_l_1 = LODWORD(l_1)
hi_l_1 = HIDWORD(l_1)
lo_s = LODWORD(s)
hi_s = HIDWORD(s)
return ((ROR(hi_s, 0x12) & 0xffffffff) ^ lo_l_1 ^ hi_l_1) << 0x20 | ((ROR(lo_s, 0x1a) & 0xffffffff) ^ hi_l_1) & 0xffffffff
La fonction I()
est également réversible i.e etape3/3.py .
Somme toute, M()
est bien réversible et par conséquent les valeurs id_1, id_2, id_3, id_4
initiales utilisées à la génération des valeurs lignes 7-10 peuvent être obtenues comme suit :
def A_inv():
id_1 = 0x65850b36e76aaed5
id_2 = 0xd9c69b74a86ec613
id_3 = 0xdc7564f1612e5347
id_4 = 0x658302a68e8e1c24
for _ in range(8):
id_1, id_2, id_3, id_4 = id_3, id_4, id_1, id_2
id_1, id_2 = M_inv(id_1, id_2, K(id_3, id_4))
key = struct.pack("<Q", id_1) + struct.pack("<Q", id_2) + struct.pack("<Q", id_3) + struct.pack("<Q", id_4)
print(key.decode())
# SSTIC{Dw4rf_VM_1s_co0l_isn_t_It}
Etape 4¶
Après avoir soumis le flag de l’étape précedente au script /root/tools/add_key
, le second conteneur safe_02
est déchiffré. Il est necessaire de redémarrer pour charger le Secure-OS.
[+] Container /root/safe_02/.encrypted decrypted to /root/safe_02/decrypted_file
[w] You must reboot in order to decrypt Secure OS
Le Secure-OS est ici un TEE-OS basé sur la technologie TrustZone d’ARM. Comme informé lors du reboot, il intervient en BL32. (Bootloader stage 3-2).
NOTICE: BL1: Booting BL31
NOTICE: BL31: Initializing BL32
NOTICE: Booting Secure-OS
En étudiant l’initialisation du BL32 dans un désassembleur, on remarquera que la ligne de commande utilisée pour démarrer l’environnement virtuel est parsée i.e (ps -ef |egrep -q 'qemu-system.* -[s] |qemu-system.* -[g]db ') && exit 42
, à la recherche des arguments -s
ou -g
afin de détecter si QEMU est à l’écoute d’un possible debugging via gdb.
[...]
mmap_add_region(0xE053000, 0x413000, 0x1000);
if (debug_is_present())
addr = 0xE055000;
else
addr = 0xE054000;
mmap_add_region(addr, 0x414000, 0x100000);
[...]
Dans le cas où debug_is_present()
est vrai, le mapping [0xE054000, 0xE055000[
sera omis, créant une discontinuité sur 0x1000 + 0x100000
i.e
0x0413000 ... 0x1414000
[0xE053000, 0xE054000[ [0xE055000, 0xE155000[.
Lorsqu’il est venu d’étudier le Secure Monitor, il a été découvert qu’un buffer (de taille 0x101010
) envoyé par decrypted_file
est finalement copiée en 0xE053000
, couvrant le mapping précédent.
La table des vecteurs d’exceptions (EVT) du Secure OS a ensuite été étudiée. Le format de cette dernière en ARMv8 est comme suit :

Le handler d’exception à 0xE203000 + 0x200
(correspondant à une exception synchrone venant du même level avec l’utilisation de SP_EL1
) se positionne comme un spécifique write()/load()
lors d’une page fault.
Pour toutes lectures comprises entre [0x0, 0x100fff]
, le handler commence par translater cette adresse à 0x413000
; ce qui coïncide avec le mapping vu précédemment. La valeur correspondante est ensuite chiffrée via l’utilisation de quelques instructions de l’extension cryptographique de ARMv8 avant d’être retournée à l’initiateur de la lecture.
Les instructions de l’extension e.g sm4ekey
, sm4e
ne sont pas encore reconnues par IDA Pro. Cela a ainsi été l’occasion de remplacer IDA par Ghidra le temps de cette étape 4 (qui supporte bien ces instructions).
Le handler d’exception à 0xE203000 + 0x600
(correspondant à une exception synchrone venant d’un level inférieur en AArch32) a été implémenté. Dans le cas d’un SVC #0x1338
, le handler sert de pivot au Secure Monitor.
En recherchant des fonctions modifiant le SPSR_EL1
, deux fonctions ont été identifiées i.e sub_E2025A4()
et sub_E202610()
mettant à jour le bit #4 (M) à 1 du SPSR_EL1
afin de simuler une exception venant d’un contexte AArch32 et ainsi y retourner. sub_E202610()
modifie également le bit #5 (T) du SPSR_EL1
pour changer le jeu d’instruction (ARM / Thumb). Ces 2 fonctions modifient le ELR_EL1
avant de ERET
.
En étudiant les parties de code (Thumb/ARM) aux différentes adresses de retour, un anti-debug a été découvert utilisant l’horloge interne de QEMU pour mesurer le temps d’exécution entre deux parties.
Le binaire decrypted_file
du second conteneur safe_02
a ensuite été regardé.
# /root/safe_02/decrypted_file
usage: /root/safe_02/decrypted_file [32-bytes-key-hex-encoded]
Le rôle principal du binaire est de transmettre un tableau d’octets et le flag décodé au character device /dev/sstic
. Une boucle est utilisée pour faire avancer une certaine machine à état et tester le retour de celle-ci.
[...]
ioctl(fd, 0xC0105300, &array_44DBD8, 0x101010);
ioctl(fd, 0xC0105301, &flag, 0);
while (1) {
ioctl(fd, 0xC0105302, 0, 0);
res = ioctl(fd, 0xC0105303, 0, 0);
if (LOWORD(res) == 1) {
if (HIWORD(res) != 0)
printf("Loose\n");
else
printf("Win\n");
return 0;
}
[...]
}

Le driver SSTIC
en EL1 sert principalement à relayer les informations entre decrypted_file
et le Secure Monitor (qui s’exécute en EL3) via des Secure Monitor Calls (SMC). Le Trusted OS est ici le Secure-OS qui s’exécute en Secure-EL1.
- En étudiant le Secure Monitor, deux principaux handlers ont été identifiés :
sub_E034158()
: code SMC commençant par0xF200xxxx
sub_E032014()
: code SMC commençant par0x8301xxxx
sub_E032014()
maintient une structure de 16 lignes à 4 entiers dont le contenu est chiffré (en utilisant comme clef les 4-7ième DWORD
composants le flag saisi par l’utilisateur).
Quelques instructions des extensions Advanced SIMD et cryptographique de ARMv8 sont ici utilisées pour effectuer la majorité des opérations arithmétiques et logiques sur les lignes de cette structure.
- Par l’utilisation des codes SMC
0xF2005001
,0xF2005002
,0xF2005003
, le handlersub_E034158()
(qui s’exécute en EL3) redirige l’exécution en S-EL1 en0xE200094
. La fonctionsub_E200C08()
, qui est ensuite appelée, gère principalement la logique de l’algorithme sous-jacent. Ce dernier se présente en deux phases: 0xF2005001
permet de « charger » le prochain code (non SMC, spécifique à l’algorithme)0xF2005002
permet de décoder ce « code » à l’aide de la sous-fonctionsub_E2005A4()
La logique de sub_E2005A4()
et sub_E032014()
a ensuite été retranscrite et simplifiée (etape4/1.c) afin d’extraire par la suite la logique de l’algorithme sous-jacent.
En pseudo-code, la logique de l’algorithme peut se traduire comme suit :
for (i = 0; i < 8; i += 2)
{
z0 = REVWORD(LOWORD(user[i]));
z1 = REVWORD(HIWORD(user[i]));
z2 = REVWORD(LOWORD(user[i+1]));
z3 = REVWORD(HIWORD(user[i+1]));
k = 0x7f;
for (j = 0x20; j > 0; j--)
{
l = L(&k, z1);
if ((((j-1) >> 0x3) & 0x1) == 1) {
z0, z1, z2, z3 = l, j ^ l ^ z2, z3, z0;
} else {
z0, z1, z2, z3 = l, z2, z3, j ^ z0 ^ z1;
}
}
res[i] = REVWORD(z1) << 0x10 | REVWORD(z0);
res[i+1] = REVWORD(z3) << 0x10 | REVWORD(z2);
}
Note
Le code complet de ce dernier ainsi que la version reverse peuvent être retrouvés à etape4/2.c
La solution obtenue acadaa8b5b55306fb3c6dfc3b2d1c80770084644225febd71a9189aa26ec740e
permet de valider l’étape :
[+] Envoyez le flag SSTIC{acadaa8b5b55306fb3c6dfc3b2d1c80770084644225febd71a9189aa26ec740e} à l'adresse challenge2019@sstic.org pour valider votre avancée
Etape 5¶
Suite à la validation de l’étape 4, le dernier conteneur safe_03
est déchiffré. Il s’agit d’une archive .tar.gz
des données des applications du téléphone de l’individu « au comportement suspect ». Une recherche de la chaîne challenge.sstic.org
sur le dossier révèle un message contenu dans la base de données MMS/SMS :
« Mission accomplie, comme d’habitude la perspective d’une tournée de shooter au cactus a suffi à corrompre le CO et à chosir la date de publication du challenge. Pour être sûr que les experts sont toujours occupés, j’ai redirigé l’adresse 9e915a63d3c4d57eb3da968570d69e95@challenge.sstic.org vers votre boîte mail. Tant que vous ne voyez passer aucun mail, la voie est libre… »