S3cr37 4g3nt

Catégorie: Embedded

Points: 100

Description:

======================================================================================================
 _______  ______   _______  _______  ______   ______        ___    _______  ______   _       _________
(  ____ \/ ___  \ (  ____ \(  ____ )/ ___  \ / ___  \      /   )  (  ____ \/ ___  \ ( (    /|\__   __/
| (    \/\/   \  \| (    \/| (    )|\/   \  \\/   )  )    / /) |  | (    \/\/   \  \|  \  ( |   ) (   
| (_____    ___) /| |      | (____)|   ___) /    /  /    / (_) (_ | |         ___) /|   \ | |   | |   
(_____  )  (___ ( | |      |     __)  (___ (    /  /    (____   _)| | ____   (___ ( | (\ \) |   | |   
      ) |      ) \| |      | (\ (         ) \  /  /          ) (  | | \_  )      ) \| | \   |   | |   
/\____) |/\___/  /| (____/\| ) \ \__/\___/  / /  /           | |  | (___) |/\___/  /| )  \  |   | |   
\_______)\______/ (_______/|/   \__/\______/  \_/            (_)  (_______)\______/ |/    )_)   )_(   

======================================================================================================

En tant qu'agent spécial au service de la faction XXX vous devez décoder un message intercepté sur un 
théâtre d'opération entre deux agents ennemis. Pour ce faire, votre spécialiste radio vous livre le 
message (qui semble être chiffré) ainsi que le firmware d'un terminal de chiffrement malheureusement
détruit.
A vous de jouer agent XXX 007...

                             +--^----------,--------,-----,--------^-,
                             | |||||||||   `--------'     |          O
                             `+---------------------------^----------|
                               `\_,---------,---------,--------------'
                                 / XXXXXX /'|       /'
                                / ====== /  `\    /'
                               / ECW007 /`-------'
                              / ====== /
                             / XXXXXX /
                            (________(                
                             `------'    

Fichiers: secret.enc, cryptomachine.bin

Note: Les organisateurs du challenge n'autorisent pas la mise à disposition des sources.

TL;DR

L'émulation d'un firmware grace à Unicorn Engine permet de retrouver un message chiffré grace à du brute force.

Méthodologie

Notre objectif est de retrouver le message chiffré grace au frimware d'un "terminal de chiffrement" livré par un "spécialiste radio".

On se retrouve vite face à un gros problème car on n'a aucune infos sur ce firmware.

>_ file -k cryptomachine.bin
cryptomachine.bin: data

>_ strings cryptomachine.bin
iyEI8KL
| /h?
,AC`@
d-!4a

Analyse statique

Pour analyser le firmware il nous faudrait au moins son architecture. En cherchant sur google des puces dédiés au chiffrement on tombe sur des DSP qui semblent correspondre aux caractéristiques de notre firmware d'après la description du challenge.

Sur Wikipedia on trouve une liste de puces qui pourraient potentiellement être implémentés dans notre "terminal de chiffrement".

Le problème est que ces puces ne tournent pas toutes sous la même architecture. Heureusement binwalk peut nous aider sur ce point avec l'option -A.

-A, --opcodes Scan target file(s) for common executable opcode signatures

>_ binwalk -A cryptomachine.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
132           0x84            ARM instructions, function prologue
640           0x280           ARM instructions, function prologue

Cool, maintenant on sait que c'est de l'ARM mais pas quelle version. Heureusement, en l'analysant avec IDA on peut ne pas lui spécifier la version d'ARM et il se débrouille tout seul.

IDA_config

Et la décompilation marche plutôt bien :)

IDA_code_extract

On remarque que dans l'instruction loc_40, SP est set à l'adresse 0x10000. Cela veut dire que la mémoire allouée au programme commence ou se termine à cette adresse. Ca dépend de la façon d'ont est implémentée la stack. (Voir ici pour plus d'infos)

stack

Pour déterminer où se trouve la mémoire je me suis servi du début le la fonction principale où des données sont chargés. Dans var_X sont chargés des données depuis des adresses négatives et dans le registre R1 est chargé une donnée depuis l'adresse 0x10618 qui correspond à 0x618 dans le code décompilé, donc à la valeur 0x94.

ROM:00000618 DCB 0x94

On en déduis que l'adresse 0x10000 est le haut de la stack. Donc le firmware est chargé depuis cette adresse.

entry_point_sub_27C

En analysant la suite des instructions on remarque que le code se passe en 2 temps. Dans un premier temps, il semblerai qu'il y ai une phase d'initialisation où des données sont chargés depuis des adresses spécifiques 0x101F1000 et 0x101F1018.

uart_load

La suite ressemble plus à du chiffrement au vue de la structure. On remarque qu'il y a 4 gros blocks distincts.

cipher_blocks

Après analyse, il s'avère que les blocks servent à chiffrer. On remarque aussi qu'ils n'ont pas de dépendance entre eux.

On peut voir qu'après chaque block de chiffrement le programme effectue toujours les mêmes opérations. Exemple avec les fonctions loc_4B0 et loc_4C0.

cipher_block_2

On remarque que la fonction sub_84 est appelée après chaque block de chiffrement. On retrouve nos 2 adresses "spéciales" 0x101F1000 et 0x101F1018. Après une recherche sur qwant il s'avère que ce sont des adresses qui correspondent à le l'UART00x101F1000 correspond au Data Register où transite la donnée en lecture/écriture et 0x101F1018 au Flag Register qui indique si il reste de la donnée à lire ou écrire.

sub_84

On peut donc imaginer qu'après chaque block de chiffrement la sortie est affichée sur le terminal de chiffrement.

Maintenant qu'on sait comment le firmware fonctionne pour chiffrer il ne reste plus qu'à implémenter un algo de déchiffrement.

Sauf que j'ai la flemme...

meme_decryption_algo

Emulation

Étant donne que les blocks de chiffrement sont indépendant les uns des autres, un brute force est possible en un temps raisonnable.

Pour ne pas m'embêter à implémenter l'algo en un autre langage j'ai décidé d'utiliser Unicorn Engine. Ce framework est très pratique car il permet d'émuler un binaire sur n'importe quelle architecture. En plus il a une api qui permet de l'utiliser avec python.

Il y a juste un petit problème...

meme_no_documentation.jpg

Il y a juste un petit script d'exemple pour chaque architecture... Et quelques exemples d'implémentations trouvés ici et via yandex.

Unicorn Engine

Pour utiliser Unicorn avec python il faut d'abord initialiser l'émulateur.

# Initialize emulator in ARM mode
mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)

# map memory for this emulation
# - Stack + ROM
mu.mem_map(0x0, 0x20000)
# - UART addresses
mu.mem_map(0x101F1000, 1*1024)

# write machine code to be emulated to memory
ARM_CODE = open("cryptomachine.bin" ,'rb').read()
mu.mem_write(0x10000, ARM_CODE)

Ensuite on va initialiser un HOOK_CODE qui va nous permettre de contrôler l'exécution du code après chaque instruction.

Exemple avec l'affichage du contenu des registres après chaque instruction.

def hook_code(uc, address, size, user_data):
    print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))
    print("> R0 = 0x%x" %mu.reg_read(UC_ARM_REG_R0))
    print("> R1 = 0x%x" %mu.reg_read(UC_ARM_REG_R1))
    print("> R2 = 0x%x" %mu.reg_read(UC_ARM_REG_R2))
    print("> R3 = 0x%x" %mu.reg_read(UC_ARM_REG_R3))
    print("> R4 = 0x%x" %mu.reg_read(UC_ARM_REG_R4))
    print("> R5 = 0x%x" %mu.reg_read(UC_ARM_REG_R5))
    print("> LR = 0x%x" %mu.reg_read(UC_ARM_REG_LR))
    print("> SP = 0x%x" %mu.reg_read(UC_ARM_REG_SP))

ADDRESS = 0x10000
mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS+len(ARM_CODE))

Maintenant on va pouvoir émuler notre firmware avec cette fonction.

ADDRESS_START = 0x10040
ADDRESS_STOP = 0x105E0
mu.emu_start(ADDRESS_START, ADDRESS_STOP, count=100000)

Et voici le script complet pour brute forcer le message chiffré.

#!/usr/bin/env python2

from unicorn import *
from unicorn.arm_const import *

import struct
import binascii

# memory address where emulation starts
ADDRESS = 0x10000
ADDRESS_START = 0x10040
ADDRESS_STOP = 0x105E0
ARM_CODE = open("cryptomachine.bin" ,'rb').read()
INPUT_A_STR = 0x101F1000
INPUT_A_CONTINUE = 0x101F1018

instructions_skip_list = [0x1030C, 0x10334, 0x10374, 0x105E8, 0x103C8, 0x1043C, 0x104B0, 0x10530, 0x105A8, 0x105CC]

OUTPUT = ''
first_loop = True

secret = "49D29B343820ADFF3DBCFC29392DFCFD3B90FCF7390DAFF9347EFBFF3AA9F8FA38E6FB003A3AFE2B3DBCFB2B37BCFEFB36EAFEFE363AF82D653AF5CD35A9FF046990A8CA81"
flag = ''
string_tested = ''

# Right format int
def p32(num):
    return struct.pack("I", num)

# callback for tracing instructions
def hook_code(uc, address, size, user_data):
    global OUTPUT
    global first_loop
    global string_tested

    #End the programm on second loop i.e. redo cipher
    if address == 0x102EC :
        if first_loop:
            first_loop = not first_loop
        else:
            #Skip the instruction because there is an input
            mu.reg_write(UC_ARM_REG_PC, 0x105E0)

    if address == 0x1032C:
        #Skip the instruction because there is an input
        mu.reg_write(UC_ARM_REG_PC, address+size) 
        mu.mem_write(0xFEE8, string_tested)

    if address in instructions_skip_list:
        mu.mem_write(INPUT_A_CONTINUE, p32(0x80))
        OUTPUT += mu.mem_read(0x101F1000, 0x1)

def emule_firmware():
    # map memory for this emulation
    mu.mem_map(0x0, 0x20000)
    mu.mem_map(0x101F1000, 1*1024)

    # write machine code to be emulated to memory
    mu.mem_write(ADDRESS, ARM_CODE)

    # write in memory
    mu.mem_write(INPUT_A_CONTINUE, p32(0x40))

    # tracing one instruction at ADDRESS with customized callback
    mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS+len(ARM_CODE))

    # emulate machine code
    #100000 instructions max -> stop if infinite loop
    mu.emu_start(ADDRESS_START, ADDRESS_STOP, count=100000)

if __name__ == '__main__':
    # Initialize emulator in ARM mode
    # and define mu as global
    mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)

    CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_{}"
    chr_hex = ''

    #Compute chars
    for c in CHARS:
        chr_hex += binascii.hexlify(c)
    chr_hex = chr_hex.upper()

    flag = ''
    for i in xrange(0, len(secret), 2):
        for c in xrange(0, len(chr_hex), 2):
            mu = None
            mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)
            first_loop = True
            OUTPUT = ''
            string_tested = flag+chr_hex[c:c+2]
            emule_firmware()
            out = OUTPUT.split("C:")[1].strip()

            if secret[i:i+2] in out[i:i+2]:
                flag += chr_hex[c:c+2]
                print("flag = %s" %binascii.unhexlify(flag))
                break

On lance le script et en 5 min on obtient le flag :)

>_ ./secret_brute.py
flag = E
flag = EC
flag = ECW
flag = ECW{
flag = ECW{4
flag = ECW{48
flag = ECW{48a
flag = ECW{48a5
flag = ECW{48a59
flag = ECW{48a59c
flag = ECW{48a59c0
flag = ECW{48a59c0d
flag = ECW{48a59c0d5
flag = ECW{48a59c0d57
flag = ECW{48a59c0d570
flag = ECW{48a59c0d5704
flag = ECW{48a59c0d57047
flag = ECW{48a59c0d570475
flag = ECW{48a59c0d5704750
flag = ECW{48a59c0d57047500
flag = ECW{48a59c0d570475005
flag = ECW{48a59c0d5704750050
flag = ECW{48a59c0d5704750050c
flag = ECW{48a59c0d5704750050c0
flag = ECW{48a59c0d5704750050c00
flag = ECW{48a59c0d5704750050c006
flag = ECW{48a59c0d5704750050c0067
flag = ECW{48a59c0d5704750050c00671
flag = ECW{48a59c0d5704750050c006716
flag = ECW{48a59c0d5704750050c006716e
flag = ECW{48a59c0d5704750050c006716e4
flag = ECW{48a59c0d5704750050c006716e42
flag = ECW{48a59c0d5704750050c006716e424
flag = ECW{48a59c0d5704750050c006716e424a
flag = ECW{48a59c0d5704750050c006716e424a7
flag = ECW{48a59c0d5704750050c006716e424a76
flag = ECW{48a59c0d5704750050c006716e424a766
flag = ECW{48a59c0d5704750050c006716e424a7662
flag = ECW{48a59c0d5704750050c006716e424a76622
flag = ECW{48a59c0d5704750050c006716e424a76622c
flag = ECW{48a59c0d5704750050c006716e424a76622c9
flag = ECW{48a59c0d5704750050c006716e424a76622c9c
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c2
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c20
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b2
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b222
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b2222
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224a
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa29
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa290
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e3
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e37
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e37e
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e37e5
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e37e5d
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e37e5d1
flag = ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e37e5d1}

FLAG_IS:

ECW{48a59c0d5704750050c006716e424a76622c9c7f3c202b22224aa2901e37e5d1}