JavaScript を使用して検証コードを解読する
最近、検証コードを解読できる JavaScript スクリプトがインターネット上に登場しました - GreaseMonkey! 「Shaun Friedle」によって開発されたこのスクリプトは、Megaupload サイトの CAPTCHA を簡単に解決できます。信じられない場合は、 http://herecomethelizards.co.uk/mu_captcha/で試してみてください。
正直に言うと、Megaupload サイトが提供する CAPTCHA は上記のコードでは無効です。ここのコードは設計されています。いいえ、あまり良くありません。しかし、さらに興味深いのは、
1. HTML 5 の Canvas アプリケーション インターフェイス getImageData を使用して、検証コード イメージからピクセル データを取得できることです。 Canvas を使用すると、画像を Canvas に埋め込むだけでなく、後でそこから再抽出することもできます。
2. 上記のスクリプトには、完全に JavaScript で実装されたニューラル ネットワークが含まれています。
3. Canvas を使用して画像からピクセル データを抽出した後、それをニューラル ネットワークに送信し、簡単な光学式文字認識テクノロジを使用して検証コードでどの文字が使用されているかを推測します。
ソースコードを読むことで、それがどのように機能するかをよりよく理解できるだけでなく、この検証コードがどのように実装されているかも理解できます。前に見たように、ここで使用される CAPTCHA はそれほど複雑ではありません。各 CAPTCHA は 3 つの文字で構成され、各文字は異なる色を使用し、26 文字のアルファベットの文字のみを使用しますが、すべての文字は同じフォントを使用します。
最初のステップの目的は明らかで、検証コードをキャンバスにコピーし、グレースケール画像に変換することです。
関数convert_grey(image_data){
for (var x = 0; x < image_data.width; x++){
for (var y = 0; y < image_data.height; y++){
var i = x*4+y*4*image_data.width;
var luma = Math.floor(image_data.data[i] * 299/1000 +
画像データ.データ[i+1] * 587/1000 +
画像データ.データ[i+2] * 114/1000);
image_data.data[i] = ルマ;
image_data.data[i+1] = 輝度;
image_data.data[i+2] = 輝度;
画像データ.データ[i+3] = 255;
}
}
}
次に、キャンバスを 3 つの個別のピクセル マトリックスに分割し、それぞれに 1 つの文字が含まれます。各キャラクターは別々の色を使用するため、色で区別できるため、このステップは非常に簡単に実行できます。
フィルター(画像データ[0], 105);
フィルター(画像データ[1], 120);
フィルター(画像データ[2], 135);
関数フィルター(画像データ, カラー){
for (var x = 0; x < image_data.width; x++){
for (var y = 0; y < image_data.height; y++){
var i = x*4+y*4*image_data.width;
// 特定の色のピクセルをすべて白にします
if (image_data.data[i] == color) {
画像データ.データ[i] = 255;
画像データ.データ[i+1] = 255;
画像データ.データ[i+2] = 255;
// それ以外はすべて黒にします
} それ以外 {
画像データ.データ[i] = 0;
画像データ.データ[i+1] = 0;
画像データ.データ[i+2] = 0;
}
}
}
}
最後に、無関係な干渉ピクセルがすべて除去されます。これを行うには、まず前後の黒 (不一致) ピクセルに囲まれた白 (一致) ピクセルを見つけて、一致したピクセルを削除します。
var i = x*4+y*4*image_data.width;
上の変数 = x*4+(y-1)*4*image_data.width;
以下の変数 = x*4+(y+1)*4*image_data.width;
if (画像データ.データ[i] == 255 &&
画像データ.データ[上] == 0 &&
image_data.data[下記] == 0) {
画像データ.データ[i] = 0;
画像データ.データ[i+1] = 0;
画像データ.データ[i+2] = 0;
}
キャラクターのおおよその形状がわかったので、スクリプトはニューラル ネットワークにロードする前に、必要なエッジ検出をさらに実行します。スクリプトは、グラフィックの左端、右端、上端、下端のピクセルを検索し、それらを長方形に変換し、その長方形を 20*25 ピクセルのマトリックスに再変換します。
Cropped_canvas.getContext("2d").fillRect(0, 0, 20, 25);
varedges = find_edges(image_data[i]);
Cropped_canvas.getContext("2d").drawImage(canvas, エッジ[0], エッジ[1],
エッジ[2]-エッジ[0]、エッジ[3]-エッジ[1]、0、0、
エッジ[2]-エッジ[0]、エッジ[3]-エッジ[1]);
image_data[i] = Cropped_canvas.getContext("2d").getImageData(0, 0,
Cropped_canvas.width、cropped_canvas.height);
上記の処理の後、白と黒で塗りつぶされた 1 つの長方形を含む 20*25 の行列が得られます。それは素晴らしいですね。
次に、長方形をさらに単純化します。私たちは、ニューラル ネットワークに入力される「光受容体」としてマトリックスからポイントを戦略的に抽出します。たとえば、特定の光受容体は、ピクセルの有無にかかわらず、9*6 に位置するピクセルに対応する場合があります。スクリプトは、そのような一連の状態を抽出し (20x25 の行列計算全体よりもはるかに少なく、64 個の状態のみが抽出されます)、これらの状態をニューラル ネットワークに供給します。
なぜピクセルを直接比較しないのかと疑問に思われるかもしれませんが、ニューラル ネットワークを使用する必要があるのでしょうか。重要なのは、こうした曖昧な状況を排除したいということです。前のデモを試したことがある場合は、ピクセルを直接比較する方が、それほど多くは発生しないものの、ニューラル ネットワークを介して比較するよりもエラーが発生しやすいことがわかります。しかし、ほとんどのユーザーにとっては、ピクセルを直接比較するだけで十分であることを認めなければなりません。
次のステップは、文字を推測することです。 64 個のブール値 (文字画像の 1 つから取得) と、事前に計算された一連のデータがニューラル ネットワークにインポートされます。ニューラル ネットワークの概念の 1 つは、取得したい結果が事前にわかっているため、その結果に基づいてニューラル ネットワークを適切にトレーニングできるというものです。スクリプト作成者はスクリプトを複数回実行し、生成された値から逆算してニューラル ネットワークが答えを推測するのに役立つ一連の最高スコアを収集できますが、これらのスコアには特別な意味はありません。
ニューラル ネットワークが検証コード内の文字に対応する 64 個のブール値を計算すると、それが事前に計算されたアルファベットと比較され、各文字との一致のスコアが与えられます。 (最終結果は同様になる可能性があります。98% が文字 A、36% が文字 B などです。)
確認コード内の 3 文字すべてが処理されると、最終結果が得られます。このスクリプトは 100% 正しいわけではないことに注意してください (最初に文字を長方形に変換しないと採点の精度が向上するのではないかと思います) が、少なくとも現在の目的ではかなり優れています。そう言ってください。
補足として、このスクリプトは他の単純な検証コードではうまく機能する可能性がありますが、複雑な検証コードでは少し問題があります
。
手の届かないところにあります (特にこの種のクライアントベースの分析)。このプロジェクトの可能性は非常に大きいので、より多くの人がこのプロジェクトに触発されて、より素晴らしいものを開発できることを願っています。