Linux-Kernel: Beispiel für das Hooken von Systemaufrufen

Lesezeit: 13 Minuten

Benutzeravatar von Stephen
Stefan

Ich versuche, einen einfachen Testcode zu schreiben, um das Hooken der Systemaufruftabelle zu demonstrieren.

“sys_call_table” wird in 2.6 nicht mehr exportiert, also nehme ich einfach die Adresse aus der Datei System.map und kann sehen, dass sie korrekt ist (Wenn ich den Speicher an der gefundenen Adresse durchsehe, kann ich die Zeiger auf die Systemaufrufe).

Wenn ich jedoch versuche, diese Tabelle zu ändern, gibt der Kernel ein “Oops” mit “Kernel-Paging-Anforderung an virtueller Adresse c061e4f4 kann nicht verarbeitet werden” aus und der Computer wird neu gestartet.

Dies ist CentOS 5.4 mit 2.6.18-164.10.1.el5. Gibt es eine Art Schutz oder habe ich nur einen Fehler? Ich weiß, dass es mit SELinux geliefert wird, und ich habe versucht, es in den zulässigen Modus zu versetzen, aber es macht keinen Unterschied

Hier ist mein Code:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>

void **sys_call_table;

asmlinkage int (*original_call) (const char*, int, int);

asmlinkage int our_sys_open(const char* file, int flags, int mode)
{
   printk("A file was opened\n");
   return original_call(file, flags, mode);
}

int init_module()
{
    // sys_call_table address in System.map
    sys_call_table = (void*)0xc061e4e0;
    original_call = sys_call_table[__NR_open];

    // Hook: Crashes here
    sys_call_table[__NR_open] = our_sys_open;
}

void cleanup_module()
{
   // Restore the original call
   sys_call_table[__NR_open] = original_call;
}

  • Hast du damit experimentiert LD_PRELOAD oder ptrace? Befriedigen sie nicht, was Sie zu tun versuchen?

    – ezpz

    20. Januar 2010 um 17:15 Uhr

  • Nicht wirklich, der Zweck der Übung besteht darin, ein Kernelmodul zu laden, das einen Systemaufruf für das gesamte System einklinkt. Es spielt keine Rolle, was es zu diesem Zeitpunkt tut.

    – Stefan

    20. Januar 2010 um 17:17 Uhr

  • Bitte beachten Sie, dass es für Unterrichtszwecke in Ordnung sein könnte, dies zu untersuchen, aber es hat sowohl technische als auch lizenzrechtliche Probleme. Verwenden Sie dies nicht in der realen Welt!

    – robert.berger

    7. Dezember 2011 um 18:41 Uhr

  • Was könnte ein Anwendungsfall dieses Codes sein? Kann ich jeden Linux-Systemaufruf auf diese Weise einbinden?

    – Incerteza

    15. April 2014 um 15:05 Uhr

  • @robert.berger, was? Möchtest du das ein wenig erweitern?

    – Tyler

    30. Juli 2015 um 13:55 Uhr

Benutzeravatar von Stephen
Stefan

Endlich habe ich die Antwort selbst gefunden.

http://www.linuxforums.org/forum/linux-kernel/133982-cannot-modify-sys_call_table.html

Der Kernel wurde irgendwann so geändert, dass die Systemaufruftabelle nur lesbar ist.

Cypherpunk:

Auch wenn es spät ist, aber die Lösung könnte auch andere interessieren: In der entry.S-Datei finden Sie: Code:

.section .rodata,"a"
#include "syscall_table_32.S"

sys_call_table -> ReadOnly Man muss den Kernel neu kompilieren, wenn man mit sys_call_table “rumhacken” will…

Der Link enthält auch ein Beispiel zum Ändern des Speichers, damit er beschreibbar ist.

Nasenkoe:

Hallo zusammen. Danke für Antworten. Ich habe das Problem vor langer Zeit gelöst, indem ich den Zugriff auf Speicherseiten geändert habe. Ich habe zwei Funktionen implementiert, die dies für meinen Code der oberen Ebene tun:

#include <asm/cacheflush.h>
#ifdef KERN_2_6_24
#include <asm/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ | VM_WRITE;
    return change_page_attr(pg, 1, prot);
}

int set_page_ro(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ;
    return change_page_attr(pg, 1, prot);
}

#else
#include <linux/semaphore.h>
int set_page_rw(long unsigned int _addr)
{
    return set_memory_rw(_addr, 1);
}

int set_page_ro(long unsigned int _addr)
{
    return set_memory_ro(_addr, 1);
}

#endif // KERN_2_6_24

Hier ist eine modifizierte Version des Originalcodes, die für mich funktioniert.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
#include <asm/semaphore.h>
#include <asm/cacheflush.h>

void **sys_call_table;

asmlinkage int (*original_call) (const char*, int, int);

asmlinkage int our_sys_open(const char* file, int flags, int mode)
{
   printk("A file was opened\n");
   return original_call(file, flags, mode);
}

int set_page_rw(long unsigned int _addr)
{
   struct page *pg;
   pgprot_t prot;
   pg = virt_to_page(_addr);
   prot.pgprot = VM_READ | VM_WRITE;
   return change_page_attr(pg, 1, prot);
}

int init_module()
{
    // sys_call_table address in System.map
    sys_call_table = (void*)0xc061e4e0;
    original_call = sys_call_table[__NR_open];

    set_page_rw(sys_call_table);
    sys_call_table[__NR_open] = our_sys_open;
}

void cleanup_module()
{
   // Restore the original call
   sys_call_table[__NR_open] = original_call;
}

  • Beachten Sie, dass Linuxerlive im bereitgestellten Link behauptet, dass change_page_attr nicht für Kernel > 2.6.24 funktioniert, da es veraltet ist.

    – Stefan

    20. Januar 2010 um 18:28 Uhr

  • +1 für die Dokumentation der Lösung, zu der Sie gekommen sind, damit andere sie sehen können.

    – jgottula

    24. Oktober 2010 um 0:18 Uhr

  • Beachten Sie, dass Sie Folgendes erhalten, wenn Sie set_memory_rw() aufrufen und die Adresse nicht seitenausgerichtet ist: WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Nicht verdorben). Ich verwende 2.6.32 und arbeite immer noch an einer Lösung (da der Speicher immer noch schreibgeschützt zu sein scheint, nachdem ich dies aufgerufen habe).

    – Corey Henderson

    18. Juli 2011 um 0:49 Uhr

  • Tolle Antwort auf deine eigene Frage. Sehr detailiert. +1 sicher. Prost Mann.

    – A.Smith

    6. Juli 2012 um 13:02 Uhr

Danke Stephen, deine Recherche hier war hilfreich für mich. Ich hatte jedoch ein paar Probleme, als ich dies auf einem 2.6.32-Kernel versuchte und bekam WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted) gefolgt von einem Kernel-OOPS darüber, dass er nicht in der Lage ist, an die Speicheradresse zu schreiben.

Der Kommentar über der erwähnten Zeile besagt:

// People should not be passing in unaligned addresses

Der folgende modifizierte Code funktioniert:

int set_page_rw(long unsigned int _addr)
{
    return set_memory_rw(PAGE_ALIGN(_addr) - PAGE_SIZE, 1);
}

int set_page_ro(long unsigned int _addr)
{
    return set_memory_ro(PAGE_ALIGN(_addr) - PAGE_SIZE, 1);
}

Beachten Sie, dass dies die Seite in einigen Situationen immer noch nicht als Lese-/Schreibzugriff einstellt. Das static_protections() Funktion, die innerhalb von aufgerufen wird set_memory_rw()entfernt die _PAGE_RW kennzeichnen, wenn:

  • Es befindet sich im BIOS-Bereich
  • Die Adresse befindet sich in .rodata
  • CONFIG_DEBUG_RODATA ist gesetzt und der Kernel ist schreibgeschützt

Ich habe dies nach dem Debuggen herausgefunden, warum ich beim Versuch, die Adresse von Kernelfunktionen zu ändern, immer noch “Kernel-Paging-Anforderung nicht verarbeiten” bekam. Ich konnte dieses Problem schließlich lösen, indem ich den Seitentabelleneintrag für die Adresse selbst fand und ihn manuell auf beschreibbar setzte. Zum Glück ist die lookup_address() Funktion wird in Version 2.6.26+ exportiert. Hier ist der Code, den ich geschrieben habe, um das zu tun:

void set_addr_rw(unsigned long addr) {

    unsigned int level;
    pte_t *pte = lookup_address(addr, &level);

    if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;

}

void set_addr_ro(unsigned long addr) {

    unsigned int level;
    pte_t *pte = lookup_address(addr, &level);

    pte->pte = pte->pte &~_PAGE_RW;

}

Obwohl Marks Antwort technisch korrekt ist, tritt schließlich ein Problem auf, wenn sie in Xen ausgeführt wird. Wenn Sie den Schreibschutz deaktivieren möchten, verwenden Sie die cr0-Lese-/Schreibfunktionen. Ich makro sie so:

#define GPF_DISABLE write_cr0(read_cr0() & (~ 0x10000))
#define GPF_ENABLE write_cr0(read_cr0() | 0x10000)

Hoffe, das hilft allen anderen, die über diese Frage stolpern.

  • Hallo, in Bezug auf Ihren Kommentar zu Marks Antwort, nur neugierig: Was ist das Problem, das es verursacht, wenn es in Xen ausgeführt wird?

    – mrduclaw

    19. Juli 2011 um 3:54 Uhr

  • Auf den Xen-Kerneln, die ich ausprobiert habe, verursacht es eine “allgemeine Schutzverletzung”. Wie Sie bemerken werden, definiert xen seine eigene Funktion xen_write_cr0(), die den Schreibschutz nicht deaktiviert, da der Hypervisor dies handhabt und das Gastbetriebssystem keinen solchen Zugriff auf die CPU-Register hat.

    – Corey Henderson

    19. Juli 2011 um 4:11 Uhr

  • Corey, vielen Dank, dass Sie Ihre Erkenntnisse geteilt haben … wünschte, ich könnte noch 100 Mal positiv abstimmen!

    – Schleife für immer

    25. Juli 2011 um 13:18 Uhr

  • Sie finden es hier: github.com/cormander/tpe-lkm/blob/… Beachten Sie, dass ich diese Funktionen zweimal für verschiedene Kernel-Versionen habe.

    – Corey Henderson

    26. November 2011 um 19:30 Uhr

  • Danke für den Code, hat mein Problem gelöst. Warum hast du verwendet unsigned long für den Parameter Adr. Dies führt zu vielen Warnungen. ich benutzte void** als Typ für den Parameter addr. Gibt es einen besonderen Grund für die Verwendung unsigned long?

    – BitSchupser

    19. Dezember 2011 um 13:32 Uhr

Benutzeravatar von mark
Kennzeichen

Beachten Sie, dass Folgendes auch anstelle von change_page_attr funktioniert und nicht abgeschrieben werden kann:

static void disable_page_protection(void) {

    unsigned long value;
    asm volatile("mov %%cr0,%0" : "=r" (value));
    if (value & 0x00010000) {
            value &= ~0x00010000;
            asm volatile("mov %0,%%cr0": : "r" (value));
    }
}

static void enable_page_protection(void) {

    unsigned long value;
    asm volatile("mov %%cr0,%0" : "=r" (value));
    if (!(value & 0x00010000)) {
            value |= 0x00010000;
            asm volatile("mov %0,%%cr0": : "r" (value));
    }
}

  • Welche Art von Vodou wird hier gemacht? Welcher Loa-Geist wird mit dem Zauber 0x00010000 bezeichnet?

    – osgx

    19. Juli 2011 um 18:05 Uhr

  • @osgx cr0 ist ein Steuerregister. Das 16. Bit steuert die Durchsetzung des Seitenschutzes – schalten Sie es um und plötzlich spielt es keine Rolle mehr, dass Seiten “nur gelesen” werden. Sie können dies im Kernel-Space tun, da der Code auf der Berechtigungsstufe (Ring) 0 markiert ist. Normale Programme können sich das nicht selbst antun. Also im Grunde den Schreibschutz ausschalten, den “Nur-Lese”-Speicher mit Füßen treten, ihn wieder einschalten, voila. Sie können dies nicht ablehnen, da es Teil des Kernel-Designs ist, da es monolithisch ist, dass alle Module in Ring 0 laufen.

    Benutzer257111

    14. Dezember 2011 um 18:44 Uhr

  • Wenn Sie dies tun, sollten Sie Interrupts deaktivieren, clivor dem Ändern cr0 und Interrupts wieder aktivieren, sti, sobald Sie fertig sind. Sehen vulnfactory.org/blog/2011/08/12/wp-safe-or-not für Details.

    – mttrb

    2. Mai 2013 um 2:46 Uhr

  • Ändert cr0 auf diese Weise implizieren, dass es auf der aktuellen Seite wirkt?

    – sherrelbc

    28. Dezember 2016 um 20:20 Uhr

  • Wenn Sie cr0 ändern, zählt es für die CPU, sodass alle Anweisungen auf der CPU, wenn sie deaktiviert ist, diese Schutzmaßnahmen unabhängig von der Adresse deaktiviert haben. (en.wikipedia.org/wiki/Control_register)

    – Benutzer7296055

    20. Juli 2017 um 19:19 Uhr

Benutzeravatar von Sebastian Mountaniol
Sebastian Mountaniol

Wenn Sie es mit Kernel 3.4 und höher zu tun haben (es funktioniert auch mit früheren Kerneln, ich habe es nicht getestet), würde ich einen intelligenteren Weg empfehlen, um den Speicherort der Systemaufruftabelle zu erhalten.

Zum Beispiel

#include <linux/module.h>
#include <linux/kallsyms.h>

static unsigned long **p_sys_call_table;
/* Aquire system calls table address */
p_sys_call_table = (void *) kallsyms_lookup_name("sys_call_table");

Das ist es. Keine Adressen, es funktioniert gut mit jedem Kernel, den ich getestet habe.

Auf die gleiche Weise können Sie eine nicht exportierte Kernel-Funktion aus Ihrem Modul verwenden:

static int (*ref_access_remote_vm)(struct mm_struct *mm, unsigned long addr,
                void *buf, int len, int write);
ref_access_remote_vm = (void *)kallsyms_lookup_name("access_remote_vm");

Genießen!

Benutzeravatar von Marco Bonelli
Marco Bonelli

Wie andere angedeutet haben, ist die ganze Geschichte jetzt bei modernen Kerneln etwas anders. Ich werde hier x86-64 behandeln, für Syscall-Hijacking auf modernem Arm64 verweisen wir auf diese andere Antwort von mir. Ebenfalls HINWEIS: Dies ist schlicht und einfach ein Systemaufruf Entführung. Nicht-invasives Hooken kann auf eine viel schönere Art und Weise durchgeführt werden ksonden.

Seit Linux v4.17 verwendet x86 (sowohl 64- als auch 32-Bit) jetzt Syscall-Wrapper, die a struct pt_regs * als einziges Argument (vgl begehen 1, verpflichten 2). Du kannst sehen arch/x86/include/asm/syscall.h für die Definitionen.

Außerdem, wie andere bereits in verschiedenen Antworten beschrieben haben, der einfachste Weg zum Ändern sys_call_table ist das vorübergehende Deaktivieren des CR0 WP (Write-Protect)-Bits, was mit erfolgen könnte read_cr0() und write_cr0(). Jedoch seit Linux v5.3, [native_]write_cr0 überprüft sensible Bits, die sich nie ändern sollten (wie WP) und weigert sich, sie zu ändern (verpflichten). Um dies zu umgehen, müssen wir CR0 mithilfe der Inline-Assemblierung manuell schreiben.

Hier ist ein funktionierendes Kernelmodul (getestet unter Linux 5.10 und 5.18), das Syscall-Hijacking unter modernem Linux x86-64 unter Berücksichtigung der oben genannten Einschränkungen und unter der Annahme, dass Sie die Adresse von bereits kennen, durchführt sys_call_table (wenn Sie das auch im Modul finden möchten, siehe Richtige Methode zum Abrufen der Adresse von nicht exportierten Kernelsymbolen in einem Linux-Kernelmodul):

// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/**
 * Test syscall table hijacking on x86-64. This module will replace the `read`
 * syscall with a simple wrapper which logs every invocation of `read` using
 * printk().
 *
 * Tested on Linux x86-64 v5.10, v5.18.
 *
 * Usage:
 *
 *     sudo cat /proc/kallsyms | grep sys_call_table # grab address
 *     sudo insmod syscall_hijack.ko sys_call_table_addr=0x<address_here>
 */

#include <linux/init.h>          // module_{init,exit}()
#include <linux/module.h>        // THIS_MODULE, MODULE_VERSION, ...
#include <linux/kernel.h>        // printk(), pr_*()
#include <asm/special_insns.h>   // {read,write}_cr0()
#include <asm/processor-flags.h> // X86_CR0_WP
#include <asm/unistd.h>          // __NR_*

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

typedef long (*sys_call_ptr_t)(const struct pt_regs *);

static sys_call_ptr_t *real_sys_call_table;
static sys_call_ptr_t original_read;

static unsigned long sys_call_table_addr;
module_param(sys_call_table_addr, ulong, 0);
MODULE_PARM_DESC(sys_call_table_addr, "Address of sys_call_table");

// Since Linux v5.3 [native_]write_cr0 won't change "sensitive" CR0 bits, need
// to re-implement this ourselves.
static void write_cr0_unsafe(unsigned long val)
{
    asm volatile("mov %0,%%cr0": "+r" (val) : : "memory");
}

static long myread(const struct pt_regs *regs)
{
    pr_info("read(%ld, 0x%lx, %lx)\n", regs->di, regs->si, regs->dx);
    return original_read(regs);
}

static int __init modinit(void)
{
    unsigned long old_cr0;

    real_sys_call_table = (typeof(real_sys_call_table))sys_call_table_addr;

    pr_info("init\n");

    // Temporarily disable CR0 WP to be able to write to read-only pages
    old_cr0 = read_cr0();
    write_cr0_unsafe(old_cr0 & ~(X86_CR0_WP));

    // Overwrite syscall and save original to be restored later
    original_read = real_sys_call_table[__NR_read];
    real_sys_call_table[__NR_read] = myread;

    // Restore CR0 WP
    write_cr0_unsafe(old_cr0);
    pr_info("init done\n");

    return 0;
}

static void __exit modexit(void)
{
    unsigned long old_cr0;

    pr_info("exit\n");

    old_cr0 = read_cr0();
    write_cr0_unsafe(old_cr0 & ~(X86_CR0_WP));

    // Restore original syscall
    real_sys_call_table[__NR_read] = original_read;

    write_cr0_unsafe(old_cr0);

    pr_info("goodbye\n");
}

module_init(modinit);
module_exit(modexit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Test syscall table hijacking on x86-64.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("Dual MIT/GPL");

1417360cookie-checkLinux-Kernel: Beispiel für das Hooken von Systemaufrufen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy