Solution challenge SSTIC 2014 : =============================== Introduction : -------------- Une archive en téléchargement usbtrace.xz. On décompresse et on commence. Troll number 1 : air gapped --------------------------- Le fichier contient un mail concernant un pc "air-gapped" connecté à un téléphone. Premier troll à propos de "air-gapped" qui fait penser à la conférence sur ce sujet présentée à CanSecWest 2014... La trace USB n'est autre qu'un dialogue entre un téléphone sous Android et un PC via le protocole ADB. Une fois désencapsulé le protocole Adb on peut voir les commandes envoyées depuis le PC vers le shell du téléphone. Toutes ne sont pas listées ici mais une des commandes nous donne un indice sur l'architecture cible : shell:uname -a Linux localhost 4.1.0-g4e972ee #1 SMP PREEMPT Mon Feb 24 21:16:40 PST 2015 armv8l GNU/Linux Il s'agit d'un linux sur aarch64, aïe... Ensuite on peut voir que l'utilisateur liste des répertoires du télépbone puis envoie un fichier ELF (badbios.bin) qui est placé dans /tmp/badbios.bin et exécuté. Etape suivante : reverser ce binaire. Troll number 2 : badbios : -------------------------- Le deuxième troll nous ramène encore sur CanSecWest, décidémment c'est une manie :) Après avoir galéré à trouver un qemu-aarche64 et un gdb-multiarch qui marchent vraiement l'analyse peut commencer. On s'apperçoit alors rapidement de deux choses : l'algorithme de chiffrement salsa est utilisé (le magic se ballade en mémoire) et le binaire implémente une machine virtuelle. Une fois identifié qu'il y'a deux wrappers qui permettent de chiffrer/déchiffrer une zone mémoire (offset dans une zone, une taille) on comprend que la mémoire du programme est chiffrée et qu'elle est lue/écrite via des fonctions ad-hoc. On peut ensuite déchiffrer toute la mémoire du programme (64k) exécuté par la machine virtuelle : Le mapping mémoire est le suivant : 0x00 - 0x3F : 16 registres de 32 bits 0x40 - 0x40F : byte code et strings du programme 0x8000 - 0x9FFFF : un bloc de données qui semble chiffré La machine virtuelle implémente 32 instructions. Les instructions correspondant aux opcodes de 0 à 7 sont encodées sur 4 octets, les autres sont encodées sur 2 octets. Une fois réversé les handlers on peut écrire un désassembleur et identifier la routine de déchiffrement du block binaire : xoring_file_loop: 0x194(02) : mov r8, r10 0x198(02) : mov r9, r11 0x19c(12) : and r8, r12 0x19e(12) : and r9, r13 0x1a0(10) : xor r8, r9 unknown opcode : 30 , int : 891e 0x1a4(00) : mov r8, 0x0 0x1a8(01) : or r8, 0x1 0x1ac(00) : mov r7, 0x0 0x1b0(01) : or r7, 0x1f 0x1b4(02) : mov r6, r10 0x1b8(12) : and r6, r8 0x1ba(13) : lsl r6, r7 0x1bc(14) : lsr r11, r8 0x1be(11) : or r11, r6 0x1c0(14) : lsr r10, r8 0x1c2(13) : lsl r9, r7 0x1c4(11) : or r10, r9 0x1c6(23) : dec r3 0x1c8(02) : mov r7, r11 0x1cc(12) : and r7, r8 0x1ce(13) : lsl r7, r3 0x1d0(11) : or r4, r7 0x1d2(08) : branch(cond=3), r3, pc=continue_loop 0x1d6(00) : mov r7, 0x0 0x1da(01) : or r7, 0x8000 0x1de(18) : add r7, r1 0x1e0(04) : mov r8, byte ptr[r7+0] 0x1e4(10) : xor r8, r4 0x1e6(07) : mov byte ptr[r7+0], r8 0x1ea(00) : mov r3, 0x0 0x1ee(01) : or r3, 0x8 0x1f2(22) : inc r1 0x1f4(10) : xor r4, r4 continue_loop: 0x1f6(00) : mov r8, 0x0 0x1fa(01) : or r8, 0x2000 0x1fe(19) : sub r8, r1 0x200(08) : branch(cond=5), r8, pc=xoring_file_loop La clé entrée est "déséxifiée" pour constituer un registre de 64 bits. La boucle implémente une instruction de type ROR légérement modifiée, le bit injecté à gauche du registre dépend de trois bits. Le bit injecté à gauche du registre est calculé comme suit : ((b63 xor b61 xor b60) + b0) mod 2 Où les bN sont les bits du registre avant décallage. Une fois compris cette logique on peut implémenter le ROR et le ROL sur le registre. On voit alors que programme attend un padding à 0x0000000000000000 sur le dernier bloc du fichier déchiffré. On peut donc caculer l'état du registre de 64 bits sur le dernier bloc et "remonter" le registre en effectuant des ROL pour retrouver sa valeur initiale et extraire la clé de chiffrement. La clé est la suivante : 0BADB10515DEAD11 Badbios is dead !! Le programme déchiffre alors un fichier payload.bin qui constitue l'étape suivante. Troll number 3 : Yeah RISC is good ! ------------------------------------ La troisième partie se présente comme une archive contenant un firmware et script python permettant d'envoyer ce firmware à un micro controlleur se trouvant sur un serveur distant. Une analyse rapide du fichier de firmware uploadé permet d'en déterminer le format pour envoyer un firmware modifié. Chaque ligne contient 32 octets, commencée par l'offset où va être écrit le bloc et terminée par un checksum. Le micro-controlleur n'est pas documenté mais l'indice dans le fichier upload.py nous permet déjà de comprendre ce qu'il va falloir faire : # == MEMORY MAP == # # [0000-07FF] - Firmware \ # [0800-0FFF] - Unmapped | User # [1000-F7FF] - RAM / # [F000-FBFF] - Secret memory area \ # [FC00-FCFF] - HW Registers | Privileged # [FD00-FFFF] - ROM (kernel) / # Il va probablement falloir lire la mémoire située de 0xF000 et 0xFBFF pour y chercher la solution du challenge. Mais d'abord il faut analyser le code du microcontrolleur. Si on met un opcode à 0 le controlleur s'arrête et nous affiche une exception avec son contexte. Ceci est très utile et on peut faire bouger cet opcode à 0 pour "suivre" l'exécution du firmware et en déduire quelles la sémantique des instructions. Je n'ai pas compris parfaitement toutes les instructions mais suffisamment pour voir la structure du firmware. Exemple : 0x0(2100) : mov r1.hi, 0x0 0x2(111b) : mov r1.lw, 0x1b 0x4(2001) : mov r0.hi, 0x1 0x6(108c) : mov r0.lw, 0x8c 0x8(c0d2) : call 0xdc 0xa(2010) : mov r0.hi, 0x10 0xc(1000) : mov r0.lw, 0x0 rc4 key 0xe(2101) : mov r1.hi, 0x1 0x10(117c) : mov r1.lw, 0x7c : key = YeahRiscIsGood! 0x12(2200) : mov r2.hi, 0x0 0x14(120f) : mov r2.lw, 0xf : len(key) = 9 0x16(c03c) : call 0x54 0x18(2010) : mov r0.hi, 0x10 0x1a(1000) : mov r0.lw, 0x0 0x1c(2101) : mov r1.hi, 0x1 0x1e(11b2) : mov r1.lw, 0xb2 0x20(2200) : mov r2.hi, 0x0 0x22(1229) : mov r2.lw, 0x29 0x24(c076) : call rc4_decrypt Le programme utilise l'algorithme RC4 pour déchiffrer une chaîne qui est affichée. Celà occupe une grande partie du code du firmware mais celà ne nous sert a rien. Il est bon de noter que la clé est "YeahRiscIsGood". Référence à hackers. Y a t'il un lien avec le social event de CanSecWest dans une salle d'arcades ? Je le soupçonne :) Très peu de syscalls sont disponibles et un seul a un bug qui va nous permettre d'obtenir une élévation de privilèges. Il s'agit du syscall qui récupère le nombre de cycles CPU et l'écrit dans un word. Il n'y a pas de vérification sur l'adresse de ce word (kernel mode vs user mode). On obtient alors un write4 à une adresse entièrement contrôllé et dont on contrôle au moins partiellement le contenu grâce aux nombre d'instruction qu'on va effectuer avant de déclencher le syscall. Grâce à celà on peut ré-écrire le premier mot à l'adresse 0xF000 qui n'est autre que l'adresse du handler du syscall 1 (récupération du nombres de cycles CPU). Grâce à deux appels consécutifs à ce syscall on fait exécuter au controlleur du code kernel mode qu'on contrôle. Et là c'est gagné on peut lire le contenu de la secret memory area et retrouver l'adresse mail magique. Fin : ----- Encore merci aux organisateurs du challenge que j'ai trouvé très fun. Cette solution est très courte car je suis pris par le temps. Je ne cherche pas à gagner le concours qualité néanmoins je pense qu'il est bon de mener la démarche jusqu'au bout. Julien Leblanc.