W tym poście chciałbym opisać rozwiązanie zadania z substitution2 z działu Cryptography.
Do zadania dołączona jest zaszyfrowana wiadomość (poniżej fragment z miejsce gdzie znajduje się flaga):
- ... jxckdisngihjniinfanenkaisncfgmusckntisnexmdctqcuhUIE{K6F4G_4K41R515_15_73A10B5_702E03EU}
Wiadomość powyżej została zakodowana szyfrem zamieniającym miejscami litery na inne. Do dekodowania tej wiadomości należy użyć narzedzia frequency attack. Bądź skorzystać z gotowego narzędzia (https://www.guballa.de/substitution-solver), które odrazu poda odpowiedź.
Jeśli chcemy zastosować podejście ręczne. To w narzędziu frequency alalysis (https://www.101computing.net/frequency-analysis/) wprowadzamy litery jakie wydają się poprawne. Na samym początku należy zacząć od picoCTF:
Kolejnym krokiem jest analizowanie odszyfrowywaneg tekstu i dopasowywanie liter, tak aby tekst miał sens.
Po analizie całości i zapełnieniu większości znaków otrzymamy:
Do dekodowania można się też posłużyć np. takim programem w Pythonie, który działa jak narzędzie online powyżej:
- # Klucz podstawieniowy
- key = {
- 'A': 'D',
- 'B': 'U',
- 'C': 'I',
- 'D': 'G',
- 'E': 'F',
- 'F': 'R',
- 'G': 'G',
- 'H': 'O',
- 'I': 'T',
- 'J': 'B',
- 'K': 'N',
- 'L': '*',
- 'M': 'A',
- 'N': 'E',
- 'O': 'K',
- 'P': 'X',
- 'Q': 'P',
- 'R': 'Y',
- 'S': 'H',
- 'T': 'S',
- 'U': 'C',
- 'V': '*',
- 'W': 'W',
- 'X': 'L',
- 'Y': '*',
- 'Z': 'V'
- }
- # Tekst zaszyfrowany
- cipherText = "<text>"
- key = {**key, **{k.lower(): v.lower() for k, v in key.items()}}
- # Zamiana liter
- decipherText = ''.join([key.get(l, l) for l in cipherText])
- print(decipherText)
Podczas analizy dobrze jest zastępować litery tymi samymi literami. Czyli np klucz po wprowadzeniu picoCTF będzie wyglądał tak:
- key = {
- 'A': '1',
- 'B': '2',
- 'C': 'I',
- 'D': '3',
- 'E': 'F',
- 'F': '4',
- 'G': '5',
- 'H': 'O',
- 'I': 'T',
- 'J': '7',
- 'K': '8',
- 'L': '9',
- 'M': '0',
- 'N': '!',
- 'O': '@',
- 'P': '#',
- 'Q': 'P',
- 'R': '$',
- 'S': '%',
- 'T': '^',
- 'U': 'C',
- 'V': '&',
- 'W': '*',
- 'X': '(',
- 'Y': ')',
- 'Z': 'Z'
- }
Dzięki takiemu podejściu widać jaki znak należy zamienić. Zamiast obliczać pozycję znaku w tekscie zaszyfrowanym.
Przykład w C:
- #include <stdio.h>
- #include <string.h>
- // Funkcja wypełniająca tablicę klucza
- void initialize_key(char *key) {
- // Wypełnij tablicę znakami domyślnymi (identyczne mapowanie)
- for (int i = 0; i < 256; i++) {
- key[i] = i;
- }
- // Klucz podstawieniowy dla wielkich liter
- key['A'] = 'D'; key['B'] = 'U'; key['C'] = 'I'; key['D'] = 'G'; key['E'] = 'F';
- key['F'] = 'R'; key['G'] = 'G'; key['H'] = 'O'; key['I'] = 'T'; key['J'] = 'B';
- key['K'] = 'N'; key['L'] = '*'; key['M'] = 'A'; key['N'] = 'E'; key['O'] = 'K';
- key['P'] = 'X'; key['Q'] = 'P'; key['R'] = 'Y'; key['S'] = 'H'; key['T'] = 'S';
- key['U'] = 'C'; key['V'] = '*'; key['W'] = 'W'; key['X'] = 'L'; key['Y'] = '*';
- key['Z'] = 'V';
- // Klucz podstawieniowy dla małych liter
- key['a'] = 'd'; key['b'] = 'u'; key['c'] = 'i'; key['d'] = 'g'; key['e'] = 'f';
- key['f'] = 'r'; key['g'] = 'g'; key['h'] = 'o'; key['i'] = 't'; key['j'] = 'b';
- key['k'] = 'n'; key['l'] = '*'; key['m'] = 'a'; key['n'] = 'e'; key['o'] = 'k';
- key['p'] = 'x'; key['q'] = 'p'; key['r'] = 'y'; key['s'] = 'h'; key['t'] = 's';
- key['u'] = 'c'; key['v'] = '*'; key['w'] = 'w'; key['x'] = 'l'; key['y'] = '*';
- key['z'] = 'v';
- }
- int main() {
- char key[256];
- initialize_key(key);
- // Tekst zaszyfrowany
- const char *cipherText = "<text>";
- // Bufor na tekst odszyfrowany
- char decipherText[1024];
- size_t i;
- // Deszyfrowanie
- for (i = 0; i < strlen(cipherText) && i < sizeof(decipherText) - 1; i++) {
- decipherText[i] = key[(unsigned char)cipherText[i]];
- }
- decipherText[i] = '\0';
- printf("Decipher text: %s\n", decipherText);
- return 0;
- }
Program tworzy tablicę do której wprowadza litery na jakie ma wykonać zamianę. Indeksem tablicy jest litera (kod ASCII), która zostanie pobrana z tekstu. Można też wykorzystać instruckje switch case., jednak wydaje mi się to mniej czytelne.