Author: @greg0r0
Вот вам флаг. Бесплатно! Вам нужно только установить его.
ptctf{w3ll_l0c4l_ch3ck_1s_b4d_1d34}
Перед нами обычный необфусцированный установщик флага без антидебага. Написан на C#+WinForms. Это означает что задача обратной разработки сводится к чтению исходного кода после декомпиляции при помощи специализированной тулзы. Один из лучших инструментов для этой задачи - dnspyEx. Правда при первоначальном анализе окажется что это классический бинарь PE/x64, так как проект скомпилирован в один исполняемый файл с данными в оверлее, в том числе библиотека, которую откроем в dnspy, лежит там же.
Но первым шагом нам надо вытащить из оверлея собственно dll с кодом на шарпах. Это можно сделать различными способами, например использовать binwalk -e
. Или использовать DetectItEasу и использовать функцию экстрактора.
Интерес представляют файлы Form1.cs и LicenseKeyCheck.cs - в них основная и интересующая нас логика.
Из кода Form1.cs узнаем, что у нас есть четыре стадии установки - лицензия, лицензионный ключ, установка и завершение установки.
Нам важно, как проверяется лицензионный ключ - его реализация находится в класе LicenseKeyCheck
. Формально, класс делится на две части - проверку частей ключа и генерацию данных флага.
Из функций генерации данных флага можно увидеть, что первая часть (ptctf{
) - просто лежит байтами в классе StaticStorage, а вот остальные части флага лежат там же, но зашифрованные примитивным четырехбайтовым ксором. В теории можно побрутить, так как всего 4 байта в 2023 году перебрать можно очень быстро. Но - это не интересно и особо не имеет смысла, т.к. семантика флага нам не известна и в таком случае придется брутить скорборд неправильными флагами. Поэтому наша цель - проанализировать первую часть чекера и выяснить алгоритмы проверки ключа.
Нулевой шаг находится в коде формы, так как в конструктор флага данные из форм передаются в другом порядке. Получается, нулевой шаг - это формирование ключа методом перестановки:
...
/*
1 2 3 4
V
3 2 4 1
*/
string data_part1 = textBox_Lkey_3.Text.Trim();
string data_part2 = textBox_Lkey_2.Text.Trim();
string data_part3 = textBox_Lkey_4.Text.Trim();
string data_part4 = textBox_Lkey_1.Text.Trim();
checker = new(data_part1, data_part2, data_part3, data_part4);
...
Далее, при формировании ключа, происходит его проверка. Функции называются просто - check[1-4](). Делают они следующее:
- check1() - Произведение байт должно равнятся фиксированному значению. Декомпилятор мог сбить немного с толку, так как маски вместо привычного вида
0xFF000000
имеют целочисленный вид4278190080
. Существует множество подходящих значений для этой проверки - поэтому кусочек ключа не используется для расшифрования флага. - check2() - Обычный xor.
- check3() - стандартный шарповый AES. Эту часть надо локально перебирать при помощи аналогичного кода на C#, но опять же - всего-то 2 байта... Код вообще можно скопировать из декомпилятора и чуть подправить. UPD: два байта, тк эта часть ключа берется по маске 0x0000FFFF
- check4() - Тут отдельная реализация генерации данных флага и чекера. В чекере идет проверка даты - в ключе необходима дата в формате UnixTimestamp, старше чем октябрь 2077 года. В генерации же ключ для расшифрования берется из лицензионного соглашения.
Подходящий ключ под эти проверки:
cac3422f 3315732d 561affb1 6d731337
После проверки имитируется процесс установки, но пользователь видит только "Готово!", без конкретного места установки флага.
Для того, чтобы узнать куда установился флаг, откроем опять код формы и увидим в функции обработки кнопки код, который создает объект StreamWriter для файла System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal)
, который является стандартным путем "My Documents". Там и будет flag.txt с флагом.
P.S. Пример брутера:
using System.Security.Cryptography;
byte[] k = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };
byte[] iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte[] enc = { 0x5a, 0xf1, 0xcf, 0x02, 0x68, 0x3d, 0x2d, 0x62, 0x6d, 0xb2, 0x2f, 0x9c, 0x8d, 0x90, 0xb5, 0x85 };
uint a = 0;
for (uint i = 0; i < uint.MaxValue; i++)
{
a = uint.Parse(i.ToString("x8"), System.Globalization.NumberStyles.HexNumber) & 0x0000FFFF;
byte[] encrypted;
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = k;
aesAlg.IV = iv;
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(a);
}
encrypted = msEncrypt.ToArray();
if (encrypted.SequenceEqual(enc))
{
System.Console.WriteLine("FOUND");
System.Console.WriteLine(a.ToString("x8"));
}
}
}
}
}
System.Console.WriteLine("Done");