「WEBRickをGDBでいじる」を実際に試して、バイナリーハックを体験してみよう
DECONで高林さんが紹介された「Binary Hacks in Action」のスライドを見ていて、「WEBRickをGDBでいじる」という所がとても興味深く感じました。本当にそんなことができるのか?と一瞬思いました。そこで、ちょっと試してみました。最初はうまくいかなかったのですが、最終的にWEBRickをGDBから本当に操作できることを確認しました。うまくいかなかった原因は、パッケージからRubyをインストールいたためのようです。(GDBが理解できるデバッグ情報が、パッケージからインストールしRubyには入っていなかったのが原因だと考えられます。) ソースからビルドしてインストールしたRubyでは、期待通りの結果が得られました。
準備
パッケージからインストールしたRubyでは、WEBRickをGDBからうまくいじれなかったので、ソースからRubyをインストールしておきます。configureする時にprefixオプションでホームディレクトリの適当などこかを指定しておけば、root権限が無くても手軽にインストールして試せます。
手順
スライドに載っているWEBRickベースのWebサーバのコードを書きます。
[1] % vim webrick.rb require 'webrick' include WEBrick s = HTTPServer.new(:Port => 2000) s.mount("/", HTTPServlet::FileHandler, "/tmp", true) trap("INT") { s.shutdown } s.start
そして、WEBRickを起動します。
[1] % ruby webrick.rb [2006-09-17 22:06:57] INFO WEBrick 1.3.1 [2006-09-17 22:06:57] INFO ruby 1.8.5 (2006-08-25) [i686-darwin8.5.2] [2006-09-17 22:06:58] INFO WEBrick::HTTPServer#start: pid=13645 port=2000
もう1つのターミナル[2]で、index.htmlを用意し、telnetで起動したWEBRickのプロセスに接続します。接続が完了したら、HTTPのGETメソッドで「/」(すなわち、index.html)を取得します。以下のように、index.htmlのコンテンツをうまく取得できることを確認できました。
[2] % echo "hello, world" > /tmp/index.html [2] % telnet localhost 2000 Trying ::1... Connected to localhost. Escape character is '^]'. GET / hello, world Connection closed by foreign host.
WEBRickのサーバのプロセスIDを調べて、そのプロセスIDでGDBからそのプロセスにアタッチします。僕の環境にはプロセスIDを簡単に調べられるpgrepはインストールされていないので、適当にコマンドを組み合わせてプロセスIDを取得することにしました。どうやら、うまくWEBRickのサーバにアタッチできたようです。
[3] % pid=`ps au | grep webrick | awk '{ print $2 }'` [3] % gdb -p $pid GNU gdb 6.1-20040303 (Apple version gdb-437) (Fri Jan 13 18:45:48 GMT 2006) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-apple-darwin". Attaching to process 13671. Reading symbols for shared libraries . done Reading symbols for shared libraries ............... done 0x9001ab3c in select () (gdb)
そして、rb_io_write関数にブレークポイントをセットします。「c」で再び実行状態にします。
(gdb) b rb_io_write Breakpoint 1 at 0x257bf: file io.c, line 591. (gdb) c Continuing.
この状態で、他のターミナルからtelnetでWEBRickのサーバを先ほどと同じように叩きます。そうすると、先ほどとは挙動が違って、GETリクエストを送信しても、WEBRickのサーバから応答がありません。
[2] % telnet localhost 2000 Trying ::1... Connected to localhost. Escape character is '^]'. GET /
そこで、GDBを動かしているターミナルを見ると、先ほどセットしたブレークポイントで止まったのがわかります。あとは、スライドの説明にあるように、GDBでrb_io_write関数の第2引数のstrの内容を表示したり、strの内容を改ざんしたりして遊びます。
Breakpoint 1, rb_io_write (io=5368480, str=5362860) at io.c:591 591 return rb_funcall(io, id_write, 1, str); (gdb) p ((struct RString*)str)->ptr $1 = 0x112b1d0 "hello, world\n" (gdb) set ((struct RString*)str)->ptr = "crazy" (gdb) set ((struct RString*)str)->len = 5 (gdb) c Continuing.
おわりに
今までGDBを全然使った経験がありませんでしたが、このバイナリーハックのおかげで、GDBの凄さがわかるようになりました。そして、バイナリーハックにはGDBが非常に強力なツールとなることを学びました。
あと、余談ですが、GDBについてちょっと調べてみて、GDBのbtコマンドでスタックトレースを表示したり、任意の呼び出し位置に移動したりする方法を知りました。こんな感じです。GDBを自由自在に使えるようになったら、かなり強い気がします。
(gdb) bt #0 rb_io_write (io=5368480, str=4) at io.c:591 #1 0x00025802 in rb_io_addstr (io=5368480, str=4) at io.c:613 #2 0x0000c905 in rb_call0 (klass=2020060, recv=5368480, id=333, oid=333, argc=1, argv=0xbfff98d0, body=0x1ecbfc, flags=0) at eval.c:5810 #3 0x0000d451 in rb_call (klass=2020060, recv=5368480, mid=333, argc=1, argv=0xbfff98d0, scope=0) at eval.c:6048 #4 0x0000b341 in rb_eval (self=5368000, n=0x1) at eval.c:3443 #5 0x0000d109 in rb_call0 (klass=5725800, recv=5368000, id=19065, oid=19065, argc=2, argv=0xbfff9d50, body=0x5762c8, flags=2) at eval.c:5954 #6 0x0000d451 in rb_call (klass=5725800, recv=5368000, mid=19065, argc=2, argv=0xbfff9d50, scope=1) at eval.c:6048 #7 0x0000a321 in rb_eval (self=5368000, n=0x1) at eval.c:3458 #8 0x000093af in rb_eval (self=5368000, n=0x1) at eval.c:3097 #9 0x0000d109 in rb_call0 (klass=5725800, recv=5368000, id=19121, oid=19121, argc=4, argv=0xbfffa470, body=0x576458, flags=2) at eval.c:5954 #10 0x0000d451 in rb_call (klass=5725800, recv=5368000, mid=19121, argc=4, argv=0xbfffa470, scope=1) at eval.c:6048 [...] (gdb) frame 1 #1 0x00025802 in rb_io_addstr (io=5368480, str=4) at io.c:613 613 rb_io_write(io, str); (gdb) list 608 609 VALUE 610 rb_io_addstr(io, str) 611 VALUE io, str; 612 { 613 rb_io_write(io, str); 614 return io; 615 } 616 617 /*
- 作者: 青木峰郎,まつもとゆきひろ
- 出版社/メーカー: インプレス
- 発売日: 2002/12
- メディア: 単行本
- 購入: 1人 クリック: 339回
- この商品を含むブログ (60件) を見る