Cryptopals zestaw 2 ćwiczenie 13

przez | 9 lipca 2020

Kolejne ćwiczenie w którym „pastwimy się” nad trybem ECB i znów wykorzystujemy właściwość tego trybu, ale tym razem trochę inaczej. W trybie ECB każdy blok danych jest od siebie niezależny dlatego, dane zaszyfrowane można przemieszać i po odszyfrowaniu uzyskamy prawidłowe dane, ale w innej kolejności. Trzeba tylko zadbać o to, żeby mieszać dane bloki danych a nie pojedyncze bajty.

Zanim przejdziemy do rozwiązania samego zadania, musimy przygotować sobie środowisko, które będzie symulowało naszą wyrocznię. Zaczniemy od funkcji parsującej podany ciąg znaków i stworzy z niego słownik. Przykładowo: funkcja po dostaniu na wejściu:

foo=bar&baz=qux&zap=zazzle

Powinna zwrócić:

{
  foo: 'bar',
  baz: 'qux',
  zap: 'zazzle'
}

Moja implementacja wygląda tak:

def urlParamsToJson(url_string):
    urlVariables = url_string.split('&')
    result = {}
    for urlVariable in urlVariables:
        tmpParams = urlVariable.split('=')
        result[tmpParams[0]] = tmpParams[1]
    return result

Najpierw rozbijam łańcuch wejściowy, tak żeby podzielić go na krótsze łańcuchy oddzielone od siebie znakiem &. Następnie każdy taki mniejszy ciąg rozbijany jest na dwa kolejne łańcuchy – 1. to klucz a 2. to wartość. Wszystko dodawane jest do słownika i zwracane jako rezultat. To nie jest kod produkcyjny, więc zmienne nie są sprawdzanie, zakładam że podane dane są prawidłowe i dobrze sformatowane.

Teraz czas na nasza wyrocznię. Jej zadanie jest proste. Na początku generowany jest 128-bitowy klucz szyfrujący. Potem do dyspozycji użytkownika są dostępne dwie metody:
1. getProfile – Dla podanego jako parametr adresu e-mail, dodaje uid oraz rolę, zamienia na postać: [email protected]&uid=10&role=user a następnie szyfruje AES w trybie ECB i zwraca zaszyfrowany profil. Funkcja nie pozwala aby w adresie e-mail znajdowały się znaki & oraz =, są one usuwane.
2. decodeProfile – Odszyfrowuje podany profil parsuje go przy pomocy wcześniej stworzonej funkcji i zwraca wynik.
Poniżej moja implementacja:

class OracleProfileFor(object):
    M_ENCRYPTION_KEY = b''
    
    def __init__(self):
        self.M_ENCRYPTION_KEY = generateRandomData(16)
    
    def getProfile(self, user_mail):
        user_mail = user_mail.replace('&', '').replace('=', '')
        tmpProfile = 'email='+user_mail+'&uid=10&role=user'
        tmpProfile = addPaddingPKCS7(bytearray(tmpProfile, 'ascii'), 16)
        
        cipher = AES.new(self.M_ENCRYPTION_KEY, AES.MODE_ECB)
        return cipher.encrypt(tmpProfile)
    
    def decodeProfile(self, profile_data):
        cipher = AES.new(self.M_ENCRYPTION_KEY, AES.MODE_ECB)
        plain = cipher.decrypt(profile_data)
        return urlParamsToJson(plain.decode('ascii'))

Naszym zadaniem teraz jest sprawienie, że rola przypisana do naszego adresu e-mail to nie 'user’ a 'admin’. Okazuje się, że można to zrobić w bardzo prosty sposób. Wiemy, że przed zaszyfrowaniem nasz profil ma postać: [email protected]&uid=10&role=user. Jeżeli rozbijemy to na bloki o długości 16 bajtów to otrzymamy:
email=adres@mail – pierwszy blok
.com&uid=10&role – drugi blok
=userPPPPPPPPPPP – trzeci blok (P oznacza padding).

Gdybyśmy podali inny adres e-mail:
email=aaaaaaaaaa – pierwszy blok
admin’aaaaaaaaaa – drugi blok
aaaaauser@gmail. – trzeci blok
com&uid=10&role= – czwarty blok
userPPPPPPPPPPPP – piąty blok (P oznacza padding).
Teraz ostatni blok zawiera tylko rolę rpzypisaną do naszego adresu e-mail. Z kolei drugi blok zaczyna się od słowa admin! Bardzo szczęśliwy dla nas zbieg okoliczności. Gdyby tak zamienić je miejscami otrzymamy:
email=aaaaaaaaaa – pierwszy blok
userPPPPPPPPPPPP – drugi blok
aaaaauser@gmail. – trzeci blok
com&uid=10&role= – czwarty blok
admin’aaaaaaaaaa – piąty blok

Pojawia się pewnie problem, padding w ostatnim bloku jest teraz nieprawidłowy, ale przecież mamy wpływ na to jaka jest zawartość tego bloku. Nie mogą w nim występować znaki & oraz =, ale cała reszta jest akceptowalna, gdyby zamienić litery 'a’ po słowie „admin'” na bajty o wartości 0x0a to wszystko się będzie zgadzało. Oczywiście nie możemy zmienić kolejności bloków przed zaszyfrowaniem, ale to nie ma znaczeni – pisałem o tym na początku. Moje rozwiązanie tego zadania:

def s2challenge13():
    print("Challenge 13 start")
    oracle = OracleProfileFor()
    encProf = oracle.getProfile("aaaaaaaaaaadmin'\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\[email protected]")
    decProf = oracle.decodeProfile(encProf[:16]+encProf[32:-16]+encProf[16:32])
    print(decProf, len(decProf))

Kod dostępny jest również tutaj: https://gitlab.com/akoltys/cryptopals .

Dodaj komentarz

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