ソースコードのないファイル
ある会社が、自社のサーバーで小さなツールを見つけました。だれが作ったのか、もう覚えている人はいません。ソースコードはなく、プログラムだけがあります。実行する前に、だれかがそれが何をするかを読み取らなければなりません。これがリバースエンジニアリングです。プログラムそのものから、そのしくみを突き止めることをいいます。
このレッスンが終わると
メモ
ソースコードがなくても、ツールを使ってプログラムが何をするかを調べられる。
守るべきルール
メモ
このラボは、守る側の人やコンパイラを学ぶ人がするように、プログラムのしくみを読み取るためのものです。自分のラボの中のプログラムにだけ使い、ほかには使いません。次のことは教えませんし、してもいけません。マルウェア、本物のソフトウェアのライセンスを破ること、キー生成ツール、デバッガーからプログラムを隠すこと、自分のものではないプログラムへの攻撃です。イギリスでは Computer Misuse Act 1990 により犯罪になり、日本でも不正指令電磁的記録に関する罪などで処罰されることがあります。
キーワード
- バイナリ:コンピューターが実行する形のプログラム。機械語の命令でできたファイル。
- 逆アセンブラー:機械語の命令を、読めるアセンブリに戻すツール。
- デバッガー:プログラムを実行し、指定した場所で止めて、メモリを見せるツール。
- レジスタ:プロセッサの中にある、とても小さな記憶場所。このマシンでは
rdiが呼び出しの1つ目の引数を持つ。 - ライブラリ呼び出し:
putsのように、できあがったコードを呼び出すこと。putsは文字を表示する。 - システムコール:書き込みなど、プログラムがオペレーティングシステムに頼むこと。
ウォームアップ:すでに知っていること
メモ
頭の中で答えましょう。1. ソースコードとは何ですか。2. コンパイラはそれをどうしますか。3. 関数の引数とは何ですか。3つ目の答えはすぐに使います。
はじめる前に
メモ
このレッスンの上のバーにある ラボを開始 を押します。このラボは、あなた専用の Ubuntu マシン まるごとなので、コンテナより起動に少し時間がかかります。各 実行 ボタンは、そのコマンドをマシンのターミナルに、同じセッションで順番に入力します。マシンがまだ起動中と表示されたら、数秒待ってからもう一度押します。様子を見たり自分で入力したりするには、バーの ターミナルを開く を押します。
バイナリは説明できるファイルである
ラボはプログラムを revlab フォルダーに置いています。hello に秘密はなく、1行を表示するだけです。file コマンドは、実行せずに、どんな種類のファイルかを教えてくれます。Linux のプログラムの形式である ELF、どのプロセッサ向けに作られたか、そして not stripped に注目しましょう。関数の名前がまだ中に残っているという意味です。
file revlab/helloここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
strings はプログラムの中の文字を見つける
プログラムは、見せるつもりがあってもなくても、読める形で自分の文字を中に持っています。strings は読める文字の並びをすべて表示するので長い一覧になります。そこでこのコマンドは Hello を含む行だけを残します。実行しなくても、プログラムが何と言うかが分かります。
strings revlab/hello | grep Helloここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
チェック
ヒント
プログラムを一度も実行していないのに、その言葉はどこから来たのでしょう。答え: ファイルの中からです。プログラムは自分の文字を一緒に持っています。
よくある間違い
注意
多くの人が、コンパイルするとプログラムの内容が隠れると思っています。コンパイルは形を変えるだけで、事実は変えません。文字も、呼び出しの名前も、手順の順番も、まだそこにあります。プログラムが動くために知っていなければならないことは、読む人にも見つけられます。だから、プログラムの中に秘密を置いてはいけません。
逆アセンブラーは命令を見せる
objdump -d は命令を1行に1つずつのアセンブリにします。このコマンドは関数 main だけを求め、puts を含む行を残します。1行あり、文字を表示するライブラリ関数への call です。radare2 も同じ仕事をして、名前を付けてくれます。aa はファイルを解析させ、pdf @ main は関数を表示します。puts という名前と、それに渡される文字を探しましょう。
objdump -d revlab/hello --disassemble=main | grep putsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
r2 -q -e scr.color=0 -c 'aa' -c 'pdf @ main' revlab/hello 2>/dev/nullここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
デバッガーはプログラムを止めてメモリを見せる
プログラムを gdb で実行し、puts の最初の行で止めます。このマシンでの呼び出しのしくみでは、puts は自分の文字のアドレスをレジスタ rdi の中に見つけます。x/s $rdi は、そのアドレスにある文字列(string)を見せて、という意味です。こうして、プログラムが止まっている間に関数の引数を読みます。パスワードもこうして読むことになります。
gdb -batch -ex 'break puts' -ex 'run' -ex 'x/s $rdi' revlab/helloここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
トレースはプログラムの呼び出しを見せる
ltrace は ライブラリ呼び出し を引数つきで表示し、strace は システムコール を表示します。前者では puts が文字を受け取る様子が見えます。後者では、プログラムが同じバイト列を画面に書くようオペレーティングシステムに頼む様子が write で見えます。ここではあいさつの表示を捨てて、トレースだけが見えるようにしています。どちらもアセンブリを読む必要はありません。
ltrace revlab/helloここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
strace -e trace=write revlab/hello >/dev/nullここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
さあ、ラボに取り組もう
ヒント
あなたは無害なプログラムを、それぞれのツールで見ました。あなたのラボには5つのプログラムがあり、それぞれが、あなたのコピーでは違う鍵を隠しています。合ったツールで1つずつ読みましょう。ホームフォルダーの README.md を、ターミナルで cat README.md と打って開き、見つけた鍵を answers.txt に書きます。
最後のチェック
ヒント
前を見ないで答えましょう。1. プログラムの中の読める文字を一覧にするツールはどれですか。2. 動いているプログラムを止めてメモリを読めるツールはどれですか。3. ライブラリ呼び出しとシステムコールは何が違いますか。4. プログラムの中の秘密が本当の秘密にならないのはなぜですか。答え: strings。デバッガーの gdb。ライブラリ呼び出しはできあがったコードへ、システムコールはオペレーティングシステムへ向かう。ファイルの中身はだれでも読めるから。
先生へ
ヒント
このレッスンはラボの中で行うレッスンの小さな例です。手順と実行ボタンがレッスンで、ラボは生徒一人ひとりのコピーです。VM ラボで行います。生徒それぞれが自分の Ubuntu マシンを持ち、実行ボタンはそのターミナルに入力して、表示された内容を見せます。要点:1. プログラムはソースコードがなくても読める。2. 文字と呼び出しは、コンパイルしたプログラムの中に残る。3. デバッガーは動いているプログラムのメモリを見せる。4. トレースはプログラムの呼び出しを見せる。よくある間違い:コンパイルするとプログラムの内容が隠れると思うこと。準備するもの:ラボとブラウザー。ヒント:どのツールを使う前にも、hello が何をするかをクラスに言わせ、そのあと予想を1つずつ確かめてください。ラボ自体には、answers.txt の鍵を、コピーごとに作られた鍵と比べるチェックがあります。ラボ自身のプログラムを守る側の立場で読むだけで、ほかには使いません。レッスンを自分のモジュールにコピーして変えるか、レッスン作成ツールの ラボ メニューで最初から作ってください。