この記事では、例として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はリクエストに依存します
- PTRACE_SINGLESTEP-ステップバイステップのプログラム実行。各命令の実行後に制御が移行されます。 そのようなトレースは十分に遅い
- PTRACE_SYSCALL-システムコールが開始または終了するまでプログラムを続行します
- PTRACE_CONT-プログラムの実行を続ける
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-バイト数
// 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テーブル