VoiceAttackセキュリティスタディ





最終的にはいくつかの楽しい驚きを投げました。 注意してください。カットの下には、ヒルズではなくヒルズのスクリーンショットがたくさんありますが、それらは非常に重要なので、スポイラーの下では削除されません。 ああ、ケンタウロスの性器についての冗談がありますが、それは文脈のためだけに含まれています。



VoiceAttackはStar CitizenやElite:Dangerousのようなゲーマーの間で知られており、キーストロークを音声コマンドに関連付けるための非常にシンプルなツールです。 PCの制御にも使用できますが、頭にVRメガネを装着すると完全に開きます



VRについての冗談の休憩






...上記のゲームのいずれかを起動します。



VoiceAttackを初めて知ったのは、Rimas氏から非常に配信されたビデオを見たときでした。その間、Windows音声認識プログラムを無意識のうちに尊重し、そのような恐ろしいアクセントを理解しました。



私はVRヘルメットを持っていなかったので(そしてそのためのお金もありませんでした)、私は最近共有したSteamをElite:Dangerousでプレイし、標準的なボートを私の声で操作することにしました。 VoiceAttackのWebサイトにたどり着いた後、私は開発者の欲求(組み込みの音声認識プログラムをラップするための10ドル)に驚嘆し、すぐにプログラムの最大デモバージョンを使用することに決めました。 しかし、ダウンロードしたプロファイルをダウンロードしようとしたときに、開発者から別の贈り物を見つけました-プログラムのデモ版では、一度に20チーム以上を保存することはできません。もちろん、船との通信を多少複雑にします-E:Dのコントロールの数は明らかに20以上です



欲の丸薬を探す







500ルーブルを免許で払うことを望まなかった誇り高き海賊である私は、ひび割れのためにインターネットに登りました。 壊れたリンク、ランサムウェア、および必要なデータがない場合にネットワークによって提供される他の濁りの山の形で失望を待っていました。 唯一の情報はexetoolsフォーラムで見つかりましたが、プログラムの古いバージョンについて説明しています。 何かをするか、つばを吐いて何もしないことが必要でした。



研究作業



したがって、まずはVoiceAttackを起動するだけです。 ヘルプの後、予想どおり、キーを登録するように求められます:







組み込みのヘルプを見ると、キーは16桁であり、数字と文字の両方で構成されていることがわかります。







フィールドをゴミで埋めようとした後、キーの有効性のチェックがあることが明らかになりました。











みんなのお気に入りのtest@example.comを16桁で入力し、サーバーのキーチェックとエラーメッセージを確認します。











実際、これについて知性を完成させることができます。 登録の原則は非常に単純です。プログラムはリモートサーバーと明示的に通信し、登録データを転送し、応答を分析します。 この方法は、プログラムのパッチによって修正され、ネットワークに入らず、キーがすぐに正しいと見なされるようにするか、またはムッシューの利益を満たすために、登録サーバーエミュレーターが作成されます。 これが私たちがやることです。



分析と難読化解除



実行可能ファイルの分析のために、exeinfopeを使用するのが好きです-安くて怒って(そしてラマーのための情報の行があります!)



exeinfopeでVoiceAttack.exeを開きます。







幸運なことに、.NET + SmartAssemblyは大当たりに似ています-プログラムがC ++で書かれていても(そしてx64でのみ禁止されている場合でも)、もっと面白いもので難読化された場合は、スパゲッティで曲がって分解された出力を選択する必要があります。 そのため、dnSpyデコンパイラーとde4dot難読化解除ツールで簡単に対応できます。



プログラムファイルを別のフォルダーにコピーし、難読化を解除します。







デバッグとコード分析の準備ができました。



ネットワークアクティビティ登録の分析



まず、VoiceAttackを起動し、無効な登録データを入力します。 [OK]をクリックして、デバッガーでプログラムをすばやく停止します。 以下が表示されます。







思ったように、登録時にプログラムはvoiceattack.comサーバーをノックし、そこに登録データを送信します。 リクエスト行を取り出します:



http://voiceattack.com/Validate.aspx?pv=JcArVyeUmUOZfJVj6utdKw==&em=iEm1cpNSBqMsA06LJExtLntuDo0yvQwPzKuIJhhbLt8=&vk=A1Zkz8zPx2VUdM1oi+mKHHtuDo0yvQwPzKuIJhhbLt8=&sg=OBaQB9KBl8iHF1miBzpp/Q==&nn=gXWP+H1uDbYW+crJfgFNs8gexDihTyvNmjpBzp/I0//f3IvaGDFmFz+ll1WxqPdu6iC0SAGY1eJBMRvl2GIr2A==&pr=0+jAkAfwc3I1ZMHk2zdDz3tuDo0yvQwPzKuIJhhbLt8=
      
      





新しい問題がすぐに明らかになります。明らかに、指定した数よりも多くのパラメーターがサーバーに渡されます。base64でもです。 base64のデコードを試みます。







悪くない、データも暗号化されています。 さらに理解する必要があります。



分析とハッキング暗号化



dnSpyで登録フォームを見つけます。 短い検索の後、適切なフォームfrmRegisterを見つけて、btnOk_Clickイベントコードに進みます。







必要なようです。 私たちがすでに知っている間違いのテキストが表示されます。 サーバーに接続する必要がある時点までコードを巻き戻します。







HTTP応答は応答変数に書き込まれます。 暗号化された応答本文が書き込まれている場所を見つけます。







変数text8への参照を探してコードをさらにスピンし、特定の関数の助けを借りてそれを上書きするとつまずきます。







関数に移動して、ポイントに到達したことを確認します。







明らかに、この関数は入力文字列のAES復号化を実装し、復号化された結果を返します。 あとは、this.byte_0変数に含まれるキーをキャッチするだけです。 ブレークポイントを設定して、プログラムを実行します。 ブレークポイントに到達した瞬間に、変数の値に到達します。







これで、出席者とパスワードはすべて手元にあります。 登録サーバーのエミュレート作業を開始できます。



登録サーバー



まず、見つかったキーの暗号化および復号化ツールを作成します。



 using System; using System.Security.Cryptography; using System.Text; public class Program { public static void enc(string text) { string result; Rijndael rijndael = Rijndael.Create(); rijndael.Mode = CipherMode.ECB; byte[] bytes = Encoding.ASCII.GetBytes(text); byte[] key = {0x74,0x72,0x75,0x65,0x47,0x52,0x49,0x54}; //  result = Convert.ToBase64String(rijndael.CreateEncryptor(key, null).TransformFinalBlock(bytes, 0, bytes.Length)); Console.WriteLine(result); } public static void dec(string text) { string result; Rijndael rijndael = Rijndael.Create(); rijndael.Mode = CipherMode.ECB; byte[] key = {0x74,0x72,0x75,0x65,0x47,0x52,0x49,0x54}; byte[] bytes = Convert.FromBase64String(text); result = Encoding.ASCII.GetString(rijndael.CreateDecryptor(key, null).TransformFinalBlock(bytes, 0, bytes.Length)); Console.WriteLine(result); } public static void Main(string[] args) { if(args[0]=="enc") { enc(args[1]); } else if (args[0]=="dec") { dec(args[1]); } } }
      
      





サーバーに送信されたパラメーターを復号化してみましょう。







素晴らしい。 これで、プログラムがサーバーに送信するものを確認できます。 すでに知っているURLを使用して結果を要求し、解読します。







サーバーは、%Validation result%_%Information%の形式で応答を送信しているようです。 再びコードに目を向け、答えが成功を意味するものを見つけます。







ホストにwww.voiceattack.comを追加し、プログラムで「Validation Success_some useless info」行を暗号化し、ページをホストし、プログラムを登録します...



それに応じて、バターでクッキー全体を取得します-プログラムを再度開くと、登録が提案されます。 この動作は、1つのことだけを意味します-起動時にライセンスチェックが導入されます。



サーバーの変更



デコンパイルされたコードに再び登ります。 メインフォームfrmMain_Loadをロードする方法を見つけ、短い検索の後に、登録の必要性についてフォームを呼び出すコードが表示されます。







フォーム読み込みブランチに行く条件に注意を払います-特定の変数this.bool_89がチェックされます。 コードを少しだけ巻き戻し、その値がどのように形成されるかを確認します。







ある行が別の行に適合している証拠があります。 右側には特定のValidationKeyがあり、左側には文字列のリストに対するメソッドの適用があります。 これらの行すべてに順番に適用される方法はどれだと思いますか?



string_およびtext文字列の値を見つけるために、比較行にブレークポイントを配置します。 取得するもの:







registrationEmailおよびregistrationKey行でどの値が置換されるのか、それは明らかだと思います。



GETリクエストを解析して適切な応答を生成するためにサーバーをファイナライズしています:



 from SimpleHTTPServer import BaseHTTPServer import SocketServer import subprocess from urlparse import urlparse from urllib import unquote import ctypes, sys, os PORT = 80 def aes(action, text): return subprocess.Popen(["VoiceAttackCipher.exe", action, text], stdout=subprocess.PIPE).communicate()[0][:-2] def patch_hosts(): with open("C:\WINDOWS\System32\drivers\etc\hosts", "a") as hosts_file: hosts_file.write("\n127.0.0.1 www.voiceattack.com") def unpatch_hosts(): with open("C:\WINDOWS\System32\drivers\etc\hosts", "r") as hosts_file: hosts_lines = [] for line in hosts_file: if line != "127.0.0.1 www.voiceattack.com": hosts_lines.append(line) hosts_lines[-1] = hosts_lines[-1].strip("\n") with open("C:\WINDOWS\System32\drivers\etc\hosts", "w") as hosts_file: for line in hosts_lines: hosts_file.write(line) class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_HEAD(s): s.send_response(200) s.send_header("Content-type", "text/html") s.end_headers() def do_GET(s): """Respond to a GET request.""" query = urlparse(s.path).query query_components = dict(qc.split("=") for qc in query.split("&")) print "Got registration request! Processing..." key = aes("dec", unquote(query_components["vk"])) mail = aes("dec", unquote(query_components["em"])) magic_1 = aes("dec", unquote(query_components["pv"])) magic_2 = aes("dec", unquote(query_components["sg"])) print "Key:", key print "E-Mail:", mail print "Magic number #1:", magic_1 print "Magic number #2:", magic_2 print "Creating validation key..." validation_key = aes("enc", "{}{}{}{}".format(magic_1, mail, key, magic_2)) print "Validation key:", validation_key print "Creating response..." response = aes("enc", "Validation Success_{}".format(validation_key)) print "Response:", response s.send_response(200) s.send_header("Content-type", "text/html") s.end_headers() #   "Validation Success_{}".format(enc(dec(pv)+dec(em)+dec(vk)+dec(sg))) s.wfile.write(response) print "Done! Your program should be registered now!" if __name__ == "__main__": print "Starting VoiceAttack Activation Server..." print "Checking privileges..." is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 if not is_admin: print "Error: please run this program with admin rights." os.system("pause") sys.exit() print "All good." print "Patching hosts..." patch_hosts() print "VoiceAttack Server Started!" print "You can try to register VoiceAttack." print "Press [Ctrl+C] after successful registration." try: httpd = SocketServer.TCPServer(("", PORT), MyHandler, bind_and_activate=False) httpd.allow_reuse_address = True httpd.server_bind() httpd.server_activate() httpd.serve_forever() except KeyboardInterrupt: httpd.server_close() except Exception as e: print e finally: print "Voice Attack Server is shutting down..." #print "Un-patching hosts..." #unpatch_hosts() #    ,        print "Bye-bye." os.system("pause")
      
      





結論の代わりに



上記のすべては、スポーツへの関心だけで行われたものであり、プログラムを取り上げる次の試みでIdaのリストに唾を吐く必要がなかったという幸福によるものです。 私は長い間最初の投稿で生まれたかったのですが、ここでUFOも私を招待しました-何かが間違っているか、どういうわけかそうでない場合はコメントで書くことをお勧めします。



All Articles