ptrace (プロセストレースから)-一部のUnix系システム(Linux、FreeBSD、Max OS Xなど)でのシステムコール。選択したプロセスをトレースまたはデバッグできます。 ptraceを使用すると、プロセスを完全に制御できます。プログラムのコースを変更したり、メモリ内の値やレジスタのステータスを監視および変更したりできます。 追加の権利を取得することはできません-実行可能なプロセスの権利によって可能なアクションが制限されます。 さらに、setuidビットを使用してプログラムをトレースする場合、この同じビットは機能しません-特権は増加しません。
この記事では、例としてLinux OSを使用してシステムコールをインターセプトする方法を示します。
1. ptraceについて少し
ptrace関数のプロトタイプは次のようになります。
#include <sys/ptrace.h>
long ptrace( enum __ptrace_request request, pid_t pid, void *addr, void *data);
- リクエストは、実行するアクションです。たとえば、PTRACE_CONT、PTRACE_PEEKTEXT
- pid-トレースされたプロセスの識別子
- addrとdataはリクエストに依存します
トレースを開始するには、2つの方法があります。すでに実行中のプロセスに接続する(PTRACE_ATTACH)か、PTRACE_TRACEMEを使用して自分で開始します。 2番目のケースを検討します。これは少し単純ですが、本質は同じです。 次の引数を使用して、トレースを制御できます。
- PTRACE_SINGLESTEP-ステップバイステップのプログラム実行。各命令の実行後に制御が移行されます。 そのようなトレースは十分に遅い
- PTRACE_SYSCALL-システムコールが開始または終了するまでプログラムを続行します
- PTRACE_CONT-プログラムの実行を続ける
詳細については、
man ptraceを参照してください。
2.システムコールを表示する
プログラムが使用するシステムコールのリストを表示するプログラムを作成します(straceユーティリティの単純な類似物)。
したがって、最初に
フォークを作成する必要があります-親プロセスは子をデバッグします:
int main( int argc, char *argv[]) {
pid_t pid = fork();
if (pid)
parent(pid);
else
child();
return 0;
}
子プロセスでは、すべてが単純です-PTRACE_TRACEMEでトレースを開始し、目的のプログラムを実行します。
void child() {
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl( "/bin/echo" , "/bin/echo" , "Hello, world!" , NULL);
perror( "execl" );
}
execlが実行されると、トレースされたプロセスは新しい状態を親に渡すのを停止します。 したがって、親プロセスは、最初に
waitpidの使用を開始するプログラムを待機する必要があります(子プロセスは1つしかないため、単に
待機できます)。
int status;
waitpid(pid, &status, 0);
システムコールと他の停止(SIGTRAPなど)を何らかの方法で区別するために、特別なパラメーターPTRACE_O_TRACESYSGOODが提供されます-システムコールが停止すると、親プロセスは
SIGTRAP | 0x80 :
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD);
これで、プログラムを終了する前にループでPTRACE_SYSCALLを実行し、
eaxレジスタの値を見てシステムコール番号を決定できます。 これを行うには、PTRACE_GETREGSを使用します。 停止時に
eaxレジスタが置き換えられるため、保存された
state.orig_eaxを使用する必要があることに注意して
ください 。
while (!WIFEXITED(status)) {
struct user_regs_struct state;
ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, &status, 0);
// at syscall
if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) {
ptrace(PTRACE_GETREGS, pid, 0, &state);
printf( "SYSCALL %d at %08lx\n" , state.orig_eax, state.eip);
// skip after syscall
ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, &status, 0);
}
}
プログラムを実行すると、次のように表示されます。
...
SYSCALL 6 at b783a430
SYSCALL 197 at b783a430
SYSCALL 192 at b783a430
SYSCALL 4 at b783a430
Hello, world!
SYSCALL 6 at b783a430
SYSCALL 91 at b783a430
SYSCALL 6 at b783a430
SYSCALL 252 at b783a430
ご覧のとおり、システムコール番号4(および
sys_write )の後に、テキストが表示されます。
3.システムコールのインターセプト
それでは、チャレンジを傍受して、良いことをしてみましょう。
書き込みシステムコールは次のようになります。
write(fd, buf, n);
- ebx:fd-ファイル記述子(数値)
- ecx:buf-表示するテキストへのポインター
- edx:n-バイト数
テキストを置き換えるには、PTRACE_POKETEXTを使用します。
// sys_write
if (state.orig_eax == 4) {
char * text = ( char *)state.ecx;
ptrace(PTRACE_POKETEXT, pid, ( void *)(text+7), 0x72626168); //habr
ptrace(PTRACE_POKETEXT, pid, ( void *)(text+11), 0x00000a21); //!\n
}
立ち上げて...
...
SYSCALL 6 at 00556416
SYSCALL 197 at 00556416
SYSCALL 192 at 00556416
SYSCALL 4 at 00556416
Hello, habr!
SYSCALL 6 at 00556416
SYSCALL 91 at 00556416
SYSCALL 6 at 00556416
SYSCALL 252 at 00556416
したがって、テキストを出力するために
/ bin / echoプログラムで
sys_writeシステムコールをインターセプトしました。 これは、ptraceを使用した簡単な例です。 また、メモリダンプを簡単に作成し(これは、Linuxのクラックミックスを解決する際に非常に役立ちます)、ブレークポイントを設定し(PTRACE_SINGLESTEPを使用するか、命令を0xCCに置き換える)、レジスタ/変数などを分析します。
ptraceは 、たとえば、コードの問題部分にすばやく到達できない場合-デバッガーでデータをジャンプして置換する必要があり、その後プログラムが停止し、すべてを新たに行う必要がある場合に非常に便利です。 ptraceを使用してデバッグ用のプログラムを作成する場合、これらのアクションはすべて1回だけ記述する必要があり、自動的に実行されます。 もちろん、一部のデバッガーではスクリプトを作成できますが、おそらく機能が劣っています。
UPD:完全な
ソースを投稿するのを忘れた
4.読むもの
男ptrace男は待つptraceで遊ぶ、パートIptraceで遊ぶ、パートIIsyscallsテーブル