Исследование защиты игры 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] & 0×7F > 0) (string)p_res_10 += (char)v64_4[i] & 0×7F, i++; Функция mix_symbols_492688 работает так — значения из первой половины строки становятся на нечетные места (если считать от 0), из второй на четные. Наша строка превращается в 314253647586970819203142.В функции StrToInt64Def вызывается другая системная функция ValInt64. У нее есть одна особенность — если после обработки в конце строки еще остались символы, то в выходную переменную (code) возвращается текущая позиция, иначе 0. Обработка заканчивается, если текущее значение превышает 0×0CCCCCCCCCCCCCCC (потому что 0×0CCCCCCCCCCCCCCC = 0×7FFFFFFFFFFFFFFF / 0×0A; 0×0A — основание системы счисления). В StrToInt64Def есть проверка на это, и если в code вернулось не 0, то вместо результата возвращается значение по умолчанию (в данном случае 0).

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

0×0CCCCCCCCCCCCCCC = 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 байт равен 0×33. Интересно, но не очень понятно, как это использовать. Идем дальше.

Скрытый текст 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. Чтобы совсем было непонятно.— Точно, давай.Другими словами, матрица 8×8 бит обращается вокруг главной диагонали (транспонируется), после чего делается повторный XOR с ключом. В этом и заключается ошибка.

Взлом Что мы делаем? Мы ксорим блок 8×8 бит с байтами ключа, обращаем эту матрицу, и ксорим еще раз с этим же ключом. Получается, биты на главной диагонали не шифруются вообще. Остальные биты имеют зависимость: 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-й байт в начале файла равен 0×33. Теперь мы знаем, как он появляется — это (старшие биты блока из 8 символов) XOR (8-й байт матрицы T). Если предположить, что текст на латинице, то старшие биты везде 0, и 0×33 — это 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 равна 0×823, то есть последние 3 байта зашифрованы обычным XOR с ключом.Сначала нужно найти предполагаемый ключ и расшифровать 0×820 байт.На основе текста предположить, какими должны быть 3 оставшиеся байта.Затем найти XOR между 3 зашифрованными и расшифрованными вручную байтами, это будут настоящие байты ключа.Записываем побитово байты предполагаемого ключа.Поверх записываем биты настоящих байтов. При этом, если какие-то биты не совпадают, нужно инвертировать симметричный бит в другой половине матрицы.Если бит на главной диагонали, просто перезаписываем.

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

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

0×0D,0×0A Результат 8 байт ключа: Bre6Vqd3Текст на латинице в начале файла: Some years ago the small pussy cat came to his house and ask the bug lived there about dinner.

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

Скрытый текст #include #pragma hdrstop

#include «MainUnit.h» #include //--------------------------------------------------------------------------- #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 &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 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); } //---------------------------------------------------------------------------

© Habrahabr.ru