web-dev-qa-db-fra.com

Noyau Linux: exemple de raccordement d'appels système

J'essaie d'écrire du code de test simple pour démontrer le raccordement de la table d'appels système.

"sys_call_table" n'est plus exporté en 2.6, donc je saisis simplement l'adresse du fichier System.map, et je peux voir qu'elle est correcte (En regardant dans la mémoire à l'adresse que j'ai trouvée, je peux voir les pointeurs vers le appels système).

Cependant, lorsque j'essaye de modifier cette table, le noyau donne un "Oups" avec "incapable de gérer la requête de pagination du noyau à l'adresse virtuelle c061e4f4" et la machine redémarre.

Il s'agit de CentOS 5.4 exécutant 2.6.18-164.10.1.el5. Y a-t-il une sorte de protection ou ai-je juste un bug? Je sais que cela vient avec SELinux, et j'ai essayé de le mettre en mode permissif, mais cela ne fait aucune différence

Voici mon 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;
}
65
Stephen

J'ai finalement trouvé la réponse moi-même.

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

Le noyau a été modifié à un moment donné afin que la table d'appels système soit en lecture seule.

cypherpunk:

Même s'il est en retard mais que la Solution peut aussi intéresser les autres: Dans le fichier entry.S vous trouverez: Code:

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

sys_call_table -> ReadOnly Vous devez compiler le noyau nouveau si vous voulez "pirater" autour avec sys_call_table ...

Le lien contient également un exemple de modification de la mémoire en écriture.

nasekomoe:

Salut tout le monde. Merci pour les réponses. J'ai résolu le problème il y a longtemps en modifiant l'accès aux pages mémoire. J'ai implémenté deux fonctions qui le font pour mon code de niveau supérieur:

#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

Voici une version modifiée du code original qui fonctionne pour moi.

#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;
}
54
Stephen

Merci Stephen, vos recherches ici m'ont été utiles. J'ai eu quelques problèmes, cependant, alors que j'essayais cela sur un noyau 2.6.32, et que j'obtenais WARNING: at Arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted) suivi d'un OOPS du noyau sur le fait de ne pas pouvoir écrire sur l'adresse mémoire.

Le commentaire au-dessus de la ligne mentionnée indique:

// People should not be passing in unaligned addresses

Le code modifié suivant fonctionne:

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

Notez que cela ne définit toujours pas la page en lecture/écriture dans certaines situations. La fonction static_protections(), qui est appelée à l'intérieur de set_memory_rw(), supprime l'indicateur _PAGE_RW Si:

  • C'est dans la zone du BIOS
  • L'adresse est à l'intérieur de .rodata
  • CONFIG_DEBUG_RODATA est défini et le noyau est défini en lecture seule

J'ai découvert cela après avoir débogué pourquoi je n'arrivais toujours pas à gérer la demande de pagination du noyau lorsque j'essayais de modifier l'adresse des fonctions du noyau. J'ai finalement réussi à résoudre ce problème en trouvant moi-même l'entrée de table de pages pour l'adresse et en la définissant manuellement en écriture. Heureusement, la fonction lookup_address() est exportée dans la version 2.6.26+. Voici le code que j'ai écrit pour le faire:

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;

}

Enfin, bien que la réponse de Mark soit techniquement correcte, elle rencontrera un problème lorsqu'elle sera exécutée dans Xen. Si vous souhaitez désactiver la protection en écriture, utilisez les fonctions cr0 en lecture/écriture. Je les macro comme ça:

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

J'espère que cela aide toute autre personne qui tombe sur cette question.

24
Corey Henderson

Notez que les éléments suivants fonctionnent également au lieu d'utiliser change_page_attr et ne peuvent pas être amortis:

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));
    }
}
18
mark

Si vous traitez avec le noyau 3.4 et ultérieur (il peut également fonctionner avec les noyaux antérieurs, je ne l'ai pas testé), je recommanderais une façon plus intelligente d'acquérir l'emplacement de la table des appels système.

Par exemple

#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");

C'est ça. Aucune adresse, cela fonctionne très bien avec tous les noyaux que j'ai testés.

De la même manière, vous pouvez utiliser une fonction de noyau non exportée à partir de votre module:

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");

Prendre plaisir!

10