Исследование защиты игры 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. Чтобы совсем было непонятно.
— Точно, давай.

Другими словами, матрица 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-й байт в начале файла равен 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); } //--------------------------------------------------------------------------- 

ссылка на оригинал статьи http://habrahabr.ru/post/260071/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *