piątek, 17 stycznia 2025

picoCTF - Substitution2

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):

  1. ... 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:

  1. # Klucz podstawieniowy
  2. key = {
  3.     'A': 'D',
  4.     'B': 'U',
  5.     'C': 'I',
  6.     'D': 'G',
  7.     'E': 'F',
  8.     'F': 'R',
  9.     'G': 'G',
  10.     'H': 'O',
  11.     'I': 'T',
  12.     'J': 'B',
  13.     'K': 'N',
  14.     'L': '*',
  15.     'M': 'A',
  16.     'N': 'E',
  17.     'O': 'K',
  18.     'P': 'X',
  19.     'Q': 'P',
  20.     'R': 'Y',
  21.     'S': 'H',
  22.     'T': 'S',
  23.     'U': 'C',
  24.     'V': '*',
  25.     'W': 'W',
  26.     'X': 'L',
  27.     'Y': '*',
  28.     'Z': 'V'
  29. }
  30.  
  31. # Tekst zaszyfrowany
  32. cipherText = "<text>"
  33.  
  34. key = {**key, **{k.lower(): v.lower() for k, v in key.items()}}
  35.  
  36. # Zamiana liter
  37. decipherText = ''.join([key.get(l, l) for l in cipherText])
  38. print(decipherText)

Podczas analizy dobrze jest zastępować litery tymi samymi literami. Czyli np klucz po wprowadzeniu picoCTF będzie wyglądał tak:

  1. key = {
  2.     'A': '1',
  3.     'B': '2',
  4.     'C': 'I',
  5.     'D': '3',
  6.     'E': 'F',
  7.     'F': '4',
  8.     'G': '5',
  9.     'H': 'O',
  10.     'I': 'T',
  11.     'J': '7',
  12.     'K': '8',
  13.     'L': '9',
  14.     'M': '0',
  15.     'N': '!',
  16.     'O': '@',
  17.     'P': '#',
  18.     'Q': 'P',
  19.     'R': '$',
  20.     'S': '%',
  21.     'T': '^',
  22.     'U': 'C',
  23.     'V': '&',
  24.     'W': '*',
  25.     'X': '(',
  26.     'Y': ')',
  27.     'Z': 'Z'
  28. }

Dzięki takiemu podejściu widać jaki znak należy zamienić. Zamiast obliczać pozycję znaku w tekscie zaszyfrowanym.

Przykład w C: 

  1. #include <stdio.h>
  2. #include <string.h>
  3.  
  4. // Funkcja wypełniająca tablicę klucza
  5. void initialize_key(char *key) {
  6.     // Wypełnij tablicę znakami domyślnymi (identyczne mapowanie)
  7.     for (int i = 0; i < 256; i++) {
  8.         key[i] = i;
  9.     }
  10.    
  11.     // Klucz podstawieniowy dla wielkich liter
  12.     key['A'] = 'D'; key['B'] = 'U'; key['C'] = 'I'; key['D'] = 'G'; key['E'] = 'F';
  13.     key['F'] = 'R'; key['G'] = 'G'; key['H'] = 'O'; key['I'] = 'T'; key['J'] = 'B';
  14.     key['K'] = 'N'; key['L'] = '*'; key['M'] = 'A'; key['N'] = 'E'; key['O'] = 'K';
  15.     key['P'] = 'X'; key['Q'] = 'P'; key['R'] = 'Y'; key['S'] = 'H'; key['T'] = 'S';
  16.     key['U'] = 'C'; key['V'] = '*'; key['W'] = 'W'; key['X'] = 'L'; key['Y'] = '*';
  17.     key['Z'] = 'V';
  18.  
  19.     // Klucz podstawieniowy dla małych liter
  20.     key['a'] = 'd'; key['b'] = 'u'; key['c'] = 'i'; key['d'] = 'g'; key['e'] = 'f';
  21.     key['f'] = 'r'; key['g'] = 'g'; key['h'] = 'o'; key['i'] = 't'; key['j'] = 'b';
  22.     key['k'] = 'n'; key['l'] = '*'; key['m'] = 'a'; key['n'] = 'e'; key['o'] = 'k';
  23.     key['p'] = 'x'; key['q'] = 'p'; key['r'] = 'y'; key['s'] = 'h'; key['t'] = 's';
  24.     key['u'] = 'c'; key['v'] = '*'; key['w'] = 'w'; key['x'] = 'l'; key['y'] = '*';
  25.     key['z'] = 'v';
  26. }
  27.  
  28. int main() {
  29.     char key[256];
  30.     initialize_key(key);
  31.  
  32.     // Tekst zaszyfrowany
  33.     const char *cipherText = "<text>";
  34.  
  35.     // Bufor na tekst odszyfrowany
  36.     char decipherText[1024];
  37.     size_t i;
  38.  
  39.     // Deszyfrowanie
  40.     for (i = 0; i < strlen(cipherText) && i < sizeof(decipherText) - 1; i++) {
  41.         decipherText[i] = key[(unsigned char)cipherText[i]];
  42.     }
  43.     decipherText[i] = '\0';
  44.  
  45.     printf("Decipher text: %s\n", decipherText);
  46.  
  47.     return 0;
  48. }

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.