Radare2で爆弾を解除する



こんにちは、%username%! 本日は、リバース用フレームワークの無数の可能性を探りに行きます。 被験者の形で、私は最初の爆弾を取りました。それはカーネギーメロン大学のウェブサイトからでした。



ハッシュ
なぜなら ハッシュの場合にのみ、バイナリを変更できます。

md5sum:1f38d04188a1d08f95d8c302f5361e9f

sha1sum:31022a4baa524f6275209f7424c616226bc9814b

sha256sum:8849e033691d51426c0c91a76eeb0c346eddd37e8fdf21cd93acd16669f1b461



それは何ですか?



Radare2 (別名r2)は、バイナリファイル(最初は16進エディター)を探索するためのオープンソースのクロスプラットフォームフレームワークです。 これの主な競争相手は悪名高いIlfak IDAですが、残念ながら、学生にとっては高価であり、x86-64の無料版は友好的ではありません。 そしてレーダーはより涼しいようです。

バイナリ爆弾または単なる爆弾は、トレーニング用の実行可能ファイルであり、特定の行数を受け取り、すべての行がチェックに合格した場合、若いアナリストにこれを祝福します。 これらはいわゆるレベルまたはフェーズであり、6つあります。



フレームワークについてもう少し



Radare2は既に述べたように、単なる逆アセンブラーではなく、フレームワークです。 デバッガー、16進エディター、コンパイラー、 ROPガジェットの検索など、さまざまなツールが多数含まれています。 非コンソール愛好家の場合、WebUI( $ r2 -c "=H" file



)とBokkenという 2つの未加工のフロントエンドもあり$ r2 -c "=H" file





マニュアルは通常通り、各コマンドの後に「?」を追加することにより作成されます。 たとえば、「pd?」は、pdで始まるコマンドの説明を表示します。



いくつかの関連リンク:



行くぞ!



実行ファイル自体に加えて、ソースコードファイルが提供されました。 ただし、入力の初期化、フェーズの呼び出し、および面白いコメントがあります。 残りの関数は対応するヘッダーから取得されますが、これらはありません。

同じファイルで十分に大きい
 /*************************************************************************** * Dr. Evil's Insidious Bomb, Version 1.1 * Copyright 2011, Dr. Evil Incorporated. All rights reserved. * * LICENSE: * * Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the * VICTIM) explicit permission to use this bomb (the BOMB). This is a * time limited license, which expires on the death of the VICTIM. * The PERPETRATOR takes no responsibility for damage, frustration, * insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other * harm to the VICTIM. Unless the PERPETRATOR wants to take credit, * that is. The VICTIM may not distribute this bomb source code to * any enemies of the PERPETRATOR. No VICTIM may debug, * reverse-engineer, run "strings" on, decompile, decrypt, or use any * other technique to gain knowledge of and defuse the BOMB. BOMB * proof clothing may not be worn when handling this program. The * PERPETRATOR will not apologize for the PERPETRATOR's poor sense of * humor. This license is null and void where the BOMB is prohibited * by law. ***************************************************************************/ #include <stdio.h> #include <stdlib.h> #include "support.h" #include "phases.h" /* * Note to self: Remember to erase this file so my victims will have no * idea what is going on, and so they will all blow up in a * spectaculary fiendish explosion. -- Dr. Evil */ FILE *infile; int main(int argc, char *argv[]) { char *input; /* Note to self: remember to port this bomb to Windows and put a * fantastic GUI on it. */ /* When run with no arguments, the bomb reads its input lines * from standard input. */ if (argc == 1) { infile = stdin; } /* When run with one argument <file>, the bomb reads from <file> * until EOF, and then switches to standard input. Thus, as you * defuse each phase, you can add its defusing string to <file> and * avoid having to retype it. */ else if (argc == 2) { if (!(infile = fopen(argv[1], "r"))) { printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]); exit(8); } } /* You can't call the bomb with more than 1 command line argument. */ else { printf("Usage: %s [<input_file>]\n", argv[0]); exit(8); } /* Do all sorts of secret stuff that makes the bomb harder to defuse. */ initialize_bomb(); printf("Welcome to my fiendish little bomb. You have 6 phases with\n"); printf("which to blow yourself up. Have a nice day!\n"); /* Hmm... Six phases must be more secure than one phase! */ input = read_line(); /* Get input */ phase_1(input); /* Run the phase */ phase_defused(); /* Drat! They figured it out! * Let me know how they did it. */ printf("Phase 1 defused. How about the next one?\n"); /* The second phase is harder. No one will ever figure out * how to defuse this... */ input = read_line(); phase_2(input); phase_defused(); printf("That's number 2. Keep going!\n"); /* I guess this is too easy so far. Some more complex code will * confuse people. */ input = read_line(); phase_3(input); phase_defused(); printf("Halfway there!\n"); /* Oh yeah? Well, how good is your math? Try on this saucy problem! */ input = read_line(); phase_4(input); phase_defused(); printf("So you got that one. Try this one.\n"); /* Round and 'round in memory we go, where we stop, the bomb blows! */ input = read_line(); phase_5(input); phase_defused(); printf("Good work! On to the next...\n"); /* This phase will never be used, since no one will get past the * earlier ones. But just in case, make this one extra hard. */ input = read_line(); phase_6(input); phase_defused(); /* Wow, they got it! But isn't something... missing? Perhaps * something they overlooked? Mua ha ha ha ha! */ return 0; }
      
      







r2を開始した後、彼はランダムなフレーズで私たちに会います。 次に、現在のポインターをエントリポイントに設定し、コマンドを待ちます。 ファイルを開くときの-Aフラグは、すぐに分析します。

同じことがaで始まるコマンドのブロックでも実行できます。たとえばafl-バイナリから関数のリストを抽出します。 -grep-aのアナログ(フィルター)。 私たちの機能について彼を見てみましょう。

 $ r2 -A bomb -- In soviet Afghanistan, you debug radare2! [0x00400c90]> afl~phase 0x00400ee0 28 3 sym.phase_1 0x004015c4 149 8 sym.phase_defused 0x00400efc 71 8 sym.phase_2 0x00400f43 139 8 sym.phase_3 0x0040100c 86 7 sym.phase_4 0x00401062 146 9 sym.phase_5 0x004010f4 272 26 sym.phase_6 0x00401242 81 5 sym.secret_phase
      
      





レベル1



素晴らしいことに、ソースからのすべての機能が適切に配置されていますが、同時に秘密の段階も発見しました。 最後に、最初のレベルの内容を見てみましょう。 これを行うには、ポインターを関数の特定のアドレスに移動し、逆アセンブルするために必要な数のオペコードを表示します。

 [0x00400c90]> s 0x00400ee0 #    's' -   [0x00400ee0]> pd 8 #  8    
      
      





なぜなら ボックスからのr2の出力は問題ないので、わかりやすくするために、アセンブラーのリストを写真の形で投稿します。



レーダーは、ジャンプニーモニックを備えたXREF(制御の転送元)を提供してくれました。 さらに、指定されたアドレスで文字列を置換し、最も重要なこととして、ブロック内の遷移をアスキー矢印として示しました。

比較のため、objdumpで出力
 0000000000400ee0 <phase_1>: 400ee0: 48 83 ec 08 sub rsp,0x8 400ee4: be 00 24 40 00 mov esi,0x402400 400ee9: e8 4a 04 00 00 call 401338 <strings_not_equal> 400eee: 85 c0 test eax,eax 400ef0: 74 05 je 400ef7 <phase_1+0x17> 400ef2: e8 43 05 00 00 call 40143a <explode_bomb> 400ef7: 48 83 c4 08 add rsp,0x8 400efb: c3 ret retq
      
      







それを詳しく調べることなく、私たちが必要とする最初の行は「カナダとの国境関係はかつてないほど良くなった」ことであることが明らかになります。 そしてもちろん、爆弾を与えるとき、彼女は私たちを第二段階に入れます。

 $ ./bomb Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Border relations with Canada have never been better. Phase 1 defused. How about the next one?
      
      





レベル2



逆アセンブルされた関数を出力する2番目のオプションは、絶対アドレス指定/ラベルを使用することです。 2番目のフェーズの例では、次のようになります。

 [0x00400ee0]> pdf @ 0x00400efc
      
      





または、住所が不明な場合:

 [0x00400ee0]> pdf @ sym.phase_2
      
      







私たちの究極の目標は、爆弾を爆発させることではありません。 sym.explode_bomb呼び出すための呼び出しを行わないでください 。そのため、これに基づいて構築します。 したがって、両方のjeジャンプが常に機能するはずです。

私たちの邪魔になる最初のことは、 sym.read_six_numbersを呼び出すことです 。 したがって、この呼び出しの後、スタックの最上部に1つ存在するはずです。 この関数で何が起こるか見てみましょう。



以前は、r2はファイルをopオペコードに基づいて関数に解析しました。これにより、多くの場合、いくつかの関数が出力されました(たとえば、exit()システムコールがある場合)。 そのような場合、レーダーが機能を誤って判断した場合、手動でこれを行うことができます。

 [0x00400ee0]> s 0x0040149e #       [0x0040149e]> af+ sym.read_six_numbers `?vi $$-sym.read_six_numbers` rsn #   rsn,    sym.read_six_numbers   .
      
      





ちなみに、この例では、ペアの括弧``を使用して別のコマンドが最初の引数として使用されました

関数自体では興味深いことは何も起こりません;読み取り行から6つの数字を受け取り、渡されたポインターに順番に書き込みます。 それから彼は5つ以上の数字があることを確認します。

Cでは、次のようになります。

 void read_six_numbers(char *str, long long *p) { if (sscanf(str, "%d %d %d %d %d %d", p, p+1, p+2, p+3, p+4, p+5) <= 5) explode_bomb(); }
      
      







phase_2関数に戻ります。 配列へのポインターには、スタックの最上部へのポインターが渡されます( mov rsi、rsp )。 したがって、行の最初の数は-1でなければなりません。

ご覧のように、非常に多くの移行があります。 IDAユーザーは、おそらくスペースバーを押して遷移グラフを見るでしょう。 信じられませんが、ここにあります。 vimと同様に、ビジュアルモード( Vコマンド)があり、 Vコマンド(またはすぐにVV )による同じ遷移グラフが含まれます。 各modを終了します-q

出力はこのようなかわいいASCIIグラフになります




最小の交差点で(以前のバージョンと比較して)完全に表示されます。 この奇跡を矢印またはvimのような'hjkl'で移動できます。 ブロックの配置が気に入らない場合は、 Shift + 'hjkl'ホットキーを使用してブロックを移動することもできます。 同時に、選択したブロックが移動し(青)、選択できるのはTab / Shift-Tabです。

最初のブロックでは、2番目の数値へのポインターがrbxおよびrbpに書き込まれます -数値の配列の最後に。 そして、コントロールは、隣接する数値がペアで比較されるサイクルにあふれます。

 mov eax, dword [rbx - 4] ;     eax add eax, eax ;   cmp dword [rbx], eax ;    je 0x400f25 ;  ,   call sym.explode_bomb ;  ,    add rbx, 4 ;      cmp rbx, rbp ; ,      jne 0x400f17 ;  ,     jmp 0x400f3c ;  
      
      





1から32までの2の累乗のシーケンスを導入する必要があることがわかりました。すばらしいですが、これが必要なものであることを確認してください。

 $ ./bomb ... Phase 1 defused. How about the next one? 1 2 4 8 16 32 That's number 2. Keep going!
      
      





レベル3





たぶん、今ではてんかん発作のアセンブラーから遠く離れた人が、窓の端で十字架に乗り込もうとしています。 実際、何も恐れることはありません。それは、最も一般的なswitch-caseブロックのように見えます。

数値は、前のケースと同じ方法で読み取られますが、関数を呼び出すことはなく、2つだけです。 それらは、それぞれ[rsp + 8]および[rsp + 0xc]に保存されます。

次に、両方の数値が正常に読み取られたこと、および最初の引数が7以下であることを確認します。その後、スイッチオフセット(入力された数値)* 8でアドレス0x402470に移動します。

ケースラベルの住所があると推測するのは難しくありません。 根拠がないように、そこに本当にあるものを見てみましょう。 これは、 pxコマンドグループを使用して実行できます。 この場合、8バイトの単語( Q uad-word)に興味があります。

 [0x0040149e]> pxQ 72 @ 0x402470 0x00402470 0x0000000000400f7c sym.phase_3+57 0x00402478 0x0000000000400fb9 sym.phase_3+118 0x00402480 0x0000000000400f83 sym.phase_3+64 0x00402488 0x0000000000400f8a sym.phase_3+71 0x00402490 0x0000000000400f91 sym.phase_3+78 0x00402498 0x0000000000400f98 sym.phase_3+85 0x004024a0 0x0000000000400f9f sym.phase_3+92 0x004024a8 0x0000000000400fa6 sym.phase_3+99
      
      





実際には、予想通りですが、かなり連続的ではありません。 次に、切り替え中にeaxで記録されたマジックで2番目の数値をチェックします。 10進数の入力は16進数から変換する必要があるため。 これは、新しいプログラムを作成せずに、シェルコールを介してrax2 (電卓のアナログ)を使用して、または組み込みの電卓(ホットキー- )を介して直接計算できます。 常に押し込まないために、チームをバッシュのようにグループ化できます。

 [0x0040149e]> !rax2 0xcf 207 [0x0040149e]> ?vi 0x2c3; ?vi 0x100; ?vi 0x185; ?vi 0xce; ?vi 0x2aa; ?vi 0x147; ?vi 0x137 707 256 389 206 682 327 311
      
      





可能な解決策は次のとおりです。



また、任意のオプションで、すべてが機能することを確信します。

 $ ./bomb ... That's number 2. Keep going! 4 389 Halfway there!
      
      





レベル4



sym.phase_4




sym.func4




このレベルでは、再帰が表示されます。 組み込みのASCIIグラフに加えて、ドットユーティリティ用のファイルの形式でグラフを取得し、たとえば、それらをpngに変換することもできます。

 [0x0040149e]> ag sym.func4 > func4.dot [0x0040149e]> dot -Tpng -o func4.png func4.dot
      
      







これをすべてCに変換すると、 ほとんど怖くないように見えます。

 void phase_4(char *str) { int x, y; if (sscanf(str, "%d %d", &x, &y) != 2 || x > 14 || func4(x, 0, 14) || y != 0) explode_bomb(); } int func4(int x, int y, int z) { unsigned diff = (z - y)/2; int p = y + diff; if (p > x) { func4(x, y, p-1); return diff * 2; } else if (p < x) { func4(x, p + 1, z); return diff * 2 + 1; } return 0; }
      
      





最も簡単で明白な解決策は、func4関数がelse-if内に入らない場合に0を返すことです。 ユーザー入力はxのみを制御し、最初の呼び出しでp = 7を制御します。 したがって、 x = 7の場合、関数は再帰呼び出しをせずに単に0を返します。 2番目の変数は厳密にゼロに設定されます。 これを確認します。

 $ ./bomb in.tmp ... Halfway there! 7 0 So you got that one. Try this one.
      
      





レベル5





これではさらに難しくなり、ここには多くのコードが積み上げられます。

0x00401073では、 カナリアがスタックに書き込まれます。 バイナリに関する同様の情報は、 iコマンドを使用して取得できます。 たとえば、この場合、 i〜canarytrueを返します

その後、string_lengthの戻り値が6と比較され、分析が困難なスパゲッティコードが開始されます。 このデバッガーを扱うのは長くて難しいので、それを使わないのは罪です。 これを行うには、起動時にデバッグフラグを指定してファイルを開きます。

 $ r2 -Ad bomb
      
      





または単にファイルを再発見します:

 [0x0040149e]> ood
      
      





ポインタアドレスは、エントリポイントの最初のオペコードに自動的に変更されます。このオペコードは既にメモリにロードされています。 通常どおり、ブレークポイントを設定し、実行を継続します。

 [0x7f2960b99d80]> db sym.phase_5 #  s sym.phase_5; db $$ [0x7f2960b99d80]> dc #    1 
      
      





分析には2つの方法があります。1つ目はd / dbコマンドブロックを使用する方法、2つ目はビジュアルモードに切り替える方法です。 最初の方法はそれほど明白ではないので、2番目の方法に注目しましょう。

前と同様に、ビジュアルモードに切り替えてから、ホットキーp / Pを使用してデバッグに必要なデバッグレイアウトを選択します

n / N-次/前の関数を使用してコード内を移動できます。 j / kは、次の前のオペコードです。

そして最も興味深いもの: b / F2-ブレークポイントを設定します、 s / F7-ステップ1オペコード、 S / F8-呼び出しを行わずにステップ1オペコード、 F9-ブレークポイントに進みます。

それはそれがすべて見える方法です




デバッガーに数行を供給することで、そこで何が起こっているかを推測することは難しくありません。

16を法とする文字列の各文字について、文字列char *s = "maduiersnfotvbyl"



対応する文字char *s = "maduiersnfotvbyl"



取得され、結果の文字列が "flyers"と比較されます。

実際には、str内のインデックスが目的の文字列を提供するような文字を見つけることがタスクです。

チラシの行は一意に取得できます:{s [0x9]、s [0xe]、s [0xf]、s [0x5]、s [0x6]、s [0x7]}。 誰もが彼の頭にASCIIテーブルを保存しているわけではないと思うので、助けが必要な場合は再びrax2ユーティリティに戻ることができます。 -sスイッチを使用すると、16進数から文字列に変換されます。 なぜなら 文字は16を法として取られ、美学のために、印刷された値を選択できます。

 $ rax2 -s 49 4e 4f 45 46 47 INOEFG
      
      





 $ ./bomb ... So you got that one. Try this one. INOEFG Good work! On to the next...
      
      





回線に関する余談
私は非常に長い間考えていましたが、まだ意味のある言葉が出てくるかもしれないと思います。 だからあなたが突然誰かを見つけたら、私は非常に感謝します:)



最後の



記事が大きすぎるため、すべてのフェーズを検討するつもりはありません。 特に、フレームワークに直接関与することなく多くの骨の折れる理解があるため、フェーズ6は省略します。 それが最も不思議な宿題のままにしてください。

秘密の段階に入るのはそれほど簡単ではないので、バイナリにパッチを当てることで生活を簡素化します。 -wフラグを使用して書き込み権限を付与するか、r2を離れずにoo +を使用して再検出することにより、ファイルを再度開く必要があります。

 $ r2 -Aw bomb -- Did you ever ordered a pizza using radare2? [0x00400c90]> s 0x00400ec6 # call sym.phase_6 [0x00400ec6]> wa call sym.secret_phase #    [0x00400ec6]> pdf @ sym.secret_phase; pdf @ sym.fun7
      
      





secret_phase




fun7




繰り返しますが、再帰関数が表示されますが、今回はそれを回避するのはそれほど簡単ではありません。再帰呼び出しがなければ、目的の値は最後ではないのと同じくらい簡単だからです。 ASCIIグラフは人生を再び簡素化できます。



分析では、Cで次のようになります。

 void secret_phase() { long num = strtol(read_line()) - 1; if (num > 0x3e8 || fun7((long*)(0x6030f0), num) != 2) explode_bomb(); puts("Wow! You've defused the secret stage!"); phase_defused(); } int fun7(long *array, int num) { if (array == 0) return -1; if (*array <= num) { if (*array == num) return 0; // 1 else { return 2 * fun7(array + 1, num); // 2 } } else { return 2 * fun7(array + 2, num) + 1; // 3 } }
      
      





したがって、 fun7の出力で正確に2つを取得するには、最初に行2でretを呼び出し、次に3で 、最後に1で呼び出す必要があります。 謎が1つだけ残っています-0x6030f0に保存されているもの

480バイト
 [0x00401204]> px 480 @ 0x6030f0 - offset - 0 1 2 3 4 5 6 7 8 9 ABCDEF 0123456789ABCDEF 0x006030f0 2400 0000 0000 0000 1031 6000 0000 0000 $........1`..... 0x00603100 3031 6000 0000 0000 0000 0000 0000 0000 01`............. 0x00603110 0800 0000 0000 0000 9031 6000 0000 0000 .........1`..... 0x00603120 5031 6000 0000 0000 0000 0000 0000 0000 P1`............. 0x00603130 3200 0000 0000 0000 7031 6000 0000 0000 2.......p1`..... 0x00603140 b031 6000 0000 0000 0000 0000 0000 0000 .1`............. 0x00603150 1600 0000 0000 0000 7032 6000 0000 0000 ........p2`..... 0x00603160 3032 6000 0000 0000 0000 0000 0000 0000 02`............. 0x00603170 2d00 0000 0000 0000 d031 6000 0000 0000 -........1`..... 0x00603180 9032 6000 0000 0000 0000 0000 0000 0000 .2`............. 0x00603190 0600 0000 0000 0000 f031 6000 0000 0000 .........1`..... 0x006031a0 5032 6000 0000 0000 0000 0000 0000 0000 P2`............. 0x006031b0 6b00 0000 0000 0000 1032 6000 0000 0000 k........2`..... 0x006031c0 b032 6000 0000 0000 0000 0000 0000 0000 .2`............. 0x006031d0 2800 0000 0000 0000 0000 0000 0000 0000 (............... 0x006031e0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x006031f0 0100 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603200 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603210 6300 0000 0000 0000 0000 0000 0000 0000 c............... 0x00603220 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603230 2300 0000 0000 0000 0000 0000 0000 0000 #............... 0x00603240 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603250 0700 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603260 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603270 1400 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603280 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x00603290 2f00 0000 0000 0000 0000 0000 0000 0000 /............... 0x006032a0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x006032b0 e903 0000 0000 0000 0000 0000 0000 0000 ................ 0x006032c0 0000 0000 0000 0000 0000 0000 0000 0000 ................
      
      







ここで、8バイトの変数は4のブロックに格納されます。最初の変数は、数値と比較するために使用されます。 2番目と3番目は、比較のための次の文字へのポインターです。 4番目は単なるパディングであり、使用されません。

これで、すべての条件を展開して、検索する変数を見つけることができます。



 $ ./bombSec in.tmp ... Good work! On to the next... 22 Wow! You've defused the secret stage! Congratulations! You've defused the bomb!
      
      







エフ、やった! 人類は平和に生きることができます。 この記事は少し大きすぎるという事実にもかかわらず、フレームワークのかなりの数の機能がカバーされていないため、興味のある人はまだ多くのことを発見できます。 また、可能な継続のためのアイデアを聞いてうれしいです。



All Articles