poniedziałek, 13 stycznia 2025

PicoCTF - babygame01

W tym poście chciałbym opisać rozwiązanie zadania babygame01 z picoCTF


Wykorzystam dekompiler online. W nim wybrałem Ghirda i Hex-Rays. 

Po sprawdzeniu kodu znajduje funkcje odpowiedzialną za obsługę klawiszy w grze:

  1. void move_player(int *param_1,char param_2,int param_3)
  2.  
  3. {
  4.   int iVar1;
  5.  
  6.   if (param_2 == 'l') {
  7.     iVar1 = getchar();
  8.     player_tile = (undefined)iVar1;
  9.   }
  10.   if (param_2 == 'p') {
  11.     solve_round(param_3,param_1);
  12.   }
  13.   *(undefined *)(*param_1 * 0x5a + param_3 + param_1[1]) = 0x2e;
  14.   if (param_2 == 'w') {
  15.     *param_1 = *param_1 + -1;
  16.   }
  17.   else if (param_2 == 's') {
  18.     *param_1 = *param_1 + 1;
  19.   }
  20.   else if (param_2 == 'a') {
  21.     param_1[1] = param_1[1] + -1;
  22.   }
  23.   else if (param_2 == 'd') {
  24.     param_1[1] = param_1[1] + 1;
  25.   }
  26.   *(undefined *)(*param_1 * 0x5a + param_3 + param_1[1]) = player_tile;
  27.   return;
  28. }

Funkcja wprowadza parametry param_1 i param_3 bezpośrednio do obliczania kolejnego położenia gracza. W powyższym kodzie nie widać żadnej metody weryfikacji obliczonej pozycji. 

Jak można zaobserwować powyżej są dwie dodatkowe opcje w menu czyli klawisze 'p' oraz 'l'. Pierwszy nich przenosi gracze na pole zwycięzcy. Drugi pozwala na zmianę litery gracza. Żaden z nich nie zapewnia wygranej. 

Funckja odpowiadająca za inicjalizację mapy:

  1. void init_map(int param_1,int *param_2)
  2. {
  3.   int local_10;
  4.   int local_c;
  5.  
  6.   for (local_10 = 0; local_10 < 0x1e; local_10 = local_10 + 1) {
  7.     for (local_c = 0; local_c < 0x5a; local_c = local_c + 1) {
  8.       if ((local_10 == 0x1d) && (local_c == 0x59)) {
  9.         *(undefined *)(param_1 + 0xa8b) = 0x58;
  10.       }
  11.       else if ((local_10 == *param_2) && (local_c == param_2[1])) {
  12.         *(undefined *)(local_c + param_1 + local_10 * 0x5a) = player_tile;
  13.       }
  14.       else {
  15.         *(undefined *)(local_c + param_1 + local_10 * 0x5a) = 0x2e;
  16.       }
  17.     }
  18.   }
  19.   return;
  20. }

Odczytanie flagi:

  1. if (local_aa4 != '\0') {
  2.    puts("flage");
  3.    win();
  4.    fflush(_stdout);
  5. }

Czyli zmienna local_aa4 musi być różna od zera w celu odczytania flagi. 

  1. char local_aa4;
  2. undefined local_aa0 [2700];

Wynika z tego, że zmienna local_aa4 jest zapisana na stosie za zmienna local_aa0. Tablica local_aa0 jest tablicą wykorzystywaną do inicjalizacji planszy (29x89 z zapasem). Wobec tego aby wprowadzić dane do wartości local_aa4, tak aby nie była wartością zerową, należy cofnąć się poza plansze. Dokładnie poniżej punktu zerowego. 


Czyli z tej pozycji należy przesunąć się o cztery pozycje w tył klawiszem 'a'. A następnie w celu szybszego wygrania gry, użyć klawisza 'p'. 


Teraz zmienna z wartością flaga zmieniła wartość z 0 na 64. 

Po wprowadzeniu litery 'p' gracz przechodzi na pozycje koniec i dostaje flagę: