[13] [13]または: 単一のプログラムから、カスタマイズされた未知の CPU をリバース エンジニアリングする
tl;dr: 完全にカスタマイズされた不明な CPU 構造用に作成されたプログラムをリバース エンジニアリングしました。CPU に関するドキュメントはありません (エミュレーターも ISA リファレンスも何もありません)。時間。 私たちがやったモデルを教えられるように学びましょう…
先週末、躁病後のストレス解消の抽選として、CMU PPP チームと一緒に Dragon Sector の 2019 Teaser CTF に参加しました CHI 2020のスラッシュオフ日。 Dragon Sector は賢明に尊敬されているポーランドのチームであり、鋭い CTF の歴史を持っているので、小売店で彼らが持っていたものをのぞき見したかった. Micromega uM-FPU フローティング レベル ユニットのバイトコードを反転させるという不都合である「ummmfpu」を解決した後、CPU クロールの不都合に対処する必要があることは明らかです。 概要はこちらCPUクロールの不便さのために、愛らしい架空の裏話があります:
socat tcp4-connect:cpuadventure.hackable.instrument: 1234 fd:0,rawerヒント: これはカスタマイズされた CPU です。わざわざグーグルで検索しないでください。
sport.bin
ここに居酒屋があります。 居酒屋の中にはヴァリスが見えます。 オプションを選択してください: - (北)北に行く - (E)ASTに行く - (T)ALK TO VALIS - (D)RINK - (I)インベントリーを表示する あなたの選択:効果的。 ベテラン大学アドベンチャースポーツです。 それに参加するということは、敵と戦ってこのヴァリスのキャラクターからフラグを獲得する立場にあることを意味します。あなたの選択: T あなたは酒場に入り、ヴァリスに近づきます。 -ねえ、旗を見つけるのを手伝ってくれませんか? - 旗? かもしれませんが、まず、REDBULL が必要です。 - 私は... 私はレッドブルを持っていません。 - それなら、自分を便利にして見つけてください。 ここに居酒屋があります。 居酒屋の中にはヴァリスが見えます。 オプションを選択してください: - (北)北に行く - (E)ASTに行く - (T)ALK TO VALIS - (D)RINK - (I)インベントリーを表示する あなたの選択:最初のステップというわけで、急いで調べてみると、このファイルの単位次元は 20 (整列時のウィンドウの幅) で割り切らなければならないことがわかりました。 特定の単位の次元を突き止めるために、長く繰り返される文字列をスパイする一時的なスクリプトを作成しました (どのコードもステレオタイプのシーケンスの繰り返しを所有することになると考えました)。 最長の繰り返し文字列は、位置 43625 と 44510 を調べた次の 425 ビット ブロックに 1 回変更されました:
sport.bin
ファイルはかつて間違いなくより重要なものに変更されました。 バイナリを期待して、16 進エディタで起動しました。 1100111111010000001111001100100011100000110011010000000000011001001110101000000110100111100001111110011001110000000111000000011...[12]... それは 文字通り バイナリ ファイル – テキストASCII の 1 と 0 だけを含む whine マテリアル ファイル。 ここにカスタマイズされた CPU のマシン コードがあることは誰もが知っています。 何もありません このCPUについては他にありません。 したがって、私たちの最初の巨大な課題は、このバイナリ ファイルの単位次元を解決することです (eg is it 8 -ビット整列? または、おそらく 12 ビットまたは 18 ビット整列です ベテランの世話をしますアーキテクチャ ?) 未知のファイルの位置合わせを解決するために、私はベテランのトリックに目を向けます。行の折り返しの長さがファイルの位置合わせに合うまで、テキストの泣き言マテリアル ウィンドウのサイズを変更します。 これは、反復 XOR 暗号文、未知の (圧縮されていない) ファイル形式、および未知の CPU からのコードを処理する問題に対して驚くほど機能します:10000011111110000001010100011111110100000101100010111000001001000101000100001000100001010001011000101000000001111111111100010000011110010100100001010100111100000110000010100000101000101000011110001111001101111001010100001010000111110100001010000110010011011110011111000000111011101000000001100000110000111101011010111011000100100010100000111000100011100011000000000101010101100010111000001010000001101010010000000011000001100
リピート間のギャップが 1 回 885 に変化したため、単位次元は常に 5 ビットである必要があると結論付けました。つまり、未知の CPU は 5 ビットの「バイト」を所有する必要があります。 開発! 5 ビットのパンチ カード エンコーディングを検討し、フラッシュのように歴史的な ボードコード エンコーディング。 確かに、いくつかのスニペットをデコードするためにオンライン デコーダーを使いこなした後、読みやすいテキストの泣き言の素材が得られました! ⇩ドラゴン⇩ここ⇧;⇧⇧⇩SHE⇩APPEARS⇩TO⇩BE⇩ガード⇩SOME⇩KIND⇩OF⇩A⇩BOTTLE⇧;&.&.⇩␀THERE⇩IS⇩A⇩B
LSB Baudot ITA 1
Baudot コードを使用してファイル全体をデコードしようとした後、最初の 20,000 ビット程度が意味不明になり、その後、完全に読みやすいテキストの泣き言の素材。 これにより、ファイルの最初の部分が「コード」部分に対応し、これが固定文字列を含む「情報」部分によって採用されるようになりました。 このマシンは間違いなく I/O 用のベテラン Baudot コードであると推測しました。これが、Baudot エンコーディングを使用してメモリ内に固定文字列を保持していた理由です。 コード セグメントをより読みやすくするために、16 進エンコーディングに対応するがアルファベット 0-9a-v まで拡張された恐ろしい 32 エンコーディングを使用してコード化することは明らかです。 これがgame.binファイルが処理しているように見えるもので、最初の部分はhorror-32でエンコードされ、Baudotの後半はデコードされています(分厚いファイルはこちら:sport.b32):pv83pi70pk00p7a0qfgvpjg3f0kf13f28p5f3pv10pk40pn60f0sf1sf24p5f3r9c11qad0f0sf1df26p5f39c21qad0f05f1ff26p5f39c41qad0f08f1df26p5f39c81qad0f0hf1ef26p5f3r1c00qaq15c20qcl0f01f1of27p5f3p3g3psf35c10qal0f02f1nf27p5f3p3g3psf3rf0hf1nf27p5f3f05f16f27p5f3rf84f95101311fl0f510f84907qa40b518447qa40b514f84f95k9m0k9m0k9m0907qa40b511447qa40b512ruougf10f20g0i9g0i910931b320u2u1u0ro9f0o9f0ojh0o9f0o9f0o9f0olj0o9f0o9f0o9f0o9f0o9f0o9f0o9f0o9k1onp0o9f0o9f0o9f0o9f0onf0ot82odi0o9f0o9f0o9f0o9f0o9f0o9f0o9f0olg0o9f0f0gf1df24p5f3r9c11qa835548755 93e9n59ka8fo87r85g8ui8ml8ed87b9h89u291u82333333333456789abcdb01234567892)%c3SOPLICA PIGWOWAENEMY HEALTH のボトル: あなたは REDFORD に近づきます。 あなたは酒場に入り、ヴァリスに近づきます。簡単にするために、次のセクションで参照できます5ビットのガジェットに「バイト」として。 私たちは内部でこのようなものに多くの他の名前を持っていました – 私はそれらをkibblesと呼び、Zachはそれらをhecs. 不明な CPU 構造の反転 正しく、今は骨の折れる部分を稼いでいます - 完全に未知のカスタマイズされた CPU 構造で逃げることができる 4000 バイトのコードを逆にします。 コードから、可変長 命令が平和的であることはある程度明らかです。ある段階で保持される明らかな繰り返しパターンはありません。私はこれに何時間も費やし、後でチームメイトのザカリー・ウェイド (@zwad3) の助けを借りました。 私の最初のビジネス記録は、繰り返しのコードをフェッチしようとすることから始まりました。 私はコードを、より評価しやすい短い反復シーケンスに分割し始めました。 私が厳密なコースを持っていたことを明かせたらいいのにと思いますが、それは次の非常に強力なアルゴリズムに変わりました: コードをスキャンして、多くのことが明らかになることが 1 つある場合は横目で見てください
fetch-substitute をかがめて、改行をその繰り返しに貼り付けます の類似点/相違点に注意してください次の分割行このコースを約1時間繰り返し… たとえば、私が見つけたパターンの 1 つは、セット「.」が一度「0f0.f」に変わったものです。 未知のキャラクターを表現しました。 私は次を稼ぐためにそれを分割します:
pv83pi70pk00p7a0qfgvpjg3f0kf13f28p5f3pv10pk40pn60f0sf 1sf24p5f3r9c11qad0f0sf 1df26p5f39c21qad0f05f 1ff26p5f39c41qad0f08f 1df26p5f39c81qad0f0hf 1ef26p5f3r1c00qaq15c20qcl0f01f 1of27p5f3p3g3psf35c10qal0f02f
まさにここ、とてつもなく貴重! 2 行目と 3 行目から、「...p5f3r9c...」と「...p5f39c...」を所有していることを察知できます。これは、「r」が 1 バイトのオペコードであることを示唆しています。 1 つのオペコードの先端であり、「9c..」は別のオペコードの始まりです。 最後の 2 行で、「p5f3r1c…」を所有しています。これは、「1c..」が別のオペコードであり、「p3…」が別のオペコードであるという青写真です。
私は命令を分割し続け、これを何度も処理し、同じブロック間の共通点と相違点を利用して、実行可能な命令にタイトルを付けました。 最後に、これを処理することが 1 つあります:
pv83 pi70 pk00 p7a0 qfgv pjg3 f0k f13 f28 p5f3 pv10 pk40 pn60 f0s f1s f24 p5f3 r 9c11 qad0 f0s f1d f26 p5f3 9c21 qad0 f05 f1f
「p」と「q」はオペランドが 3 バイトの命令、「f0」と「f1」と「f2」はオペランドが 1 バイトの命令だと推測しました。 、および「9c」は、2 つのオペランドを持つ命令に一度変更されました。 確かに何の説明書か分からなかったけど カタログ見たら抽出した最後の「p」命令を調べてみると、最も全体的な「p」命令が 1 回「p5f3」に変更されていることがわかりました。 さらに、全体の場所を見てみると、常に「f0」、「f1」、「f2」命令が先行するように変更されていることがわかりました。 「f0」、「f1」、「f2」オペランドの合計、f2 オペランドは常に 4 ~ 8 の範囲内にあることがわかりました。 CPU には 32 KiB のプログラム メモリ (アドレス指定に 15 ビットが必要) があることを思い出して、私は賭けをしました。 「f0」、「f1」、および「f2」は、上位バイトとして f2 を使用して、多かれ少なかれ取引をロードしていました。 いくつかのアドレスをつなぎ合わせてみたところ、ヒントの固定文字列の開始を正確に示していることがわかりました。「印刷」機能が適切に見つかりました!! これは、「p5f3」が多かれ少なかれ印刷文字列または呼び出し命令に変更されたことを意味します。 3 バイトのオペランドを指定すると、「呼び出し」は確実に 1 回に変更されます。 もう一度試してみましたが、合計「p」命令で、3 つのオペランド バイトが シュリンプ エンディアン レコード [7] で処理されるプログラムを表していることがわかりました。 – つまり、最後のオペランド バイトは、1 回の非常に重要なバイトに変更されました。 これが一転して大ブレイク! 私たちは最初の命令を発見しました。 「f0」と「f1」のベテランが別の 1 つの場所にいるのを見て、おそらくアドレスをロードしていないのではないかと推測しました。別の例として、おそらくビューを 4 つのレジスター (f0 ロード レジスター 0 、たとえば) 5 ビットの瞬時定数を使用します。 これは、p5f3 の意味をなすでしょう – 機能 3f5 (“print_string”) の 3 つのレジスタ引数を 1 回ロードするように変更されました。私は逆アセンブラを書き始めました。これは、「印刷」イディオム (f0x、f1x、f2x、p5f3) を識別し、印刷された文字列をインラインで逆アセンブリに入れます。 このシステムの非常に多様な文字列に起因する、これは逆アセンブリを非常に読みやすくし、機能ブロックのセットを簡単に識別できるようにしました (分厚い逆アセンブリ ここ
):
0: 38v に電話 4: 7i に電話 8: 大丈夫 c: a7 に電話 g: q vgf 大丈夫: 3gj に電話 o: print 83okay # 'オプションを選択x0e: rnrnx0fx00' 15: call 1v 19: call 4k 1d: call 6n 1h: print 4ss # 'rnYOUR CHOICEx0e: x0fx00' 1u: ret 1v: unk 9 20: unk c 21: unk 1 22: unk 1 23: q 0da 27: print 6ds # 'x0e- x0fGO x0e(x0fSx0e)x0fOUTHrnx00' 2k: unk 9 2l : unk c 2m: unk 2 2n: unk 1 2o: q 0da 2s: print 6f5 # 'x0e- x0fGO x0e(x0fNx0e)x0fORTHrnx00' 39: unk 9 3a: unk c 3b: unk 4 3c: unk 1 3d: q 0da 3h: print 6d8 # 'x0e- x0fGO x0e(x0fEx0e)x0fASTrnx00' 3u: unk 9 3v: unk c 40 : unk 8 41: unk 1 42: q 0da 46: print 6eh # 'x0e- x0fGO x0e(x0fWx0e) x0fESTrnx00' 4j:ret このエビのようなコード スニペットとまったく同じように、いくつかの問題を賭ける準備ができたら、「q0」命令に変更しました。多かれ少なかれ条件付き分岐を描写する必要があり (1v 機能で無効な命令の印刷をスキップするのはベテランのため)、命令「9c11」、「9c21」、「9c41」、「9c81」は常にもう少し平和的に描写する必要があります。またははるかに少ないAND命令 – これらの命令が許可されているかどうかを覗き見するために生きているビットをチェックします(これらの命令で「1」、「2」、「4」、および「8」を使用することはひどくわかります). 次の 2 時間、私とザッカリー・ウェイド (@zwad3) は多くの指示に取り組み、何が何であるかについて推測を行い、改良しました。指示はしました。 多くの読み取り可能な印刷ステートメントが存在することで、私たちの仕事はずっと簡単になりました. 私たちはすべてのメンテ逆アセンブラを作成することを決心しました。そのため、メンテ テンポで命令を探索し、調査結果を適切に断片化し、前後に進めます。コードを逆にする
ほんの数時間後、私たちは分解して巨大な開発を製造し始めていました。 クライアントの在庫 (特に、「ドリンク」機能とそれに関連付けられた各ハンドラー) を操作するコードに注目することで、メモリの小売業者と貨物の指示 (1 KiB の情報メモリがリンクされている可能性があることを確認してください) を見つけました。 CPU)。 次に、明確な算術/適正判断 (ALU) 命令がメモリ オペランドを使用することを突き止めました (たとえば、「9c41」は、間違いなく「AND 情報取引の価値は 1 と瞬間的な 4 である」と想定されていました)。 そこから、情報メモリに存在する変数を再構築します。たとえば、[0] は新しい役割の NPC の ID を保持し、[6,7] は新しいヘルス ( の下位 5 ビット) を含みました。 、[7]) の上位 5 ビット。 このレベルでは、私は指示を逆にすることから遠く離れて、分解に注釈を付け、このシステム自体をリバース エンジニアリングし始めました。 以下の 5 ビット値 (「0y…」、「0x」の戯れ) に対する私のユーモラスな表記法を利用するのは良いことかもしれません:
_start: 初期化の呼び出し L4: check_moves の呼び出しcall print_menu call handle_command br 4 print_menu: call print_itemname print 83okay # 'オプションを選択x0e:rnrnx0fx00' call print_moves call print_npcmenu call print_itemmenu print 4ss # 'rnあなたの選択x0e : x0fx00' ret print_moves: and 0y1, [1] brz 2k print 6ds # 'x0e- x0fGO x0e(x0fSx0e)x0fOUTHrnx00' 2k: and 0y2, [1] brz 39 print 6f5 # 'x0e- x0fGO x0e(x0fNx0e)x0fORTHrnx00' 39: and 0y4, [1] brz 3u print 6d8 # 'x0e- x0fGO x0e(x0fEx0e)x0fASTrnx00' 3u: and 0y8, [1] brz 4j print 6eh # ' x0e- x0fGO x0e(x0fWx0e)x0fESTrnx00' 4j: ret print_npcmenu: add 0y0, [0] brz 6m sub 0y2, [0] br 5p print 7o1 # 'x0e- (x0fTx0e)x0fALK TO x00' call print_npcname call print_crlf 5p: sub 0y1, [0] brz 6 m print 7n2 # 'x0e- (x0fFx0e)x0fIGHT x00' call print_npcname call print_crlf 6m: ret print_itemmenu: print 7nh # 'x0e- (x0fDx0e)x0fRINKrnx00' print 765 # 'x0e- x0fSHOW x0e(x0fIx0e)x0fNVENTORYrnx00' ret
平和的な未知のオペコードが大量にありました。条件付き分岐ゼロ (brz) に 1 回変更されたため、「qc」が 1 回変更されたもの (br その上)。 しかし、このシステムの良識を判断し始めるには、十分に 1 回に変更されました。 ほとんどの場合、8 の周りを散歩できるスポーツ能力。 ×8 設計図。NPC (ドラゴン、「クリムゾン ブル」、フォーク) がランダムに配置されます。 おそらくどのNPCとも戦うのが良いでしょう(そうするメニューのチャンスが不足しているにもかかわらず、ヴァリスでさえ)。 戦いの直後に、ランダムな量の傷や行方不明を与えて相手を攻撃し、その後相手があなたを攻撃しますが、再びランダムな傷や行方不明を与えます。 または、防御を維持することもできます。これにより、対戦相手の攻撃が防御を逃すかヒットし、ダメージを受けなくなります。 チートできるダメージ内で、あなたの健康を 1000 に設定するだけでなく、隠し変数 (「チート」、10 に対処) を 1 に設定します。合計で、ある種のアルコールのボトル(明らかに、もはややや好ましいスポーツではありません).
あなたの選択: W ここに黄色いドラゴンがいます。 彼女はある種のボトルを守っているようです。 オプションを選択してください: - (南)南に行く - (北)北に行く - (東)西に行く - (西)東に行く - (西)東に行く - (D)リンク - (I)目録を見せる あなたの選択: F YOU ATTACK THE YELLOW DRAGON. - (A) アタック - (S) ヒールを使用 - (C) HEAT YOUR CHOICE: A YOU HIT THE YELLOW DRAGON. 敵の体力: 94% 黄色いドラゴンに襲われる。 現在の体力: 88% - (A) 攻撃 - (S) ヒールを使用 - (C) 選択したヒートを選択: C 現在の体力: 1000%。 - (A) アタック - (S) ヒールを使用 - (C) HEAT YOUR CHOICE: A YOU HIT THE YELLOW DRAGON. 敵の体力: 88% 黄色いドラゴンが攻撃してきますが、失敗します。 - (A)TTACK - (S)HIELD を使用 - (C)HEAT YOUR CHOICE: S 黄色いドラゴンがあなたを攻撃しますが、あなたのシールドで跳ね返ります。 - (A)TTACK - (S)HIELD を使用 - (C)HEAT YOUR CHOICE: S イエロー ドラゴンがあなたを攻撃しますが、ミスします。あなたの選択: A あなたは黄色いドラゴンに当たります。 敵の体力: 10% 黄色いドラゴンが攻撃してきますが、失敗します。 - (A) アタック - (S) ヒールを使用 - (C) HEAT YOUR CHOICE: A YELLOW DRAGON を倒す。 アイテムを獲得: ラム酒のボトル。 ここには興味深いものは何もありません。 オプションを選択してください: - (南)南に行く - (北)北に行く - (東)西に行く - (西)東に行く - (D)リンク - (I)インベントリーを表示する あなたの選択: D 飲み物を選ぶ: - BOTTLE OF R(U)M YOUR CHOICE: あなたはボトルのラム酒を飲みます。
Valis はフラグを獲得するのに重要な最初の NPC で、関連マシンを持っています。これにより、彼はあなたにたくさんのオブジェクトを要求します – 真紅の雄牛の束飲み物(クリムゾンブルの敵を倒すことで得られます)、大量の混合飲み物(例:ブルードラゴンとグレイドラゴンを倒してそれらのドロップを混ぜ合わせることで得られるジントニック)、および倒すかサーブすることで得られる活力ストリップゲーム内の別の人間の NPC (レッドフォード) に。 あなたが彼の長い一連の要求を終了するイベント内で、彼はあなたにフラグを立てます – しかし、完全に 「だまされた」変数は生きていません。 したがって、私たちの目標は、不正を行わずにゲームに勝つことです。 すべての敵と同じ合計 100 HP で起動するため、合計でプレイする場合、すべての敵を倒すことはできなくなります (必要なすべてのオブジェクトを獲得するには、約 20 を倒すことが重要です)。 対戦相手が常にミスするように、ある程度の容量で RNG をリグする必要があります。 乱数技術は、多かれ少なかれ PRNG (37a に対処) のように見える機能ですが、他の場所ではベテランではない奇妙な命令を利用しているため、元に戻すことはできませんでした。 繰り返しになりますが、3 つのメモリ領域 [12]、[12]、および から関連ベクトルをロードすることを確認しました。その分厚い関係の青写真は、合計で 15 ビットの次元です。 これは、RNG が一時的な期間を所有する必要があることを意味します – 長さは 2^15=32768 以下です.
Jay Bosamiya (@jay_f0xtr0t) と Matthew Savage (@thebluepichu) がエクスプロイトを実装しましたが、私は RNG 実装を元に戻そうと (無駄に) 平和的に試みました。 100,000 回連続してエクスポーズ「プロテクト」を送信するだけで、RNG によって出力されたビットに対応する敵の「ヒット」と「ミス」のチェーンを獲得する準備が整いました。 このシーケンスが 32767 の周期で繰り返されることを確認しました。したがって、マスター エクスプロイトを発明する準備ができました。最初に遭遇した敵で、ヒットとミスのチェーンを強化するために 40 のインスタンスをシールドし、マンモスでシーケンスを探しました。定期的なシーケンス、そして敵が常に見落とすように、いつ保護し、いつ攻撃するかを発見しました。 次に、 の設計図を横切って歩いただけです) )murderhobo モード、あらゆるものを殺し、戦利品を奪う。 被害の範囲内で、私たちはヴァリスに戻り、賢明に旗を求めました。
DrgnS{m4kin9-v4lis-totally chuffed-w1th-n4t1ve-b4ud0t-cpu}
[13]うわー! 確かに、なんて冒険でしょう。 10 時間もかからずに、バイナリ文字列と CPU に関するゼロのドキュメントから、完全な逆アセンブラと優れた鋭い逆アセンブル コードに関して 2 つになったことを、私はいくらか考慮することができません! コード全体の概要 GitHub: 私の逆アセンブラ, ザックの逆アセンブラー, 私の生分解, 私の注釈付き分解, Matt のエクスプロイト クライアント。 𝚆𝚊𝚝𝚌𝚑 𝙽𝙾𝚆 📺