October 10

Альтернативные методы решения задач.

Сегодня я хочу рассказать об альтернативном методе решения задач. Иногда у нас есть возможность пойти не тем путём, которого от нас хочет автор задачи, а более лёгким и простым. Объясню на примере.

Рассмотрим программу, требующую ввода правильного серийного номера для выдачи флага:

Наша задача - сгенерировать корректный серийный номер и получить флаг.

Открываем бинарь в IDA:

Я тут уже всё преобразовал, переименовал и оставил комментарии. И здесь надо внимательно посмотреть на структуру main() - нам доступна функция, которая проверяет серийный номер и функция, которая печатает непосредственно флаг.

О чём это говорит? Мы можем проигнорировать вообще всё, что написано в программе и попробовать напрямую слепить флаг. Идём в функцию print_flag() и смотрим что там написано:

Здесь я уже выполнил необходимые преобразования, оставил комментарии и увидел простую функцию, которую легко можно воспроизвести. Осталось только понять что такое массив Table. Посмотрим как он выглядит в памяти:

Это буквально массив, содержащий строки. Теперь мы знаем как лепится флаг и знаем, что нам для этого не надо разбирать всю остальную программу.

Давайте слепим флаг, для этого я написал такой код:

#include <stdio.h>


char strings[100][16] = {"a986cfb376bc1eca","eeae58789739434b","8ccbdab72388543c","099324313a90a684","5ff502a74d037127","f25b64eb9463a210","5c66920c4ad8b8a7","c10b077e446297aa","819b5965f4bb1543","45e8a738a69078bb","53f89ec7a761e847","5c626a731ce1db7b","b7e7ec135de4aaa5","e94dd655500e7d1f","5a4345adb584aab2","ce6aaedda379445e","104586c6b26543cb","49756878a66b42fb","db44c8bb438f34f0","77a9ac563618a927","1b39568c0344b98a","69e122e50a2f7646","d9605a06af10eb01","d21c60e2b087e68a","a149ccbdb718c616","8fc73a9d6de88c8b","54e351b4e1988d67","97384ddd0fa2d8bb","5074d5335287a269","619682f98122af1c","ad803bf5c5bf291e","a19b0a4e74547840","9ff6498f54b89184","fa4678c432662973","cd104b7f0e2df347","c54d3de0a84e3733","b36b5a9aca292a91","6b062acd74f1ee62","eee1f5987ec4f64c","3b5b8153c17f4ce3","85a05a5a27294416","a1823e11e919c96f","2a9ab4b71458c7ca","1b99373085e985f4","1504ca81c13a9b2e","ca6d238ad61485f7","6db184ad1b6900c6","af9a89d528c4cb3d","d968990fe8fe6838","9ba258b94fe9d5da","0d7baaf48dac9753","c2e69d684f39e751","081aec3cdfd02fc3","f08c54920ae43a97","355c08de684a1b61","60192972d8f3b192","97847010696c4ce5","3773c3fe51d98d2c","b41f9ef839301a52","caafa65b147b0751","902c36b466ef31c1","957ba36c36ebb006","7481d108c7f00e95","93b790bb0caba9b7","9a9b6ed3ad0d916f","6179625d74d5fd93","c9e363dc1e36894c","c03a7b264113a98e","fd20aa77cda8a092","a12be1c5df373aa6","6b4850a9717b94d2","06158f4f8255e8e4","30252f877a724306","1687f0773869db9b","6590e823fb5081fc","ffc34698539188cd","d063e5477732ed97","214c919ce9feda44","749bfe86ac338d35","c638d337e6750a17","5e5fa0a71e7735cd","e2082ff5be0c42ee","e9f874cbda15bae1","8b7f971c25c587f6","80483cb0c6bc88cb","59b69c545ec4669d","1c6cea6e62754c92","eb4e6bae536b74f2","9ac792d2f900a7a6","911373d4376f9aa5","2077032cc637b27f","a26a2af67cbedc28","59d858e347fc5202","d614532773a3f0e4","f1e14148f0d35377","6510df2fc1da22e5","20891d00ba6d1c7d","6cf8b608b57a33cf","a9b1a2ca33b03647","ceb04128077f7698"};


int main(){

    int flag;

    printf("FLAG{");
    
    for(int i = 0; i <= 15; ++i) {
        
        flag = 0;
        
        for(int j = 0; j <= 99; ++j)
            flag ^= strings[j][i];
            
        printf("%02x", flag);

    }

    puts("}");

    return 0;
}

В целом, он просто повторяет функцию print_flag() с маленькими правками.

Запускаем и ловим флаг:

Зачем это было сделано? У меня не так много догадок:

  • Специально для неосиляторов
  • Бонус для внимательных
  • Это просто факап автора

Не знаю что из этого правда, думайте сами, решайте сами. Конечно, меня это не устроило и я также решил задачу "в лоб".

Ещё раз глянем на main():

Нас интересует функция check(). Функции gen_login(), gen_uid() можно не смотреть, потому что там всё равно данные генерируются рандомно, надо работать с тем, что сгенерируется.

Я провёл все необходимые преобразования и оставил комментарии.

В целом, мы также без труда можем написать генератор серийного номера на основе логина и идентификатора, который мы получим при запуске этого бинаря.

Я написал такой код:

#include <stdio.h>
#include <string.h>

int gen_serial_number(char *login, char *uid){

        char key[64];
        char *key_address;

        memset(key, 0, sizeof(key));

        key_address = key;

        printf("\n[*] Drinking some tea...\n");
        printf("[*] Rolling up our sleeves...\n");
        printf("[*] Generating a key...\n");

        for(int i = 0; (strlen(login) - 1) > i; ++i){

                sprintf(key_address, "%02x", (login[i] ^ uid[i]) + 7);
                key_address += 2;

        }

        printf("[+] Done! Your key is: %s\n", key);

}

int main(){

        char login[32];
        char uid[32];


        printf("[*] Please, input login: ");
        fgets(login, 32, stdin);
        printf("\n[+] Login was sucsessfuly saved!\n");
        printf("[*] Please, input UID: ");
        fgets(uid, 32, stdin);
        printf("\n[+] UID was sucsessfuly saved!");

        gen_serial_number(login, uid);

        return 0;
}

Попытался немного украсить вывод, чтобы не казался скучным.

Настало время проверить работает ли моё решение. Запустим бинарь и получим логин с идентификатором:

Теперь скормим это генератору:

Генератор дал мне серийный номер, теперь закинем его в бинарь:

В итоге мы тоже получили флаг.

Да, первое решение было очень лёгким, в реальной работе явно стоило бы пойти именно этим путём, наиболее лёгким и быстрым, но всё же это учебная задача, а значит надо разбирать всё и прорабатывать все пути решения. Задача на самом деле простая, но хотелось рассказать какие иногда вещи я встречаю по ходу обучения.

Спасибо за внимание!