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 は買うといいと思います。少なくとも私が書いてないところはお役立ちなはずです。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h