Manipulation de stack en C sans utiliser d’assemblage en ligne

Je me préparais pour un concours de codage et je suis tombé sur cette question sur Internet:

#include  void a(); void b(); void c(); int main() { a(); printf("\n"); return 0; } void a() { b(); printf("hi "); } void b() { c(); printf("there "); } void c() { int x; // code here and nowhere else } 

La solution consiste à écrire du code qui afficherait “hi there” au lieu de “there hi” (aucune fonction d’impression supplémentaire ne peut être utilisée et le code est uniquement inséré dans le bloc de commentaires).

Depuis que j’ai codé les assemblages de base, je me suis rendu compte que cela pourrait se faire avec une manipulation de stack utilisant le nombre entier x comme base.

J’ai essayé d’utiliser gdb pour trouver l’adresse de retour des fonctions, puis j’ai échangé les adresses de retour de a et b . Le compilateur renvoie une erreur de segmentation et je suppose donc que je n’ai pas utilisé l’adresse de retour appropriée.

Comment calculer correctement le décalage pour trouver l’adresse de retour? La commande du cadre d’informations sur gdb n’a pas été utile, car l’utilisation de la valeur de l’adresse de stack donnée ne fonctionnait pas.

J’utilise ceci sous Linux en utilisant gcc.

Je ne sais pas si ce qui suit va compter. C’est portable sur POSIX. En gros, vous modifiez le tampon de printf avant son premier appel et vous le manipulez avant de le vider au terminal

 void c() { static int first = 1; if (first) { first = 0; char buf0[BUFSIZ]; char buf1[BUFSIZ]; setvbuf(stdout, buf0, _IOFBF, BUFSIZ); a(); memcpy(buf1, buf0 + 6, 3); memcpy(buf1 + 3, buf0, 6); memcpy(buf0, buf1, 9); buf0[8] = '\n'; fflush(stdout); exit(0); } } 

Vous obtiendrez des avertissements sur la déclaration implicite des fonctions de bibliothèque memcpy et exit . C’est légal sur C89 bien que découragé. Mais dans votre cas, aucun tour n’est trop sale, je suppose. Vous pouvez éviter la mémoire en copiant les caractères manuellement. Vous pouvez éviter la exit en redirigeant stdout via freopen . Vous pouvez remplacer BUFSIZ par une constante importante si la taille de la mémoire tampon du système est étrangement petite (inférieure à 9). Il existe des variantes de cette solution qui ne nécessitent pas l’insertion manuelle de \n , mais laissent le programme quitter normalement de main et dispose du printf("\n") pour mettre cette fin de ligne.

Ce problème ne peut être résolu que si vous écrasez la stack de la même manière qu’un attaquant écrase la stack d’un processus.

Et pour smesh la stack ne peut être faite que si vous connaissez chaque détail de l’implémentation du compilateur, le problème est insoluble autrement.

Si vous connaissez les détails de la compilation (la structure de la stack en particulier), vous pouvez utiliser l’adresse de la variable x locale pour obtenir l’adresse de la trame actuelle à partir de la stack (de FRAME_C). dans chaque image est le pointeur de base de l’image précédente et modifiez-le.

La stack ressemble à ça:

  FRAME_MAIN = RET_OS some-data FRAME_A = RET_MAIN some-data FRAME_B = RET_A some-data FRAME_C = RET_B some-data(including the variable `x`) 

En utilisant le &x nous pouvons détecter la position du FRAME_C.

Une solution est

  1. imprimer “Bonjour” dans la fonction c ()
  2. Modifier FRAME_B de telle sorte que RET_A devienne RET_MAIN
  3. retourne de la fonction c () avec return

L’opération délicate est 2. mais si chaque trame a une taille connue, nous pouvons modifier le pointeur de retour RET_A de la trame B et détecter RET_MAIN à peu près comme ceci:

 *(&x+FRAME_C_SIZE+some-small-offset1) = /* *&RET_A = */ *(&x+(FRAME_C_SIZE+FRAME_B_SIZE)+some-small-offset2). /* *&RET_MAIN */ 

Comme vous pouvez le constater, vous devez connaître beaucoup de détails sur l’implémentation du compilateur, ce n’est donc pas du tout une solution portable.


Une autre solution serait d’imprimer “hi, there” et de redirect la stdout vers /dev/null . Je suppose que exit () ou d’autres astuces dépendantes du compilateur ne sont pas autorisées, sinon le problème n’a aucune signification pour un concours.

ma solution est pour x86 / x64 et pour le compilateur CL , mais pensez pour gcc existe également.

question only – existe-t-il un équivalent pour la fonction:

void ** _AddressOfReturnAddress();

et sont équivalents pour __declspec(noinline) – pour dire au compilateur de ne jamais intégrer une fonction particulière

let void* pb – est l’adresse dans void b() juste après c(); et void* pa est une adresse dans void a() juste après b();

parce a et b presque pareil – on peut supposer que

(ULONG_PTR)pa - (ULONG_PTR)&a == (ULONG_PTR)pb - (ULONG_PTR)&b;

et bien sûr, la disposition des stacks dans a et b doit être la même. basé sur ceci et solution. code suivant testé / fonctionné avec le compilateur CL – sur x86 / x64 (windows) et avec /Ox (Full Optimization) et avec /Od (Disable (Debug)) – tout fonctionnait.

 extern "C" void ** _AddressOfReturnAddress(); void a(); void b(); void c(); int main() { a(); printf("\n"); return 0; } __declspec(noinline) void a() { b(); printf("hi "); } __declspec(noinline) void b() { c(); printf("there "); } __declspec(noinline) void c() { void** pp = _AddressOfReturnAddress(); void* pb = *pp; void* pa = (void*)((ULONG_PTR)&a + ((ULONG_PTR)pb - (ULONG_PTR)&b)); for (;;) { if (*++pp == pa) { *pp = pb; *_AddressOfReturnAddress() = pa; return; } } }