LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

ptraceとELFとLinuxレジスタ

Last updated at Posted at 2017-05-30

ptrace(2)に入門。ptrace(2) は Linux を含む Unix 系OS にあるシステムコールで、実行中のプロセスに対して、メモリ上のデータやレジスタの値を抜き出したり、書き換えたりすることができる。

これを使ってごにょごにょすると、実行中の関数とその引数を取り出して、実行中のプロセスを止めずにスタックトレースを取得したり、デバッガを作ったり標準出力を横取りして audit log を取ったりオンラインでパッチをあてて脆弱性対応したりできるはず。夢が広がる。

例えば、普通のやつらの下を行け: ptrace で実行中のプロセスにちょっかいを出す では、32bit executable なバイナリに対して、実行中に出力文字列を置き換える例を紹介している。

strace コマンドは ptrace(2) を利用して、システムコールを追って出力している。これについては udzura さんの straceがどうやってシステムコールの情報を取得しているか の記事に情報があった。

参考図書

ptrace 周りの書籍またはウェブサイトがないのか探していたのだが、古い時代のものしかみつからない。サンプルも 32bit が基本で、入門したい身なのに色々置き換えながら読まないといけなくてツライ。

と思っていたところで、Learning Linux Binary Analysis という本に載ってそうと教えてもらった。2016/2/29 出版で新しい。ptrace(2) についても ELF についても載っていて、欲しかったやつだった。

バイナリやアセンブリ周りは、和書は古いものしかないが、洋書だったら最近出版されているものもあるようで、次は最初から洋書を探そうと思った。

※ この本は基本的にはリバースエンジニアリングの本で、Chapter 4 からは virus がどのようにバイナリが実行可能なまま自分自身を盛り込むのか、virus に injection されたことをどのように発見するのか、という内容になる。興味深いは自分はまだ読んでない。

Learning Linux Binary Analysis
Learning Linux Binary Analysis
posted with amazlet at 17.05.30
Ryan O'neil
Packt Publishing (2016-02-29)
売り上げランキング: 69,854

https://www.packtpub.com/networking-and-servers/learning-linux-binary-analysis

Linux システムコールの ABI

Linux のシステムコールを呼び出すには ABI (Application Binary Interface) が決まっていて、CPUの決まったレジスタに値を書き込んで INT 0x80 (Interrupt) 命令を投げると、カーネルに割り込みをしてシステムコールを実行してもらうことができる。

rax レジスタにシステムコール番号を書き込み、以下のようにアーキテクチャごとに異なるレジスタに引数の値を書き込み、INT 0x80 命令を投げる

arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
──────────────────────────────────────────────────────────────
i386          ebx   ecx   edx   esi   edi   ebp   -
x86_64        rdi   rsi   rdx   r10   r8    r9    -

ref. man : syscall(2)

例えば x86_64 アーキテクチャで sys_write して sys_exit するコードをアセンブリで書くと次のようになる。システムコールの番号は linux のヘッダから取ってくる。

;------------------------------------
; hellol.s
;   nasm -f elf64 hellol.s
;   ld -o hellol hellol.o
;   ./hellol
;------------------------------------

bits 64
section .text
global _start

_start:
        mov rax, 1      ; sys_write
        mov rdi, 1      ; stdout
        mov rsi, msg    ; address
        mov rdx, len    ; length (13)
        int 0x80

        mov rax, 60     ; sys_exit
        xor rdi, rdi    ; 0
        int 0x80

section .data
        msg     db      'hello, world', 0x0A
        len     equ     $ - msg

ref. Linux で64bitアセンブリプログラミング (01) - hello world

References:

ptrace でシステムコールを追う

ptrace(2)で対象プロセスのシステムコールを追うC言語プログラムはざっくり言うと以下の手順になる

  • #include <sys/ptrace.h>
  • ptrace(PTRACE_ATTACH, pid, NULL, NULL);
  • システムコール直前、または直後に停止した状態で
    • int e = ptrace(PTRACE_GETREGS, pid, 0, &regs);
  • ptrace(PTRACE_DETACH, pid, NULL, NULL);

regs の定義は struct user_regs_struct regs であり、user_regs_struct (x86_64用構造体) は以下のようになっている。

struct user_regs_struct
{
  unsigned long r15;
  unsigned long r14;
  unsigned long r13;
  unsigned long r12;
  unsigned long rbp;
  unsigned long rbx;
  unsigned long r11;
  unsigned long r10;
  unsigned long r9;
  unsigned long r8;
  unsigned long rax;
  unsigned long rcx;
  unsigned long rdx;
  unsigned long rsi;
  unsigned long rdi;
  unsigned long orig_rax;
  unsigned long rip;
  unsigned long cs;
  unsigned long eflags;
  unsigned long rsp;
  unsigned long ss;
  unsigned long fs_base;
  unsigned long gs_base;
  unsigned long ds;
  unsigned long es;
  unsigned long fs;
  unsigned long gs;
};

つまり、レジスタの値を取り出せるということだ。あとは rax レジスタのシステムコール番号から、どのシステムコールを呼んでいるのかがわかる。なお、i386_user_regs_struct (i386用構造体) は以下のようになっている。

struct i386_user_regs_struct {
    uint32_t ebx;
    uint32_t ecx;
    uint32_t edx;
    uint32_t esi;
    uint32_t edi;
    uint32_t ebp;
    uint32_t eax;
    uint32_t xds;
    uint32_t xes;
    uint32_t xfs;
    uint32_t xgs;
    uint32_t orig_eax;
    uint32_t eip;
    uint32_t xcs;
    uint32_t eflags;
    uint32_t esp;
    uint32_t xss;
};

strace はシステムコール番号を取得する以上のことをやっていて、システムコールごとに引数の型がなにかを定義して、適切に値を取り出して表示している。特に、レジスタにポインタのアドレスが書き込まれている場合、アドレスが示すメモリ領域から値を取り出す必要もある。このような処理をシステムコールごとに地道にコードを書いて対応しているとのこと。頭が下がる。

ref. straceがどうやってシステムコールの情報を取得しているか

ELF

ELF は Linux のような Unix 系OSで標準的なバイナリフォーマットで、実行ファイル、共有ライブラリ(.so)、オブジェクトファイル(.o)、コアダンプなどに使われている。

ELFバイナリは、実行するためにメモリに読み込まれた場合でもフォーマットはほとんど変わらないので、ELFバイナリフォーマットについて知識があれば値を取り出せる。このELFフォーマットについては、最初に紹介した「Learning Linux Binary Analysis」で詳細に解説があった。

通常はこのメモリ領域は、データを書き換えようとすると SEGV が起きるわけだけど、ptrace(2) を使うとなんと書き換えることができる。

PTRACE_POKEDATA を使って hello, worldhippo, world に置き換えるサンプルが 普通のやつらの下を行け: ptrace で実行中のプロセスにちょっかいを出すにあったので、やってみると面白い。記事が 32bit 時代のものなので、64 bit に置き換えて動かすのも良い練習になる。

まとめ

ptrace(2)に入門した。これを使いつつさらにごにょごにょすれば、生きているプロセスにアタッチして、Cレベルのスタックトレースを出しつつ、Rubyレベルのスタックトレースを出すなんてこともできるだろう。まぁ、sigdumpでいいんだけど。

FYI: sigdump は対象 ruby プロセスに sigdump gem を入れておいて require 'sigdump/setup' しておかないといけない。ptrace(2) ベースでアプローチすれば、何も入れておく必要はなくなる。ただし、そのツールはCRubyのバイナリレベルの変更に追随する必要がある(´・ω・`)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up