55 votes

Comment appeler le code machine stocké dans un tableau de caractères ?

J'essaie d'appeler un code natif en langage machine. Voici ce que j'ai jusqu'à présent (il y a une erreur de bus) :

char prog[] = {'\xc3'}; // x86 ret instruction

int main()
{
    typedef double (*dfunc)();

    dfunc d = (dfunc)(&prog[0]);
    (*d)();
    return 0;
}

Il appelle correctement la fonction et arrive à l'instruction ret. Mais lorsqu'il essaie d'exécuter l'instruction ret, il obtient une erreur SIGBUS. Est-ce parce que j'exécute du code sur une page qui n'est pas autorisée à être exécutée ou quelque chose comme ça ?

Alors qu'est-ce que je fais de mal ici ?

51voto

Horia Coman Points 6847

Un premier problème pourrait être que l'emplacement où sont stockées les données du prog n'est pas exécutable.

Sous Linux au moins, le binaire résultant placera le contenu des variables globales dans le répertoire "segment "données o ici qui n'est pas exécutable dans les cas les plus normaux .

Le deuxième problème pourrait être que le code que vous invoquez n'est pas valide d'une certaine manière. Il existe une certaine procédure pour appeler une méthode en C, appelée la méthode convention d'appel (vous utilisez peut-être celui de "cdecl", par exemple). Il se peut que la fonction appelée ne se contente pas de "ret". Elle doit également nettoyer la pile, etc., sinon le programme se comportera de manière inattendue. Cela pourrait s'avérer un problème une fois que vous aurez surmonté le premier problème.

49voto

Rudi Points 8756

Vous devez appeler memprotect afin de rendre exécutable la page où se trouve prog. Le code suivant fait cet appel, et peut exécuter le texte dans prog.

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

char prog[] = {
   0x55,             // push   %rbp
   0x48, 0x89, 0xe5, // mov    %rsp,%rbp
   0xf2, 0x0f, 0x10, 0x05, 0x00, 0x00, 0x00,
       //movsd  0x0(%rip),%xmm0        # c <x+0xc>
   0x00,
   0x5d,             // pop    %rbp
   0xc3,             // retq
};

int main()
{
    long pagesize = sysconf(_SC_PAGE_SIZE);
    long page_no = (long)prog/pagesize;
    int res = mprotect((void*)(page_no*pagesize), (long)page_no+sizeof(prog), PROT_EXEC|PROT_READ|PROT_WRITE);
    if(res)
    {
        fprintf(stderr, "mprotect error:%d\n", res);
        return 1;
    }
    typedef double (*dfunc)(void);

    dfunc d = (dfunc)(&prog[0]);
    double x = (*d)();
    printf("x=%f\n", x);
    fflush(stdout);
    return 0;
}

30voto

Ismael Points 647

Comme tout le monde l'a déjà dit, vous devez vous assurer prog[] est exécutable, cependant la manière correcte de le faire, à moins que vous n'écriviez un compilateur JIT, est de mettre le symbole dans une zone exécutable, soit en utilisant un linker script, soit en spécifiant la section dans le code C si le compilateur le permet , par exemple :

const char prog[] __attribute__((section(".text"))) = {...}

28voto

Graham Points 461

Pratiquement tous les compilateurs C vous permettent de le faire en intégrant du langage assembleur régulier dans votre code. Il s'agit bien sûr d'une extension non standard du C, mais les auteurs de compilateurs reconnaissent qu'elle est souvent nécessaire. Comme il s'agit d'une extension non standard, vous devrez lire le manuel de votre compilateur et vérifier comment le faire, mais l'option Extension "asm" de GCC est une approche assez standard.

 void DoCheck(uint32_t dwSomeValue)
 {
    uint32_t dwRes;

    // Assumes dwSomeValue is not zero.
    asm ("bsfl %1,%0"
      : "=r" (dwRes)
      : "r" (dwSomeValue)
      : "cc");

    assert(dwRes > 3);
 }

Comme il est facile de détruire la pile en assembleur, les compilateurs vous permettent souvent d'identifier les registres que vous utiliserez dans le cadre de votre assembleur. Le compilateur peut alors s'assurer que le reste de cette fonction évite ces registres.

Si vous écrivez vous-même le code de l'assembleur, il n'y a aucune bonne raison de configurer cet assembleur comme un tableau d'octets. Ce n'est pas seulement une odeur de code - je dirais que c'est une véritable erreur qui ne peut se produire que par ignorance de l'extension "asm" qui est la bonne façon d'intégrer l'assembleur dans votre C.

9voto

Malcolm McLean Points 5437

En fait, ces mesures ont été interdites parce qu'elles constituaient une invitation ouverte aux auteurs de virus. Mais vous pouvez allouer un tampon et le configurer avec un code machine natif en C pur - ce n'est pas un problème. Le problème est de l'appeler. Bien que vous puissiez essayer de configurer un pointeur de fonction avec l'adresse du tampon et de l'appeler, il est très peu probable que cela fonctionne, et il est très probable que cela se casse avec la prochaine version du compilateur si vous parvenez à le convaincre de faire ce que vous voulez. Le mieux est donc de recourir simplement à un peu d'assemblage en ligne, pour configurer le retour et sauter au code généré automatiquement. Mais si le système s'y oppose, vous devrez trouver des méthodes pour contourner la protection, comme l'a décrit Rudi dans sa réponse (mais très spécifique à un système particulier).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X