GDB で Hello world! を書こう。
しつこく Hello world! を作ります。
http://shinh.skr.jp/ttyplay/gdb_hello.html
えーと上記はデモです。内容は、
main;
だけのコードで gdb を起動して、対話環境チックに動作内容を確認しつつ main の中身を作って、うまくできたらそれを保存して実行ファイルにする、というような内容です。
デモの説明とかは今度気が向いた時に書きます。あと Hello world! の改行が出てなかったのはそもそも改行を忘れてたからでした…
すぐに気が向きました。解説入ります。
一番近いのはあれです。BASIC。 ウソ
ウソと言ってますが後述しているように、割と似てます。同じ行番号のコードを書いたら上書きできるとか、 LIST コマンドぽいのがあるとか。
ここで冒頭の main; だけのファイルを作ります。ここで重要なことは .data セクションに置くだけです。こうしておかないと書き換えがスムーズにできないからです。
さてとりあえず対話環境を作ります。 たった5バイトです!!!
これは irb がなんかやたら小さいRubyスクリプトであるとかウサギ本あたりに書いてあったので対抗意識というか。
まぁ普通に実行すると落ちてしまいます。
main=195; なら落ちないのは勉強ずみですよね!
GDB で書き込みに使ったコマンドをはっておきます。
set *((char*)&main+40)='H' set *((char*)&main+41)='e' set *((char*)&main+42)='l' set *((char*)&main+43)='l' set *((char*)&main+44)='o' set *((char*)&main+45)=' ' set *((char*)&main+46)='w' set *((char*)&main+47)='o' set *((char*)&main+48)='r' set *((char*)&main+49)='l' set *((char*)&main+50)='d' set *((char*)&main+51)='!' set *((char*)&main+52)=0
ここで
p (char*)&main+40
をして Hello world を置いたメモリ位置を確認しています。このメモリアドレスは直後で使用するわけです。
set *((char*)&main) = 0xb9 set *((char*)&main+1) = 0x46 set *((char*)&main+2) = 0x95 set *((char*)&main+3) = 0x04 set *((char*)&main+4) = 0x08 set *((char*)&main+5) = 0xcc
こんな感じで mov を作りました。ここはデモとは違うアドレスになってると思います。ここだけ手入力してたのはデモを調整してるうちに変わってたからです。ケツに 0xcc を置いておいて、 cont で実行させて、 i r でレジスタを確認しました。なんてインタラクティブ性の高い環境でしょう。戻る時には
(gdb) p &main $4 = (<data variable, no debug info> *) 0x8049528 (gdb) set $eip=$4
などとしてました。いったん &main を表示させてたのは、 set $eip=&main とかできないなーという感じだったので適当な workaround です。
さて続くのは
set *((char*)&main+5) = 0xbb set *((char*)&main+6) = 0x01 set *((char*)&main+7) = 0x00 set *((char*)&main+8) = 0x00 set *((char*)&main+9) = 0x00 set *((char*)&main+10) = 0xba set *((char*)&main+11) = 13 set *((char*)&main+12) = 0x00 set *((char*)&main+13) = 0x00 set *((char*)&main+14) = 0x00 set *((char*)&main+15) = 0xb8 set *((char*)&main+16) = 0x04 set *((char*)&main+17) = 0x00 set *((char*)&main+18) = 0x00 set *((char*)&main+19) = 0x00 set *((char*)&main+20) = 0xcd set *((char*)&main+21) = 0x80 set *((char*)&main+22) = 0xbb set *((char*)&main+23) = 0x00 set *((char*)&main+24) = 0x00 set *((char*)&main+25) = 0x00 set *((char*)&main+26) = 0x00 set *((char*)&main+27) = 0xb8 set *((char*)&main+28) = 0x01 set *((char*)&main+29) = 0x00 set *((char*)&main+30) = 0x00 set *((char*)&main+31) = 0x00 set *((char*)&main+32) = 0xcd set *((char*)&main+33) = 0x80
というような要するに write して exit するだけのコード。保存は gcore コマンドで行いました。そんで実行してみた後、 gdb を去りました。
gcore で吐いた core の中身を
od -t x1z core.15392
で確認して、 Hello world で検索してプログラムがどこから始まってるか調べました。そんで dd で適当に切り出しです。以下のような感じで。
dd skip=7016 bs=1 count=60 if=core.15392 of=hello.bin
あとはこれをリンカで無理矢理 .text セクションに置いて実行ファイルを作りました。使ったのはこんなリンカスクリプト。
OUTPUT_FORMAT("elf32-i386") OUTPUT_ARCH(i386) SECTIONS { . = 0x8049528; .text : { *(.data) } }
実際に使ったものは余計な記述が入ってたのでそれは削除してあります。 . = 0x8049528; でもとの main の位置を記してあるわけですが、これは非常に重要なことです。なぜなら、作成したプログラムは Hello world! の位置を決め打ちにしていたので、別の位置に再配置されてしまうと、正しくないものを表示してしまうことになるからです。
あとは、
ld -b binary hello.bin -T hello.ld
などとすれば、単なる生データであるところの hello.bin をリンクしてやることができます。
あ、 gdb とか詳しくないのでまどろっこしいことやってる可能性大です。この程度の知識は身に着くはずなので高林さんが紹介されていた Binary Hacks は買うといいと思います。少なくとも私が書いてないところはお役立ちなはずです。