Продолжаем решать головоломки: сегодня это 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;
}