GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie könnte ich Linux-Systemaufrufe abfangen?

Warum können / wollen Sie den LD_PRELOAD-Trick nicht verwenden?

Beispielcode hier:

/*
 * 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);
}

Soweit ich weiß, ist es so ziemlich der LD_PRELOAD-Trick oder ein Kernelmodul. Es gibt nicht viel Mittelweg, es sei denn, Sie möchten es unter einem Emulator ausführen, der Ihre Funktion abfangen kann, oder Code auf der tatsächlichen Binärdatei umschreiben, um Ihre Funktion abzufangen.

Angenommen, Sie können das Programm nicht modifizieren und können (oder wollen) den Kernel nicht modifizieren, ist der LD_PRELOAD-Ansatz der beste, vorausgesetzt, Ihre Anwendung ist ziemlich standardisiert und nicht wirklich eine, die böswillig versucht, vorbeizukommen Ihr Abfangen. (In diesem Fall benötigen Sie eine der anderen Techniken.)


Valgrind kann verwendet werden, um jeden Funktionsaufruf abzufangen. Wenn Sie einen Systemaufruf in Ihrem fertigen Produkt abfangen müssen, nützt dies nichts. Wenn Sie jedoch versuchen, während der Entwicklung abzufangen, kann dies sehr nützlich sein. Ich habe diese Technik häufig verwendet, um Hash-Funktionen abzufangen, damit ich den zurückgegebenen Hash zu Testzwecken kontrollieren kann.

Falls Sie es nicht wissen, Valgrind wird hauptsächlich zum Auffinden von Speicherlecks und anderen speicherbezogenen Fehlern verwendet. Aber die zugrunde liegende Technologie ist im Grunde ein x86-Emulator. Es emuliert Ihr Programm und fängt Aufrufe von malloc/free usw. ab. Das Gute daran ist, dass Sie es nicht neu kompilieren müssen, um es zu verwenden.

Valgrind hat eine Funktion, die sie Function Wrapping nennen , die verwendet wird, um das Abfangen von Funktionen zu steuern. Einzelheiten finden Sie in Abschnitt 3.2 des Valgrind-Handbuchs. Sie können den Funktionsumbruch für jede beliebige Funktion einrichten. Sobald der Anruf abgefangen wird, wird die von Ihnen bereitgestellte alternative Funktion aufgerufen.


Lassen Sie uns zuerst einige Nicht-Antworten beseitigen, die andere Leute gegeben haben:

  • Verwenden Sie LD_PRELOAD . Ja, Sie sagten:"Neben LD_PRELOAD ..." in der Frage, aber anscheinend reicht das einigen Leuten nicht. Dies ist keine gute Option, da es nur funktioniert, wenn das Programm libc verwendet, was nicht unbedingt der Fall ist.
  • Verwenden Sie Systemtap. Ja, Sie haben in der Frage "Neben ... Linux-Kernel-Modulen" gesagt, aber anscheinend reicht das einigen Leuten nicht aus. Dies ist keine gute Option, da Sie ein benutzerdefiniertes Kernel-Modul laden müssen, was eine große Nervensäge ist und auch root erfordert.
  • Valgrind. Das funktioniert irgendwie, aber es funktioniert, indem es die CPU simuliert, also ist es wirklich langsam und sehr kompliziert. Gut, wenn Sie dies nur zum einmaligen Debuggen tun. Nicht wirklich eine Option, wenn Sie etwas produktionswürdiges tun.
  • Verschiedene Syscall-Audits. Ich glaube nicht, dass das Protokollieren von Systemaufrufen als "Abfangen" gilt. Wir wollen eindeutig die Syscall-Parameter / Rückgabewerte ändern oder das Programm durch einen anderen Code umleiten.

Es gibt jedoch noch andere Möglichkeiten, die hier noch nicht erwähnt wurden. Beachten Sie, dass ich neu in all diesen Dingen bin und noch nichts davon ausprobiert habe, daher kann es sein, dass ich mich in einigen Dingen irre.

Code neu schreiben

Theoretisch könnten Sie eine Art benutzerdefinierten Lader verwenden, der die Syscall-Anweisungen umschreibt, um stattdessen zu einem benutzerdefinierten Handler zu springen. Aber ich denke, das wäre ein absoluter Albtraum zu implementieren.

kprobes

kprobes sind eine Art Kernel-Instrumentierungssystem. Sie haben nur Lesezugriff auf alles, also können Sie sie nicht zum Abfangen von Systemaufrufen verwenden, sondern nur protokollieren.

ptrace

ptrace ist die API, die Debugger wie GDB zum Debuggen verwenden. Es gibt eine PTRACE_SYSCALL Option, die die Ausführung unmittelbar vor/nach Systemaufrufen anhält. Von dort aus können Sie so ziemlich alles tun, was Sie wollen, genauso wie GDB es kann. Hier ist ein Artikel darüber, wie man Syscall-Parameter mit Ptrace ändert. Allerdings hat es anscheinend einen hohen Overhead.

Seccomp

Seccomp ist ein System, das Ihnen das Filtern ermöglicht Systemaufrufe. Sie können die Argumente nicht ändern, aber Sie können Blockieren Sie sie oder geben Sie benutzerdefinierte Fehler zurück. Seccomp-Filter sind BPF-Programme. Wenn Sie nicht vertraut sind, handelt es sich im Grunde genommen um beliebige Programme, die Benutzer in einer Kernel-Space-VM ausführen können. Dies vermeidet den Benutzer/Kernel-Kontextwechsel, der sie schneller als ptrace macht.

Obwohl Sie Argumente nicht direkt von Ihrem BPF-Programm aus ändern können, können Sie gib SECCOMP_RET_TRACE zurück was einen ptrace auslöst ing Elternteil zu brechen. Es ist also im Grunde dasselbe wie PTRACE_SYSCALL außer Sie müssen ein Programm im Kernel-Space ausführen, um zu entscheiden, ob Sie einen Syscall tatsächlich abfangen möchten, basierend auf seinen Argumenten. Es sollte also schneller sein, wenn Sie nur einige Systemaufrufe abfangen möchten (z. B. open() mit bestimmten Pfaden).

Ich denke, das ist wahrscheinlich die beste Option. Hier ist ein Artikel darüber vom gleichen Autor wie oben. Beachten Sie, dass sie klassisches BPF anstelle von eBPF verwenden, aber ich denke, Sie können auch eBPF verwenden.

Bearbeiten:Eigentlich können Sie nur klassisches BPF verwenden, nicht eBPF. Es gibt einen LWN-Artikel darüber.

Hier sind einige verwandte Fragen. Der erste ist auf jeden Fall lesenswert.

  • Kann eBPF den Rückgabewert oder die Parameter eines Systemaufrufs ändern?
  • Nur Syscall mit PTRACE_SINGLESTEP abfangen
  • Ist dies eine gute Möglichkeit, Systemaufrufe abzufangen?
  • Minimaler Aufwand zum Abfangen von Systemaufrufen, ohne den Kernel zu modifizieren

Es gibt auch einen guten Artikel über das Manipulieren von Systemaufrufen über Ptrace hier.


Einige Anwendungen können strace/ptrace dazu verleiten, nicht zu laufen, daher ist die einzige wirkliche Option, die ich hatte, die Verwendung von systemtap

Systemtap kann aufgrund seines Wildcard-Matchings bei Bedarf eine Reihe von Systemaufrufen abfangen. Systemtap ist nicht C, sondern eine eigene Sprache. Im einfachen Modus sollte der Systemtap Sie daran hindern, Dummheiten zu machen, aber er kann auch im "Expertenmodus" laufen, der darauf zurückgreift, einem Entwickler zu erlauben, C zu verwenden, wenn dies erforderlich ist.

Es erfordert nicht, dass Sie Ihren Kernel patchen (oder sollten es zumindest nicht), und sobald ein Modul kompiliert wurde, können Sie es aus einer Test-/Entwicklungsbox kopieren und (über insmod) in ein Produktionssystem einfügen. P>

Ich muss noch eine Linux-Anwendung finden, die einen Weg gefunden hat, das Problem zu umgehen/zu vermeiden, dass ich von Systemtap erwischt werde.


Linux
  1. So verwenden Sie BusyBox unter Linux

  2. Wie ich Cron unter Linux verwende

  3. Wie stellen Sie die Hardwareuhr unter Linux programmgesteuert ein?

  4. Wie man Strg+C unter Windows und Linux mit Qt abfängt

  5. Wie bestimmt der Linux-Kernel die Reihenfolge der __init-Aufrufe?

So finden Sie Dateien unter Linux

So installieren Sie Kali Linux

So installieren Sie FFmpeg unter Linux

So beenden Sie einen Prozess in Linux

So erstellen Sie eine Datei unter Linux

So benennen Sie Verzeichnisse in Linux um