on
clone systemcallをフックする
概要
Linux でシステムコールをフックする手法はいくつかあります. 例えば,syscall 命令によって参照される MSR レジスタの値を書き換えたり,システムコールテーブルを書き換えるのが代表的です. 前者の手法は前のエントリで紹介しました. 今回は,後者のシステムコールテーブルを書き換える手法で,clone システムコールをフックしようとした時にハマったポイントを書きます.
環境
ホスト OS: Ubuntu16.04 64bit
アーキテクチャ: x86-64
sys_call_table の書き換え
システムコールをフックするには,以下のようにsys_call_table
の特定のエントリの値を書き換えます.
|
|
clone システムコールをフックする場合も上記と同じように,
sys_call_table[__NR_clone]
の値を fake_sys_clone に書き換えると、ユーザ空間で SIGSEGV の嵐でシステムがクラッシュしました(新たにプロセスを生成できなくなった).
結論から言うと,sys_call_table[__NR__clone]
の値は,clone システムコールの実態であるsys_clone
ではなく,stub_clone
です.このstub_clone
内でsys_clone
が呼び出される実装です.
clone の他にも,fork,execve などのプロセスの生成に関わる特別なシステムコールは,スタブを経由して呼び出されます.
よって,横取りしてやるには,このスタブを考慮してシステムコールをフックする必要があります.
嵌りそうなポイントは以下の 3 つ
clone システムコールをフックするにあたって,スタブの考慮だけでなく,他にもハマりそうなポイントがあるので以下にまとめた.
Kernel3 系と 4 系で,
sys_clone
のプロトタイプが異なる.Kernel4 系でも,CONFIG によってプロトタイプが異なる(kernel/fork.c).
- /boot/config-4.4.0-21-generic で CONFIG を確認可能
sys_call_table[_NR_clone]
の値は,sys_clone
ではなく,stub_clone
.
3.の該当コードは以下の通りです.
arch/x86/entry/entry_64.S に
stub_clone
==>sys_clone
呼び出しを行うマクロがある (1)arch/x86/entry/calling.h に
SAVE_EXTRA_REGSマクロ
がある (2)arch/x86/entry/entry_64.S に
sys_call_table
からシステムコールを呼び出すエントリポイントENTRY(entry_SYSCALL_64)
がある (3)- コメントに bp, bx, r12-15 not saved とあり,sub して領域を確保するだけで,レジスタの退避を行なっていない
(2)の stub 処理では,(3)で退避されなかったレジスタの退避を行なっている.
特に,ENTRY(entry_SYSCALL_64)
で作られたスタックを破壊しないように,必要な処理だけをし,jmp 命令で,システムコール本体に処理を移している.
|
|
|
|
|
|
- その他、
movq %r10, %rcx
でシステムコールにおける第 4 引数レジスタの r10 を,関数呼び出しにおける第4引数レジスタの rcx に代入している. つまり,システムコール呼び出しを関数呼び出しに変換している.
解決策
stub_cloneからsys_clone
が呼ばれる実装なので,sys_call_table[__NR_clone]
のstub_clone
の値をfake_sys_clone
にしてはいけない(正規のスタックレイアウトにならないので).
そこで,sys_call_table[__NR_clone]
のstub_clone
の値をfake_stub_clone
に設定し,fake_stub_clone
から,fake_sys_clone
を呼び出すようにする.
他にも,fork 系や,execve にスタブがあるので,ABI に注意が必要.
ただし,fork,vfork は,引数がないので,sys_call_table[__NR_(v)fork]
のstub_(v)fork
の値をfake_sys_(v)fork
に変更しても,問題ないと考えられる.
まとめ
clone システムコールなどでは,rbx - r15 が重要.
参考: x64 の引数
system call
- rax にシステムコール番号を設定
- 第 1 引数 を rdi に設定
- 第 2 引数 を rsi に設定
- 第 3 引数 を rdx に設定
- 第 4 引数 を r10 に設定
- 第 5 引数 を r8 に設定
- 第 6 引数 を r9 に設定
- システムコール命令( syscall ) を実行
なお,rax に戻り値が保存され,rcx と r11 の値は破壊される可能性がある.
関数呼び出し
- 第 1 引数 を rdi に設定
- 第 2 引数 を rsi に設定
- 第 3 引数 を rdx に設定
- 第 4 引数 を rcx に設定
- 第 5 引数 を r8 に設定
- 第 6 引数 を r9 に設定
system call とは,第4引数が異なる. また,これ以上の引数は,スタックにつむ. 戻り値は rax に保存される