Продолжаем решать головоломки: сегодня это 1337ReverseEngineer's VMAdventures 1
Задача: узнать верный пароль, на который программа выдаст "Correct key!".
Проверка пароля
С помощью дизассемблера находим строку "Correct key!" и код, что на нее ссылается. Над ним - цикл проверки пароля: eax пробегает по символам, а в edi - длина пароля.
Строка byte_4032E0 содержит непечатные символы: это не сам пароль, а хеш.
При помощи отладчика выясняется, что в esi хранится указатель на строку пароля, затем вызов loc_401170 "портит" символы.
Выполнение байт-кода
Работа функции управляется байт-кодом: цикл перебирает байты строки .rdata:004032D0 и выполняет команды. Функция предусматривает 5 случаев для следующих значений байтов: 0x0, 0x1, 0x2, 0x3, 0x4. За исключением команды 0, которая останавливает выполнение байт-кода, команды 1-4 изменяют строку пароля. Таким образом программа шифрует пароль: чтобы расшифровать, выполним обратные действия в обратном порядке.
Расшифровка пароля
Изучим команды.
0x1 Если внимательно изучать код, увидим, что на блоки размером 64 байта операцией XOR накладывается 16-байтная константа, а для остальных байтов выполняет XOR 54h. Заметим, что длина верного пароля - 32 и XOR по 64-блокам не будет выполняться. Повторное применение XOR,восстанавливает исходное значение: A XOR B XOR B = A.
0x2 Тот же алгоритм, что и у 0x1, но отличается константа: выполняет XOR 24h.
0x3 Выполняет циклический сдвиг влево на 2 бита для каждого символа пароля. Обратная операция - сдвиг вправо.
0x4 Выполняет сложение каждого байта пароля с 0xEB. Обратная операция - вычитание.
Теперь программа сама выдаст секретный пароль:
вводим пароль из 32-х символов;
подадим на вход функции loc_401170 шифр пароля из .rdata:004032E0;
-
изменим код loc_401170, чтобы выполнить обратные операции:
заменим в case 3 операцию сдвига ROL на ROR;
заменим в case 4 сложение на вычитание;
запишем команды байт-кода в обратном порядке.
Несложно написать и код дешифратора.
#include <bit>
#include <vector>
#include <iostream>
#include <string>
using namespace std;
using BytesVector = vector<uint8_t>;
BytesVector code{0x01, 0x03, 0x04, 0x02, 0x01, 0x03, 0x02, 0x01, 0x02, 0x01, 0x03, 0x02, 0x03, 0x04, 0x02, 0x03};
void xor1(BytesVector& key) {
for (auto &c: key) c ^= 0x54;
}
void xor2(BytesVector& key) {
for (auto &c: key) c ^= 0x24;
}
void rol2(BytesVector& key) {
for (auto &c: key) c = rotl(c, 2);
}
void add(BytesVector& key) {
for (auto &c: key) c += 0xEB;
}
void ror2(BytesVector& key) {
for (auto &c: key) c = rotr(c, 2);
}
void sub(BytesVector& key) {
for (auto &c: key) c -= 0xEB;
}
enum Opcode {
XOR1 = 0x1,
XOR2,
ROL,
ADD,
ROR,
SUB
};
void exec(const BytesVector& code, BytesVector& key) {
for (auto i: code) {
switch(i) {
case XOR1:
xor1(key);
break;
case XOR2:
xor2(key);
break;
case ROL:
rol2(key);
break;
case ADD:
add(key);
break;
case ROR:
ror2(key);
break;
case SUB:
sub(key);
break;
}
}
}
BytesVector explode(const string& s) {
BytesVector result;
for (char c: s) {
result.push_back(static_cast<uint8_t>(c));
}
return result;
}
void encrypt(BytesVector& text) {
exec(code, text);
}
void reverseBytecode(BytesVector& code) {
for (uint8_t& op: code) {
switch(op) {
case ROL:
op = ROR;
break;
case ADD:
op = SUB;
break;
case ROR:
op = ROL;
break;
case SUB:
op = ADD;
break;
}
}
}
void decrypt(BytesVector& text) {
BytesVector rev{code.rbegin(), code.rend()};
reverseBytecode(rev);
exec(rev, text);
}
int main() {
BytesVector magic{0xBD, 0x35, 0xA9, 0xA1, 0xD1, 0xE1, 0xD9, 0x35,
0x31, 0x01, 0x39, 0xD9, 0xAA, 0x95, 0x01, 0xAA,
0xFD, 0xB9, 0x28, 0xD5, 0x7C, 0xD9, 0x1D, 0x95,
0x99, 0xCD, 0xD9, 0xF1, 0xAA, 0xD2, 0xEE, 0xF9};
string password;
cout << "Enter password: "s;
cin >> password;
cout << endl;
BytesVector key = explode(password);
encrypt(key);
cout << "Encrypted: "s;
for (auto k: key) {
printf("%X ", k);
}
decrypt(key);
cout << "\nDecrypted: "s;
for (auto k: key) {
printf("%c", k);
}
decrypt(magic);
cout << "\nTOP SECRET: "s;
for(auto b: magic) {
printf("%c", b);
}
return 0;
}
Комментарии (5)
EXL
25.01.2023 23:09Спасибо за интересную статью. Кстати, можете посоветовать какой-нибудь удобный и функциональный HEX-редактор? В различных инструментах реверс-инжиниринга типа IDA Pro зачастую встроенные HEX-редакторы достаточно простенькие и различные полезные и часто используемые операции над бинарными файлами по типу: сохранить выделенный кусок в отдельный файл, добить файл определённым байтом до нужного размера и прочие подобные делаются в них несколько топорно и неудобно.
khe404
Очень интригующая статья.
Это как публикация сообщения которое зашифровано с закрытым ключом, а все обладатели открытого прочли статью и поняли что происходит.
И открытый ключ представляет из себя собственно глубокое понимание ассемблера и желание поковыряться в инструкциях.
Я люблю головоломки. Но тут прям головоломка головоломки.
Вроде как сама задача не сложная взять вот такую хеш функцию.
{XOR1, ROL, ADD, XOR2, XOR1, ROL, XOR2, XOR1, XOR2, XOR1, ROL, XOR2, ROL, ADD, XOR2, ROL};
Реализация каждой из функции есть в статье, но по факту простейшие операции вроде сложения, вычитания, смещения.
И построить к ней обратную.
Что остается зашифрованным так это как взять exe файлик и понять где в нем хеш пароля, где прописан сам алгоритм хеширования и даже как из бинарника получить список инструкций в удобочитаемом ассемблере. Но еще сложнее делать это заведомо понимая, что скорее всего все усилия будут тщетны, так как простейший алгоритм sha1 уже не подастся подобному реверсу. ( не смотря на то что он вроде как считается ненадежным)
А так как дизассемблирование и вообще ассемблер это очень специфическая область знаний то разобраться дано почти что лишь не всем.
В общем сложно разобраться откуда взялась информация о хеширующей функции. Тут бы начать с ликвидации безграмотности в отношении ассемблера, дебагера и как вообще устроены бинарники.
sa2304 Автор
Спасибо за отзыв :) Здорово, что заинтриговало.
Вы прочли текст, сразу поняли идею, задались вопросами, остались силы написать подробный комментарий :)
Интерес помогает решать сложные задачи. Беда в том, что интерес легко отбить заумными текстами, что с первых страниц сыплют подробностями.
Знатокам скучно читать подробное описание "очевидных" шагов, а непосвященным быстро наскучит продираться сквозь детали. Те и другие бросят чтение после второго абзаца :)
Ассемблер учат и по ходу дизассемблирования: загрузил готовую программу в отладчик и изучай. Учебник по ассемблеру и Intel Software Developers Manual лежат под рукой как справочники.
Инструменты: IDA Pro, OllyDbg, ImmDbg, x64dbg.
khe404
Предпринимал много попыток уложить в себя ассемблер. Даже более того проектировал свой простейший процессор. Нельзя сказать что я вообще не подготовлен.
К сожалению, "очевидные" шаги пока кажутся более сложными чем все приведенное описание решения.
Спасибо за набор инструментов.
Жалею о том, что существует этот серьезный провал в доступности туториалов по ассемблеру в сравнении с туторалами по тому же питону и html.
sa2304 Автор
Авторы языка Си проделали много работы, чтобы избавить программисты не писали на ассемблере :) Многие задачи проще решаются на C++, Python. Даже прошивки микроконтроллеров пишут на Си.
Ассемблер пригодится разработчику трансляторов, драйверов, вирусному аналитику, взломщику и спецу по информационной безопасности. Знание языка ничего не дает, если знание не применяется на практике.
Задача определяет требования: появится цель, под нее найдутся инструменты и знания.