Настало время подвести итоги мартовских задач на собеседованиях и прославить тех парней, которые их с блеском решили. Спасибо победителю — его решение оказалось настолько подробным, что потянет на целую статью по крякингу. ????
Вот она, золотая пятерка чемпионов!
Главный победитель, Егор Василенко, получил обещанную стипендию на сумму в 100 тысяч и написал офигенное решение задач для нашего журнала, проиллюстрировав его двадцатью девятью листингами. Это просто пир духа, огромное удовольствие для всех, кто интересуется взломом и форензикой!
Сертификаты на меньшие суммы получили:
На рабочем компьютере секретного агента была найдена подозрительная программа, которая крадет данные. Агент не растерялся и снял дамп программы непосредственно перед отправкой зашифрованных данных.
Требуется: найти флаг.
Проведем статический анализ дампа с помощью дизассемблера IDA.
Обрати внимание, что файл был собран с отладочной информацией. Название pdb-файла уже в некотором смысле служит подсказкой:
Формат EntryPoint’а свидетельствует о том, что исполняемый файл написан на языке C++:
Аналогичный вывод можно сделать при обнаружении функции WinMain
, с которой и следует начинать анализ. Функция WinMain
выглядит следующим образом:
В самом начале своей работы исследуемый процесс создает и отображает окно приложения. Параметры окна являются аргументами функции CreateWindowExW
:
Более подробно о сообщениях и очередях сообщений можно узнать здесь. Важнее всего то, что функция обратного вызова TimerFunc
срабатывает каждый раз при вызове функции DispatchMessageW
. Таким образом, анализ дампа сводится к анализу функции TimerFunc
.
Переходим в функцию TimerFunc
. Она описывается следующим псевдокодом:
Вот что здесь происходит:
%TEMP%
;Get_Screen
, Encrypt_Screen
и Send_Screen
(функции GdiplusStartup
и GdiplusShutdown
используются для инициализации Windows GDI+ и окончания работы с Windows GDI+ соответственно).В функции Get_Screen исследуемый процесс делает скриншот экрана и сохраняет его в файл fIsdWlasd.bin
, который должен располагаться в директории %TEMP%
:
После незначительных правок функция Encrypt_Screen
выглядит так:
Чтобы понять, что здесь осуществляется шифрование, зайдем в Encrypt_sub_3B1F50
. В ней трудно не заметить вызовы таких функций, как EVP_CIPHER_CTX_new
, EVP_EncryptInit_ex
и других функций из EVP, которые обеспечивают интерфейс высокого уровня для функций из библиотеки OpenSSL.
Для того чтобы понять, какой алгоритм используется при шифровании, идем сюда:
В функции Set_Cipher_Parameters_sub_CE1260
видим выбор параметров блочного шифра:
А вызывается данная функция следующим образом:
Если сопоставить передаваемые аргументы со значениями внутри Set_Cipher_Parameters_sub_CE1260
, получится структура, где Name = 0 ("AES-")
, Mode = 1 ("CBC")
и KeyLength = 1 ("128-")
:
Делаем вывод, что скриншот экрана шифруется алгоритмом AES-128-CBC с помощью OpenSSL. Ключ шифрования берется из ресурсов:
Его можно без труда получить с помощью программы Resource Hacker или любого hex-редактора:
Теперь мы знаем, что скриншот шифруется алгоритмом AES-128-CBC по известному нам ключу, но этого недостаточно. Для полноты анализа необходимо узнать инициализирующий вектор, который используется в CBC-режиме.
В EVP ключ шифрования и инициализирующий вектор передаются в функцию EVP_EncryptInit_ex
:
Немного поиграв со структурами данных в IDA, можно увидеть, что в качестве инициализирующего вектора злоумышленник использует ключ шифрования:
К слову, так делать нельзя.
В функции Send_Screen
выполняется отправка данных на сервер злоумышленника:
Причем отправляется тот самый зашифрованный файл fIsdWlasd.bin
. Содержимое зашифрованного файла, полученное с помощью функции ReadFile
, находится в переменной buf
:
Так как по условию задания дамп процесса был снят непосредственно перед отправкой зашифрованных данных, содержимое зашифрованного файла все еще находится в памяти процесса:
Помимо этого, злоумышленник оставил нам подсказку в виде размера зашифрованного скриншота:
Сохранить зашифрованный скриншот в файл можно прямо из IDA:
Зная все параметры алгоритма шифрования, мы можем без труда восстановить исходный скриншот из дампа, это и будет наш флаг.
Python-скрипт, с помощью которого можно получить исходный скриншот, я положил сюда. Помимо этого, расшифровать скриншот можно в OpenSSL:
> openssl enc -aes-128-cbc -d -in enc_screen -out dec_screen.jpeg -K 2682377FB2C7898E5EB781D941C09C5A -iv 2682377FB2C7898E5EB781D941C09C5A
А вот и флаг:
В вашем распоряжении оказалась зашифрованная информация в файле flag.enc
. Данный файл зашифрован алгоритмом AES-256-ECB. К счастью, у вас есть ключ от зашифрованного файла key.enc
. Проблема в том, что он зашифрован алгоритмом RSA публичным ключом key.pub
.
Требуется: расшифровать файл flag.enc
и достать флаг.
Посмотрим, что собой представляет публичный ключ RSA:
Видим, что длина модуля составляет 256 бит. C таким модулем ни о какой стойкости криптосистемы говорить не приходится. Первое, что пришло на ум, — факторизовать модуль с помощью YAFU.
Нам удалось получить разложение модуля на простые множители. Этого вполне достаточно, чтобы сгенерировать секретный ключ RSA-256 и расшифровать им key.enc
. В OpenSSL есть возможность генерации секретного ключа RSA с помощью команды asn1parse
. Для этого необходимо знать следующие параметры ключа:
modulus
: модуль, в нашем случае
modulus = 71521286555472299312252291246589709247849481750774732993492805730954699131789
pubExp
: открытая экспонента, в нашем случае
pubExp = 65537
privExp
: секретная экспонента, элемент, обратный к pubExp
по модулю
?(modulus) = (p - 1) ? (q - 1)
p
: первый множитель modulus
, в нашем случае
p = 261857794043121448213410325750790838383
q
: второй множитель modulus
, в нашем случае
q = 273130256889334854338089243490350662083
e1
: элемент, обратный к pubExp
по модулю (p - 1)
;
e2
: элемент, обратный к pubExp
по модулю (q - 1)
;
coeff
: элемент, обратный к q
по модулю p
.
Как правило, для нахождения обратного элемента по модулю используют расширенный алгоритм Евклида. Таким образом, все недостающие параметры можно получить с помощью следующего Python-скрипта. Результат работы скрипта:
Для того чтобы сгенерировать секретный ключ RSA, необходимо создать конфигурационный файл такого содержания:
И выполнить набор команд в OpenSSL:
Здесь мы сгенерировали приватный ключ RSA-256 в формате DER по параметрам, указанным в asn1_struct
, перевели его в формат PEM и записали в файл key.priv
. Теперь ничто не мешает нам расшифровать ключ AES и достать флаг:
Можно заметить, что после расшифровки ключ AES имеет длину не 256, а 128 бит. Поэтому реальный ключ выглядит так:
197b706e1595cf81c533136a2990249700000000000000000000000000000000
В OpenSSL это не имеет значения, но при расшифровке вручную может сыграть определенную роль.
Флаг: Stvgn6CFHKYd3BDE
Честно говоря, я думал, что эти вопросы слишком простые, легко гуглятся и публиковать их смысла нет. По результатам оказалось, что нашлись такие невнимательные товарищи, которые накосячили даже в них. ???? Так что — публикуем правильные ответы (выделены жирным).
Миссия этой мини-рубрики — образовательная, поэтому мы бесплатно публикуем качественные задачки, которые различные компании предлагают соискателям. Вы шлете задачки на lozovsky@glc.ru — мы их публикуем. Никаких актов, договоров, экспертиз и отчетностей. Читателям — задачки, решателям — подарки, вам — респект от нашей многосоттысячной аудитории, пиарщикам — строчки отчетности по публикациям в топовом компьютерном журнале.
Читайте также
Последние новости