Cryptopals zestaw 2 ćwiczenie 16

przez | 6 sierpnia 2020

Ostatnie zadanie z zestawu drugiego i kolejne zadanie w którym będziemy musieli wykorzystać właściwości trybu CBC, aby osiągnąć to czego oczekują od nas autorzy. Napisałem”osiągnąć” bo tym razem naszym celem nie jest próba odkrycia tajnej treści, ale modyfikacja zakodowanych danych tak, żeby pod odszyfrowaniu uzyskać inną wartość niż ta którą przekazaliśmy do zaszyfrowania. Co więcej ta nowa wartość będzie musiała spełnić pewne kryteria.

Jak zwykle, najpierw musimy sobie przygotować wyrocznię, której użyjemy w dalszej części zadania. Nasz nowa wyrocznie będzie:

  1. Generowała losowy klucz który będą szyfrowane wiadomości.
  2. Generowała losowy wektor inicjalizujący.
  3. Posiadała funkcję zwracającą zaszyfrowane dane,
  4. Posiadała funkcję, która będzie odszyfrowywała dane.

Pierwsza funkcja będzie:

  1. Otrzymywała jako parametr dane od użytkownika.
  2. Przed danymi od użytkownika będzie dodawała ciąg: „comment1=cooking%20MCs;userdata=”.
  3. Za danymi od użytkownika będzie dodawała ciąg: „;comment2=%20like%20a%20pound%20of%20bacon”.
  4. Z danych przekazanych przez użytkownika funkcja będzie usuwała wszystkie znaki „;” oraz „=”.
  5. Tak powstały ciąg znaków: „comment1=cooking%20MCs;userdata=”+DANE_UŻYTKOWNIKA+„;comment2=%20like%20a%20pound%20of%20bacon” będzie następny wypełniany paddingiem.
  6. Całość zostanie zaszyfrowana AES w trybie CBC, a wynik zwrócony użytkownikowi.

Druga funkcja będzie:

  1. Otrzymywała jako parametr zaszyfrowane dane.
  2. Dane będą odszyfrowywane.
  3. Jeżeli w odszyfrowanych danych będzie znajdował się ciąg znaków „;admin=true;” to funkcja zwróci wartość True w przeciwnym wypadku False.

Poniżej moja implementacja wyroczni:

class OracleCbcBitFlipping(object):
    M_ENCRYPTION_KEY = b''
    M_IV = b''
    
    def __init__(self):
        self.M_ENCRYPTION_KEY = generateRandomData(16)
        self.M_IV = generateRandomData(16)
    
    def encryptData(self, user_data):
        user_data = user_data.replace(';', '')
        user_data = user_data.replace('=', '')
        plain_data = ("comment1=cooking%20MCs;userdata="+
                      user_data+
                      ";comment2=%20like%20a%20pound%20of%20bacon")
        plain_data = addPaddingPKCS7(bytearray(plain_data, 'ascii'), 16)
        cipher = AES.new(self.M_ENCRYPTION_KEY, AES.MODE_CBC, self.M_IV)
        return cipher.encrypt(plain_data)

    def isAdmin(self, ciphered_data):
        result = False
        cipher = AES.new(self.M_ENCRYPTION_KEY, AES.MODE_CBC, self.M_IV)
        plain_data = cipher.decrypt(ciphered_data)
        print(plain_data)
        if b";admin=true;" in plain_data:
            result = True
        return result

Naszym zadaniem jest sprawienie, żeby druga funkcja (w moim wypadku metoda isAdmin w klasie OracleCbcBitFlipping) zwróciła wartość True. Wiemy, że nie da się tego zrobić wprost podając jako parametr dane zawierające wartość „;admin=true;”, ale wiemy też że tryb CBC ma bardzo ciekawą właściwość. Otóż jeżeli zmienimy jeden bit w bloku danych zaszyfrowanych to po odszyfrowaniu staną się dwie rzeczy:

  1. Zmodyfikowany blok będzie zawierał śmieciowe dane.
  2. Kolejny blok będzie różnił się dokładnie o jeden bit od oryginalne bloku przed zaszyfrowaniem.

Przykład: załóżmy że mamy dwa bloki danych o długości 2 bajty. Przed zaszyfrowaniem mamy 0x12 0x34 0x56 0x78, po zaszyfrowaniu otrzymaliśmy 0xAB 0x38 0x29 0x10. Zmieniamy Pierwszy blok blok na 0xAB 0x28. Po odszyfrowaniu dostaniemy 0xXX 0xXX 0x56 0x68. Czyli ostatni bajt w ostatnim bloku zmienił się z 0x78 na 0x68 po odszyfrowaniu. Gdybyśmy zmodyfikowali więcej bitów/bajtów to zadziałało by to tak samo, ale zmian było by również więcej.

Dzięki tej właściwości, nie musimy podawać znaków „;” ani „=” do pierwszej funkcji, wystarczy, żebyśmy wiedzieli które bajty w zaszyfrowanym ciągu zmienić i na jakie wartości, tak żeby druga funkcja po odszyfrowaniu znalazła ciąg „;admin=true;”. Bajt „;” po operacji xor z wartością 0x01 zwraca znak „:”, natomiast „=” zwraca „<„.
„;” ^ 0x01 = „:”
„=” ^ 0x01 = „<„

Wiemy teraz już wszystko, wystarczy przygotować odpowiedni ciąg znaków który przekażemy do pierwszej funkcji a następnie zmodyfikować 3 bajty w zaszyfrowanych danych i przekazać nowe „zaszyfrowane” dane do drugiej funkcji. Moja implementacja wygląda następująco:

def s2challenge16():
    print("Challenge 16 start")
    oracle = OracleCbcBitFlipping()
    user_data = "aaaaaaaaaaaaaaaa"
    user_data += ":admin<true:aaaa"
    secret_data = bytearray(oracle.encryptData(user_data))
    secret_data[32] = secret_data[32] ^ 0x1
    secret_data[38] = secret_data[38] ^ 0x1
    secret_data[43] = secret_data[43] ^ 0x1
    print("IsAdmin: ", oracle.isAdmin(secret_data))

Skąd indeksy 32, 38 i 43? Przed danymi nad którymi mamy kontrolę (przekazujemy do pierwszej funkcji) doklejany jest ciąg znaków o długości 32 bajty, czyli pierwszy kontrolowany przez nas bajt znajduje się właśnie pod indeksem 32. Ja w swojej implementacji przekazuję dane o długości 32 bajty czy dwa bloki danych. Pierwszy blok zostanie uszkodzony po to, żeby w drugim pojawiła się ciąg „;admin=true;”. Drugi ciąg ma wartość: „:admin<true:aaaa” czyli zależy nam na tym, żeby zmodyfikowac bajty 1, 7 i 12 czyli:
31 + 1 = 32,
31 + 7 = 38,
31 + 12 = 43.

Cała implementacja znajduje się tutaj: https://gitlab.com/akoltys/cryptopals .

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *