Comment un buffer underflow peut conduire à lexécution de code à distance sur 64 bits?

Jai le pseudo-code résumé suivant en C89 à partir dune bibliothèque de serveur ssh qui ne donne accès quà des éléments tels que git ‑ shell (/bin/bash est remplacé par le programme à exécuter, il nest donc pas possible de faire autre chose) :

struct _raw_uncapped_ssh_string { // no limit on the size of the string; uint32_t len; char non_null_terminated_string[]; // by protocol design it have a minimum length of 1 }; typedef struct _raw_uncapped_ssh_string raw_ssh_string; union buffer { void * uncapped_zlib_decompressed_network_data; // yes, the size is uncapped, so it’s possible to put 4Gb of // data in it that would be copied later into memory. zlib // allow easily to turn some Mb in Gb of data, but it’s not // the point of the question. raw_ssh_string st; }; get_command (compressed_network_data) { size_t len; char * command; buffer string=uncompress_to_buffer(compressed_network_data); len=ntohl(string.st.len)+1; command=malloc(len+1); command[len]=0; // here’s the point, both the string length and content as // well it’s supplied size is controlled by the attacker. memcpy(command,string.st.data,len); return command; } 

Voici comment la commande est exécutée ultérieurement (la chaîne command est inchangée après get_command()) .

const char *args[]={"/bin/bash",command,NULL}; // /bin/bash isn’t the shell, it has been replaced by git‑shell. // redirect the program output to the network. dup2(stdin, 0); dup2(stdout,1); dup2(stdout,2); close(stdin); close(stdout); //if this return execution failed and print an error message return execv(args[0],(char * const *)args); // I don’t know which is the system, so I can’t know about the libc behaviour. 

Je ne peux pas faire memcpy(command,string.st.data,0) car le troisième membre de memcpy a un taille minimale de 1, et dans mon contexte, size_t utilise un entier de 64 bits, je ne peux pas effectuer de buffer overflow, car il y a len.

Tout ce que je peux faire est de définir len sur une valeur supérieure à celle allouée à string.st.data. Il sagit dun dépassement de capacité de la mémoire tampon me permettant de lire la mémoire non allouée.
Je peux lire la mémoire du serveur, cependant, je ne peux pas voir quelles données sensibles un serveur ssh public peut contenir (dans mon cas, la liste des utilisateurs qui peut effectuer ssh est public) .

Il en va de même pour un buffer underflow sur memcpy autoriser à distance exécution de code?

Commentaires

  • Si le code fait vraiment command[len] = 0 alors cest un débordement de tampon, puisque lindice max pour un tampon de longueur len est len-1. Sinon, si le code réel fait un malloc(len+1) au lieu dun malloc(len), vous pouvez effectuer un énorme dépassement de mémoire tampon en définissant le len valeur à 0xFFFFFFFF.
  • @ThomasPornin: Non, cest une erreur de ma part, en fait il appelle plusieurs fonctions à faire ce. Supposons que le reste est vrai.
  • @ThomasPornin: Je parle de 64 bits, puisque len est size_t, il utilise un entier de 64 bits. Il ny a donc aucun moyen de débordement dentier.
  • @ThomasPornin Ils allouent len+1 donc le paramètre à 0 doit être valide.
  • @ RoraΖ: Il parlait de définir string.st.len sur -1.

Réponse

En général, non, un buffer underflow ne peut pas être utilisé pour lexécution de code à distance. Étant donné que les données contrôlées par les attaquants ne quittent jamais lespace qui leur est alloué, elles nont jamais la capacité de prendre le contrôle du flux dexécution du programme.

Un dépassement de la mémoire tampon a le potentiel pour dautres types dattaques, telles que divulgation dinformations (si le programme compte sur le contenu original du tampon effacé par les nouvelles données).

Commentaires

  • Ou comme je vu récemment dans git, être transformé plus tard en un buffer overflow.

Answer

Quand jai initialement écrit cette réponse , il semble que jai passé sous silence certains malentendus que vous tenez. Ces malentendus vous empêcheraient probablement de comprendre ma réponse. Pour être clair, je vais agrandir ce texte pour mettre laccent, pour ne pas manquer de respect:

De nombreux termes que vous utilisez ne signifient pas ce que vous pensez quils signifient.

Par exemple:

  • « le troisième membre de memcpy » est inexistant car memcpy est une fonction, pas un struct ou union.
  • « Je ne peux pas effectuer de débordement de tampon, car il y a len » et je ne peux « pas manquer dessence, car il y a de lessence. Suis-je en train de vous en prier question? Cela me semble être une analogie appropriée, dautant plus quen théorie, votre code pourrait provoquer un buffer overflow.
  • « Tout ce que je peux faire est de définir len à une valeur supérieure à celle allouée à string.st.data. Ceci est un dépassement de la mémoire tampon … «  Non, ce nest pas la définition dun dépassement de la mémoire tampon. Un dépassement de la mémoire tampon se produit lorsque vous accédez hors limites à un tableau en utilisant un index négatif, ou un index qui provoquerait le wrapping arithmétique du pointeur se produira. Ce dernier peut être possible pour certaines configurations (même sur les «systèmes 64 bits», quoi que cela signifie), mais je doute que ce soit ce que vous vouliez dire lorsque vous écriviez ces mots, car vous les avez ensuite suivis avec:
  • « … me permettant de lire la mémoire non allouée. » Je pense que vous vouliez peut-être dire « mémoire non initialisée ». Pour être clair, je pense que vous vouliez dire que vous « Jai alloué un excédent et laissé lexcédent non initialisé , et peut-être voulez-vous faire quelque chose à ce sujet (voir calloc ou memset).

Considérons un instant char *fubar = NULL; … ce type de pointeur a généralement un zéro valeur.Le déréférencement est considéré comme un déréférencement de pointeur nul , mais si nous écrivons quelque chose comme 0["hello"], nous obtenons la même chose que "hello"[0] (cest-à-dire "h"). Ainsi, un déréférencement de pointeur nul peut être utilisé dans les attaques lorsque lattaquant contrôle lexpression entre les accolades (comme cest le cas dans votre situation).

Pour en revenir à fubar chose; disons que nous memset(&fubar, UCHAR_MAX, sizeof fubar);, maintenant fubar est tous 1 bits, ce qui signifie que pourrait être une adresse cest la plus grande adresse que notre système pourrait (théoriquement) accepter. Et si nous accédions à fubar[1]? Comment pouvez-vous accéder à lélément après ladresse la plus grande? Techniquement, tout cela est un comportement indéfini, mais si je devais nommer cela sur une architecture commune, ce serait:

  1. Débordement arithmétique de un pointeur, menant à …
  2. Déréférence de pointeur nul, et / ou potentiellement un dépassement de la mémoire tampon .

Un dépassement de tampon sur memcpy permet-il lexécution de code à distance?

Un tampon underflow peut permettre à un attaquant décraser les pointeurs de fonction situés dans les régions de mémoire avant le tableau en question. Si ces pointeurs de fonction sont amenés à pointer vers le shellcode, ce shellcode peut être exécutés lorsquils « seront invoqués plus tard.

Dans ce morceau de code quelque peu obscur, il semble quil y ait » un risque de dépassement de la mémoire tampon lorsque int a un domaine supérieur à uint32_t, car ntohl(string.st.len)+1 entraînerait la conversion de la valeur uint32_t en int type. Considérez, par exemple, si INT_MIN est -4294967296 (qui est un de moins que 0 - UINT32_MAX) et INT_MAX est 4294967295 … ce serait essentiellement un int 33 bits avec padding pour compléter à la largeur dun octet; rare, mais possible. Dans ce cas, lexpression ntohl(string.st.len)+1 nest pas uint32_t de type; il « s int de type, et plutôt que de revenir à 0 quand un débordement dentier non signé se produit, il revient probablement à -4294967296 lorsquun débordement dentier signé se produit.

Si vous « recherchez une garantie contre le dépassement de la mémoire tampon , utilisez le U suffixe littéral entier (cest-à-dire ntohl(string.st.len)+1U). Ensuite, dans cette situation, vous vous retrouverez avec lexpression étant soit un uint32_t soit un unsigned int (selon le type qui a le plus grand domain).

Si vous considérez que ntohl(string.st.len) peut renvoyer un de moins que la valeur maximale pour ce type non signé (quel quil soit), alors len=ntohl(string.st.len)+1 donnera la valeur maximale, malloc(len+1) provoquera un wrapping non signé donc vous » finirez par appeler malloc(0) puis command[len]=0 écrira bel et bien au-delà de la fin du tableau. Alors, bien sûr, vous « ll aussi un problème avec memcpy(command,string.st.data,len); (il sagit dun buffer overflow).

Buffer underflows et les débordements ne sont pas le seul risque. Si vous ne « t vérifiez la valeur de retour de malloc et malloc renvoie NULL alors un déréférencement de pointeur nul peut être utilisé pour provoquer lexécution de code arbitraire. Cela implique que vous devez avoir une méthode pour communiquer malloc échoue de nouveau à lappelant. Cela implique également que vous pouvez utiliser cette même méthode pour vérifier et communiquer ce problème dencapsulation à lappelant.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *