【参考書籍】 ハロー“Hello, World"OSと標準ライブラリのシゴトとしくみ 坂井 弘亮
1.概略
リンカとローダの本は一通り読み終わったため
(環境の違いがめんどくさくなって後ろ1/5は読むだけにした。
またGOT/PLTの部分は重要そうで繰り返し呼んだがまだ理解が甘い)
hello worldに戻ることにした
以前読んだときは
システムコールとそのラッパーとなる関数がたくさん出てきて
どこまでがカーネル側・アプリ側か混同していたが
もう一度読むとだいぶわかったため
簡単にまとめておく
環境はVM上で
2.printf()以降に呼ばれる関数:アプリケーション側
printf()を呼んだ後には以下の順に関数が呼ばれる
(なおこれが全てではない)
printf() vfprintf() _IO_new_file_xsputn() _IO_new_file_overflow() _IO_new_do_write() _IO_new_file_write() ※1 (write()) ※2 __write_nocancel() ※3 __kernel_vsyscall() ※4 __syscall_error() ※5 __syscall_error1()
以上はgdbのwhereでトレースした結果である
※1:_IO_new_file_write()
スタックの様子を確認すると
_IO_new_file_write()のReturnAddressが積まれており
その下には出力文字列やら出力文字数やらと
4(=EAX:システムコール番号)が積まれていた
よってその先の__write_nocancel()が
レジスタ設定を行なっているということが推察される
勿論Cの力ではできないためアセンブラで書かれている
それをCプログラマが書くのがめんどくさいから
システムコールとC(ユーザ)との間に噛ませるのが
システムコールラッパであった
ということは少なくとも__write_nocancel()は
システムコールラッパの1つであると言える
(寧ろユーザ使用のライブラリ関数と実際のシステムコール以外は
全てラッパのような気もするが・・・)
※2:write()
この関数からfall through的に__write_nocancel()に降りるが
GDBはスタック上のRAをもとにバックトレースするため
RAの積まれていないwrite()は表示されていないと考えられる
※3:__write_nocancel()
前述した通りシステムコールのための
レジスタ設定をしている嫌疑をかけられている
やはりスタックに積まれた値をレジスタに保存している
call *0x80d6750をしているが
これが__vsyscall_kernel()である
※4:__vsyscall_kernel()
INT 0x80のみで構成される関数
この命令によって例外(割り込み)を発生させる
以前はこの命令自体がシステムコールだと思っていたが
実際にはこの命令によって
CPUは処理をアプリケーションからカーネルに移し
すなわち上でトレースした関数は
全てアプリケーション側で実行される関数である
なおシステムコールと呼ばれるものにはいくつかの種類があり
C Libraryにあるwrite()関数がコールと呼ばれることもあれば
INT 0x80がコールと呼ばれることもあるし
その先のカーネルの内部での関数やら呼び出しがコールと呼ばれることもある
※5:__syscall_error()
エラーが発生した場合に処理が飛ぶ
eaxの値を反転するだけ
※6:__syscall_error1()
eaxの値をerrnoに設定し
eaxを-1に設定する
前述の__syscall_error()からfall through的に降りてくる
3.printf()以降に呼ばれる関数:Linux Kernel側
ここから先は全てを辿ったわけではないが
一部を抽出してみた
割り込み発生 ENTRY(system_call) syscall_call() sys_write() ... syscall_after_call() syscall_exit() restore_all() restore_all_notrace() restore_nocheck() irq_return()
おそらくだが
システムコール要求が入った場合にkernelは
system_call (entry_32.S) というエントリに移ると思われる
そこではSAVE_ALLマクロによる
レジスタの(スタックへの)保存などが行われている
その後fall throughでsyscall_call()に入る
これは call *sys_call_table(,%eax,4) のみの関数である
上の命令は sys_call_table ( syscall_table_32.S)という
システムコールのテーブルを参照しその先にジャンプするというもの
今回はeax==4であったため
long sys_write()が呼ばれることになる
この関数より先の後追いは一旦しないこととする
その後のsyscall_after_call()では
eax(返り値)をスタック場のeax退避位置に上書きする
そのあとの諸々の関数では
レジスタの値などを復旧して
INTERRUPT_RETURNマクロで処理を終了しているものと思われる
4.まとめ
すごくざっくりだがシステムコールの流れをまとめてみた
どこまでがシステムコールと呼ばれるものかは未だ曖昧だが
どこまでがアプリケーション担当かなどはわかった
CTFのための予備知識として勉強しているが
予備知識の勉強中にまたわからない知識が出て
そのための予備勉強をして・・・
いい加減早くCTFの練習に入らないと
と思いながら続く・・・
5.追記
これらシステムコールラッパ(おそらく特に__write_nocancel?)は
その実態がPSEUDO( ... )というマクロを定義することで生成される
この定義を行う(ラッパを生成している)のはどこか
sysdep.h内ではSYSCALLというマクロがPSEUDOとして定義されている
他にもsyscall-template.S内ではT_PSEUDO()も同様に定義されている
このT_PSEUDO()は同ファイル内を見ると
SYSCALL_SYMBOL/SYSCALL_NAMEを設定することで
writeのスタブ(下位モジュールのダミーみたいな)が生成されるらしい
そして上述のシンボルはシェルスクリプト内で定義されている
つまりglibcのビルド時に
上のシンボルが定義され
そのあとでsyscall-template.Sがアセンブルされることで
ラッパの実体が生成されるといったところか
セルフビルドしたglibcを使って実行形式を生成し
write()のソースの位置は ~/sysdeps/unix/syscall-template.Sであるようだ
ということはやはりあのT_PSEUDOマクロの位置なのだが
読んだ感じ生成されているのはスタブであり
その先がないように思われる
???
なんだかよくわからない気がする