ptraceを使用したシステムコールのインターセプト

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




トレースを開始するには、2つの方法があります。すでに実行中のプロセスに接続する(PTRACE_ATTACH)か、PTRACE_TRACEMEを使用して自分で開始します。 2番目のケースを検討します。これは少し単純ですが、本質は同じです。 次の引数を使用して、トレースを制御できます。

詳細については、 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);



テキストを置き換えるには、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で遊ぶ、パートI

ptraceで遊ぶ、パートII

syscallsテーブル



All Articles