2012-05-04 1 views
6

J'ai un problème avec les détours. Les détours, comme vous le savez tous, ne peuvent se déplacer que dans 5 octets d'espace (c'est-à-dire un appel 'jmp' et une adresse à 4 octets). Pour cette raison il est impossible d'avoir la fonction 'hook' dans une classe (une méthode), vous ne pouvez pas fournir le pointeur 'this' car il n'y a tout simplement pas assez d'espace (here's le problème est expliqué plus en détail). J'ai donc réfléchi toute la journée à une solution, et maintenant je veux avoir vos idées sur le sujet afin de ne pas commencer un projet de 3 à 5 jours sans savoir si ce serait possible ou non. J'avais initialement 3 objectifs, je voulais que les fonctions 'hook' soient des méthodes de classe, je voulais que toute l'approche soit orientée objet (pas de fonctions statiques ni d'objets globaux) et, le pire/le plus difficile, être complètement dynamique. C'est ma solution (en théorie); avec l'assemblage on peut modifier les fonctions au moment de l'exécution (un exemple parfait est n'importe quelle méthode de déviation). Donc, puisque je peux modifier les fonctions dynamiquement, ne devrais-je pas aussi être en mesure de les créer dynamiquement? Par exemple; J'alloue de la mémoire pour, disons ~ 30 octets (via malloc/new). Ne serait-il pas possible de simplement remplacer tous les octets par des nombres binaires correspondant à différents opérateurs d'assemblage (comme 0xE9 est 'jmp') et d'appeler directement l'adresse (puisqu'elle contiendrait une fonction)?Fonctions C++ et ENTIÈREMENT dynamiques

NOTE: Je connais à l'avance la valeur de retour, et tous les arguments de toutes les fonctions que je veux détourer, et puisque j'utilise GCC, la convention thiscall est pratiquement identique à celle de _cdecl.

Donc c'est ma pensée/bientôt-à-être mise en œuvre; Je crée une classe 'Function'. Ce constructeur prend une quantité variée d'arguments (sauf le premier argument, qui décrit la valeur de retour de la fonction cible). Chaque argument est une description des arguments que le hook recevra (la taille, et si c'est un pointeur ou non). Alors disons que je veux créer une classe de fonction pour un int * RandomClass::IntCheckNum(short arg1);. Ensuite, je voudrais juste faire comme ceci: Function func(Type(4, true), Type(4, true), Type(2, false));. Où 'Type' est défini comme Type(uint size, bool pointer). Ensuite, grâce à l'assemblage, je pourrais créer dynamiquement la fonction (note: tout cela utiliserait la convention d'appel _cdecl) puisque je peux calculer le nombre d'arguments et la taille totale.

EDIT: Avec l'exemple, Type(4, true) est la valeur de retour (int *), le scond Type(4, true) est le RandomClass pointeur 'this' et Type(2, false) décrit le premier argument (court arg1). Avec cette implémentation, je pourrais facilement avoir des méthodes de classe comme callbacks, mais cela nécessiterait une quantité importante de code d'assemblage (que je ne suis même pas particulièrement connu). En fin de compte, la seule chose non-dynamique serait les méthodes de ma classe de callback (qui nécessiteraient aussi des callbacks avant et après).

Alors je voulais savoir; Est-ce possible? Combien de travail cela exigerait-il, et suis-je bien au-dessus de ma tête ici?

EDIT: Je suis désolé si j'ai présenté tout un peu flou, mais s'il y a quelque chose que vous voulez plus expliqué, demandez!

EDIT2: Je voudrais aussi savoir, si je peux trouver les valeurs hexadécimales pour tous les opérateurs d'assemblage quelque part? Une liste aiderait une tonne! Et/ou s'il est possible de «sauver» en quelque sorte l'asm (""); code à une adresse mémoire (dont je doute fortement).

+0

Pourquoi utiliser des détours? Vous ne pouvez pas utiliser une solution C++ pure comme 'std :: function' ou est-ce que je manque quelque chose? –

+0

Pas comme si je pouvais vous aider à clarifier les choses. Vous voulez une fonction réinscriptible dans une classe? (Je veux dire que vous pouvez les changer à l'exécution) Si tel est le cas, je pense que cela pourrait ouvrir des opportunités géantes pour la programmation AI en C++. +1 – akaltar

+0

@akaltar Ceci est connu comme [programmation génétique] (http://en.wikipedia.org/wiki/Genetic_programming) et n'a pas vraiment besoin de fonctions réinscriptibles. –

Répondre

4

Ce que vous décrivez est généralement appelé "thunking", et est assez couramment mis en œuvre. Historiquement, le but le plus commun a été de mettre en correspondance le code 16 bits et le code 32 bits (en générant automatiquement une nouvelle fonction 32 bits qui appelle une version 16 bits existante ou vice versa). Je crois que certains compilateurs C++ génèrent des fonctions similaires pour ajuster les pointeurs de classe de base aux pointeurs de sous-classes dans l'héritage multiple, aussi.

Cela semble certainement une solution viable à votre problème, et je ne prévois pas d'énormes problèmes. Assurez-vous juste d'allouer la mémoire avec tous les drapeaux nécessaires dans votre système d'exploitation pour vous assurer que la mémoire est exécutable (la plupart des systèmes d'exploitation modernes donnent de la mémoire non-exécutable par défaut).

Vous pouvez trouver ce lien utile, surtout si vous travaillez dans Win32: http://www.codeproject.com/Articles/16785/Thunking-in-Win32-Simplifying-Callbacks-to-Non-sta

En ce qui concerne la recherche des valeurs hexagonales des opérations d'assemblage, la meilleure référence que je connaisse est l'annexe au manuel de l'assembleur NASM (et je ne dis pas ça parce que j'ai aidé à l'écrire). Il y a une copie disponible ici: http://www.posix.nl/linuxassembly/nasmdochtml/nasmdoca.html

+0

Wow grands liens! C'était vraiment intéressant de lire sur le processus thunking (dommage que c'était Win32 cependant). Maintenant excusez-moi si j'ai l'air bête, mais comme mentionné plus tôt, je ne suis pas spécialement expérimenté avec l'assemblage (je ne connais qu'un peu la syntaxe AT & T) donc j'ai dû poser des questions sur l'assembleur NASM auquel vous avez référé. J'ai 2 questions; tous les opérateurs ASM utilisent-ils seulement 1 octet? Et deuxièmement, étant donné qu'il existe de nombreuses valeurs différentes pour chaque opérateur, qu'est-ce qui m'intéresse? Je suppose que cela dépend de la taille de mes variables, mais pour 'push' il y a 13 valeurs différentes, comment puis-je savoir lequel je veux? –

+1

Il s'agit de variantes différentes pour différents types d'instructions push (types de registre, valeurs immédiates, références de mémoire indirecte). Le haut du guide contient une description de tous les différents modes, utilisez-le pour déterminer celui que vous voulez, puis regardez dans la liste pour trouver le format d'instruction dont vous avez besoin. Dites que vous voulez pousser EBX: c'est un reg32, donc vous voulez la deuxième variante, qui est "o32 50 + r". o32 est un préfixe de taille d'opérande, qui est ignoré si vous exécutez du code 32 bits; 50 + r est 50 hex plus le code pour le registre (3, ils sont listés en haut), donc 53h est votre code. – Jules

+1

En réponse à votre première question, il n'y a pas d'instructions de plus d'un octet, et certaines instructions varient en taille selon le contexte (voir l'exemple PUSH ci-dessus: le préfixe 'o32' ne génère pas de code en 32 bits mode, cependant si vous produisez du code 16 bits, ce serait un octet supplémentaire de 66h qui apparaît au début de l'instruction). Cependant, toutes les instructions les plus courantes sont à un octet. – Jules