コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

sshでログインしている端末の出力をのぞき見る

やりたいこと

  • 自分は端末Aでログインしている状態。
    端末Aから別の端末である端末Bのコマンドの入力、応答をのぞき見たい。
  • できればC言語で実装したい


実現方法

ググってみたところ、perl製のttylogを使ったり、shellスクリプトで頑張ればできるようです。
どちらも共通するのはstraceの出力を加工していること。

sshdに対してstraceを実行し、read(fd=13)の第2引数を取っています。

参考
orebibou.com

strace自体はptraceを使って実装されているので、マネすればc言語でもいけそうです。


ptraceについて

manページ見てもよくわからなかったのですが、下記の流れで実装すれば良いみたいです。

attach
↓
option設定
↓
システムコールをcatch
↓
レジスタから引数を解析

- システムコールの番号、引数はレジスタ(struct user_regs_struct)で判別する。<br>
- readの第2引数(buf)の中身を取得するにはPTRACE_PEEKDATAを使う。


X86のレジスタ対応表(とりあえずreadの捕捉に必要なものだけ)

種目 レジスタ(メンバ名)
システムコールの番号 orig_rax
リターン値 rax
第1引数 rdi
第2引数 rsi
第3引数 rdx


ソースコード

ttytrace.c

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

#define FD_TERMINAL (13)

static int read_args(pid_t pid, long addr, size_t size)
{
    int rc = 0;
    long dat = 0;
    size_t datsz = 0;

    for (int i = 0; i < size; i+= sizeof(long)) {
        errno = 0;
        dat = ptrace(PTRACE_PEEKDATA, pid, addr + i, NULL);
        int err = errno;
        if (err != 0) {
            perror("ptrace");
            rc = -err;
            break;
        }
 
        if (i + sizeof(long) > size) {
            write(1, (void*)&dat, size % sizeof(long));
        } else {
            write(1, (void*)&dat, sizeof(long));
        }
    }

    return rc;
}


static int
get_syscall_args_read(
    pid_t pid,
    struct user_regs_struct *regs)
{
    int rc = 0;

#if defined(__x86_64__)
    /* skip other than "read" */
    if (!((regs->orig_rax == SYS_read) &&
         (regs->rdi == FD_TERMINAL) &&
         ((ssize_t)regs->rax > 0))) {
        goto end;
    }

    rc = read_args(pid, (long)regs->rsi, (size_t)regs->rax);
    if (rc < 0) {
        goto end;
    }
#endif   

 end:

    return rc;
}

static void start_trace(pid_t pid)
{
    int rc = 0;
    int status = 0;
    struct user_regs_struct regs;

    rc = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
    if (rc < 0) {
        perror("ptrace");
        goto end;
    }

    ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESYSGOOD);

    while (1) {
        ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

        rc = waitpid(pid, &status, 0);
        if (rc < 0) {
            perror("waitpid");
            goto detach_end;
        }

        if (WIFEXITED(status)) {
            break;
        }

        rc = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
        if (rc < 0) {
            perror("ptrace");
            goto detach_end;
        }

        rc = get_syscall_args_read(pid, &regs);
        if (rc < 0) {
            goto detach_end;
        }
    }

  detach_end:
    ptrace(PTRACE_DETACH, pid, NULL, NULL);

  end:
    return;
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("%s <pid>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid_t pid = atoi(argv[1]);

    start_trace(pid);

    return 0;
}


ビルド

gcc -o ttytrace ttytrace.c -std=gnu99


実行

監視したい端末のsshdのpidを指定する。
実行にはroot権限が必要です。

sudo ./ttytrace <sshdのpid>


実行に成功していれば、ttylogとかと同様に監視対象の端末での操作内容がそのまま表示されます。
意外と短い実装で済んでよかった。