...

четверг, 11 июня 2015 г.

Исследование защиты игры Charm Solitaire

Charm Solitaire
Лет 7-8 назад мне случайно попалась игра CharmSolitaire, скопированная вместе с другими играми с чужого винта в процессе обмена информацией. Это такой не совсем обычный карточный пасьянс. В незарегистрированной версии на игру отводится один час, и открыта только половина уровней. В той копии время уже почти закончилось. Денег на покупку у меня не было, поэтому скорее всего я бы ее удалил. Но в то время я немного увлекался взломом и решил попробовать найти регистрационный код. Опыт был довольно интересным. В статье рассказывается об основных особенностях защиты, а также о том, как security through obscurity может ее ослабить.

Ключа в открытом виде в статье нет, но кому интересно, сможет найти его самостоятельно.

Все действия вы выполняете на свой страх и риск.

Исследование защиты


Загружаем файл в IDA, запускаем игру и нажимаем кнопку «Ключ». Вводим что-нибудь, например 123321, и ищем через ArtMoney.

image

Ставим hardware breakpoint на этот адрес. Переключаемся на окно игры, пропускаем срабатывания брейкпойнта, пока окно игры не станет активным. Жмем OK, брейкпойнт срабатывает.

Жмем Ctrl + F7 (Run until return), пока не попадем из системных dll в пространство процесса. Это будет процедура обработки сообщений Controls::TWinControl::DefaultHandler(). Продолжаем выходить из функций, пока не попадем на вызов Controls::TControl::GetText(void):

Скрытый текст
004C2700:
                call    @Controls@TControl@GetText$qqrv ; Controls::TControl::GetText(void)
EIP->           mov     edx, [ebp+var_8]
                mov     eax, [ebp+var_4]
                call    sub_4C23A8
                test    al, al
                jnz     short loc_4C277C
                
                ...
                mov     edx, offset _str_WKeyError.Text
                call    sub_4AF2F8
                ...
                jmp     short loc_4C27D2

loc_4C277C:
                ...
                mov     edx, offset _str_WRegistrationTh.Text
                call    sub_4AF2F8



В переменной [ebp+var_8] находится указатель на строку с введенным кодом. Далее видим вызов функции sub_4C23A8() и условный переход. По строкам _str_WKeyError и _str_WRegistrationThanks можно догадаться, что sub_4C23A8() и есть функция проверки ключа.
Скрытый текст
check_key_4C23A8 proc near

                ...
                mov     [ebp+key_8], edx
                mov     [ebp+this_4], eax
                ...
                mov     [ebp+is_right_key_9], 0
                cmp     [ebp+key_8], 0
                jz      loc_4C257B
                
                lea     edx, [ebp+key_copy_28]
                mov     eax, [ebp+key_8]
                call    copy_digits_492734
                
                mov     edx, [ebp+key_copy_28]
                mov     eax, ds:pp_key_4F305C
                call    @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void)
                ...
                
                mov     eax, ds:pp_dirname_4F2F54
                push    dword ptr [eax]
                push    offset _str_slash.Text
                push    offset _str_CharmSolitaire.Text
                push    offset _str__udf.Text
                lea     eax, [ebp+udf_filename_2C]
                mov     edx, 4
                call    str_cat_40522C
                
                mov     edx, [ebp+udf_filename_2C]          ; %GAME_DIR%\CharmSolitaire.udf
                mov     eax, [ebp+mem_stream_encrypted_10]
                call    @Classes@TMemoryStream@LoadFromFile$qqrx17System@AnsiString_0 ; Classes::TMemoryStream::LoadFromFile(System::AnsiString)
                
                mov     eax, ds:pp_key_4F305C
                cmp     dword ptr [eax], 0
                jz      short loc_4C2496
                
                mov     eax, ds:pp_key_4F305C
                mov     eax, [eax]
                call    strlen_40516C
                
004C2473:       cmp     eax, 18h
                jnz     short loc_4C2496
                
004C2478:       ...


loc_4C25A5:
                mov     al, [ebp+is_right_key_9]
                ...
                retn
check_key_4C23A8 endp



Из инструкции по адресу 004C2473 видно, что длина строки должна быть 18h, то есть 24 символа. ОК, ставим брейкпойнт, запускаем, вводим ключ 123456789012345678901234.
Скрытый текст
004C2478:       lea     edx, [ebp+var_30]
                mov     eax, ds:pp_key_4F305C
                mov     eax, [eax]
                call    sub_4924D0
                
                mov     edx, [ebp+var_30]
                mov     eax, ds:off_4F2C24
                call    @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void)
                jmp     short loc_4C24A5



Процедура sub_4924D0 производит некоторые манипуляции со строкой и переводит результат в int64
Скрытый текст
key_to_hex_4924D0 proc near
                ...
                mov     [ebp+p_res_10], edx
                mov     [ebp+key_C], eax
                ...
                lea     edx, [ebp+key_copy_24]
                mov     eax, [ebp+key_C]
                call    copy_digits_492734
                
                mov     eax, [ebp+key_copy_24]
                lea     edx, [ebp+mixed_str_14]
                call    mix_symbols_492688
                
                lea     eax, [ebp+mixed_str_14]
                mov     ecx, 3
                mov     edx, 1
                call    delete_symbols_40540C
                jmp     short loc_492539

loc_492527:
                lea     eax, [ebp+mixed_str_14]
                mov     ecx, 1
                mov     edx, 1
                call    delete_symbols_40540C
loc_492539:
                mov     eax, [ebp+mixed_str_14]
                cmp     byte ptr [eax], 30h ; '0'
                jz      short loc_492527            ; delete leading zeros
                
                push    0       ; default value
                push    0       ; default value
                mov     eax, [ebp+mixed_str_14]
                call    @Sysutils@StrToInt64Def$qqrx17System@AnsiStringj ; Sysutils::StrToInt64Def(System::AnsiString,__int64)
                mov     [ebp+v64lo_8], eax
                mov     [ebp+v64hi_4], edx
                
                mov     eax, [ebp+p_res_10]
                call    @System@@LStrClr$qqrr17System@AnsiString ; System::__linkproc__ LStrClr(System::AnsiString &)
                mov     [ebp+i_18], 1

loc_492562:
                mov     eax, [ebp+i_18]
                test    byte ptr [ebp+eax-1+v64lo_8], 7Fh
                jbe     short loc_49258C
                
                lea     eax, [ebp+char_str_28]
                mov     edx, [ebp+i_18]
                mov     dl, byte ptr [ebp+edx-1+v64lo_8]
                and     dl, 7Fh
                call    str_from_pchar_405084 ; Borland Visual Component Library & Packages
                
                mov     edx, [ebp+char_str_28]
                mov     eax, [ebp+p_res_10]
                call    @System@@LStrCat$qqrv ; System::__linkproc__ LStrCat(void)
                mov     eax, [ebp+p_res_10]

loc_49258C:
                inc     [ebp+i_18]
                cmp     [ebp+i_18], 9
                jnz     short loc_492562

loc_4925A2:
                ...
                retn
key_to_hex_4924D0 endp



Пседокод:
mixed_str_14 = mix_symbols_492688(key);
v64_4 = StrToInt64Def(mixed_str_14, 0);
while (v64_4[i] & 0x7F > 0) (string)p_res_10 += (char)v64_4[i] & 0x7F, i++;


Функция mix_symbols_492688 работает так — значения из первой половины строки становятся на нечетные места (если считать от 0), из второй на четные. Наша строка превращается в 314253647586970819203142.

В функции StrToInt64Def вызывается другая системная функция ValInt64. У нее есть одна особенность — если после обработки в конце строки еще остались символы, то в выходную переменную (code) возвращается текущая позиция, иначе 0. Обработка заканчивается, если текущее значение превышает 0x0CCCCCCCCCCCCCCC (потому что 0x0CCCCCCCCCCCCCCC = 0x7FFFFFFFFFFFFFFF / 0x0A; 0x0A — основание системы счисления). В StrToInt64Def есть проверка на это, и если в code вернулось не 0, то вместо результата возвращается значение по умолчанию (в данном случае 0).

314253647586970819203142 явно превышает это значение. Возьмем в качестве кода к примеру то же 0x0CCCCCCCCCCCCCCC. Переведем в десятичную систему и совершим действия, обратные действиям функции mix_symbols_492688 — нечетные символы запишем в первую половину, четные во вторую:

0x0CCCCCCCCCCCCCCC = 922337203685477580

000000922337203685477580
 0 0 0 2 3 7 0 6 5 7 5 0
0 0 0 9 2 3 2 3 8 4 7 8 
000237065750000923238478

Запускаем снова, вводим новый код, возвращаемся к функции check_key_4C23A8.

Скрытый текст
004C2478:       lea     edx, [ebp+p_key64_30]
                mov     eax, ds:pp_key_4F305C
                mov     eax, [eax]
                call    key_to_hex_4924D0
                
                mov     edx, [ebp+p_key64_30]
                mov     eax, ds:p_key_bytes_4F2C24
                call    @System@@LStrAsg$qqrv ; System::__linkproc__ LStrAsg(void)
                jmp     short loc_4C24A5
                ...
loc_4C24A5:
                mov     ecx, ds:p_key_bytes_4F2C24
                mov     ecx, [ecx]
                mov     edx, [ebp+mem_stream_decrypted_14]
                mov     eax, [ebp+mem_stream_encrypted_10]
                call    sub_492B48
                
                mov     eax, [ebp+mem_stream_decrypted_14]
                call    sub_492C94
                
                test    al, al
                jz      loc_4C255F
                ...
loc_4C255F:
                mov     [ebp+is_right_key_9], 0
                ...
loc_4C25A5:
                mov     al, [ebp+is_right_key_9]
                ...
                ret
check_key_4C23A8 endp



Сначала взглянем на функцию sub_492C94. Константы 20h, 09h, 0Dh, 0Ah, в инструкциях сравнения говорят о том, что здесь есть какая-то работа с текстом. Более подробное изучение показывает, что эта функция проверяет, является ли содержимое mem_stream_decrypted_14 текстом. Назовем ее is_text_492C94.

Основная работа происходит в функции sub_492B48. Там с помощью ключа расшифровываются данные из mem_stream_encrypted_10, в который до этого было загружено содержимое файла CharmSolitaire.udf. Можно посмотреть на него в HEX-редакторе. В начале есть блок, где каждый 8 байт равен 0x33. Интересно, но не очень понятно, как это использовать. Идем дальше.

Скрытый текст
decrypt_492B48  proc near
                ...
                mov     [ebp+key_bytes_28], ecx
                mov     [ebp+mem_stream_decrypted_24], edx
                mov     [ebp+mem_stream_encrypted_20], eax
                ...
                mov     eax, [ebp+key_bytes_28]
                call    strlen_40516C
                test    eax, eax
                jnz     short loc_492B87
                ...
loc_492B87:
                ...     ; key_bytes_28 может быть меньше 8 символов
                ...     ; key_bytes8_34 заполняется до 8 символов повторением key_bytes_28
                ...     ; в принципе можно считать, что они равны

loc_492BD4:
                lea     edx, [ebp+buf_14]
                mov     ecx, 8
                mov     eax, [ebp+mem_stream_encrypted_20]
                mov     ebx, [eax]
                call    dword ptr [ebx+0Ch]         ; read bytes
                mov     [ebp+bytes_read_C], eax
                
                xor     eax, eax
                mov     [ebp+i_2C], eax

loc_492BEC:
                mov     eax, [ebp+i_2C]
                mov     al, [ebp+eax+key_bytes8_34]
                mov     edx, [ebp+i_2C]
                xor     byte ptr [ebp+edx+buf_14], al
                inc     [ebp+i_2C]
                cmp     [ebp+i_2C], 8
                jnz     short loc_492BEC
                
                cmp     [ebp+bytes_read_C], 8
                jnz     short loc_492C3C
                
                push    ebp
                call    sub_492A6C
                pop     ecx
                xor     eax, eax
                mov     [ebp+i_2C], eax
loc_492C15:
                mov     eax, [ebp+i_2C]
                mov     al, [ebp+eax+key_bytes8_34]
                mov     edx, [ebp+i_2C]
                xor     byte ptr [ebp+edx+decrypted_buf_1C], al
                inc     [ebp+i_2C]
                cmp     [ebp+i_2C], 8
                jnz     short loc_492C15
                
                lea     edx, [ebp+decrypted_buf_1C]
                mov     ecx, [ebp+bytes_read_C]
                mov     eax, [ebp+mem_stream_decrypted_24]
                mov     ebx, [eax]
                call    dword ptr [ebx+10h]     ; write bytes
                jmp     short loc_492C4A

loc_492C3C:
                lea     edx, [ebp+buf_14]
                mov     ecx, [ebp+bytes_read_C]
                mov     eax, [ebp+mem_stream_decrypted_24]
                mov     ebx, [eax]
                call    dword ptr [ebx+10h]     ; write bytes

loc_492C4A:
                cmp     [ebp+bytes_read_C], 8
                jz      short loc_492BD4
                
                ...
                retn
decrypt_492B48  endp



Псевдокод:
bytes_read = mem_stream_encrypted->read(buf, 8);
for (i = 0; i < 8; i++) buf ^= key[i];

if (bytes_read == 8)
{
    sub_492A6C(buf, out decrypted_buf);
    for (i = 0; i < 8; i++) decrypted_buf ^= key[i];
    mem_stream_decrypted->write(decrypted_buf, bytes_read);
}
else
{
    mem_stream_decrypted->write(buf, bytes_read);
}


Сначала делается XOR с ключом.
Затем, если число прочитанных байт равно 8, вызывается процедура sub_492A6C, которая некоторым образом перемешивает биты результата.
Затем еще раз делается XOR с ключом.
Результат записывается в выходной буфер.
Если не равно (последние байты зашифрованного буфера), то ничего не вызывается, и второй XOR не делается.

sub_492A6C это вложенная функция, туда передается ebp родительской функции. Псевдокод довольно большой, проще описать словами. Она принимает на вход 8 байт, из первых битов составляет первый байт результата, из вторых бит второй и т.д.

(младший бит числа слева)

11111111      10000000
00000000      10000000
00000000      10000000
00000000  ->  10000000
00000000      10000000
00000000      10000000
00000000      10000000
00000000      10000000


Мне представляется примерно такой диалог:
— Давай через XOR с ключом зашифруем.
— Не, давай XOR, потом вот так вот перевернем, и еще раз XOR. Чтобы совсем было непонятно.
— Точно, давай.

Другими словами, матрица 8x8 бит обращается вокруг главной диагонали (транспонируется), после чего делается повторный XOR с ключом. В этом и заключается ошибка.

Взлом


Что мы делаем? Мы ксорим блок 8x8 бит с байтами ключа, обращаем эту матрицу, и ксорим еще раз с этим же ключом. Получается, биты на главной диагонали не шифруются вообще. Остальные биты имеют зависимость:
C[x][y] ^ K[x][y] ^ K[y][x] = D[y][x]
C[y][x] ^ K[y][x] ^ K[x][y] = D[x][y]

где C - зашифрованный блок, D - расшифрованный блок, K - ключ, x и y - произвольные координаты в матрице.


Элементы K[x][y] ^ K[y][x] образуют симметричную матрицу T[x][y]:
T[x][y] = T[y][x]

C[x][y] ^ T[x][y] = D[y][x]
C[y][x] ^ T[x][y] = D[x][y]


Это значит, что нам не надо искать все исходные биты ключа. Можно предположить, что верхний треугольник матрицы ключа составляют нули. Тогда верхний и нижний треугольники матрицы T будут равны нижнему треугольнику ключа.

Это позволяет уменьшить количество вариантов для перебора более чем в 2 раза — половина матрицы + диагональ. Количество неизвестных бит: (7 + 6 + 5 + 4 + 3 + 2 + 1) = 28. Итого 2^28 вариантов, причем после расшифровки матрицы должен получиться текст.

Метод перебора:
— читаем зашифрованный блок 8 байт
— размещаем текущее значение счетчика в нижнем треугольнике ключа
— делаем XOR с зашифрованным блоком
— обращаем полученную матрицу
— делаем XOR еще раз
— повторяем
— после расшифровки проверяем на текст; если текст не получился, то ключ неправильный
— для ускорения поиска можно проверять каждый расшифрованный блок

Типы битов в матрице (младший бит слева):

#define FIXED_0 0
#define FIXED_1 1
#define UNKNOWN 2

const unsigned char bitTypes[8][8] = {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {2, 0, 0, 0, 0, 0, 0, 0},
        {2, 2, 0, 0, 0, 0, 0, 0},
        {2, 2, 2, 0, 0, 0, 0, 0},
        {2, 2, 2, 2, 0, 0, 0, 0},
        {2, 2, 2, 2, 2, 0, 0, 0},
        {2, 2, 2, 2, 2, 2, 0, 0},
        {2, 2, 2, 2, 2, 2, 2, 0}
};

Текущее значение счетчика перебора распределяется по битам UNKNOWN (в конце статьи есть код).

Также есть любопытная особенность. Сделав XOR частей, которые находятся по одинаковые стороны от знака ' = ', получаем:

C[x][y] ^ K[x][y] ^ K[y][x] ^ C[y][x] ^ K[y][x] ^ K[x][y] = D[y][x] ^ D[x][y]
C[x][y] ^ C[y][x] ^ (K[x][y] ^ K[x][y]) ^ (K[y][x] ^ K[y][x]) = D[y][x] ^ D[x][y]
C[x][y] ^ C[y][x] = D[y][x] ^ D[x][y]


XOR двух симметричных битов зашифрованного текста равен XOR этих битов расшифрованного текста. Возможно, это могло бы пригодиться в каких-то случаях, но здесь это не важно.

Не все так просто


1. После завершения перебора получится 65 вариантов текста.

На моей системе это заняло минут 15-20. Можно посмотреть их все вручную и выбрать подходящий. Но у нас есть подсказка — каждый 8-й байт в начале файла равен 0x33. Теперь мы знаем, как он появляется — это (старшие биты блока из 8 символов) XOR (8-й байт матрицы T). Если предположить, что текст на латинице, то старшие биты везде 0, и 0x33 — это 8-й байт матрицы в открытом виде.

Тогда можно перебирать в 256 раз меньше вариантов, то есть 2^20:


#define FIXED_0 0
#define FIXED_1 1
#define UNKNOWN 2

const unsigned char bitTypes[8][8] = {
        {0, 0, 0, 0, 0, 0, 0, 0},
        {2, 0, 0, 0, 0, 0, 0, 0},
        {2, 2, 0, 0, 0, 0, 0, 0},
        {2, 2, 2, 0, 0, 0, 0, 0},
        {2, 2, 2, 2, 0, 0, 0, 0},
        {2, 2, 2, 2, 2, 0, 0, 0},
        {2, 2, 2, 2, 2, 2, 0, 0},
        {1, 1, 0, 0, 1, 1, 0, 0}
};


После запуска перебора найдем единственный вариант. Это и будет предполагаемый ключ. Но в таком виде он не подходит.

2. Если количество байт в файле не кратно 8, то остаток получается зашифрованным обычным побайтовым XOR с ключом.

Здесь нужно проверять только вручную — смотреть расшифрованный текст, предполагать, какими могут быть последние байты, и на основе их восстанавливать матрицу.

Скрытый текст
Например, длина файла CharmSolitaire.udf равна 0x823, то есть последние 3 байта зашифрованы обычным XOR с ключом.
Сначала нужно найти предполагаемый ключ и расшифровать 0x820 байт.
На основе текста предположить, какими должны быть 3 оставшиеся байта.
Затем найти XOR между 3 зашифрованными и расшифрованными вручную байтами, это будут настоящие байты ключа.

Записываем побитово байты предполагаемого ключа.
Поверх записываем биты настоящих байтов. При этом, если какие-то биты не совпадают, нужно инвертировать симметричный бит в другой половине матрицы.
Если бит на главной диагонали, просто перезаписываем.

Подсказка: расшифрованный текст заканчивается на «ять.» :)


Уровни после 30-го тоже зашифрованы. Уровень представляет собой XML-файл. Если ключ неточный, то могут быть разные проблемы — от артефактов в графике до исключения с сообщением о неправильной разметке XML. На основе файла CharmSolitaire.udf можно найти 3 младшие байта настоящего ключа и с таким ключом доиграть до 39 уровня. Его длина равна 0x246F, то есть можно найти все 7 неизвестных байт.
Скрытый текст
— перевести игру в оконный режим.
— поставить брейкпойнт на loc_492C3C в процедуре decrypt_492B48 (это код записи последних расшифрованных байт)
— взять байты, которые находятся в переменной buf_14
— сделать XOR с текущим ключом
— сделать XOR с текстом, который должен быть

Подсказка: уровень заканчивается тегом

</Level>0x0D,0x0A

Результат


8 байт ключа:
Bre6Vqd3

Текст на латинице в начале файла:
Some years ago the small pussy cat came to his house and ask the bug lived there about dinner.

Код программы:

Скрытый текст
#include <vcl.h>
#pragma hdrstop

#include "MainUnit.h"
#include <vector>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMainForm *MainForm;
//---------------------------------------------------------------------------
__fastcall TMainForm::TMainForm(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------

inline unsigned int getBit(unsigned char byte, unsigned int bitNumber)
{
        return (byte & ((unsigned char)1 << bitNumber) ? 1 : 0);
}

inline void setBit(unsigned char &byte, unsigned int bitNumber, unsigned int bitValue)
{
        if (bitValue)
                byte |= ((unsigned char)1 << bitNumber);
        else
                byte &= ~((unsigned char)1 << bitNumber);
}

int isText(unsigned char *pData, int streamSize)
{
        int isText = 1;
        if (streamSize < 1) return isText;

        char prevChar = 0;
        do
        {
                if (*pData < 0x20 && *pData != 9)
                {
                        if ((*pData == 0x0D || *pData == 0x0A))
                        {
                                // для ускорения поиска можно проверять каждый расшифрованный блок
                                // так как он может начинаться с 0x0A, то эта часть кода не нужна
                                  
                                //if (*pData == 0x0A && prevChar != 0x0D)
                                //{
                                //      isText = 0;
                                //      break;
                                //}
                        }
                        else
                        {
                                isText = 0;
                                break;
                        }
                }
                
                prevChar = *pData;
                pData++;
        }
        while (--streamSize);

        return isText;
}




#define FIXED_0 0
#define FIXED_1 1
#define UNKNOWN 2

const unsigned char bitTypes[8][8] = {
        // младший бит слева
        {0, 0, 0, 0, 0, 0, 0, 0},
        {2, 0, 0, 0, 0, 0, 0, 0},
        {2, 2, 0, 0, 0, 0, 0, 0},
        {2, 2, 2, 0, 0, 0, 0, 0},
        {2, 2, 2, 2, 0, 0, 0, 0},
        {2, 2, 2, 2, 2, 0, 0, 0},
        {2, 2, 2, 2, 2, 2, 0, 0},
        {1, 1, 0, 0, 1, 1, 0, 0}
};

void getKeyMatrix(unsigned int keyBits, unsigned char matrix[8])
{
        int x, y;
        unsigned int bitValue = 0, bitNumber = 0;

        memset(matrix, 8, 0);
        for(y = 0; y < 8; y++)
        {
                for(x = 0; x < 8; x++)
                {
                        // для массива координаты идут в обратном порядке
                        if (bitTypes[y][x] == UNKNOWN)
                        {
                                bitValue = getBit(((unsigned char*)&keyBits)[bitNumber / 8], bitNumber % 8);
                                bitNumber++;
                        }
                        else if (bitTypes[y][x] == FIXED_1)
                                bitValue = 1;
                        else
                                bitValue = 0;

                        setBit(matrix[y], x, bitValue);
                }
        }
}

void reverseMatrix(unsigned char block[8])
{
        unsigned int x, y, bitValue;
        unsigned char tmpBlock[8];
        for (y = 0; y < 8; y++)
        {
                for (x = 0; x < 8; x++)
                {
                        bitValue = getBit(block[x], y);
                        setBit(tmpBlock[y], x, bitValue);
                }
        }

        memcpy(block, tmpBlock, 8);
}

void decryptBlock(unsigned int keyBits, unsigned char *encryptedBlock, unsigned char *decryptedBlock, unsigned int blockSize)
{
        unsigned char key[8];
        unsigned int i;
        getKeyMatrix(keyBits, key);

        for(i = 0; i < blockSize; i++)
                decryptedBlock[i] = encryptedBlock[i] ^ key[i];

        if (blockSize == 8)
        {
                reverseMatrix(decryptedBlock);

                for(i = 0; i < 8; i++)
                        decryptedBlock[i] = decryptedBlock[i] ^ key[i];
        }
}

void decryptText(unsigned char *encryptedText, unsigned char *decryptedText, unsigned int textSize, unsigned int keyBits)
{
        unsigned int position = 0, blockSize = 8, bytesToRead = 0;
        unsigned int i, j;

        while(position < textSize)
        {
                if (position + blockSize <= textSize)
                        bytesToRead = blockSize;
                else
                        bytesToRead = textSize - position;

                decryptBlock(keyBits, encryptedText + position, decryptedText + position, bytesToRead);
                // для ускорения поиска проверяем каждый блок
                if (bytesToRead == 8 && !isText(decryptedText + position, 8))
                        break;

                position += bytesToRead;
        }
}

void getKeyVariants(unsigned char *encryptedText, unsigned int textSize, std::vector<unsigned int> &keyList)
{
        unsigned int variantsCount = 0;
        unsigned int possibleBits = 0;

        unsigned char *decryptedText = new unsigned char[textSize];
        //for (possibleBits = 0; possibleBits < (1 << 20); possibleBits++)
        for (possibleBits = 0; possibleBits < (1 << 6); possibleBits++)
        {
                decryptText(encryptedText, decryptedText, textSize, possibleBits);

                if (isText(decryptedText, textSize - textSize % 8))
                {
                        keyList.push_back(possibleBits);
                        variantsCount++;
                }
        }
        variantsCount = variantsCount;  // для брейкпойнта
        

        delete []decryptedText;
}

AnsiString getKeyText(unsigned char keyMatrix[8])
{
        AnsiString str = "000000000000000000000000" + IntToStr(*(__int64*)keyMatrix);
        str = str.SubString(str.Length() - 24 + 1, 24);  // нумерация с 1 

        AnsiString keyText = "";
        for(int i = 0; i < 24; i += 2)
                keyText.cat_printf("%c", str.c_str()[i + 1]);
        for(int i = 0; i < 24; i += 2)
                keyText.cat_printf("%c", str.c_str()[i]);
        
        return keyText;
}

//---------------------------------------------------------------------------

std::vector<unsigned int> keyList;
void __fastcall TMainForm::btnStartClick(TObject *Sender)
{
        AnsiString filename = "F:\\Games\\Charm Solitaire\\CharmSolitaire.udf";
        TMemoryStream *encryptedStream = new TMemoryStream();
        encryptedStream->LoadFromFile(filename);

        getKeyVariants((unsigned char *)encryptedStream->Memory, encryptedStream->Size, keyList);

        unsigned char keyMatrix[8];
        getKeyMatrix(keyList[0], keyMatrix);
        getKeyText(keyMatrix);
}


void __fastcall TMainForm::btnDecryptClick(TObject *Sender)
{
        AnsiString filename = "F:\\Games\\Charm Solitaire\\CharmSolitaire.udf";
        TMemoryStream *encryptedStream = new TMemoryStream();
        encryptedStream->LoadFromFile(filename);

        unsigned int keyBits = keyList[0];
        unsigned int textSize = encryptedStream->Size;
        unsigned char *decryptedText = new unsigned char[textSize + 1];
        decryptedText[textSize] = 0;

        decryptText((unsigned char *)encryptedStream->Memory, decryptedText, textSize, keyBits);
        mDecryptedText->Text = AnsiString((char*)decryptedText);
}
//---------------------------------------------------------------------------


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий