La Barre Fixe

404CTF{x86_64-iGnor3s-5TuFF}

Catégorie: Stegano Difficulté: easy Flag: -

Challenge

Description


C'est le grand moment, tous les yeux sont rivés sur vous. Vous vous apprêtez à commencer votre prestation sur l'épreuve de la barre fixe. Mais quelques instants avant de vous élancer, vous remarquez un détail intriguant. Certaines choses sont là... alors qu'elles ne devraient pas l'être... Un message peut être ?

Solution

On commence par chercher ce que l'on a entre les mains, donc soit avec un coup de commande file, soit via un site en ligne comme Metadata2go.

Il s'agit d'un exécutable x86 en 64bits. On va donc le décompiler pour savoir ce qu'il fait, ici, j'utilise IDA. Direction la fonction _start qui est le point d'entrée du programme.

On voit plein d'instructions db. Elles signifient que le logiciel n'a pas su les comprendre, car elles ne sont pas censées être là.

On trouve toujours les mêmes valeurs hexadécimales : 65, 2E et 3E. Il s'agit de préfixes pour indiquer les segments à utiliser.

Pas besoin de comprendre ce que ça veut dire, IDA nous dit qu'elles n'ont rien à faire ici donc c'est surement une bonne piste à explorer vu l'énoncé du challenge.

Recompilation

Pour aller un peu plus loin, on peut s'amuser en Python à décompiler le binaire pour lister chaque instruction, puis à les recompiler. On pourra ainsi voir les différences puisque les instructions inutiles vont partir d'elles-mêmes.

from elftools.elf.elffile import ELFFile
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
from keystone import Ks, KS_ARCH_X86, KS_MODE_64


def compare(ops):
    ks = Ks(KS_ARCH_X86, KS_MODE_64)
    md = Cs(CS_ARCH_X86, CS_MODE_64)

    print(f'{"INSTRUCTION":^35}\t{"ORIGINAL":^25}\t{"RECOMPILÉ":^20}')
    for i in md.disasm(ops, 0):
        instruction = f'{i.mnemonic}\t{i.op_str}'
        op, _ = ks.asm(instruction)
        
        decompiled = ' '.join([f'{n:0>2x}' for n in i.bytes])
        recompiled = ' '.join([f'{n:0>2x}' for n in bytes(op)])
        print(f'{instruction:<35}\t{decompiled:<25}\t{recompiled}')


with open('./chall', 'rb') as f:
    elf = ELFFile(f)
    code = elf.get_section_by_name('.text')
    ops = code.data()

compare(ops)

#             INSTRUCTION                         ORIGINAL                     RECOMPILÉ      
# push    0                               65 6a 00                        6a 00
# mov     rax, 1                          2e 3e 48 c7 c0 01 00 00 00      48 c7 c0 01 00 00 00
# mov     rdi, 1                          48 c7 c7 01 00 00 00            48 c7 c7 01 00 00 00
# mov     rsi, rsp                        2e 3e 48 89 e6                  48 89 e6
# mov     rdx, 1                          65 48 c7 c2 01 00 00 00         48 c7 c2 01 00 00 00
# mov     byte ptr ds:[rsp], 0x55         2e 3e c6 04 24 55               c6 04 24 55
# syscall                                 2e 3e 0f 05                     0f 05
# mov     byte ptr gs:[rsp], 0x6e         65 c6 04 24 6e                  65 c6 04 24 6e
# syscall                                 65 0f 05                        0f 05
# mov     byte ptr ds:[rsp], 0x4d         65 3e c6 04 24 4d               c6 04 24 4d
# syscall                                 65 2e 0f 05                     0f 05
# ...
# syscall                                 65 0f 05                        0f 05
# mov     byte ptr ds:[rsp], 0x61         2e 3e c6 04 24 61               c6 04 24 61
# syscall                                 40 65 3e 0f 05                  0f 05                               40 0f 05                        0f 05
# ...

On a confirmation que le début de chaque instruction est étrange, et en plus de nos trois valeurs, on voit également 40 qui disparaît.

Ça nous fait un total de 4 valeurs "fantômes", et le plus bizarre, c'est qu'elles semblent toutes respecter un certain ordre.

  • 2E apparaît toujours avant 3E

  • 65 apparaît toujours avant 2E

  • 40 apparaît toujours avant 65

Ce qui donne l'ordre : 40 65 2E 3E

Éclair de génie

On a 4 valeurs qui apparaissent ou non, dans un ordre précis. Et si elles représentaient les bits du message caché que l'on cherche ?

Le flag commence par 404, ce qui donne en binaire : 0011 0100 0011 0000 0011 0100

Si l'on prend les instructions une à une et que l'on regarde si nos valeurs y sont :

  VALEURS    |       HEXADECIMAL      |        ASSEMBLEUR
             |                        |
-- 65 -- --  |  6a 00                 |  push    0
 0  1  0  0  |                        |
             |                        |
-- -- 2e 3e  |  48 c7 c0 01 00 00 00  |  mov     rax, 1
 0  0  1  1  |                        |
             |                        |
-- -- -- --  |  48 c7 c7 01 00 00 00  |  mov     rdi, 1
 0  0  0  0  |                        |
             |                        |
-- -- 2e 3e  |  48 89 e6              |  mov     rsi, rsp
 0  0  1  1  |                        |

Ça ressemble à notre flag en binaire. La première instruction correspond aux 4 bits bas du caractère "4", puis la seconde instruction aux 4 bits hauts.

Pour chaque instruction, on va donc écrire un bloc de 4 bits en fonction de si les valeurs fantômes sont présentes. Ensuite, il faudra simplement assembler 2 blocs par 2 blocs pour reformer nos octets (en veillant à ce que le 1ᵉʳ bloc corresponde bien aux bits de poids faibles).

Script de résolution

from elftools.elf.elffile import ELFFile
from capstone import Cs, CS_ARCH_X86, CS_MODE_64


def decompile(ops):
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    decompiled = []
    for i in md.disasm(ops, 0):
        decompiled.append(i.bytes)
    return decompiled
    

with open('./chall', 'rb') as f:
    elf = ELFFile(f)
    code = elf.get_section_by_name('.text')
    ops = code.data()


instructions = decompile(ops)
blocs = []
for i in instructions:
    bloc = ''
    for prefix in [0x40, 0x65, 0x2e, 0x3e]:
        if i.startswith(bytes([prefix])):
            bloc += '1'
            i = i[1:]
        else:
            bloc += '0'
    blocs.append(bloc)
print(blocs)
print(bytes([int(blocs[i+1] + blocs[i], 2) for i in range(0, len(blocs)//2*2, 2)]))

# ['0100', '0011', '0000', '0011', '0100', '0011', '0011', '0100',
#  '0100', '0101', '0110', '0100', '1011', '0111', '1000', '0111',
#  '1000', '0011', '0110', '0011', '1111', '0101', '0110', '0011',
#  '0100', '0011', '1101', '0010', '1001', '0110', '0111', '0100',
#  '1110', '0110', '1111', '0110', '0010', '0111', '0011', '0011',
#  '0011', '0111', '1101', '0010', '0101', '0011', '0100', '0101',
#  '0101', '0111', '0110', '0100', '0110', '0100', '1101', '0111',
#  '0000', '0000', '0000']
# b'404CTF{x86_64-iGnor3s-5TuFF}\x00'

Dernière mise à jour

Cet article vous a-t-il été utile ?