1337ReverseEngineer's VMAdventures 1 crackme

Продолжаем решать головоломки: сегодня это 1337ReverseEngineer’s VMAdventures 1 https://crackmes.one/crackme/63bd7f5733c5d43ab4ecf3ad

Задача: узнать верный пароль, на который программа выдаст «Correct key!».

Проверка пароля

С помощью дизассемблера находим строку «Correct key!» и код, что на нее ссылается. Над ним — цикл проверки пароля: eax пробегает по символам, а в edi — длина пароля.

fbfd128be5810c505328d14ea02ff32a.pnge08913d3d18d88d7efb53ea824186e15.png

Строка byte_4032E0 содержит непечатные символы: это не сам пароль, а хеш.

5c05a1a43f3bc341f210d5af3d5c4bb9.png

При помощи отладчика выясняется, что в esi хранится указатель на строку пароля, затем вызов loc_401170 «портит» символы.

Выполнение байт-кода

Работа функции управляется байт-кодом: цикл перебирает байты строки .rdata:004032D0 и выполняет команды. Функция предусматривает 5 случаев для следующих значений байтов: 0×0, 0×1, 0×2, 0×3, 0×4.  За исключением команды 0, которая останавливает выполнение байт-кода, команды 1–4 изменяют строку пароля. Таким образом программа шифрует пароль: чтобы расшифровать, выполним обратные действия в обратном порядке.

Расшифровка пароля

Изучим команды. 
0×1 Если внимательно изучать код, увидим, что на блоки размером 64 байта операцией XOR накладывается 16-байтная константа, а для остальных байтов выполняет XOR 54h. Заметим, что длина верного пароля — 32 и XOR по 64-блокам не будет выполняться. Повторное применение XOR, восстанавливает исходное значение: A XOR B XOR B = A.
0×2 Тот же алгоритм, что и у 0×1, но отличается константа: выполняет XOR 24h.
0×3 Выполняет циклический сдвиг влево на 2 бита для каждого символа пароля. Обратная операция — сдвиг вправо.
0×4 Выполняет сложение каждого байта пароля с 0xEB. Обратная операция — вычитание.

f5b557d6d72dbf8914bb3c8f50ab74d8.png

Теперь программа сама выдаст секретный пароль:

  • вводим пароль из 32-х символов

  • подадим на вход функции loc_401170 шифр пароля из .rdata:004032E0

  • изменим код loc_401170, чтобы выполнить обратные операции:

    • заменим в case 3 операцию сдвига ROL на ROR

    • заменим в case 4 сложение на вычитание

    • запишем команды байт-кода в обратном порядке

c7ba586df75a23f7ce172c82e2580f87.png

Несложно написать и код дешифратора.

#include 
#include 
#include 
#include 

using namespace std;
using BytesVector = vector;

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(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;
}

© Habrahabr.ru