Exécution du code machine en mémoire

j'essaie de comprendre comment exécuter du code machine stocké en mémoire.

j'ai le code suivant:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    FILE* f = fopen(argv[1], "rb");

    fseek(f, 0, SEEK_END);
    unsigned int len = ftell(f);
    fseek(f, 0, SEEK_SET);

    char* bin = (char*)malloc(len);
    fread(bin, 1, len, f);

    fclose(f);

    return ((int (*)(int, char *)) bin)(argc-1, argv[1]);
}

le code ci-dessus se compile bien dans GCC, mais quand j'essaie d'exécuter le programme à partir de la ligne de commande comme ceci:

./my_prog /bin/echo hello

le programme segfaults. J'ai compris que le problème est sur la dernière ligne, en commentant les arrêts de l'erreur de segmentation.

Je ne pense pas que je le fasse tout à fait correctement, car je suis encore à obtenir ma tête autour des pointeurs de fonction.

le problème est-il dû à une coulée défectueuse ou autre chose?

24
demandé sur anonymous coward 2010-01-07 14:35:45
la source

9 ответов

il me semble que vous chargez une image D'elfe et ensuite essayer de sauter directement dans l'en-tête D'elfe? http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

si vous essayez d'exécuter un autre binaire, pourquoi n'utilisez-vous pas les fonctions de création de processus pour la plate-forme que vous utilisez?

11
répondu ta.speot.is 2010-01-07 14:38:36
la source

Vous avez besoin d'une page avec les permissions d'exécution en écriture. Voir mmap(2) et mprotect(2) si vous êtes sous unix. Tu ne devrais pas le faire avec malloc.

aussi, lisez ce que les autres ont dit, vous ne pouvez exécuter du code machine brut qu'en utilisant votre chargeur. Si vous essayez de lancer un en-tête ELF, il sera probablement segfault tout de même.

en ce qui concerne le contenu des réponses et des downmods:

1 - OP a dit qu'il essayait d'exécuter du code machine, donc j'ai répondu sur cela plutôt que d'exécuter un fichier exécutable.

2-voir pourquoi vous ne mélangez pas les fonctions malloc et mman:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>

int main()
{
    char *a=malloc(10);
    char *b=malloc(10);
    char *c=malloc(10);
    memset (a,'a',4095);
    memset (b,'b',4095);
    memset (c,'c',4095);
    puts (a);
    memset (c,0xc3,10); /* return */

    /* c is not alligned to page boundary so this is NOOP.
     Many implementations include a header to malloc'ed data so it's always NOOP. */
    mprotect(c,10,PROT_READ|PROT_EXEC);
    b[0]='H'; /* oops it is still writeable. If you provided an alligned
    address it would segfault */
    char *d=mmap(0,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);
    memset (d,0xc3,4096);
    ((void(*)(void))d)();
    ((void(*)(void))c)(); /* oops it isn't executable */
    return 0;
}

il affiche exactement ce comportement sur Linux x86_64 d'autres comportements laids ne manqueront pas d'apparaître sur d'autres implémentations.

27
répondu jbcreix 2010-01-07 17:25:07
la source

l'utilisation de malloc fonctionne très bien.

OK c'est ma réponse finale, veuillez noter que j'ai utilisé le code de l'affiche originale. Je charge à partir du disque, la version compilée de ce code à un tas de zone attribuée "bin", comme l'a fait le code original (le nom est fixe et n'utilise pas argv, et la valeur 0x674 est de;

objdump -F -D foo|grep -i hoho
08048674 <hohoho> (File Offset: 0x674):

cela peut être consulté au moment de l'exécution avec la BFD (binaire File Descriptor library) ou quelque chose d'autre, vous pouvez appeler d'autres binaires (pas seulement vous-même) tant qu'ils sont liés statiquement le même ensemble de lib.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

unsigned char *charp;
unsigned char *bin;

void hohoho()
{
   printf("merry mas\n");
   fflush(stdout);
}

int main(int argc, char **argv)
{
   int what;

   charp = malloc(10101);
   memset(charp, 0xc3, 10101);
   mprotect(charp, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   __asm__("leal charp, %eax");
   __asm__("call (%eax)" );

   printf("am I alive?\n");

   char *more = strdup("more heap operations");
   printf("%s\n", more);

   FILE* f = fopen("foo", "rb");

   fseek(f, 0, SEEK_END);
   unsigned int len = ftell(f);
   fseek(f, 0, SEEK_SET);

   bin = (char*)malloc(len);
   printf("read in %d\n", fread(bin, 1, len, f));
   printf("%p\n", bin);

   fclose(f);
   mprotect(&bin, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   asm volatile ("movl %0, %%eax"::"g"(bin));
   __asm__("addl x674, %eax");
   __asm__("call %eax" );
   fflush(stdout);

   return 0;
}

en cours d'exécution...

co tmp # ./foo
am I alive?
more heap operations
read in 30180
0x804d910
merry mas

Vous pouvez utiliser UPX gérer la charge / modifier / exec d'un fichier.

P. S. désolé pour le précédent lien ne fonctionne pas :|

12
répondu RandomNickName42 2010-01-07 18:18:00
la source

un fichier exécutable typique a:

  • en-tête
  • code d'entrée qui est appelée avant main(int, char **)

Le premier signifie que vous ne pouvez pas attendre de l'octet 0 du fichier exécutable; intead, les informations contenues dans l'en-tête décrit comment charger le reste du fichier en mémoire et par où commencer l'exécution.

la seconde signifie que lorsque vous avez trouvé le point d'entrée, vous ne pouvez pas vous attendre à le traiter comme une fonction C prenant les arguments (int, char **). Il peut, peut-être, être utilisable comme une fonction ne prenant aucun paramétreur (et donc n'exigeant rien à pousser avant de l'appeler). Mais vous devez peupler l'environnement qui sera à son tour utilisé par le code d'entrée pour construire les chaînes de ligne de commande passées à main.

faire cela à la main sous un OS donné irait dans une certaine profondeur qui est au-delà de moi; mais je suis sûr qu'il y a une bien plus belle façon de faire ce que vous essayez de faire. Vous tentez d'exécuter une fichier externe comme une opération on-off, ou charger un binaire externe et traiter ses fonctions dans le cadre de votre programme? Les deux sont pris en charge par les bibliothèques C D'Unix.

3
répondu Edmund 2010-01-07 14:45:40
la source

ce que vous essayez de faire ressemble à ce que font les interprètes. Sauf qu'un interpréteur lit un programme écrit dans un langage interprété comme Python, compile ce code à la volée, met du code exécutable en mémoire et l'exécute ensuite.

vous voudrez peut-être en savoir plus sur la compilation just-in-time aussi:

Juste à temps de compilation

Java HotSpot JIT runtime

Il y a des bibliothèques disponibles pour Génération de code JIT comme le GNU lightning et libJIT si vous êtes intéressé. Il faudrait faire bien plus que lire du fichier et essayer d'exécuter du code. Un exemple de scénario d'utilisation sera:

  1. lire un programme écrit dans un langage de script (peut-être vôtre.)
  2. Analysez et compilez la source dans un intermédiaire langue comprise par l'équipe de la bibliothèque.
  3. utilisez la bibliothèque JIT pour générer code par cet intermédiaire représentation, pour le CPU de votre plateforme cible.
  4. exécute le code généré par JIT.

et pour exécuter le code, vous devez utiliser des techniques telles que l'utilisation de mmap() pour mapper le code exécutable dans l'espace d'adresse du processus, marquer cette page exécutable et sauter à ce morceau de mémoire. C'est plus compliqué que ça, mais c'est un bon début pour comprendre ce qui se passe sous tous ces interprètes de scripts. des langages tels que Python, Ruby, etc.

version en ligne de l'ouvrage "Linkers et les Chargeurs " vous donnera plus d'informations sur les formats de fichiers objet, ce qui se passe dans les coulisses lorsque vous exécutez un programme, les rôles des linkers et des loaders et ainsi de suite. C'est une très bonne lecture.

3
répondu Sudhanshu 2010-01-07 18:33:17
la source

il est plus probable que ce soit le code qui est sauté par l'appel via le pointeur de fonction qui cause le segfault plutôt que l'appel lui-même. Il n'y a aucun moyen à partir du code que vous avez posté pour déterminer que ce code chargé dans bin est valide. Votre meilleur pari est d'utiliser un débogueur, passer à l'assembleur vue, pause sur l'instruction de retour et étape dans l'appel de fonction pour déterminer que le code que vous attendez est effectivement en cours d'exécution, et qu'il est valide.

Notez aussi que pour exécuter tout le code devra être indépendant de la position et entièrement résolu.

de plus, si votre processeur/OS permet la prévention de l'exécution des données, alors la tentative est probablement vouée à l'échec. Il est au mieux mal avisé dans tous les cas, le code de chargement est ce que L'OS est destiné.

3
répondu Clifford 2010-01-08 12:19:53
la source

Utilisez le système d'exploitation pour charger et exécuter des programmes.

sur unix, le exec les appels peuvent faire cela.

Votre extrait de code dans la question pourrait être réécrite:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    return execv(argv[1],argv+2);
}
2
répondu Will 2010-01-07 16:04:30
la source

Vous pouvez dlopen() sur un fichier, recherchez le symbole "main" et de l'appeler avec 0, 1, 2 ou 3 arguments (tous de type char*) par une troupe de pointeur de fonction-retour-int-prendre-0,1,2,ou3-char*

1
répondu haavee 2010-01-07 14:50:26
la source

les fichiers exécutables contiennent bien plus que du code. En-tête, code, données, plus de données, ce truc est séparé et chargé dans différentes zones de mémoire par L'OS et ses bibliothèques. Vous ne pouvez pas charger un fichier de programme dans un seul morceau de mémoire et vous attendre à sauter à son premier octet.

si vous essayez d'exécuter votre propre code arbitraire, vous devez regarder dans les bibliothèques dynamiques parce que c'est exactement ce à quoi elles servent.

0
répondu Jimbo 2010-01-07 18:44:32
la source