チャームソリティア防衛研究

チャームソリティア

約7〜8年前、私は偶然、情報交換の過程で他の誰かのねじから他のゲームと一緒にコピーされたCharmSolitaireゲームに出会いました。 これは普通のカードソリティアではありません。 未登録バージョンでは、ゲームに1時間かかり、レベルの半分のみが開いています。 そのコピーでは、時間がほぼ終わりました。 購入するお金がなかったので、おそらく削除するでしょう。 しかしその時、私はハッキングに少し夢中になっていて、登録コードを見つけようとすることに決めました。 この経験は非常に興味深いものでした。 この記事では、保護の主な機能と、あいまいさによるセキュリティの弱点について説明します。







記事には明確なキーはありませんが、興味のある人は誰でも自分で見つけることができます。



あなた自身の危険とリスクで実行するすべてのアクション。





防衛研究



ファイルをIDAにアップロードし、ゲームを起動して「キー」ボタンを押します。 123321などの何かを入力し、ArtMoneyを調べます。



画像



このアドレスにハードウェアブレークポイントを配置します。 ゲームウィンドウに切り替え、ゲームウィンドウがアクティブになるまでブレークポイント操作をスキップします。 [OK]をクリックすると、ブレークポイントがトリガーされます。



システムdllからプロセススペースに到達するまで、Ctrl + F7(戻るまで実行)を押します。 これは、メッセージ処理プロシージャ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文字であることがわかります。 OK、ブレークポイントを設定、実行、キー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から数える場合)にあり、2番目から偶数になります。 私たちの行は314253647586970819203142に変わります。



StrToInt64Def関数は、別のValInt64システム関数を呼び出します。 処理後の行の最後にまだ文字がある場合、現在の位置が出力変数(コード)に返されます。それ以外の場合は0です。現在の値が0x0CCCCCCCCCCCCCCCCCCCを超えると処理が終了します(0x0CCCCCCCCCCCCCCCCCCCCC = 0x7FFFFFFFFFFFFFFFFF / 0x0A; 0x0A;番号システム)。 StrToInt64Defにはこれに対するチェックがあり、コードが0以外を返した場合、結果(この場合は0)の代わりにデフォルト値が返されます。



314253647586970819203142は明らかにこの値を超えています。 たとえば、同じ0x0CCCCCCCCCCCCCCCCCCCを使用します。 これを10進法に変換し、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が再び行われます。

結果は出力バッファに書き込まれます。

そうでない場合(暗号化されたバッファの最後のバイト)、何も呼び出されず、2番目のXORは実行されません。



sub_492A6Cはネストされた関数であり、親関数のebpがそこに渡されます。 擬似コードは非常に大きく、言葉で説明する方が簡単です。 入力には8バイトかかり、最初のビットは結果の最初のバイト、2番目のビットは2番目のビットなどとなります。



 (   ) 11111111 10000000 00000000 10000000 00000000 10000000 00000000 -> 10000000 00000000 10000000 00000000 10000000 00000000 10000000 00000000 10000000
      
      





私にはこの対話が好きです:

-キーを使用してXORで暗号化しましょう。

-いいえ、XORを取得して、そのようにひっくり返して、もう一度XORします。 それが完全に理解できなかったこと。

-そうだよ



言い換えると、8x8ビットマトリックスは主対角線(転置)を中心に回転し、その後、キーとの繰り返しXORが行われます。 これは間違いです。





ハッキング



何してるの? キーバイトで8x8ビットをXorブロックし、この行列を反転させ、同じキーでXorを再度行います。 メインの対角線上のビットはまったく暗号化されていないことがわかります。 残りのビットには依存関係があります。

 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ビットによって配信されます(記事の最後にコードがあります)。



好奇心feature盛な機能もあります。 「=」記号の同じ側にある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]
      
      





暗号文の2つの対称ビットのXORは、暗号文のこれらのビットのXORと等しくなります。 おそらくこれはいくつかの場合に役立つかもしれませんが、ここでは重要ではありません。





それほど単純ではない



1.検索が完了すると、65のテキストオプションが表示されます。



私のシステムでは、15〜20分かかりました。 それらをすべて手動で表示し、適切なものを選択できます。 しかし、ヒントがあります-ファイルの先頭の8バイトごとに0x33です。 これがどのように表示されるかがわかりました-これは(8文字のブロックの最上位ビット)XOR(行列Tの8番目のバイト)です。 テキストがラテン語であると仮定すると、最上位ビットはどこでも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バイトがどうあるべきかを仮定します。

次に、3つの暗号化されたバイトと手動で復号化されたバイトの間のXORを見つけます。これらは実際のキーバイトになります。



提案されたキーのビットバイトを書き込みます。

上部に実バイトのビットを書き込みます。 この場合、一部のビットが一致しない場合、マトリックスの残りの半分の対称ビットを反転する必要があります。

ビットが主対角線上にある場合は、上書きします。



ヒント:復号化されたテキストは「yat」で終わります:)(ビデオメモリ)



30日以降のレベルも暗号化されます。 レベルはXMLファイルです。 キーが不正確な場合、さまざまな問題が発生する可能性があります-グラフ内のアーチファクトから、不正なXMLマークアップに関するメッセージを伴う例外まで。 CharmSolitaire.udfファイルに基づいて、実際のキーの最下位3バイトを見つけて、そのようなキーをレベル39まで再生できます。 その長さは0x246Fです。つまり、7つの未知のバイトすべてを見つけることができます。

非表示のテキスト
-ゲームをウィンドウモードにします。

-decrypt_492B48プロシージャのloc_492C3Cにブレークポイントを配置します(これは最後に復号化されたバイトの記録コードです)

-変数buf_14にあるバイトを取得します

-現在のキーとXORを作成します

-あるべきテキストとXORを作成する



ヒント:レベルはタグで終了します
  </ Level> 0x0D、0x0A 






結果



8バイトのキー:

Bre6vqd3



ファイルの先頭にあるラテン語のテキスト:

数年前、小さな猫の猫が彼の家に来て、そこに住んでいる虫に夕食について尋ねました。



プログラムコード:

非表示のテキスト
 #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); } //---------------------------------------------------------------------------
      
      












All Articles