Aidez-moi à comprendre ce code C (* (void (*) ()) scode) ()

Source: http://milw0rm.org/papers/145

#include  #include  int main() { char scode[]="\x31\xc0\xb0\x01\x31\xdb\xcd\x80"; (*(void(*) ()) scode) (); } 

Cet article est un tutoriel sur le shellcode sur la plate-forme Linux, mais il n’a pas expliqué comment l’énoncé suivant ” (*(void(*) ()) scode) (); “. J’utilise le livre “Référence de programmation en langage C, rédigé par Brian.W.Kernighan, Dennis.M.Ritchie” pour rechercher une réponse, mais aucune réponse. Que quelqu’un puisse indiquer la bonne direction, peut-être un site Web, un autre livre de référence en C où je peux trouver une réponse.

En C:

 (some_type) some_var 

lance some_var pour qu’il soit de type some_type.

Dans votre exemple de code, “void (*) ()” correspond à some_type et constitue la signature d’un pointeur de fonction qui ne prend aucun argument et ne renvoie rien. “(void (*) ()) scode” convertit scode en un pointeur de fonction. “(* (void (*) ()) scode)” déréférence le pointeur. Et final () appelle la fonction définie dans scode.

Et les octets dans scode se désassemblent dans l’assemblage i386 suivant:

  31 c0 xor %eax,%eax b0 01 mov $0x1,%al 31 db xor %ebx,%ebx cd 80 int $0x80 

Son code machine (instructions d’assemblage compilées) est scode puis il est scode vers un pointeur de fonction vide appelable et appelé. GMan a démontré une approche équivalente plus claire:

 typedef void(*void_function)(void); int main() { char scode[]="\x31\xc0\xb0\x01\x31\xdb\xcd\x80"; void_function f = (void_function)scode; f(); //or (*f)(); } 

scode contient un code machine x86 qui se désassemble (merci Michael Berg )

 31 c0 xor %eax,%eax b0 01 mov $0x1,%al 31 db xor %ebx,%ebx cd 80 int $0x80 

C’est le code pour un appel système sous Linux (interruption 0x80). Selon la table d’appels du système, l’appel de l’ appel système sys_exit() ( eax=1 ) avec le paramètre 0 (in ebx ). Cela provoque la fermeture immédiate du processus, comme s’il s’appelait _exit(0) .

Jonathan Leffler a souligné que ceci est le plus souvent utilisé pour appeler un shellcode , “un petit morceau de code utilisé comme charge utile dans l’exploitation d’une vulnérabilité logicielle”. Ainsi, les systèmes d’exploitation modernes prennent des mesures pour empêcher cela.

Si la stack est non-exécutable, ce code échouera horriblement. Le code shell est chargé dans une variable locale de la stack, puis nous passons à cet emplacement. Si la stack est non exécutable, une erreur de la part de la CPU se produira dès que la CPU tentera d’exécuter le code et le contrôle sera transféré dans les gestionnaires d’interruption du kernel. Le kernel va alors tuer le processus de manière anormale. La stack pourrait ne pas être exécutable si vous utilisez un processeur prenant en charge les extensions d’adresse physique et si vous avez défini le bit NX (non exécutable) dans vos tables de page.

Il peut également y avoir des problèmes de cache d’instructions sur certaines CPU – si le cache d’instruction n’a pas été vidé, le CPU peut lire des données obsolètes (au lieu du code shell explicitement chargé dans la stack) et commencer à exécuter des instructions aléatoires.

Ce code assigne un code machine (les octets dans scode), puis convertit l’adresse de ce code en un pointeur de fonction de type void function () puis l’appelle.

En C / C ++, la définition du type de cette fonction est exprimée:

 typedef void (* basicFunctionPtr) (void); 

Un typedef aide:

 // function that takes and returns nothing typedef void(*generic_function)(void); // cast to function generic_function f = (generic_function)scode; // call (*f)(); // same thing written differently: // call f(); 

scode est une adresse. (void(*)()) scode en une fonction renvoyant void et n’acceptant aucun paramètre. Le * initial appelle le pointeur de la fonction, et le signe fin () indique qu’aucun argument n’est fourni à la fonction.

Pour en savoir plus sur la technique de codage de shell, consultez le livre:

Le Manuel du Shellcoder, 2nd Edn

Il existe plusieurs autres ouvrages similaires. Je pense que c’est le meilleur, mais on pourrait le convaincre du contraire. Vous pouvez également trouver de nombreuses ressources connexes avec Google et le “Manuel du shellcoder” (ou votre moteur de recherche de choix, sans aucun doute).

Le tableau de caractères contient du code exécutable et la conversion est une conversion de fonction.

(*(void(*) ()) signifie “converti en un pointeur de fonction qui ne produit rien, c’est-à-dire rien. Le () après le nom est l’opérateur d’appel de fonction.

Les caractères encodés dans scode sont les représentations char / byte de certains codes assembleurs compilés. Le code que vous avez publié prend cet assemblage, codé sous forme de caractères pour plus de simplicité, puis appelle cette chaîne en tant que fonction.

L’assemblée semble se traduire par:

 xor %eax, %eax mov $0x1, %al xor %ebx, %ebx int $0x80 

Yup, cela créerait effectivement un shell sous Linux.