はじめに
かつて、私はRunetでかなり有名なMMORPGをプレイしました。 それに多くの時間を費やしましたが、すぐにゲームプレイは私を退屈させました。 しかし、ゲームがどのように機能するかを学び、そのためのさまざまなガジェットを作成しようとする要望がありました。 最初の結果はC#で書かれたモブを倒すことができ、mobを倒すことができましたが、すぐに面白くないものになりました。 その時までに、私はゲームのハッキングに関連するフォーラムに出会いました。特に、暇なときにゲームのトラフィックを解析することを決めた才能あるプログラマーのトピックに出会いました。 これは私に非常に興味があり、彼の業績を繰り返すことにしました。
Wiresharkで武装した私は、いくつかのダンプを受け取りましたが、率直に言って、コンテンツにby然としました。 16進値に慣れなくなりましたが、構造を分離することは不可能でした。 私はシステムプログラミングの経験がほとんどないので、専門家(トピック作成者)のアドバイスを受け、彼にヒントを求めることにしました。 一般的な推奨事項に加えて、ゲームトラフィックはRC4アルゴリズムを使用して暗号化されており、サーバーデータも圧縮されていることがわかりました(詳細は別の機会に) 方向が設定され、C#で実装するアルゴリズムの研究を始めました。
RC4の概要
したがって、RC4はストリーム暗号です。つまり、オープン(暗号化されていない)テキストの各文字は、クリアテキスト内のキーと文字の位置という2つのパラメーターに応じて暗号化されます。 次のセクションでは、これがどのように機能するかを説明します。
この暗号化アルゴリズムは、マサチューセッツ工科大学の教授Ronald Rivestによって作成されました。RonaldRivestには、RC2、RC5、RC6、RSA、ハッシュラインMD2-MD6などのアルゴリズムもあります。
既知の脆弱性のために、誰かが新しい責任あるプロジェクトでRC4を使用する可能性は低いという事実にもかかわらず、RC4を使用する多くのテクノロジーがあります。 そのような技術の例は、WEP、SSL、TLSです。 また、私の例では、MMORPGのいずれかの開発者がそれを使用することにしました。
アルゴリズム
RC4の仕組みをみんなに理解してもらいたいので、指で説明します。
したがって、入力データはバイトの配列になります。 音声、画像、テキストなど、どんな情報でもかまいません。 もちろん、情報はストリームの形で提供されますが、長い配列ではないにしても、ストリームとは何ですか?
キーレス暗号化とは!? キーは入力としても機能します。 RC4アルゴリズムの場合、8〜2048ビットの範囲で指定できますが、通常は40〜256ビットの範囲が使用されます。
しかし、暗号化のためのデータはバイトの配列であり、何らかの理由でキーはビット単位です。 実際には、ブロックnのサイズなどがあります。 たとえば、n = 8を使用しますが、n = 16を使用すると、アルゴリズムはさらに暗号化されます。 その後、1ステップで2バイトがすぐに暗号化されます。
ストリーム暗号の強度に関して理想的なオプションは、暗号化されたデータのサイズに匹敵するキーサイズです。 次に、各平文ビットは、モジュロ2加算(XOR)によって対応するキービットと結合され、暗号化されたシーケンスを形成します。 復号化するには、受信側で同じ操作を再度行う必要があります。
キービットシーケンスがランダムに選択され、ピリオドがない場合、暗号を解読することはできませんが、長いキーを送信するという問題があります。 したがって、実際には、特定のキーに基づいた擬似乱数ジェネレータを使用して、キーストリームを生成します。 つまり、キーを任意のサイズに拡張し(入力データの処理中に動的に)、XORを入力データと結合します。
例としてC#を使用したアルゴリズムを具体的に扱います。 クラス「RC4」を作成し、次のメンバーを宣言します。
byte [] S = new byte [256];
int x = 0;
int y = 0;
キーストリームを生成するために、暗号は2つの部分で構成される非表示の内部状態を使用します。
-0x00〜0xFF(配列S)のすべての可能なバイトを含む順列 。
-カウンター変数xおよびy。
キーベクトル順列の初期化には、キースケジューリングアルゴリズムを使用します。
private void init( byte [] key)
{
int keyLength = key.Length;
for ( int i = 0; i < 256; i++)
{
S[i] = ( byte )i;
}
int j = 0;
for ( int i = 0; i < 256; i++)
{
j = (j + S[i] + key[i % keyLength]) % 256;
S.Swap(i, j);
}
}
コードの観点からは、複雑なことは何もありません。 Swapメソッド(配列の2つの要素を所定の場所にスワップする)により、Arrayクラスのメソッドの標準リストが展開されることに注意してください。
static class SwapExt
{
public static void Swap( this T[] array, int index1, int index2)
{
T temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
}
キーがわかっている場合、暗号化/復号化の前にinitメソッドを呼び出す必要があります。 これはコンストラクターで実行できます。
public RC4( byte [] key)
{
init(key);
}
次に、擬似乱数生成アルゴリズムジェネレーターを実装する必要があります。 各呼び出しで、メソッドはキーストリームの次のバイトを吐き出し、ソースデータのxor'omバイトと結合します。
private byte keyItem()
{
x = (x + 1) % 256;
y = (y + S[x]) % 256;
S.Swap(x, y);
return S[(S[x] + S[y]) % 256];
}
今、最も単純なものが残っています! 暗号化されていない入力データの配列/ストリームの各バイトに対して、キーバイトを要求し、それらをxor(^)と結合します。
public byte [] Encode( byte [] dataB, int size)
{
byte [] data = dataB.Take(size).ToArray();
byte [] cipher = new byte [data.Length];
for ( int m = 0; m < data.Length; m++)
{
cipher[m] = ( byte )(data[m] ^ keyItem());
}
return cipher;
}
復号化には、同じ方法を使用できます。 明確にするために、別の方法でラップします。
public byte [] Decode( byte [] dataB, int size)
{
return Encode(dataB, size);
}
このクラスの使用方法の例:
byte [] key = ASCIIEncoding.ASCII.GetBytes( "Key" );
RC4 encoder = new RC4(key);
string testString = "Plaintext" ;
byte [] testBytes = ASCIIEncoding.ASCII.GetBytes(testString);
byte [] result = encoder.Encode(testBytes, testBytes.Length);
RC4 decoder = new RC4(key);
byte [] decryptedBytes = decoder.Decode(result, result.Length);
string decryptedString = ASCIIEncoding.ASCII.GetString(decryptedBytes);
そして、すべてが正しく機能することを確認するための結果は次のとおりです。
トラフィックを復号化するクラスを実装することにより、パケットの解析に一歩近づきました。
結論として、この実装は最も単純で最も明白であると言えます。 耐クラック性を高めるのは複雑です。
友人のVortに助言と推奨を感謝します。
ソースへのリンク:
SourceForge-RC4.cs