LD_PRELOAD トリックを使用できない/使用したくないのはなぜですか?
コード例:
/*
* File: soft_atimes.c
* Author: D.J. Capelis
*
* Compile:
* gcc -fPIC -c -o soft_atimes.o soft_atimes.c
* gcc -shared -o soft_atimes.so soft_atimes.o -ldl
*
* Use:
* LD_PRELOAD="./soft_atimes.so" command
*
* Copyright 2007 Regents of the University of California
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>
extern int errorno;
int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;
int open(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open) {
_open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
}
if(flags & O_CREAT)
return _open(pathname, flags | O_NOATIME, mode);
else
return _open(pathname, flags | O_NOATIME, 0);
}
int open64(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open64) {
_open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
}
if(flags & O_CREAT)
return _open64(pathname, flags | O_NOATIME, mode);
else
return _open64(pathname, flags | O_NOATIME, 0);
}
私が理解していることから...それはほとんどLD_PRELOADトリックまたはカーネルモジュールです。関数をトラップできるエミュレーターで実行したり、実際のバイナリでコードを書き直して関数をトラップしたりしない限り、妥協点はあまりありません。
プログラムを変更できず、カーネルを変更できない (または変更したくない) と仮定すると、アプリケーションがかなり標準的であり、実際には悪意を持って通り抜けようとしているアプリケーションではない場合、LD_PRELOAD アプローチが最適です。あなたの傍受。 (その場合、他のテクニックのいずれかが必要になります。)
Valgrind を使用して、関数呼び出しをインターセプトできます。完成した製品でシステム コールをインターセプトする必要がある場合、これは役に立ちません。ただし、開発中に傍受しようとする場合は、非常に便利です。私はこの手法を頻繁に使用してハッシュ関数をインターセプトし、返されたハッシュをテスト目的で制御できるようにしました。
ご存じないかもしれませんが、Valgrind は主にメモリ リークやその他のメモリ関連のエラーを検出するために使用されます。しかし、基盤となるテクノロジーは基本的に x86 エミュレーターです。プログラムをエミュレートし、malloc/free などの呼び出しをインターセプトします。良いことに、使用するために再コンパイルする必要はありません。
Valgrind には、関数ラッピングと呼ばれる機能があります。 、関数の傍受を制御するために使用されます。詳細については、Valgrind マニュアルのセクション 3.2 を参照してください。好きな関数の関数ラッピングを設定できます。呼び出しがインターセプトされると、指定した代替関数が呼び出されます。
最初に、他の人が与えたいくつかの未回答を排除しましょう:
LD_PRELOAD
を使用 .ええ、あなたは「LD_PRELOAD
以外に」と言いました ...」と質問されていますが、一部の人にとっては十分ではないようです。プログラムが libc を使用している場合にのみ機能するため、これは適切なオプションではありません。これは必ずしもそうではありません。- Systemtap を使用します。ええ、あなたは質問で「その他に... Linuxカーネルモジュール」と言いましたが、明らかにそれだけでは不十分な人もいます。カスタム カーネル モジュールをロードする必要があるため、これは適切なオプションではありません。これは大変な作業であり、root も必要です。
- ヴァルグリンド。これはある程度は機能しますが、CPU をシミュレートすることで機能するため、非常に遅く、非常に複雑です。 1回限りのデバッグのためにこれを行っているだけなら問題ありません。生産に値する何かをしている場合、実際にはオプションではありません。
- さまざまなシステムコールの監査。システムコールをログに記録することは、それらを「傍受する」とは見なされません。システムコールのパラメータや戻り値を変更したり、プログラムを他のコードにリダイレクトしたりしたいのは明らかです。
ただし、ここではまだ言及されていない他の可能性があります。私はこれらすべてに慣れておらず、まだ試したことがないので、いくつか間違っている可能性があることに注意してください.
コードを書き直す
理論的には、カスタム ハンドラにジャンプするように syscall 命令を書き換えるある種のカスタム ローダーを使用できます。しかし、それを実装するのは絶対に悪夢だと思います.
kプローブ
kprobes は、ある種のカーネル計測システムです。それらは何に対しても読み取り専用アクセスしかできないため、syscall をインターセプトするために使用することはできず、ログに記録するだけです。
ptrace
ptrace は、GDB などのデバッガーがデバッグを行うために使用する API です。 PTRACE_SYSCALL
があります システムコールの直前/直後に実行を一時停止するオプション。そこから、GDB ができるのと同じ方法で、ほとんど好きなことを行うことができます。 ptrace を使用して syscall パラメータを変更する方法に関する記事を次に示します。ただし、オーバーヘッドが高いようです。
セコンプ
Seccomp は、フィルタリングできるように設計されたシステムです。 システムコール。引数を変更することはできませんが、できます それらをブロックするか、カスタム エラーを返します。 Seccomp フィルターは BPF プログラムです。ご存じない方のために説明すると、これらは基本的に、ユーザーがカーネル空間 VM で実行できる任意のプログラムです。これにより、ユーザー/カーネル コンテキストの切り替えが回避され、ptrace よりも高速になります。
BPF プログラムから引数を直接変更することはできませんが、できる SECCOMP_RET_TRACE
を返す ptrace
をトリガーします 親を壊します。基本的には PTRACE_SYSCALL
と同じです ただし、引数に基づいてシステムコールを実際にインターセプトするかどうかを決定するために、カーネル空間でプログラムを実行する必要があります。したがって、一部のシステムコールのみを傍受したい場合は、より高速になるはずです (例:open()
特定のパスで)
これはおそらく最良の選択肢だと思います。上の記事と同じ作者さんの記事です。 eBPF の代わりに従来の BPF を使用していることに注意してください。ただし、eBPF も使用できると思います。
編集:実際には、eBPF ではなく、クラシック BPF のみを使用できます。それについての LWN 記事があります。
関連する質問をいくつか紹介します。最初のものは間違いなく読む価値があります。
- eBPF はシステムコールの戻り値またはパラメーターを変更できますか?
- PTRACE_SINGLESTEP で syscall のみをインターセプト
- これはシステム コールを傍受する良い方法ですか?
- カーネルを変更せずにシステム コールをインターセプトするオーバーヘッドを最小限に抑える方法
ptrace を介したシステムコールの操作に関する優れた記事もここにあります。
一部のアプリケーションは strace/ptrace をだまして実行しないようにすることができるため、私が持っていた唯一の現実的なオプションは systemtap を使用することです
Systemtap は、ワイルドカード マッチングが原因で必要に応じて一連のシステム コールをインターセプトできます。 Systemtap は C ではなく、別の言語です。基本モードでは、systemtap は愚かなことをしないようにする必要がありますが、必要に応じて開発者が C を使用できるようにする「エキスパート モード」で実行することもできます。
カーネルにパッチを当てる必要はありません (または、少なくともそうすべきではありません)。モジュールがコンパイルされたら、テスト/開発ボックスからコピーして、(insmod 経由で) 運用システムに挿入できます。
systemtap に引っかかるのを回避/回避する方法を見つけた Linux アプリケーションをまだ見つけていません。