🇫🇷 MIDNIGHT BACKSLASH Qualif | [Hard]

Table of Contents

Deck Decoder

J’ai pu participer à la création de challenges lors de l’édition 2024 du Midnight Flag CTF.

Voici le writeup “untended” du challenge.

Initialisation

Ce challenge est un binaire ELF x86_64 strippé. Toutes les addresses mentionnées dans ce WU seront à partir de la base: 0x100000

On retrouve l’utilisation de srand dans le main qui utilise en 1er argument l’argument pin de notre programme.

On trouve arapidement un array de chaines d’octets qui semble subir plusieurs transformations, notement la fonction 0x10e5ef, appelons la randomize.

alt text

Cette fonction randomize sers à initialiser un tableau qui servira juste après au “mélange” des pointeurs de notre chaine d’array.

alt text

Défaire le mélange

Le 1er objectif sera de reproduire tous les schémas de mélange possibles (entre 0 et 2000). (Félicitation à tous ceux qui ont identifié le nom de l’algorithme de mélange).

On peut comparer dynamiquement la 1ere sortie de rand() dans notre programme avec 123 en argument et la sortie de ce script, si les deux sorties sont simillaires, on peut continuer.

from ctypes import CDLL

libc = CDLL("libc.so.6")
libc.srand(123)
print(hex(libc.rand()))

Cependant attention le srand et le rand dépendent uniquement de la libc, il est nécéssaire de garder la même libc référencée dans le binaire.

Maintenant il faut comprendre et reverse l’algorithme de mélange de l’array qui consiste uniquement a assigner un nouvel array la correspondance de notre tableau d’entiers avec l’indice de l’élement

Chaque element de l’array est composé de la structure:

  • 1 octet qui correspond à la taille du buffer
  • le buffer de la taille énnoncée

L’array est en plus chiffré en utilisant du RC4. Le RC4 a une propriété de “roulement”, c’est à dire qu’initialiser une clée puis chiffrer “hello” puis “world” donne un résultat différent que chiffrer “world” puis “hello”, l’ordre initial de l’array a donc une grande importance, le résultat sera correct uniquement si l’array est dans le bon ordre.


from ctypes import CDLL
from Crypto.Cipher.ARC4 import ARC4Cipher
from capstone import *
import math
from collections import Counter
import matplotlib.pyplot as plt

array = [b"\x05\x6E\xF2\x67\xFE\x82", b"\x01\x2B", b"\x03\xED\x9C\x1B"]#... big array

libc = CDLL("libc.so.6")
n = 0x8e7

def calculate_entropy(data):
    if not data:
        return 0
    frequency = Counter(data)
    data_len = len(data)
    probabilities = [freq / data_len for freq in frequency.values()]
    entropy = -sum(prob * math.log2(prob) for prob in probabilities if prob > 0)
    return entropy


def check_instructions(byte_code):
    """
    Autre possibilité: True uniquement si seed == 1203
    """
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    instructions = md.disasm(byte_code, 0x1000)
    return len([x for x in instructions]) == n:

def randomize (arr, n):
    for i in range(n-1, 0, -1):
        j = libc.rand() % (i+1)
        arr[i], arr[j] = arr[j], arr[i]
    return arr


entropy_list = []
for k in range(2000):
    libc.srand(k)
    init = randomize([i for i in range(n)],n)
    new_array = [0]*n
    for i in range(n):
        new_array[init[i]] = array[i]

    c = ARC4Cipher(bytes.fromhex("e1a4646ee8acd20f4578dbea4a79380a"))
    for i in range(len(new_array)):
        new_array[i] = c.decrypt(new_array[i][1:])
    # if check_instructions(b"".join(new_array)):
    #     print(f"[+] Found seed: {k}")
    #     exit(1)
    entropy_list.append(calculate_entropy(b"".join(new_array)))


plt.figure(figsize=(10, 5))
plt.plot(entropy_list, marker='o', linestyle='-', color='b')

plt.title('Result')  
plt.xlabel('PIN') 
plt.ylabel('Entropie') 
plt.grid(True)
plt.show()        

entropie_output

On trouve la seed 1203

Cette seed nous donne donc un shellcode correct, on va pouvoir l’émuler avec miasm.

Résolution du crackme

Il faut être asticieux pour avoir a bruteforce que (100) possibilitées à chaque fois et non 2 octets en s’aidant du 1er caractère du flag connu.


shellcode = bytes.fromhex("666748b8560c47c8215c3817674831f8fff348bbda3f33b05daaca314a31fb67fff16648b95b7a470a7c1170bf664a31f967526648ba7a8f6775979f273e66483")#... big shellcode

from miasm.analysis.binary import Container
from miasm.analysis.machine import Machine
from miasm.core.locationdb import LocationDB
from miasm.arch.x86.jit import jitter_x86_64
from miasm.jitter.csts import PAGE_READ, PAGE_WRITE,PAGE_EXEC
from string import printable
  
search_values = [0x751E22F6AFDC1D1A, 0x25E2D8737E1EE778, 0x5FA5062A2A463E93, 0x31E402F96E8BDF97, 0x835BE6989055C8F7, 0x5D96E686BBBB53FA, 0xECE6FF58CDD9D31A, 0xC21A2CBC37DBC25F, 0x504E496158267321, 0x1E7F1D0186F1EC8C, 0x177A0713EE72684C, 0x701183528F5AC6CF, 0xB732A1AFDC2F5679, 0x76C06994CE3A61BC, 0xF5F06AB9884DE54F, 0x701183528F5AC6CF, 0x5A37ADF157A8C201, 0xC094D5287D091C4F, 0xF5F06AB9884DE54F, 0x3DAA050C3B50450E, 0xA93C0AF6A0E9C41C, 0xF5D4CBAB3B2C88A3, 0xA27A01B298EC17A8, 0x5FCEFDDD776A26F0, 0xDEBDE5DBB1C1C4B3, 0xE08C572F24190AB, 0xD512D32829E4869, 0xFFBD122F4A434777]
result = 0

def finish(jitter: jitter_x86_64):
    global result
    result = jitter.cpu.RAX
    jitter.running = False
    return False


loc_db = LocationDB()

cont = Container.from_string(shellcode, loc_db)
machine = Machine("x86_64")
mdis = machine.dis_engine(cont.bin_stream, loc_db=loc_db)
myjit = machine.jitter(loc_db)
run_addr = 0
myjit.vm.add_memory_page(run_addr, PAGE_READ | PAGE_WRITE | PAGE_EXEC, shellcode)
myjit.init_stack()
myjit.push_uint64_t(0xdead0000)
myjit.add_breakpoint(0xdead0000, finish)

def test_combinaison(value1, value2):
    t1 = 0
    myjit.push_uint64_t(0xdead0000)
    for i in range(0,8,2):
        t1 |= (value1)<<(8*(i))
        t1 |= (value2)<<(8*(i+1))
    myjit.cpu.RDI = t1
    myjit.run(run_addr)
    return result


def find_carac(start, index):
    for elm in printable:
        a = test_combinaison(start,ord(elm))
        if(a == search_values[index]):
            return elm

start = 'M'
passwd = ""+start
for i in range(len(search_values)):
    start = find_carac(ord(start), i)
    passwd +=start
    print(passwd)

alt text

Flag: MCTF{ShUff13_th3_sh3LLc0de!!}

Conclusion

Merci à l’organisation et félicitation aux flaggers du challenge ❤

Le script de recherche

Le script miasm