Mengapa Anda tidak bisa / tidak ingin menggunakan trik LD_PRELOAD?
Contoh kode di sini:
/*
* File: soft_atimes.c
* Author: D.J. Capelis
*
* Compile:
* gcc -fPIC -c -o soft_atimes.o soft_atimes.c
* gcc -shared -o soft_atimes.so soft_atimes.o -ldl
*
* Use:
* LD_PRELOAD="./soft_atimes.so" command
*
* Copyright 2007 Regents of the University of California
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>
extern int errorno;
int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;
int open(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open) {
_open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
}
if(flags & O_CREAT)
return _open(pathname, flags | O_NOATIME, mode);
else
return _open(pathname, flags | O_NOATIME, 0);
}
int open64(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open64) {
_open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
}
if(flags & O_CREAT)
return _open64(pathname, flags | O_NOATIME, mode);
else
return _open64(pathname, flags | O_NOATIME, 0);
}
Dari apa yang saya mengerti ... ini adalah trik LD_PRELOAD atau modul kernel. Tidak banyak jalan tengah kecuali jika Anda ingin menjalankannya di bawah emulator yang dapat menjebak fungsi Anda atau melakukan penulisan ulang kode pada biner yang sebenarnya untuk menjebak ke fungsi Anda.
Dengan asumsi Anda tidak dapat memodifikasi program dan tidak dapat (atau tidak ingin) memodifikasi kernel, pendekatan LD_PRELOAD adalah yang terbaik, dengan asumsi aplikasi Anda cukup standar dan sebenarnya bukan aplikasi yang mencoba melewatinya dengan jahat intersepsi Anda. (Dalam hal ini Anda memerlukan salah satu teknik lainnya.)
Valgrind dapat digunakan untuk mencegat panggilan fungsi apa pun. Jika Anda perlu mencegat panggilan sistem di produk jadi Anda, maka ini tidak akan berguna. Namun, jika Anda mencoba mencegat selama pengembangan maka itu bisa sangat berguna. Saya sering menggunakan teknik ini untuk mencegat fungsi hashing sehingga saya dapat mengontrol hash yang dikembalikan untuk tujuan pengujian.
Jika Anda tidak menyadarinya, Valgrind terutama digunakan untuk menemukan kebocoran memori dan kesalahan terkait memori lainnya. Tetapi teknologi yang mendasarinya pada dasarnya adalah emulator x86. Ini mengemulasi program Anda dan mencegat panggilan ke malloc/free dll. Hal baiknya adalah, Anda tidak perlu mengkompilasi ulang untuk menggunakannya.
Valgrind memiliki fitur yang mereka sebut Function Wrapping , yang digunakan untuk mengontrol intersepsi fungsi. Lihat bagian 3.2 dari manual Valgrind untuk detailnya. Anda dapat mengatur pembungkusan fungsi untuk fungsi apa pun yang Anda suka. Setelah panggilan dicegat, fungsi alternatif yang Anda berikan kemudian dipanggil.
Pertama mari kita hilangkan beberapa non-jawaban yang diberikan orang lain:
- Gunakan
LD_PRELOAD
. Ya, Anda mengatakan "SelainLD_PRELOAD
..." dalam pertanyaan tetapi tampaknya itu tidak cukup bagi sebagian orang. Ini bukan pilihan yang baik karena hanya berfungsi jika program menggunakan libc yang belum tentu demikian. - Gunakan Ketuk Sistem. Ya Anda mengatakan "Selain ... Modul Kernel Linux" dalam pertanyaan tetapi tampaknya itu tidak cukup bagi sebagian orang. Ini bukan pilihan yang baik karena Anda harus memuat modul kernal khusus yang sangat merepotkan dan juga membutuhkan root.
- Valgrind. Ini memang berfungsi tetapi berfungsi mensimulasikan CPU sehingga sangat lambat dan sangat rumit. Baik jika Anda hanya melakukan ini untuk debugging satu kali. Bukan pilihan jika Anda melakukan sesuatu yang layak produksi.
- Berbagai hal audit syscall. Saya tidak berpikir syscall logging dianggap sebagai "mencegat" mereka. Kami jelas ingin memodifikasi parameter syscall / mengembalikan nilai atau mengalihkan program melalui beberapa kode lain.
Namun ada kemungkinan lain yang belum disebutkan di sini. Perhatikan bahwa saya baru mengenal semua hal ini dan belum mencobanya, jadi saya mungkin salah tentang beberapa hal.
Tulis ulang kode
Secara teori, Anda dapat menggunakan beberapa jenis pemuat khusus yang menulis ulang instruksi syscall untuk beralih ke penangan khusus. Tapi saya pikir itu akan menjadi mimpi buruk mutlak untuk diterapkan.
kprobe
kprobes adalah semacam sistem instrumentasi kernel. Mereka hanya memiliki akses baca-saja ke apa pun sehingga Anda tidak dapat menggunakannya untuk mencegat syscall, hanya mencatatnya.
ptrace
ptrace adalah API yang digunakan para debugger seperti GDB untuk melakukan debugging. Ada PTRACE_SYSCALL
opsi yang akan menjeda eksekusi tepat sebelum/sesudah syscalls. Dari sana Anda dapat melakukan hampir semua hal yang Anda suka dengan cara yang sama seperti yang dilakukan GDB. Berikut artikel tentang cara memodifikasi parameter syscall menggunakan ptrace. Namun tampaknya memiliki overhead yang tinggi.
Sekompen
Secomp adalah sistem yang dirancang untuk memungkinkan Anda memfilter syscall. Anda tidak dapat mengubah argumen, tetapi Anda bisa blokir mereka atau kembalikan kesalahan khusus. Filter seccomp adalah program BPF. Jika Anda tidak terbiasa, mereka pada dasarnya adalah program arbitrer yang dapat dijalankan pengguna di VM kernel-space. Ini menghindari peralihan konteks pengguna/kernel yang membuatnya lebih cepat daripada ptrace.
Meskipun Anda tidak dapat mengubah argumen secara langsung dari program BPF, Anda bisa kembalikan SECCOMP_RET_TRACE
yang akan memicu ptrace
orang tua untuk istirahat. Jadi pada dasarnya sama dengan PTRACE_SYSCALL
kecuali Anda dapat menjalankan program di ruang kernel untuk memutuskan apakah Anda ingin mencegat syscall berdasarkan argumennya. Jadi seharusnya lebih cepat jika Anda hanya ingin mencegat beberapa syscalls (mis. open()
dengan jalur tertentu).
Saya pikir ini mungkin pilihan terbaik. Inilah artikel tentang itu dari penulis yang sama dengan yang di atas. Perhatikan bahwa mereka menggunakan BPF klasik, bukan eBPF, tetapi saya rasa Anda juga dapat menggunakan eBPF.
Sunting:Sebenarnya Anda hanya bisa menggunakan BPF klasik, bukan eBPF. Ada artikel LWN tentang itu.
Berikut adalah beberapa pertanyaan terkait. Yang pertama pasti layak dibaca.
- Dapatkah eBPF mengubah nilai kembalian atau parameter syscall?
- Hanya mencegat syscall dengan PTRACE_SINGLESTEP
- Apakah ini cara yang baik untuk mencegat panggilan sistem?
- Cara overhead minimal untuk mencegat panggilan sistem tanpa memodifikasi kernel
Ada juga artikel bagus tentang memanipulasi syscall melalui ptrace di sini.
Beberapa aplikasi dapat mengelabui strace/ptrace agar tidak berjalan, jadi satu-satunya pilihan nyata yang saya miliki adalah menggunakan systemtap
Systemtap dapat mencegat banyak panggilan sistem jika perlu karena pencocokan kartu liarnya. Systemtap bukan C, tetapi bahasa yang terpisah. Dalam mode dasar, systemtap harus mencegah Anda melakukan hal-hal bodoh, tetapi juga dapat berjalan dalam "mode pakar" yang memungkinkan pengembang untuk menggunakan C jika diperlukan.
Itu tidak mengharuskan Anda untuk menambal kernel Anda (Atau setidaknya tidak boleh), dan setelah modul dikompilasi, Anda dapat menyalinnya dari kotak pengujian/pengembangan dan memasukkannya (melalui insmod) pada sistem produksi.
Saya belum menemukan aplikasi linux yang telah menemukan cara untuk mengatasi/menghindari tertangkap oleh systemtap.