59 votes

Comment écrire du code à modification automatique dans un assemblage x86

Je suis à la recherche, à l'écriture d'un compilateur JIT pour un passe-temps de la machine virtuelle j'ai travaillé récemment. Je sais un peu de l'assemblée, (je suis principalement un programmeur C. Je peut lire la plupart assemblée de référence pour les opcodes je ne comprends pas, et d'écrire des programmes simples.) mais je vais avoir du mal à comprendre le peu d'exemples de self-modifying code que j'ai trouvé en ligne.

C'est un exemple: http://asm.sourceforge.net/articles/smc.html

Le programme d'exemple fourni est d'environ quatre différentes modifications lors de l'exécution, qui sont clairement expliquées. Le noyau Linux interruptions sont utilisées plusieurs fois, et ne sont pas expliqués ou détaillée. (L'auteur a déplacé des données dans plusieurs registres, avant d'appeler les interruptions. Je suppose qu'il a été le passage d'arguments, mais ces arguments ne sont pas expliqués à tous, en laissant le lecteur à deviner.)

Ce que je cherche est le plus simple, le plus simple exemple dans le code de l'auto-modifier le programme. Quelque chose que je peux les regarder et de les utiliser pour comprendre comment self-modifying code x86 assemblée doit être écrit, et comment il fonctionne. Existe-il des ressources que vous pouvez m'indiquer, ou des exemples, vous pouvez donner ce que démontrent cela?

Je suis l'aide de MSNA que mon assembleur.

EDIT: je suis également en cours d'exécution de ce code sur Linux.

62voto

dwelch Points 27195

wow, il s'est avéré être beaucoup plus douloureux que ce que j'attendais. 100% de la douleur était linux protéger le programme de remplacement et/ou de l'exécution des données.

Deux solutions indiquées ci-dessous. Et beaucoup de googler a été impliqué de manière un peu simple de mettre quelques octets d'instruction et de les exécuter était le mien, le mprotect et en l'alignant sur la taille de la page a été élaboré à partir des recherches effectuées sur google, des choses que j'avais à apprendre de cet exemple.

L'auto-modifiant le code est simple, si vous prenez le programme ou au moins tout les deux fonctions simples, de compiler et puis démonter, vous obtiendrez les opérateurs de ces instructions. ou de l'utilisation des msna de compiler des blocs de l'assembleur, etc. À partir de cela j'ai décidé de l'opcode pour charger immédiatement dans eax, puis retourner.

Idéalement, il vous suffit de mettre ces octets de mémoire ram et de l'exécution de cette ram. Pour obtenir linux que vous avez à modifier la protection, ce qui signifie que vous devez envoyer un pointeur qui est aligné sur un mmap page. Afin d'allouer plus que vous avez besoin, trouver l'adresse alignée à l'intérieur de cette allocation qui est sur une frontière de page et mprotect à partir de cette adresse et d'utiliser cette mémoire pour mettre votre opcodes, et puis l'exécuter.

le deuxième exemple, on prend une fonction existante compilé dans le programme, à cause du mécanisme de protection, vous ne pouvez pas simplement viser et modifier des octets, vous devez ôter la protection de l'écrit. Donc, vous devez sauvegarder à l'état de la limite de la page d'appel mprotect avec cette adresse et assez d'octets pour couvrir le code doit être modifié. Ensuite, vous pouvez modifier les octets/opcodes pour cette fonction de la manière que vous voulez (tant que vous n'avez pas déborder sur toute fonction que vous souhaitez continuer à utiliser) et de l'exécuter. Dans ce cas, vous pouvez voir qu' fun() de travaux, puis-je le changer tout simplement de retourner une valeur, de l'appeler à nouveau, et maintenant qu'il a été modifié.

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

unsigned char *testfun;

unsigned int fun ( unsigned int a )
{
    return(a+13);
}

unsigned int fun2 ( void )
{
    return(13);
}

int main ( void )
{
    unsigned int ra;
    unsigned int pagesize;
    unsigned char *ptr;
    unsigned int offset;

    pagesize=getpagesize();
    testfun=malloc(1023+pagesize+1);
    if(testfun==NULL) return(1);
    //need to align the address on a page boundary
    printf("%p\n",testfun);
    testfun = (unsigned char *)(((long)testfun + pagesize-1) & ~(pagesize-1));
    printf("%p\n",testfun);

    if(mprotect(testfun, 1024, PROT_READ|PROT_EXEC|PROT_WRITE))
    {
        printf("mprotect failed\n");
        return(1);
    }

    //400687: b8 0d 00 00 00          mov    $0xd,%eax
    //40068d: c3                      retq

    testfun[ 0]=0xb8;
    testfun[ 1]=0x0d;
    testfun[ 2]=0x00;
    testfun[ 3]=0x00;
    testfun[ 4]=0x00;
    testfun[ 5]=0xc3;

    ra=((unsigned int (*)())testfun)();
    printf("0x%02X\n",ra);


    testfun[ 0]=0xb8;
    testfun[ 1]=0x20;
    testfun[ 2]=0x00;
    testfun[ 3]=0x00;
    testfun[ 4]=0x00;
    testfun[ 5]=0xc3;

    ra=((unsigned int (*)())testfun)();
    printf("0x%02X\n",ra);


    printf("%p\n",fun);
    offset=(unsigned int)(((long)fun)&(pagesize-1));
    ptr=(unsigned char *)((long)fun&(~(pagesize-1)));


    printf("%p 0x%X\n",ptr,offset);

    if(mprotect(ptr, pagesize, PROT_READ|PROT_EXEC|PROT_WRITE))
    {
        printf("mprotect failed\n");
        return(1);
    }

    //for(ra=0;ra&lt;20;ra++) printf("0x%02X,",ptr[offset+ra]); printf("\n");

    ra=4;
    ra=fun(ra);
    printf("0x%02X\n",ra);

    ptr[offset+0]=0xb8;
    ptr[offset+1]=0x22;
    ptr[offset+2]=0x00;
    ptr[offset+3]=0x00;
    ptr[offset+4]=0x00;
    ptr[offset+5]=0xc3;

    ra=4;
    ra=fun(ra);
    printf("0x%02X\n",ra);

    return(0);
}

12voto

Josh Haberman Points 2289

Puisque vous êtes à l'écriture d'un compilateur JIT, vous ne voulez probablement pas self-modifying code, vous voulez générer du code exécutable au moment de l'exécution. Ce sont deux choses différentes. Self-modifying code est le code qui est modifié après qu'il a déjà commencé à courir. Self-modifying code a une grande perte de performance sur les processeurs modernes, et par conséquent ne serait pas souhaitable pour un compilateur JIT.

La génération du code exécutable au moment de l'exécution devrait être une simple question de mmap()ing de la mémoire avec PROT_EXEC et PROT_WRITE autorisations. Vous pouvez aussi appeler mprotect() sur la mémoire que vous avez alloué de vous-même, comme dwelch fait ci-dessus.

4voto

Felipe Points 302

Exemple un peu plus simple basé sur l'exemple ci-dessus. Merci à Dwelch a beaucoup aidé.

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

char buffer [0x2000];
void* bufferp;

char* hola_mundo = "Hola mundo!";
void (*_printf)(const char*,...);

void hola()
{ 
    _printf(hola_mundo);
}

int main ( void )
{
    //Compute the start of the page
    bufferp = (void*)( ((unsigned long)buffer+0x1000) & 0xfffff000 );
    if(mprotect(bufferp, 1024, PROT_READ|PROT_EXEC|PROT_WRITE))
    {
        printf("mprotect failed\n");
        return(1);
    }
    //The printf function has to be called by an exact address
    _printf = printf;

    //Copy the function hola into buffer
    memcpy(bufferp,(void*)hola,60 //Arbitrary size);


    ((void (*)())bufferp)();  

    return(0);
}
 

3voto

Kevin A. Naudé Points 2384

Vous pouvez également regarder les projets comme GNU foudre. Vous donner le code pour simplifier l'RISC-type de la machine, et il génère correcte de la machine de façon dynamique.

Un problème très réel, vous devez penser est l'interfaçage avec les bibliothèques étrangères. Vous aurez probablement besoin de l'appui d'au moins un système de niveau des appels/des opérations de votre VM pour être utile. Kitsune conseils est un bon début pour vous de penser à niveau du système d'appels. Vous devriez probablement utiliser mprotect pour s'assurer que la mémoire que vous avez modifié devient juridiquement exécutable. (@KitsuneYMG)

Certains FFI permettant les appels à la dynamique des bibliothèques écrites en C devrait être suffisant pour cacher un lot de l'OS des détails spécifiques. Toutes ces questions peuvent influer sur votre conception tout à fait un peu, donc il est préférable de commencer à penser à leur début.

0voto

BlackBear Points 10069

Je n'ai jamais écrit self-modifying code, bien que j'ai une compréhension de base sur la façon dont il fonctionne. Fondamentalement, vous écrivez sur la mémoire, les instructions que vous souhaitez exécuter, puis sauter il. Le processeur d'interpréter ces octets que vous avez écrit un instructions et (tente) pour les exécuter. Par exemple, les virus et les anti-copie des programmes peuvent utiliser cette technique.
Concernant les appels système, vous avez eu raison, les arguments sont passés par les registres. Pour une référence de linux système d'appels et de leur argument, il suffit de cocher ici.

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