2017-08-22 4 views
1

J'essaie de comprendre pourquoi ret(); œuvres dans le programme C:L'exécution de code machine dans un tableau en C. Est-ce l'exécution d'un entier?

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

unsigned char code[] = \ 
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" 
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"; 

main() 
{ 
    printf("Shellcode Length: %d\n", strlen(code)); 
    int (*ret)() = (int(*)())code; 
    ret(); 
} 

Pour que cela fonctionne, vous devez compiler ci-dessus sans protection de la pile, ce qui permet la pile d'être exécutable.

Ce que je me demande si est pourquoi appelant ret();, qui semble être un nombre entier affecté à la valeur (int(*)())code; œuvres.

Je suppose qu'il a quelque chose à voir avec des pointeurs de fonction étant vraiment entiers, mais je n'ai pas été en mesure de déballer mentalement le sens de l'expression int (*ret)() = (int(*)())code;

Merci pour votre aide

+0

[cdecl.org] (https://cdecl.org/?q=%28int%28*%29%28%29%29code%3B) peut aider à traduire C en anglais – smcd

Répondre

8

Ce que je me demande si est pourquoi appeler ret(), qui semble être un entier affecté à la valeur (int(*)())code œuvres

ret est pas un entier, il est un pointeur vers une fonction renvoyant un entier. La syntaxe « en ligne », à savoir int (*ret)() est plus difficile à « déchiffrer » que l'équivalent typedef, à savoir

typedef int (*func_returning_int)(); 
... 
func_returning_int ret = (func_returning_int)code; 

Note: Il va sans dire que ce comportement est indéfini, peu importe la façon dont vous allez sur la coulée des pointeurs.

+0

Ok, déballant lentement ceci ici Donc le côté gauche: 'int (* ret)()' déclare le pointeur de la fonction 'ret', pointant sur une fonction qui ne prend aucun argument ('() ') et la fonction pointée retourne un int . Je suis encore confus sur ce que fait le côté droit, en particulier: 'int (*)()' Il semble que ce soit le tableau 'unsigned char code []' dans le type 'int (*) () '? Est-ce que int (*)() 'est juste un autre pointeur de fonction, mais sans nom à côté de' * '? – gatorface

+0

@gatorface C'est exactement vrai, 'int (*)()' est le même que 'int (* ret)()', avec 'ret' omis. – dasblinkenlight

2

Les tours de la distribution le tableau, code dans un pointeur vers une fonction et l'affecte au pointeur de fonction ret. Depuis ret est un pointeur vers une fonction, lorsque vous appelez la fonction pointée sur ce qui se passe, c'est que le code machine dans le tableau code est exécuté.

C'est la théorie. Vous ne savez pas quel est le code machine réel qui a été stocké dans le tableau code.

Donc ret est un pointeur vers une fonction qui renvoie un int.

Les pointeurs de fonction ne sont pas int mais sont plutôt des pointeurs de fonction.

Fonction pointeur Définitions et déclarations

Pour une définition des variables telles que int (*ret)() vous devez analyser la définition en commençant par le nom de la variable, ret dans ce cas. Ce qui rend les définitions de variable de pointeur de fonction plus difficiles à déchiffrer est que les parenthèses sont utilisées pour définir l'ordre dans lequel l'expression est analysée et les parenthèses sont également utilisées comme symbole spécial pour indiquer que la variable est un pointeur de fonction. Une déclaration de fonction standard ressemble à int retFunc(); qui déclare une fonction qui renvoie int. Aucun argument n'est spécifié dans cet ancien style de déclaration de fonction donc nous ne savons pas s'il n'y a pas d'arguments ou plusieurs ou leurs types, s'il y a des arguments. Par ailleurs, une déclaration de variable entière standard ressemble à int intVar;. Pour créer une variable de pointeur de fonction, vous devez spécifier les mêmes informations pour le pointeur de fonction que pour une déclaration de fonction avec une information supplémentaire, l'indication qu'il s'agit d'une déclaration ou définition d'un pointeur de fonction et non la déclaration d'une fonction.

Voici un peu de code avec des variantes de syntaxe pour montrer les différences entre la déclaration d'une fonction et la déclaration d'un pointeur de fonction.

main() { 
    extern int retFunc(); // declaration of a function, returns int 
    extern int (*ret)();  // declaration of a function pointer, function returns int 
    extern int *retFunc2(); // declaration of a function, returns int pointer 
    extern int *(*retVar)(); // declaration of a function pointer, function returns a pointer to an int 
    extern int (*((*ret2)()))(); // declaration of a function pointer, function returns a function pointer which points to a function that returns an int 
} 

Ce qui différencie ces cinq est l'utilisation de l'indicateur de pointeur dans les deuxième, quatrième et cinquième déclarations avec parenthèses pour faire respecter la façon dont le compilateur est d'interpréter la déclaration.Les parenthèses de regroupement sont nécessaires en raison des règles de priorité d'opérateur qui obligent le compilateur à placer une priorité plus élevée sur la parenthèse indiquant une fonction, de sorte que nous remplaçons les règles de priorité en utilisant des parenthèses de regroupement.

Le cinquième est particulièrement intéressant, extern int (*((*ret2)()))(); qui peut être analysé en deux étapes. Le premier est la pièce ((*ret2)())) qui indique le symbole ret2 est un pointeur sur une fonction et la deuxième étape consiste à déterminer le type de retour de la fonction pointée, un pointeur vers une fonction qui renvoie un int en remplaçant le premier morceau par un arbitraire symbole x comme dans int (*x)();. Lors de la création d'une déclaration de pointeur de fonction, nous devons connaître les règles de priorité d'opérateur de C et comment celles-ci peuvent affecter la façon dont le compilateur interprète une déclaration ou une définition. Nous devons ajouter la parenthèse supplémentaire autour du *ret dans la déclaration du pointeur de fonction afin que le compilateur voit cela comme un pointeur vers une fonction qui renvoie un int plutôt qu'une fonction qui renvoie un pointeur vers un int.

Les règles que le compilateur C utilise nécessitent parfois que des parenthèses soient utilisées pour appliquer un ordre de traduction d'une expression afin que l'expression ait la signification désirée. Et ces règles ont parfois pour résultat que le même caractère ou symbole ait des significations différentes dans différents contextes. Donc int ret(); la parenthèse fait le symbole ret pour être une fonction et int (ret); la parenthèse est utilisée pour grouper des symboles, dans ce cas juste un seul symbole et int (*ret)(); parenthèse est utilisée pour les deux symboles de groupe et pour indiquer une fonction, dans ce cas que ret est un pointeur sur une fonction.

Dans votre exemple plutôt qu'une déclaration d'une variable ret en tant que pointeur de fonction, vous définissez la variable et lui attribuez une valeur dans l'instruction int (*ret)() = (int(*)())code;. Les règles d'analyse d'une définition sont similaires aux règles d'analyse d'une déclaration.

Dans votre exemple code est défini comme un tableau de unsigned char avec ce que je suppose est le code machine spécifié dans l'initialisation du tableau. En C, une variable de tableau peut être considérée comme une variable de pointeur constante de plusieurs façons. Donc, vous pouvez déréférencer un nom de tableau qui signifie que code[1] est le même que *(code + 1) mais comme c'est un pointeur constant, vous ne pouvez pas faire quelque chose comme code = code + 1; mais vous pouvez faire quelque chose comme unsigned char *code1 = (code + 1); qui est le même que unsigned char *code1 = &code[1];.

Ainsi, dans la déclaration que vous int (*ret)() = (int (*)())code; la coulée du pointeur constant code qui pointe vers un unsigned char à un pointeur de fonction à une fonction qui renvoie un int. Tant qu'il y a un moyen de passer du type sur le côté droit de l'opérateur d'assignation au type sur le côté gauche de l'opérateur d'affectation, le compilateur C est heureux d'obliger les fantasmes que vous voulez créer.

Cependant, le simple fait que le compilateur génère du code machine à partir d'une expression ne signifie pas que le système d'exploitation et le matériel sous-jacents seront satisfaits du résultat lorsque le programme sera exécuté. Ces zones grises, zones de comportement indéfini, peuvent entraîner un programme qui s'exécute parfois et pas d'autres fois ou peut s'exécuter dans un environnement mais pas dans un autre.

Le casting du tableau code fait un peu plus difficile à comprendre parce que la syntaxe de la distribution à un pointeur de fonction qui retourne un int est similaire à la syntaxe de déclaration ou la définition d'un pointeur de fonction qui retourne un int sauf que il n'y a pas de variable après l'astérisque dans la distribution (int(*)()). Donc, toutes ces parenthèses peuvent rendre un peu confus.

Dans le cas de cette distribution, nous utilisons entre parenthèses pour regrouper la distribution de type complet, (int(*)()) ainsi que les parenthèses pour faire respecter un ordre, (*) et entre parenthèses pour indiquer que cela est une fonction, (). Il y a donc beaucoup de parenthèses dans ce type de cast. Quand il devient plus compliqué est quelque chose comme int *((*ret)()) = (int *((*)()))code; qui est un pointeur de fonction à une fonction qui renvoie un pointeur sur un int.

Dans ce cas, je préfère vraiment utiliser une parenthèse explicite pour spécifier l'ordre d'interprétation plutôt que de me fier à ma mémoire de priorité d'opérateur de commande.