S3cr37 4g3nt

Category: Embedded

Points: 100

Description:

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

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

As a special agent serving the ECW faction you must decode an intercepted message on a theater of 
operation between two enemy agents. For this task, your radio specialist delivers the message (which 
seems to be encrypted) as well as the firmware of a cryptographic terminal that is unfortunately 
destroyed.
To you to play agent ECW 007 ...

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

Files: secret.enc, cryptomachine.bin

Note: The challenge organizers do not allow the sources to be made available.

TL;DR

The emulation of a firmware with Unicorn Engine allows us to recover an encrypted message with brute force.

Methodology

Our objective is to find the encrypted message thanks to the frimware of an "cryptographic terminal" delivered by a "radio specialist".

We quickly find ourselves faced with a big problem because we have no information about this firmware.

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

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

Static analysis

To analyze the firmware we need at least its architecture. By searching on google for chips dedicated to encryption we come across DSP which seem to correspond to the characteristics of our firmware according to the description of the challenge.

On Wikipedia there is a list of chips that could potentially be implemented in our "cryptographic terminal".

The problem is that these chips do not all run under the same architecture. Fortunately binwalk can help us on this point with the -A option.

-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, now we know it's ARM but not which version. Fortunately, by analyzing it with IDA you can not specify the version of ARM and it manages on its own.

IDA_config

And decompilation works quite well:)

IDA_code_extract

Note that in the loc_40 instruction, SP is set to address 0x10000. This means that the memory allocated to the program starts or ends at this address. It depends on how we implement the stack. (See here for more information)

stack

To determine where the memory is located I used the beginning of the main function where some data are loaded. In var_X are loaded data from negative addresses and in the register R1 is loaded data from address 0x10618 which corresponds to 0x618 in the decompiled code, so to the value 0x94.

ROM:00000618 DCB 0x94

We deduce that the address 0x10000 is the top of the stack. So the firmware is loaded from this address.

entry_point_sub_27C

Analyzing the rest of the instructions, we notice that the code occurs in 2 steps. First, it seems that there is an initialization phase where data is loaded from specific addresses 0x101F1000 and 0x101F1018.

uart_load

The suite looks more like encryption when viewed from the structure. We notice that there are 4 distinct large blocks.

cipher_blocks

After analysis, it appears that the blocks are used to encryption. We also notice that they do not depend on each other.

We can see that after each encryption block the program always performs the same operations. Example with the functions loc_4B0 and loc_4C0.

cipher_block_2

Note that the function sub_84 is called after each encryption block. We find our 2 "special" addresses 0x101F1000 and 0x101F1018. After a search on qwant it turns out that these addresses correspond to UART0 where 0x101F1000 corresponds to the Data Register where the read/write data is transmitted and 0x101F1018 to the Flag Register which indicates whether there is any data left to read or write.

sub_84

We can therefore imagine that after each encryption block the output is displayed on the encryption terminal.

Now that we know how the firmware works to encrypt, all that remains is to implement a decryption algo.

But I'm lazy...

meme_decryption_algo

Emulation

Since the encryption blocks are independent of each other, a brute force is possible within a reasonable time.

To avoid bothering to implement the algo in another language I decided to use Unicorn Engine. This framework is very useful because it allows you to emulate a binary on any architecture. In addition it has an API that allows it to be used with python.

There's just one little problem....

meme_no_documentation.jpg

There is just a small example script for each architecture.... And some examples of implementations found here and here via yandex.

Unicorn Engine

To use Unicorn with python you must first initialize the emulator.

# 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)

Then we will initialize a HOOK_CODE which will allow us to control the execution of the code after each instruction.

Example with the display of the contents of the registers after each 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))

Now we will be able to emulate our firmware with this function.

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

And here is the complete script to brute force the encrypted message.

#!/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

We run the script and in 5 min we get the 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}