揮発性のメモ2

http://d.hatena.ne.jp/iww/

走行中のプロセスの標準出力を横取りする方法

横取りというか覗き見る。リベンジ

このあたりを参考に、write()しているところでデータを盗み見る。

処理の流れ

  1. アタッチして待つ
  2. システムコール(入る方)を待つ
  3. レジスタ読んで、システムコール番号ごとに中身を見る
  4. システムコール(出る方)を待つ
  5. 2に戻る

ptrace()の使い方

ptrace( PTRACE_ATTACH, pid, NULL, NULL );
プロセス番号pidをアタッチする宣言。wait()すると止まる。
ptrace( PTRACE_SYSCALL, pid, NULL, NULL );
システムコールで止まれ!と命令する。wait()すると止まる。入るときと出るときとの両方で止まるので、出るときのは無視しよう。
ptrace( PTRACE_GETREGS, pid, NULL, ®s );
システムコール時のレジスタを取得する。
ptrace( PTRACE_PEEKDATA, pid, addr, NULL );
指定したアドレスからデータを読む。普通にアクセスするとアドレス例外になるからね。戻り値が読み出したデータ、つまり、1ワードずつしか読めない。

レジスタの見方

write()の場合

regs.orig_eax システムコール番号
regs.ebx ファイルディスクリプタ
regs.ecx 文字列のあるアドレス
regs.edx サイズ
システムコール番号

SYS_writeとか__NR_writeとかの一覧は sys/syscall.h から順に辿ると定義されてる。
SYS_なんとか の形式を使ってると安定っぽい。

システムコールごとのレジスタの意味

システムコール ‐ 通信用語の基礎知識

orig_eax システムコール番号
eax 出るときは戻り値。入るときはわかんない
ebx システムコールの第1引数
ecx システムコールの第2引数
edx システムコールの第3引数
esi システムコールの第4引数
edi システムコールの第5引数

コード

usage: ptracetest []

$ ptracetest `pgrep hoge` 1

プロセス番号pidの出力を見る。fdがあればそのfdだけを、無ければ全部見る。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <asm/user.h>


void getdata(pid_t p, long addr, char *str, int len)
{
    int i;
    long data;

    for( i=0; i<len; i+=sizeof(long) ){
        // 文字列をコピーする
        data = ptrace( PTRACE_PEEKDATA, p, addr+i, NULL );
        memcpy( str+i, &data, sizeof(long) );
    }
    str[len] = '\0';
    return;
}


int main( int argc, char **argv ) {
    pid_t p;
    int st;
    int fd=0;
    int in_syscall=0;
    char *str;
    struct user_regs_struct regs;


    if( argc<2 ){
        printf( "usage: %s <pid> [<fd>]\n", argv[0] );
        return 0;
    }
    if( argc>2 ) fd=atoi(argv[2]); // ディスクリプタ指定のあるとき

    setbuf(stdout,NULL); // 標準出力をバッファリングしない

    // アタッチする
    p = atoi(argv[1]);
    if( ptrace(PTRACE_ATTACH,p,NULL,NULL)<0 ){
        perror("ptrace");
        exit(1);
    }
    wait(&st); // 無いと安定しない

    while( ptrace(PTRACE_SYSCALL,p,NULL,NULL)==0 ){
        // 止まるのを待つ
        wait(&st);
        if(WIFEXITED(st)) break;

        // レジスタを取り出す
        // regs.orig_eax  システムコール番号
        // regs.ebx  ファイルディスクリプタ
        // regs.ecx  文字列のあるアドレス
        // regs.edx  サイズ
        ptrace(PTRACE_GETREGS, p, 0, &regs);
        if( regs.orig_eax!=SYS_write ) continue; // write以外無視

        in_syscall = 1-in_syscall; // 交互に
        if(in_syscall){
            if( fd==0 || fd==regs.ebx ){
                str = malloc( regs.edx+sizeof(long) ); // 少し余計に
                getdata( p, regs.ecx, str, regs.edx );
                fputs(str,stdout);
                free(str); // 後始末
            }
        }
    }

    return 0;
}