poniedziałek, 30 grudnia 2024

PicoCTF - Two Sum

W tym poście opiszę sposób rozwiązania zadania PicoCTF (Capture The Flag link). Zadanie nazywa się Two Sum. 



Zadanie polega na uzyskaniu flagi przez wykonanie przepełnia bufora w programie. Program uruchamiamy po zalogowaniu się na stronie. 

Program działa w następujący sposób:

  1. n1 > n1 + n2 OR n2 > n1 + n2
  2. What two positive numbers can make this possible:
  3. 4000+50000
  4. You entered 4000 and 50000
  5. No overflow

Razem z programem działającym na serwerze otrzymujemy kod programu:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. static int addIntOvf(int result, int a, int b) {
  5.     result = a + b;
  6.     if(a > 0 && b > 0 && result < 0)
  7.         return -1;
  8.     if(a < 0 && b < 0 && result > 0)
  9.         return -1;
  10.     return 0;
  11. }
  12.  
  13. int main() {
  14.     int num1, num2, sum;
  15.     FILE *flag;
  16.     char c;
  17.  
  18.     printf("n1 > n1 + n2 OR n2 > n1 + n2 \n");
  19.     fflush(stdout);
  20.     printf("What two positive numbers can make this possible: \n");
  21.     fflush(stdout);
  22.    
  23.     if (scanf("%d", &num1) && scanf("%d", &num2)) {
  24.         printf("You entered %d and %d\n", num1, num2);
  25.         fflush(stdout);
  26.         sum = num1 + num2;
  27.         if (addIntOvf(sum, num1, num2) == 0) {
  28.             printf("No overflow\n");
  29.             fflush(stdout);
  30.             exit(0);
  31.         } else if (addIntOvf(sum, num1, num2) == -1) {
  32.             printf("You have an integer overflow\n");
  33.             fflush(stdout);
  34.         }
  35.  
  36.         if (num1 > 0 || num2 > 0) {
  37.             flag = fopen("flag.txt","r");
  38.             if(flag == NULL){
  39.                 printf("flag not found: please run this on the server\n");
  40.                 fflush(stdout);
  41.                 exit(0);
  42.             }
  43.             char buf[60];
  44.             fgets(buf, 59, flag);
  45.             printf("YOUR FLAG IS: %s\n", buf);
  46.             fflush(stdout);
  47.             exit(0);
  48.         }
  49.     }
  50.     return 0;
  51. }

Głównie interesuje nas funckja addIntOvf:

  1. static int addIntOvf(int result, int a, int b) {
  2.     result = a + b;
  3.     if(a > 0 && b > 0 && result < 0)
  4.         return -1;
  5.     if(a < 0 && b < 0 && result > 0)
  6.         return -1;
  7.     return 0;
  8. }

Potrzebujemy aby funkcja zwróciła wartość -1. Dzięki temu uda się odczytać flagę z serwera. Aby to wykonać wartość w zmiennej result musi przekroczyć maksymalną wartość dla zmiennej int czyli 2147483647. 

Przepełnienie bufora polega na przekroczeniu maksymalnej wartości zmiennej. W przypadku int jest to zakres od -2147483648 do 2147483647 dla zmiennej zapisanej na 32 bitach. Więc dodając dwie liczby dodatnie, oczekujemy wyniku dodatniego. Jeśli nastąpi przepełnienie bufora to wchodzimy na wartości ujemne. 

Dla przykładu:

  1. n1 > n1 + n2 OR n2 > n1 + n2
  2. What two positive numbers can make this possible:
  3. 2147483647+1000
  4. You entered 2147483647 and 1000
  5. sum: -2147482649
  6. //2147483647+1000−2^32=−2147482649

Wynik po przekroczeniu jest zawijany na część ujemną. Z tego powodu nie otrzymujemy poprawnego wyniku operacji arytmetycznej tylko wchodzimy w zakres liczb ujemnych. 

Wobec tego wystarczy wprowadzić następujące dane:

  1. n1 > n1 + n2 OR n2 > n1 + n2
  2. What two positive numbers can make this possible:
  3. 2147483647+1
  4. You entered 2147483647 and 5000
  5. You have an integer overflow
  6. YOUR FLAG IS: picoCTF{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}

W miejsca xxxx pojawi się poprawna flaga. Celowo ją tutaj zamazałem. 

Program się nie wywala, ze względu na przepełnienie wartości integer, tylko wchodzi w odopowiednią instrukcję warunkową jeśli ono nastąpi. Natomiast warto się przyjrzeć jakie elementy w tym programie mogłyby zostać poprawione. 

Jeśli tworzymy własny program, i chcemy zabezpieczyć go przed takimi zdarzeniami, należy wprowadzić sprawdzenie najlepiej przed rozpoczęciem operacji dodawania. W pierwszej wersji kodu dodawanie było robione dwa razy, bezpośrednio w funkcji addIntOvf oraz przed jej wywołaniem do zmiennej sum. Dodatkowo można usunąć przekazywanie wyniku operacji dodawania ze zmniennej addIntOvf.

  1. if (addIntOvf(num1, num2) == 0) {
  2.     sum = num1 + num2;
  3.     printf("No overflow\n");
  4.     fflush(stdout);
  5.     exit(0);
  6. }

Teraz funkcja addIntOvf mogła by wyglądać w następujący sposób:

  1. #include <limits.h>
  2.  
  3. static int addIntOvf(int a, int b) {
  4.     if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
  5.         return -1; // Overflow detected
  6.     }
  7.     return 0; // No overflow
  8. }

Wobec tego poprawiony program mógłby wyglądać w następujący sposób:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <limits.h>
  4.  
  5. static int addIntOvf(int a, int b) {
  6.   if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
  7.        return -1; // Overflow detected
  8.    }
  9.    return 0; // No overflow
  10. }
  11.  
  12. int main() {
  13.     int num1, num2, sum;
  14.     FILE *flag;
  15.     char c;
  16.  
  17.     printf("n1 > n1 + n2 OR n2 > n1 + n2 \n");
  18.     fflush(stdout);
  19.     printf("What two positive numbers can make this possible: \n");
  20.     fflush(stdout);
  21.  
  22.     if(scanf("%d", &num1) != 1 || scanf("%d", &num2) != 1) {
  23.       printf("Invalid input\n");
  24.       fflush(stdout);
  25.       exit(1);
  26.     }
  27.     else{
  28.       printf("You entered %d and %d\n", num1, num2);
  29.       fflush(stdout);
  30.      
  31.       int retVal = addIntOvf(num1, num2);
  32.    
  33.       if (retVal == 0) {
  34.           sum = num1 + num2;
  35.           printf("sum: %d\n", sum);
  36.           printf("No overflow\n");
  37.           fflush(stdout);
  38.           exit(0);
  39.       } else if (retVal == -1) {
  40.           printf("You have an integer overflow\n");
  41.           fflush(stdout);
  42.       }
  43.  
  44.       if (num1 > 0 || num2 > 0) {
  45.           flag = fopen("flag.txt","r");
  46.           if(flag == NULL){
  47.               printf("flag not found: please run this on the server\n");
  48.               fflush(stdout);
  49.               exit(1);
  50.           }
  51.           char buf[60];
  52.           fgets(buf, sizeof(buf), flag);
  53.           printf("YOUR FLAG IS: %s\n", buf);
  54.           fflush(stdout);
  55.           exit(0);
  56.       }
  57.     }
  58.    
  59.     return 0;
  60. }