2010-09-15 7 views
1

Je voudrais copier et appeler une fonction, mais le code ci-dessous segfaults lors de l'appel du tampon. Qu'est-ce que je dois changer? (Linux, x86)Fonction de copie et d'appel

#include <string.h> 
#include <malloc.h> 
#include <stdio.h> 

int foo() { return 12; } 
void foo_end() {} 

int main() { 
    int s = (unsigned long long) foo_end - (unsigned long long) foo; 
    int (*f)() = (int (*)()) malloc (s); 
    memcpy ((void*) f, (const void*) foo, s); 
    printf ("%d %d\n", f(), foo()); 
} 

EDIT: Solution de travail:

#include <string.h> 
#include <malloc.h> 
#include <stdio.h> 
#include <sys/mman.h> 
#include <unistd.h> 

int foo() { return 12; } 
void foo_end() {} 

int main() { 
    int s = (unsigned long long) foo_end - (unsigned long long) foo; 
    int (*f)() = (int (*)()) malloc (s); 
    memcpy ((void*) f, (const void*) foo, s); 
    long ps = sysconf (_SC_PAGESIZE); 
    void *fp = (void*) ((unsigned long long) f & ~((unsigned long long) (ps-1))); 
    if (mprotect ((void*) fp, ps, PROT_READ | PROT_WRITE | PROT_EXEC)) return -1; 
    printf ("%d %d\n", f(), foo()); 
} 
+0

Probablement une bonne idée de supprimer 'PROT_WRITE', pour éviter une modification accidentelle après avoir copié le code.En outre, il n'est pas nécessaire d'arrondir l'adresse à une limite de page, et vous devez indiquer la taille de la fonction (au cas où elle dépasse une page); l'appel doit être 'mprotect ((void *) f, s, PROT_READ | PROT_EXEC)' –

+0

'mprotect' échoue sur mon système lorsque l'alignement de l'adresse n'est pas effectué. – Thomas

Répondre

3

Vous utilisez potentiellement un système d'exploitation qui n'accorde pas d'autorisation d'exécution aux segments de données.

Certains environnements protégeront les pages de données contre l'exécution, afin d'éviter différents types de problèmes de sécurité (ou d'exploits pour eux). Envisagez d'appeler mprotect() pour activer l'exécution de cette page et signaler ce qui se passe.

+0

Fonctionne maintenant, merci! – Thomas

9

Whoa, ce code a tant de problèmes.

  1. Vous ne pouvez pas savoir que les fonctions sont disposées de façon séquentielle dans la mémoire, sans rembourrage entre les
  2. Vous ne pouvez pas savoir que les pointeurs à deux fonctions sont subtractable
  3. Vous ne pouvez pas savoir que la mémoire retournée par malloc() peut être appelée dans

En bref, ne faites pas cela.

Mise à jour:

Dans Linux, je pense que vous pouvez utiliser mprotect() pour définir les autorisations sur un bloc de mémoire. Je pensais que cela avait besoin de root, mais apparemment pas (tant que vous êtes dans la mémoire de votre propre processus).

+0

Je suis d'accord, ne faites pas cela :) – MarkR

+0

C'est juste un premier test, actuellement, mais plus tard, je vais devoir remplir un tampon avec mes propres instructions d'assemblage. g ++ définit 's' à 16, donc la disposition des fonctions est correcte. Comment puis-je définir les autorisations d'appel en mémoire allouées par malloc? – Thomas

+0

Ils ne sont pas connus * en général *, mais avec quelques informations supplémentaires, vous pouvez les connaître tous les trois. Le premier est connaissable si vous avez un certain contrôle sur l'éditeur de liens. La seconde est connaissable si vous connaissez le compilateur. Le troisième est connaissable si vous connaissez le système d'exploitation et ses paramètres. En outre, je ne vois aucune raison de penser que root serait le seul compte qui peut définir l'autorisation d'exécution sur la propre mémoire d'un processus. –

0

Ceci est un problème commun parmi les systèmes embarqués. Cette technique est souvent utilisée pour copier de la mémoire en lecture seule dans la mémoire à accès aléatoire (écriture et lecture). Il n'y a pas de solution élégante ni standard utilisant le standard C ou C++.

Une solution plus simple consiste à utiliser le lieur pour définir certains nouveaux segments non standard. Utilisez #pragma non standard pour indiquer au compilateur de placer la fonction dans un nouveau segment. Utilisez une directive de compilation non standard pour accéder à l'adresse de début et à l'adresse de fin de ce segment. Cela vous permettra d'obtenir la taille de la fonction.

Une méthode plus sûre pour la destination consiste à créer un autre segment avec des attributs d'exécution et d'écriture. Copiez les données du segment de fonction dans ce segment exécutable. Configurez un pointeur de fonction pour pointer vers le début de ce segment. Exécutez la fonction via le pointeur.

Une autre solution consiste à effectuer cela en langage assembleur. Souvent, les assembleurs vous donnent plus de liberté (pour tirer sur votre pied) pour manipuler la mémoire comme ceci dans un niveau inférieur.

Vérifiez également le chargeur de votre système d'exploitation, vos attributs de mémoire et vos schémas de protection. Certains systèmes d'exploitation peuvent restreindre ce type de comportement au privilège du noyau ou supérieur.