Zawody picoCTF 2017 już dawno za nami ale nic nie stoi na przeszkodzie by spróbować swoich sił w tej zabawie dla początkujących pentesterów. W tym wpisie opiszę mój sposób rozwiązania jednego z wielu czekających na zawodników ciekawych zadań: [LEVEL 2] SoRandom.
Treść zadania:
SoRandomWe found sorandom.py running at shell2017.picoctf.com:23741. It seems to be outputting the flag but randomizing all the characters first. Is there anyway to get back the original flag?Update (text only) 16:16 EST 1 Apr Running python 2 (same version as on the server)HINTSHow random can computers be?
#!/usr/bin/python -u import random,string flag = "FLAG:"+open("flag", "r").read()[:-1] encflag = "" random.seed("random") for c in flag: if c.islower(): #rotate number around alphabet a random amount encflag += chr((ord(c)-ord('a')+random.randrange(0,26))%26 + ord('a')) elif c.isupper(): encflag += chr((ord(c)-ord('A')+random.randrange(0,26))%26 + ord('A')) elif c.isdigit(): encflag += chr((ord(c)-ord('0')+random.randrange(0,10))%10 + ord('0')) else: encflag += c print "Unguessably Randomized Flag: "+encflag
- Skrypt iteruje po każdym znaku naszej flagi, następnie generuje numer w tablicy ASCII danego znaku i odejmuje numer pierwszego znaku tak aby otrzymać numer znaku liczony od zera np.: „h” ma numer 104 więc odejmujemy 97 (numer znaku „a”) i mamy 7 gdyż „h” to siódmy znak alfabetu licząc od zera.
- Następnie do numeru naszego znaku dodawana jest „losowa” liczba z zakresu od zera do 26 (26 małych liter w alfabecie łacińskim) a następnie używa operatora modulo na wypadek przekroczenia zakresu. np. dla litery „h” ma numer 7, jeśli dodamy 25 będziemy poza zakresem więc 32%26=6 czyli jedziemy od początku.
- Na końcu do naszego nowego „losowego” znaku dodajemy spowrotem 99 aby znów mieć poprawny numer z tabeli ASCII.
Odwrócenie tego procesu wydaje się dość proste, wystarczy od numeru ASCII zakodowanej litery odjąć naszą „losową” liczbę generowaną przed każdą iteracją (z uwzględnieniem możliwości wyjścia poza dolny zakres).
Kluczowa jest tu funkcja random.seed() która jest używana do losowania liczby szyfrującej. Jak czytamy w manulu python’a:
The pseudo-random generators of this module should not be used for security purposes. Use
os.urandom()
orSystemRandom
if you require a cryptographically secure pseudo-random number generator.
Funkcja ta nie jest bezpieczna, ów losowa liczba zwracana przez nią nie jest do końca losowa. W zasadzie dla podanego takiego samego seed’a jest zawsze taka sama dla każdego kolejnego wywołania. Jesteśmy zatem w domu jeśli znamy o jaką liczbę jest zmieniany kod ASCII każdego ze znaków w naszej fladze. Wystarczy odwrócić proces odejmując tą liczbę.
#!/usr/bin/python -u import random,string flag = "BNZQ:1l36de9583w5516fv3b8691102224f3e" decflag = "" random.seed("random") for c in flag: if c.islower(): decflag += chr((ord(c)-97-random.randrange(0,26))%26 + 97) elif c.isupper(): decflag += chr((ord(c)-65-random.randrange(0,26))%26 + 65) elif c.isdigit(): decflag += chr((ord(c)-48-random.randrange(0,10))%10 + 48) else: decflag += c print "Flag: "+decflag