Альтернативные методы решения задач.
Сегодня я хочу рассказать об альтернативном методе решения задач. Иногда у нас есть возможность пойти не тем путём, которого от нас хочет автор задачи, а более лёгким и простым. Объясню на примере.
Рассмотрим программу, требующую ввода правильного серийного номера для выдачи флага:
Наша задача - сгенерировать корректный серийный номер и получить флаг.
Я тут уже всё преобразовал, переименовал и оставил комментарии. И здесь надо внимательно посмотреть на структуру 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() с маленькими правками.
Зачем это было сделано? У меня не так много догадок:
Не знаю что из этого правда, думайте сами, решайте сами. Конечно, меня это не устроило и я также решил задачу "в лоб".
Нас интересует функция 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; }
Попытался немного украсить вывод, чтобы не казался скучным.
Настало время проверить работает ли моё решение. Запустим бинарь и получим логин с идентификатором:
Теперь скормим это генератору:
Генератор дал мне серийный номер, теперь закинем его в бинарь:
В итоге мы тоже получили флаг.
Да, первое решение было очень лёгким, в реальной работе явно стоило бы пойти именно этим путём, наиболее лёгким и быстрым, но всё же это учебная задача, а значит надо разбирать всё и прорабатывать все пути решения. Задача на самом деле простая, но хотелось рассказать какие иногда вещи я встречаю по ходу обучения.