Noyau Linux: exemple d’accrochage d’appel système

J’essaie d’écrire un code de test simple en guise de démonstration du raccordement de la table d’appels système.

“sys_call_table” n’est plus exporté en 2.6, alors je saisis simplement l’adresse du fichier System.map, et je vois que c’est correct (en parcourant la mémoire à l’adresse que j’ai trouvée, je peux voir les pointeurs vers le appels système).

Cependant, lorsque j’essaie de modifier cette table, le kernel donne un “Oops” avec “impossible de gérer la demande de pagination du kernel à 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 ça ne fait pas de différence

Voici mon code:

#include  #include  #include  #include  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; } 

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 kernel a été modifié à un moment donné pour que la table d’appels système soit en lecture seule.

cypherpunk:

Même s’il est tard mais que la solution peut intéresser d’autres personnes: Dans le fichier entry.S, vous trouverez:

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

sys_call_table -> ReadOnly Vous devez comstackr le nouveau kernel si vous voulez “pirater” avec sys_call_table …

Le lien a également un exemple de modification de la mémoire pour être accessible 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’access aux pages de mémoire. J’ai implémenté deux fonctions qui le font pour mon code de niveau supérieur:

 #include  #ifdef KERN_2_6_24 #include  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  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 d’origine qui fonctionne pour moi.

 #include  #include  #include  #include  #include  #include  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; } 

Merci Stephen, vos recherches ici m’ont été utile. J’ai eu quelques problèmes, cependant, comme j’essayais ceci sur un kernel 2.6.32, et obtenant WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted) suivi d’un kernel OOPS sur le fait de ne pas pouvoir écrire sur l’adresse mémoire.

Le commentaire ci-dessus 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() , 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 kernel est défini sur lecture seule

Je l’ai découvert après avoir débogué pourquoi je n’arrivais toujours pas à “gérer la demande de pagination du kernel” lorsque je tentais de modifier l’adresse des fonctions du kernel. J’ai finalement réussi à résoudre ce problème en trouvant moi-même l’entrée de la table de pages et en la définissant manuellement en écriture. Heureusement, la fonction lookup_address() est exscope 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, cela posera 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 aidera quiconque trébuche sur cette question.

Notez que ce qui suit fonctionnera également au lieu d’utiliser change_page_attr et ne peut être déprécié:

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

Si vous traitez avec le kernel 3.4 et les versions ultérieures (cela peut aussi fonctionner avec les kernelx antérieurs, je ne l’ai pas testé), je vous recommande une méthode plus intelligente pour acquérir l’emplacement de la table des appels système.

Par exemple

 #include  #include  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 tout. Aucune adresse, cela fonctionne bien avec chaque kernel que j’ai testé.

De la même manière, vous pouvez utiliser une fonction de kernel non exscope depuis 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!