poniedziałek, 3 marca 2025

picoCTF - Flag shop

W tym poście chciałbym opisać zadanie Flag_shop z działu General Skills Pico CTF. 


Po otwarciu programu wyświetlają się następujące opcje:

  1. picoctf@webshell:~$ nc jupiter.challenges.picoctf.org 9745
  2. Welcome to the flag exchange
  3. We sell flags
  4.  
  5. 1. Check Account Balance
  6.  
  7. 2. Buy Flags
  8.  
  9. 3. Exit

Można tutaj wybrać jedną z trzech opcji. 

Sprawdzenie stanu konta:

  1. Enter a menu selection
  2. 1    
  3.  
  4.  
  5.  
  6.  Balance: 1100
  7.  
  8.  
  9. Welcome to the flag exchange
  10. We sell flags
  11.  
  12. 1. Check Account Balance
  13.  
  14. 2. Buy Flags
  15.  
  16. 3. Exit
  17.  
  18.  Enter a menu selection

Zakup flagi:

  1. Enter a menu selection
  2. 2
  3. Currently for sale
  4. 1. Defintely not the flag Flag
  5. 2. 1337 Flag

lub wyjście. 

Pierwsza z flag, czyli flagi "pomocnicze", druga jest flagą którą należy znaleźć aby wykonać zadanie. 
Wobec tego modyfikacji stanu konta możemy dokonać przez odpowiedni zakup flag pomocniczych.

Do projektu dołączono kod źródłowy:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5.     setbuf(stdout, NULL);
  6.     int con;
  7.     con = 0;
  8.     int account_balance = 1100;
  9.     while(con == 0){
  10.        
  11.         printf("Welcome to the flag exchange\n");
  12.         printf("We sell flags\n");
  13.  
  14.         printf("\n1. Check Account Balance\n");
  15.         printf("\n2. Buy Flags\n");
  16.         printf("\n3. Exit\n");
  17.         int menu;
  18.         printf("\n Enter a menu selection\n");
  19.         fflush(stdin);
  20.         scanf("%d", &menu);
  21.         if(menu == 1){
  22.             printf("\n\n\n Balance: %d \n\n\n", account_balance);
  23.         }
  24.         else if(menu == 2){
  25.             printf("Currently for sale\n");
  26.             printf("1. Defintely not the flag Flag\n");
  27.             printf("2. 1337 Flag\n");
  28.             int auction_choice;
  29.             fflush(stdin);
  30.             scanf("%d", &auction_choice);
  31.             if(auction_choice == 1){
  32.                 printf("These knockoff Flags cost 900 each, enter desired quantity\n");
  33.                 int number_flags = 0;
  34.                 fflush(stdin);
  35.                 scanf("%d", &number_flags);
  36.                 if(number_flags > 0){
  37.                     int total_cost = 0;
  38.                     total_cost = 900*number_flags;
  39.                     printf("\nThe final cost is: %d\n", total_cost);
  40.                     if(total_cost <= account_balance){
  41.                         account_balance = account_balance - total_cost;
  42.                         printf("\nYour current balance after transaction: %d\n\n", account_balance);
  43.                     }
  44.                     else{
  45.                         printf("Not enough funds to complete purchase\n");
  46.                     }
  47.                 }
  48.             }
  49.             else if(auction_choice == 2){
  50.                 printf("1337 flags cost 100000 dollars, and we only have 1 in stock\n");
  51.                 printf("Enter 1 to buy one");
  52.                 int bid = 0;
  53.                 fflush(stdin);
  54.                 scanf("%d", &bid);
  55.                
  56.                 if(bid == 1){
  57.                    
  58.                     if(account_balance > 100000){
  59.                         FILE *f = fopen("flag.txt", "r");
  60.                         if(f == NULL){
  61.  
  62.                             printf("flag not found: please run this on the server\n");
  63.                             exit(0);
  64.                         }
  65.                         char buf[64];
  66.                         fgets(buf, 63, f);
  67.                         printf("YOUR FLAG IS: %s\n", buf);
  68.                         }
  69.                    
  70.                     else{
  71.                         printf("\nNot enough funds for transaction\n\n\n");
  72.                     }}
  73.  
  74.             }
  75.         }
  76.         else{
  77.             con = 1;
  78.         }
  79.  
  80.     }
  81.     return 0;
  82. }

Aby rozwiązać to zadanie należy przyjrzeć temu fragmentowi kodu z programu powyżej:

  1. if(auction_choice == 1){
  2.     printf("These knockoff Flags cost 900 each, enter desired quantity\n");
  3.                
  4.     int number_flags = 0;
  5.     fflush(stdin);
  6.     scanf("%d", &number_flags);
  7.     if(number_flags > 0){
  8.         int total_cost = 0;
  9.         total_cost = 900*number_flags;
  10.         printf("\nThe final cost is: %d\n", total_cost);
  11.         if(total_cost <= account_balance){
  12.             account_balance = account_balance - total_cost;
  13.             printf("\nYour current balance after transaction: %d\n\n", account_balance);
  14.         }
  15.         else{
  16.             printf("Not enough funds to complete purchase\n");
  17.         }
  18. }
  19. }

Tutaj widać, że do total_cost podawana jest wartość 900 * number_flags. Następnie wartości total_cost jest porównywana z zmienną account_balance. Główna flaga kosztuje 100000, więc należy przez przepełnienie zmiennych uzyskać odpowiednią wartość w zmiennej account_balance. 

Sprawdzę dla jakiego zakresu danych wejściowych, operacje będą poprawne:

  1. #include <stdio.h>
  2. #include <stdint.h>
  3.  
  4. int main()
  5. {
  6.     int testNumberOfFlags[] = {
  7.         1,10,50,100,1000,10000,1000000,1500000, 2386093, 2486093, 2484993, 3000000, 4000000, 4500000, 4750000, 4755000, 4760000, 4770000, 4771000, 5000000
  8.     };
  9.    
  10.     int auction_choice = 1;
  11.     int account_balance = 1100;
  12.    
  13.     printf("Array SIzeof%ld", sizeof(testNumberOfFlags)/sizeof(int));
  14.    
  15.     for(uint8_t i = 0; i<sizeof(testNumberOfFlags)/sizeof(int); i++) {
  16.         account_balance = 1100;
  17.         if(auction_choice == 1){
  18.             int number_flags = testNumberOfFlags[i];;
  19.             //scanf("%d", &number_flags);
  20.             if(number_flags > 0){
  21.                 int total_cost = 0;
  22.                 total_cost = 900*number_flags;
  23.                 printf("[%u] The final cost is: %d ", i, total_cost);
  24.                 if(total_cost <= account_balance){
  25.                     account_balance = account_balance - total_cost;
  26.                     printf("CORRECT, account_balance %d, number_flags %d, CONDITION %u\n", account_balance, number_flags, (account_balance >= 10000));
  27.                 }
  28.                 else{
  29.                     printf("WRONG\n");
  30.                 }
  31.             }
  32.         }
  33.     }
  34.  
  35.     return 0;
  36. }

Wprowadziłem do niego testowe wartości. Wynik działania programu jest następujący:

  1. [0] The final cost is: 900 CORRECT, account_balance 200, number_flags 1, CONDITION 0
  2. [1] The final cost is: 9000 WRONG
  3. [2] The final cost is: 45000 WRONG
  4. [3] The final cost is: 90000 WRONG
  5. [4] The final cost is: 900000 WRONG
  6. [5] The final cost is: 9000000 WRONG
  7. [6] The final cost is: 900000000 WRONG
  8. [7] The final cost is: 1350000000 WRONG
  9. [8] The final cost is: -2147483596 CORRECT, account_balance -2147482600, number_flags 2386093, CONDITION 0
  10. [9] The final cost is: -2057483596 CORRECT, account_balance 2057484696, number_flags 2486093, CONDITION 1
  11. [10] The final cost is: -2058473596 CORRECT, account_balance 2058474696, number_flags 2484993, CONDITION 1
  12. [11] The final cost is: -1594967296 CORRECT, account_balance 1594968396, number_flags 3000000, CONDITION 1
  13. [12] The final cost is: -694967296 CORRECT, account_balance 694968396, number_flags 4000000, CONDITION 1
  14. [13] The final cost is: -244967296 CORRECT, account_balance 244968396, number_flags 4500000, CONDITION 1
  15. [14] The final cost is: -19967296 CORRECT, account_balance 19968396, number_flags 4750000, CONDITION 1
  16. [15] The final cost is: -15467296 CORRECT, account_balance 15468396, number_flags 4755000, CONDITION 1
  17. [16] The final cost is: -10967296 CORRECT, account_balance 10968396, number_flags 4760000, CONDITION 1
  18. [17] The final cost is: -1967296 CORRECT, account_balance 1968396, number_flags 4770000, CONDITION 1
  19. [18] The final cost is: -1067296 CORRECT, account_balance 1068396, number_flags 4771000, CONDITION 1
  20. [19] The final cost is: 205032704 WRONG

Oznacza to, że do uzyskania poprawnych danych w account_balance należy wprowadzić wartości z zakresu od 2486093 do 4771000.

  1. picoctf@webshell:~$ nc jupiter.challenges.picoctf.org 9745
  2. Welcome to the flag exchange
  3. We sell flags
  4. 1. Check Account Balance
  5. 2. Buy Flags
  6. 3. Exit
  7.  
  8.  Enter a menu selection
  9. 2
  10.  
  11. Currently for sale
  12. 1. Defintely not the flag Flag
  13. 2. 1337 Flag
  14. 1
  15. These knockoff Flags cost 900 each, enter desired quantity
  16. 2486093
  17.  
  18. The final cost is: -2057483596
  19.  
  20. Your current balance after transaction: 2057484696
  21.  
  22. Welcome to the flag exchange
  23. We sell flags
  24. 1. Check Account Balance
  25. 2. Buy Flags
  26. 3. Exit
  27.  
  28.  Enter a menu selection
  29. 1
  30.  
  31.  Balance: 2057484696
  32.  
  33. Welcome to the flag exchange
  34. We sell flags
  35. 1. Check Account Balance
  36. 2. Buy Flags
  37. 3. Exit
  38.  
  39.  Enter a menu selection
  40. 2
  41. Currently for sale
  42. 1. Defintely not the flag Flag
  43. 2. 1337 Flag
  44. 2
  45. 1337 flags cost 100000 dollars, and we only have 1 in stock
  46. Enter 1 to buy one 1

  47. YOUR FLAG IS: picoCTF{<flaga>}

  48. Welcome to the flag exchange
  49. We sell flags
  50.  
  51. 1. Check Account Balance
  52.  
  53. 2. Buy Flags
  54.  
  55. 3. Exit
  56.  
  57.  Enter a menu selection
Wartości przedstawione powyżej nie są dokładnymi granicami, ale pozwalają na uzyskanie "poprawnego" przepełnienia zmiennej i otrzymania porządanego wyniku. 

W celu zabezpieczenia się przed takimi scenariuszami w własnych programach należy sprawdząc czy zmienna total_cost nie zostanie przepełniona.

Jednym ze sposobów aby to osiągnąć jest zwiększenie typu danych z 32 bitowego na 64 bitowy, wykonanie mnożenia i sprawdzenie czy przekroczono zakres INT_MAX:

  1. long long temp_cost = 900LL * number_flags;  //uint64_t
  2. if (temp_cost > INT_MAX) {
  3.     //OVERFLOW
  4. }

Drugi sposób polega na sprawdzeniu danych przed wykonaniem mnożenia:

  1. int total_cost = 0;
  2.  
  3. if (number_flags > INT_MAX / 900) {
  4.     //OVERFLOW
  5. }

Trzecim sposobem może być np. walidacja danych wejściowych, gdzie po otrzymaniu ilości flag należy sprawdzić czy wartość nie jest za duża.