newbieからバイナリアンへ

newbieからバイナリアンへ

昨日は海を見に行きました

【HelloWorld part.7】システムコール/ラッパの簡単なまとめ

 

 

【参考書籍】 ハロー“Hello, World"OSと標準ライブラリのシゴトとしくみ 坂井 弘亮

 

1.概略

リンカとローダの本は一通り読み終わったため

(環境の違いがめんどくさくなって後ろ1/5は読むだけにした。

またGOT/PLTの部分は重要そうで繰り返し呼んだがまだ理解が甘い)

hello worldに戻ることにした

 

以前読んだときは

システムコールとそのラッパーとなる関数がたくさん出てきて

どこまでがカーネル側・アプリ側か混同していたが

もう一度読むとだいぶわかったため

簡単にまとめておく

 

環境はVM上で

CentOS / x86 32bit

 

 

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()

前述した通りシステムコールのための

レジスタ設定をしている嫌疑をかけられている

実際にソースコード(やはりアセンブラ)を確認すると・・・

 

f:id:smallkirby:20190305184858p:plain

__write_no_cancel()

 

やはりスタックに積まれた値をレジスタに保存している

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を使って実行形式を生成し

それを使ってgdbでシンボリックデバッグしたところ

write()のソースの位置は ~/sysdeps/unix/syscall-template.Sであるようだ

ということはやはりあのT_PSEUDOマクロの位置なのだが

読んだ感じ生成されているのはスタブであり

その先がないように思われる

???

 

 

なんだかよくわからない気がする 

You can cite code or comments in my blog as you like basically.
The exceptions are when the code belongs to some other license. In that case, follow it. Also, you can't use them for evil purpose. Finally, I don't take any responsibility for using my code or comment.
If you find my blog useful, I'll appreciate if you leave comments.