87 votes

Le bug d'analyse de cmd.exe conduit à d'autres exploits ?

Avant de poursuivre, veuillez noter qu'il s'agit d'un avis d'infosécurité assez long concernant l'invite de commande Windows, car j'ai découvert un bogue qui pourrait être exploité à l'aide de simples fichiers batch. Ce bogue est présent dans toutes les versions de Windows à partir de 2000 et fonctionne sur les machines 64 et 32 bits, et étant donné qu'il s'agit d'un bogue d'analyse de fichiers batch, il ne nécessite pas d'installation de logiciel supplémentaire ( cmd.exe est une partie par défaut de Windows) et peut être initié par n'importe quel utilisateur avec n'importe quel niveau de privilège (en supposant qu'ils puissent exécuter cmd.exe et donc d'analyser les fichiers batch). Inclus dans ce résumé est l'assemblage de l'endroit où le bug se produit (avec la ventilation du flux de code pour montrer pourquoi). Il ne s'agit pas d'un bogue de niveau RCE (pas que j'ai pu trouver pour l'instant), juste d'un type DoS et il faudrait qu'un utilisateur l'exécute (ou l'ait comme élément de démarrage), mais étant donné sa simplicité et l'omniprésence des systèmes Windows, j'ai pensé qu'il méritait un second regard. Veuillez noter que je ne suis pas responsable si vous exécutez l'un de ces fichiers batch d'activation de bogues et que vous faites planter votre système (le gestionnaire de tâches et tuer le PID fonctionne sur un script en cas d'exécution).

tldr un fichier batch avec seulement cette ligne ^ nul<^ provoquera une fuite massive de mémoire, alors qu'un fichier batch avec seulement cette ligne ^|^ fait planter l'invite de commande en raison d'une "récursion infinie". Ces comportements peuvent conduire à des "hacks" intéressants de traitement par lots sur n'importe quel système Windows (Win2k+). cmd.exe traitement de fichiers batch (voir l'assemblage et le code pseudo-C ci-dessous pour plus de détails).

Contexte

En répondant à une question sur SuperUser ( lien ), je suis tombé sur une anomalie intéressante dans la façon dont l'interpréteur de commandes analyse les fichiers batch. Si le caractère caret ( ^ ) est le dernier caractère du fichier, une fuite de mémoire/un crash de cmd.exe peut se produire. Le signe d'insertion doit être le dernier caractère du fichier et ne peut pas être suivi d'un signe \n (le caractère de saut de ligne), bien que \r (le caractère de retour de chariot) est acceptable puisqu'il est supprimé avant que le signe d'insertion ne soit analysé ; rien ne peut cependant le suivre, car il ferait en sorte que l'analyseur syntaxique procède normalement (car ^\r\t deviendrait ^\t Ainsi, le \t est "ignorée"). Cette dernière remarque sur le fait que les caractères de retour de chariot sont acceptables pour que ce bogue se produise encore rend le problème un peu plus intéressant, car il "simule" une nouvelle ligne dans la plupart des éditeurs de texte, et dans le bloc-notes, on pourrait croire qu'il y a une nouvelle ligne à la fin (techniquement, c'est un retour de chariot ou une nouvelle ligne "vieux Mac").

Après avoir fait quelques recherches rapides, j'ai découvert que le ^ à la fin d'un fichier peut entraîner une fuite de mémoire ou faire planter l'invite de commande (plus précisément le programme command.com ou cmd.exe), j'ai également découvert que des fichiers batch spécifiques (et leurs séquences) peuvent entraîner un comportement très intéressant. Des recherches plus approfondies m'ont conduit à d'autres personnes qui ont constaté des problèmes similaires ; une question de Stack Overflow où un utilisateur a noté une fuite de mémoire et un sujet du tableau d'affichage de ss64.com qui a noté que d'autres comportements intéressants avec le caret à l'EOF . La question de Stack Overflow a aidé à confirmer les suspicions qu'il s'agit d'une situation de type boucle infinie, mais n'a pas essayé de plonger beaucoup plus profondément que cela. Le sujet de ss64.org a principalement discuté des différentes façons de faire planter l'invite de commande, mais n'a pas expliqué quel type de plantage c'était ou pourquoi.

Naturellement, cela m'a amené à me demander ce qui se passait et pourquoi (et si cela pouvait être exploité). Les réponses sont mitigées et la solution est assez simple (du moins, elle semble "simple" d'après l'assemblage que j'inspectais). J'ai découvert qu'il y a quelques combinaisons d'astuces de fichiers batch qui peuvent produire une fuite de mémoire, la rapidité ou la lenteur dépend de ce qui est mis dans le fichier batch (pas le nombre de carets, mais les séquences de pipes et, de manière intéressante, la longueur des lignes [plus tard]) et indépendamment du crash ou de la fuite de mémoire, le code de l'analyseur est dans une boucle serrée sur un seul thread, donc l'utilisation du CPU augmente de manière significative (un seul noyau CPU ou poussé à une seule affinité, donne une moyenne de 98+% d'utilisation du CPU).

L'écrasement

Le plantage de l'invite de commande était assez simple, un fichier batch sans nouvelle ligne contenant uniquement les caractères suivants ^&^ (ou ^|^ ), l'invite de commande sur laquelle le lot est traité se bloquera avec le code d'erreur suivant 0xC00000FD qui est un dépassement de pile ou de trame dû à une récursion infinie. Cela confirmait le scénario de la "boucle infinie", mais n'expliquait pas vraiment la fuite de mémoire (ni pourquoi, d'ailleurs, le programme se plantait à cause d'une récursion infinie). Dans cette mesure, j'ai commencé par examiner certaines des façons les plus simples de produire une fuite de mémoire ; il s'avère qu'un fichier batch de 2 octets est tout ce qu'il faut pour consommer 100% du CPU et manger de la mémoire (bien qu'à un rythme incroyablement lent, ~8k/5s sur un i7 2.2GHz).

Mangez la mémoire

Le fichier batch que j'ai utilisé pour tester contenait les octets hexadécimaux suivants :

0x01 0x5E

0x5E est le caractère caret ( ^ ) et 0x01 est le code de caractère ASCII pour "début d'en-tête". Le premier code HEX n'est qu'à moitié significatif dans le bogue, il ne peut pas être nul ( 0x00 ), \r , | ou le & car ces caractères provoquent la sortie du fichier batch par une terminaison normale (c'est-à-dire "commande invalide détectée"). J'ai utilisé les caractères 0x01 Pour démontrer qu'il n'est pas nécessaire que ce soit une commande valide (ou un caractère imprimable d'ailleurs) pour induire le bogue, un fichier batch de 2 octets contenant simplement a^ suffira également.

En effectuant d'autres tests, j'ai découvert que le moyen le plus rapide de manger de la mémoire (et le plus simple) avec un fichier batch est la ligne suivante : ^ nul<^

... Pour l'avoir moi-même exécuté par inadvertance plusieurs fois, sachez que cela paralysera un système 64 bits assez rapidement ; il a fallu environ 20 secondes pour consommer tous les 14 Go de RAM restants (16 Go au total) sur mon i7 à quatre cœurs (HTT, donc 8 cœurs effectifs), ce qui a ensuite rendu ma machine inutilisable pendant que tout essayait de s'afficher (j'ai dû redémarrer ma machine). L'exécution de cette version "rapide" sur un système 32 bits a terminé l'analyseur de commandes avec un message d'erreur out of memory car il épuise rapidement sa limite de 2 Go en 32 bits. Cela ne fait pas planter l'invite de commande, mais il semble qu'il y ait une vérification pour s'assurer que la mémoire peut être correctement utilisée. malloc d, et lorsqu'il ne le peut pas, il interrompt simplement le traitement par lots.

Le plaisir des insectes enchaînés

Notez cependant que tous ces "exploits par lots" peuvent être "enchaînés", de sorte que sur un système 32 bits (en supposant qu'il ne dispose que de 4 Go de RAM et qu'il n'y ait pas de PAE), on pourrait exécuter la commande suivante pour effectuer un DoS : cmd eat_mem.bat | eat_mem.bat (ceci lancera 2 analyseurs de commandes et épuisera 2GB chacun).

Une autre variante intéressante consiste à enchaîner les fichiers batch de "crash" ; par exemple, en supposant qu'il y ait un fichier appelé crash.bat et il contenait notre "exploit d'accident" du dessus ( ^&^ ), vous pouvez procéder comme suit : cmd crash.bat | crash.bat et noter quelques comportements intéressants. Ce cas particulier provoque la sortie de cmd crash.bat pour être écrit sur un tube qui s'est planté (à cause du bug de l'analyseur) et donne donc wrote to a non-existent pipe des erreurs lorsque vous tentez de CTRL+C mais il est intéressant de noter qu'il vous permet toujours d'exécuter des commandes, comme de taper notepad et en appuyant sur la touche Entrée, vous lancerez le bloc-notes. J'ai exploré plus avant cette possibilité de détournement de tuyaux, mais je n'ai rien trouvé de concluant pour l'instant (plus de détails sur les "exploits" ci-dessous).

Tampon de 8K

Comme la question/réponse de Stack Overflow y faisait allusion, la plus ^ que vous aviez sur une ligne, plus vite il consommait de la mémoire (à part la version 'rapide' que j'ai trouvée). Grâce à d'autres expériences, j'ai découvert que la "longueur de la ligne" peut avoir des effets intéressants sur ce bug. Si la dernière ligne du fichier batch a un caractère ^ à la fin de celle-ci et fait exactement 8192 octets de long (y compris le signe d'insertion), le bogue échoue... Pourtant, tout ce qui est inférieur et supérieur aux multiples de 8192 provoque le bogue. Plus il y a d'octets dans une chaîne, plus la mémoire est consommée rapidement (jusqu'à un multiple de 8192). Ce chiffre de 8192 est intéressant car notre fuite de mémoire est de 8k (ou de plus gros morceaux de celle-ci).

Il convient de noter que la taille du fichier n'a rien à voir avec le bogue et que, de ce fait, il peut être placé dans n'importe quel fichier batch inoffensif. Pour tester, j'ai mis la version "rapide" ( ^ nul<^ ) à la fin d'un fichier batch que j'utilise pour compiler beaucoup d'anciens codes. Ce compilateur script fait un peu moins de 21KB avec des appels récursifs et de multiples fonctions (pour me permettre de compiler certaines choses) et mettre la ligne 'buggy' à la fin du fichier m'a permis d'exécuter la compilation script normalement et de terminer normalement, mais quand il a rencontré le 'bug', il a fait tourner mon CPU et a rapidement mangé ma mémoire.

Si j'étais un ingénieur logiciel sans prétention à qui l'on donnait un "simple" compilateur script (avec un appel à :eof ), je ne réfléchirais pas à deux fois si le script faisait soudainement exploser mon CPU, et si je ne faisais pas attention à mon gestionnaire de tâches, je ne verrais pas à quelle vitesse ma RAM est consommée et je devrais alors redémarrer (pas drôle).

Creuser plus loin

A partir de là, j'ai décidé de faire quelques inspections de plus haut niveau pour voir ce qui se passait avec l'invite de commande pendant que cette fuite se produisait. J'ai ouvert le Explorateur de processus et Procmon pour inspecter ce qui se passait dans les processus (sur la pile avec PE) et dans le système (via Procmon). Procmon a confirmé la fuite de 8k (plus précisément 8191 octets) avec un appel à ReadFile avec un 8191 d'octets et PE a confirmé les appels au noyau pour ReadFile mais je ne savais pas trop pourquoi le fichier batch était lu ?

Il est intéressant de noter que ce bogue permet également de modifier les fichiers batch en cours d'exécution et de les ré-analyser (jusqu'à 8k de 'parsing', line feeds et tout). Il faut noter que le contenu peut être modifié et que certaines commandes seront traitées, mais j'ai remarqué qu'à moins qu'il n'y ait beaucoup d'autres commandes (plus spécifiquement des sauts de ligne avant la commande finale), les fichiers batch ne sont pas traités. ^ ), le timing peut également jouer un rôle dans le succès d'un "hack" de lot modifié, bien que cela arrive plus souvent qu'autrement (c'est-à-dire que cela fonctionne la plupart du temps).

Ces exploits par lots m'ont amené à me demander s'il était possible de faire la même chose par programme ; mon premier essai était un simple programme C qui avait simplement system("crash.bat"); qui a appelé le fichier batch contenant ^&^ en elle. Cela a lancé une cmd.exe qui s'est ensuite écrasé en rendant le contrôle à mon programme (tout cela était prévu). Je suis alors passé au fichier de fuite de mémoire simple (contenant juste a^ ) et exécuté à nouveau le programme, cette fois-ci le cmd.exe a fonctionné et a commencé à tourner (comme prévu), mais lorsque j'ai appuyé sur la touche CTRL+C le cmd.exe était orphelin et le contrôle revenait à mon programme, ce qui m'obligeait à mettre fin à ce processus malveillant. cmd.exe par le biais du gestionnaire de tâches. J'ai également essayé d'utiliser les lignes du fichier directement dans mon programme (c'est-à-dire en appelant quelque chose du type system("^ nul<^"); ) mais a obtenu des résultats valides (valides dans ce cas signifiant que la commande a été jugée "invalide" par l'analyseur de commandes). J'ai essayé de trouver des variantes pour m'introduire dans le processus ou tirer parti de l'erreur de dépassement de pile ou de trame, mais en raison de l'erreur de dépassement de pile ou de trame, je n'ai pas pu obtenir de résultats valides. cmd.exe étant le parseur lui-même, certains des exploits les plus évidents ne seront pas aussi faciles. Dans cette mesure, j'ai décidé d'ouvrir le fichier cmd.exe traiter et inspecter l'assemblage pour vérifier ce qui fuyait et mieux comprendre ce qui se passait et comment cela pouvait être exploité (le cas échéant).

Le code (cmd.exe)

J'ai utilisé Visual Studio 2010 et j'ai attaché à un fichier en cours d'exécution cmd.exe puis en utilisant le fichier batch "simple" de fuite de mémoire ( a^ ) a mis en pause le processus pour parcourir l'assemblage pendant que l'analyseur syntaxique était en état d'erreur. Après avoir inspecté l'assemblage et utilisé l'Explorateur de processus pour vérifier dans quel module je me trouvais pendant le débogage, j'ai pu retracer une grande partie du flux de code et j'ai trouvé où l'analyseur syntaxique recherchait l'élément ^ caractère (hex 0x5E ). Voici le déroulement de l'assemblage et je peux voir où se situe le problème.

Notez qu'une grande partie de l'assemblage des fonctions 'main' et 'parsing' a été supprimée pour des raisons de concision (le code correspondant a été posté mais le dump complet de ce que j'ai trouvé est disponible sur demande [dump ASM de 1.2 MB avec commentaires]) :

; start cmd.exe asm code flow 

000000004A161D50 ; { start main  (more init frame/code here)
; { start loop
000000004A161FC1 3B F5                cmp         esi,ebp  
; mem leak here?? esi == #bytes, ebp == our 8191 buffer size number
000000004A161FC3 7D 29                jge         000000004A161FEE  
000000004A161FC5 0F B7 44 24 20       movzx       eax,word ptr [rsp+20h]  
000000004A161FCA 48 8D 54 24 70       lea         rdx,[rsp+70h]  
000000004A161FCF 48 8D 4C 24 20       lea         rcx,[rsp+20h]  
000000004A161FD4 66 89 03             mov         word ptr [rbx],ax  
000000004A161FD7 48 83 C3 02          add         rbx,2  
000000004A161FDB FF C6                inc         esi 
000000004A161FDD 48 89 5C 24 60       mov         qword ptr [rsp+60h],rbx  
000000004A161FE2 E8 99 04 00 00       call        000000004A162480  ; main_parser_fn
000000004A161FE7 3D 00 01 00 00       cmp         eax,100h  
000000004A161FEC 75 D3                jne         000000004A161FC1
; } end loop
000000004A162058 ; } end main?? function here

; { start main_parser_fn
000000004A162480 48 8B C4             mov         rax,rsp  
000000004A162483 48 89 58 08          mov         qword ptr [rax+8],rbx  
000000004A162487 48 89 70 10          mov         qword ptr [rax+10h],rsi  
000000004A16248B 48 89 78 18          mov         qword ptr [rax+18h],rdi  
000000004A16248F 4C 89 60 20          mov         qword ptr [rax+20h],r12  
000000004A162493 41 55                push        r13  
000000004A162495 48 83 EC 20          sub         rsp,20h  
000000004A162499 48 8B DA             mov         rbx,rdx  
000000004A16249C 48 8B F9             mov         rdi,rcx  
000000004A16249F E8 BC FB FF FF       call        000000004A162060  ; get_next_char
000000004A1624A4 33 F6                xor         esi,esi  
000000004A1624A6 66 89 07             mov         word ptr [rdi],ax  
000000004A1624A9 39 35 D1 98 03 00    cmp         dword ptr [4A19BD80h],esi  
000000004A1624AF 0F 85 F1 75 01 00    jne         000000004A179AA6  
000000004A1624B5 0F B7 17             movzx       edx,word ptr [rdi]  
000000004A1624B8 41 BD 3C 00 00 00    mov         r13d,3Ch  
000000004A1624BE 8B CA                mov         ecx,edx  
000000004A1624C0 45 8D 65 CE          lea         r12d,[r13-32h]  
000000004A1624C4 3B D6                cmp         edx,esi  
000000004A1624C6 74 98                je          000000004A162460  
000000004A1624C8 41 2B CC             sub         ecx,r12d  
000000004A1624CB 74 93                je          000000004A162460  
000000004A1624CD 83 E9 1C             sub         ecx,1Ch  
000000004A1624D0 74 91                je          000000004A162463  
000000004A1624D2 83 E9 02             sub         ecx,2  
000000004A1624D5 0F 84 61 FF FF FF    je          000000004A16243C  
000000004A1624DB 83 E9 01             sub         ecx,1  
000000004A1624DE 0F 84 6A FF FF FF    je          000000004A16244E  
000000004A1624E4 83 E9 13             sub         ecx,13h  
000000004A1624E7 0F 84 76 FF FF FF    je          000000004A162463  
000000004A1624ED 83 E9 02             sub         ecx,2  
000000004A1624F0 0F 84 6D FF FF FF    je          000000004A162463  
000000004A1624F6 83 E9 02             sub         ecx,2  
000000004A1624F9 0F 84 28 FF FF FF    je          000000004A162427  ; quote_parse_fn
000000004A1624FF 41 3B CD             cmp         ecx,r13d  ; check if it's the '<'
000000004A162502 0F 84 5B FF FF FF    je          000000004A162463  
000000004A162508 83 FA 5E             cmp         edx,5Eh  ; start the '^' parse
000000004A16250B 0F 84 86 DC 00 00    je          000000004A170197  ; caret_parse
000000004A162511 83 FA 22             cmp         edx,22h  
000000004A162514 0F 84 5E 2F 00 00    je          000000004A165478  
000000004A16251A F6 03 23             test        byte ptr [rbx],23h  
000000004A16251D 0F 84 E5 00 00 00    je          000000004A162608  
000000004A162523 0F B7 0F             movzx       ecx,word ptr [rdi]  
000000004A162526 FF 15 54 6C 02 00    call        qword ptr [4A189180h]  
000000004A16252C 3B C6                cmp         eax,esi  
000000004A16252E 0F 85 C0 10 00 00    jne         000000004A1635F4  
000000004A162534 33 C0                xor         eax,eax  
000000004A162536 48 8B 5C 24 30       mov         rbx,qword ptr [rsp+30h]  
000000004A16253B 48 8B 74 24 38       mov         rsi,qword ptr [rsp+38h]  
000000004A162540 48 8B 7C 24 40       mov         rdi,qword ptr [rsp+40h]  
000000004A162545 4C 8B 64 24 48       mov         r12,qword ptr [rsp+48h]  
000000004A16254A 48 83 C4 20          add         rsp,20h  
000000004A16254E 41 5D                pop         r13  
000000004A162550 C3                   ret  
; } end main_parser_fn

; { start get_next_char
000000004A162060 FF F3                push        rbx  
000000004A162062 48 83 EC 20          sub         rsp,20h  
000000004A162066 48 8B 05 0B C2 02 00 mov         rax,qword ptr [4A18E278h]  
000000004A16206D 8B 0D ED 9B 03 00    mov         ecx,dword ptr [4A19BC60h]  
000000004A162073 33 DB                xor         ebx,ebx  
000000004A162075 66 39 18             cmp         word ptr [rax],bx 
000000004A162078 74 29                je          000000004A1620A3  ; when bx=0 
000000004A16207A 66 83 38 0D          cmp         word ptr [rax],0Dh ; 0d = \r
000000004A16207E 0F 84 69 0E 00 00    je          000000004A162EED  
000000004A162084 3B CB                cmp         ecx,ebx  
000000004A162086 0F 85 46 7A 01 00    jne         000000004A179AD2  
000000004A16208C 0F B7 08             movzx       ecx,word ptr [rax]  
000000004A16208F 48 83 C0 02          add         rax,2  
000000004A162093 48 89 05 DE C1 02 00 mov         qword ptr [4A18E278h],rax  
000000004A16209A 66 8B C1             mov         ax,cx  
000000004A16209D 48 83 C4 20          add         rsp,20h  
000000004A1620A1 5B                   pop         rbx  
000000004A1620A2 C3                   ret  
; } end get_next_char
000000004A1620A3 E8 18 00 00 00       call        000000004A1620C0  
000000004A1620A8 48 8B 05 C9 C1 02 00 mov         rax,qword ptr [4A18E278h]  
000000004A1620AF 8B 0D AB 9B 03 00    mov         ecx,dword ptr [4A19BC60h]  
000000004A1620B5 EB C3                jmp         000000004A16207A  

000000004A1620C0  ; this starts a large chunk of code that does more parsing (as well as calls
; some CriticalSection code) it's omitted from this because the issues that are prevalent in the
; rest of the code are pertaining to the 'caret_parser' not returning properly. The 'memory leak'
; is in this section code (an 8k buffer read that's also checked in main loop).

; { start quote_parse
000000004A162463 F6 03 22             test        byte ptr [rbx],22h  
000000004A162466 0F 85 9C 00 00 00    jne         000000004A162508  
000000004A16246C B8 00 01 00 00       mov         eax,100h  
000000004A162471 E9 C0 00 00 00       jmp         000000004A162536  
; } end quote_parse

; { start caret_parse
000000004A170197 F6 03 22             test        byte ptr [rbx],22h ; check if '"' 
000000004A17019A 0F 85 71 23 FF FF    jne         000000004A162511  ; if char == '"'
000000004A1701A0 E8 BB 1E FF FF       call        000000004A162060  ; get_next_char
000000004A1701A5 66 89 07             mov         word ptr [rdi],ax  ; ax will be 0 if EOF
000000004A1701A8 66 41 3B C4          cmp         ax,r12w  ; r12w is 0x0A ('\n') here, so this is a EOL check (fail in EOF case)
000000004A1701AC 0F 85 82 23 FF FF    jne         000000004A162534  ; this is the jump back to the 'main_parser_fn' <--error
000000004A1701B2 E9 0B 99 00 00       jmp         000000004A179AC2  ; == call 000000004A162060 (get_next_char)
000000004A1701B7 33 C9                xor         ecx,ecx  
000000004A1701B9 E8 22 1B FF FF       call        000000004A161CE0  
; } end caret_parse

; end cmd.exe asm code flow 

En me basant sur cet assemblage, j'ai pu constater que les cmd.exe Le code d'analyse fait quelque chose comme ça (code psuedo-C) :

void main_parser_fn() { 
    /* the 'some_read_condition' is based on a lot of things but
       interestingly one of them is an 8k buffer size; the ASM
       shows an 8191 byte buffer for reading/parsing, but I
       couldn't ascertain why having a buffer divisible by exactly
       8192 bytes in the line buffer was 'ok' but anything more or
       less causes the continuation (mem leak)?? */
    while (some_read_condition) {
        // allocate 8k buffer appropriately
        x = get_next_char();
        if (x == '|' || x == '&') {
            main_parser_fn();
        }
        if (x == '^') {
            get_next_char(); // error here
            // POSSIBLE FIX:
            // if (get_next_char() == 0) { abort_batch(); }
            continue;
        }
        // free buffer (never get here due to EOF error)
    }
}

Il s'agit (bien sûr) d'une approximation grossière basée sur l'entrée/sortie et l'assemblage, mais il semble que le code permettant d'obtenir le caractère suivant le signe d'insertion soit à l'origine du problème.

Le Bug expliqué

Le problème est que lorsqu'un caret est détecté, le caractère suivant est lu dans le fichier (pour être "échappé"). Si le caret est le dernier caractère du fichier, cela entraîne une erreur logique, comme lorsqu'un appel à get_next_char est effectuée, le pointeur de fichier est incrémenté d'une unité ; dans ce cas, il est passé devant le bouton EOF . Puisque le EOF est effectivement ignorée lorsque l'analyseur de commandes poursuit la lecture de l'entrée suivante, il "réinitialise" essentiellement son pointeur de fichier en raison de l'attribut EOF+1 erreur. Dans ce cas, mettre le pointeur de fichier à EOF+1 fait en sorte que le pointeur soit à un grand nombre négatif, et comme les fichiers ne peuvent pas descendre en dessous de 0, le pointeur de fichier est basiquement remis à 0 et l'analyse syntaxique continue depuis le début du fichier.

Cela explique les fuites de mémoire et pourquoi il y a 8k (un 'tampon de lecture' de 8k étant rempli), et peut également expliquer le problème de récursion. Lorsqu'un | ou & est détecté dans le fichier batch, il est analysé de manière récursive et, comme il existe un fichier EOF bug la récursion devient alors infinie puisqu'il n'y a pas de chemin de retour possible.

Modifier : Comme certains commentaires l'ont souligné, et comme des recherches plus approfondies l'ont montré, le caret ne doit pas nécessairement se trouver à la fin du fichier. Je suis en train d'enquêter (et de décomposer davantage l'ASM) pour voir s'il existe d'autres scénarios et pourquoi/comment cela se produit.

The Fix

Il semble qu'un simple correctif serait de vérifier si EOF sur une lecture du caractère suivant lors de l'analyse du caret. Cette vérification (et la fonctionnalité subséquente d'"interruption correcte du traitement par lots") permettrait de résoudre le problème de fuite de mémoire ainsi que la récursion infinie.

Des exploits ?

Après avoir inspecté plus en détail le processus et réfléchi à d'éventuels exploits, je ne pense pas que cela soit aussi grave que MS14-019 mais étant donné la facilité de son utilisation/implémentation (et la facilité relative de sa correction), je considérerais cette alerte de niveau 'moyen' car la plupart des 'exploits' nécessiteraient que l'utilisateur exécute le fichier batch, et les avenues 'évidentes' comme essayer d'exploiter l'erreur de débordement de pile/frame ou lancer un code shell via un fichier batch s'avéreraient plus difficiles que les nombreux autres exploits qui donneraient un résultat plus 'fructueux' que ce bug batch inoffensif. Bien que je puisse l'imaginer utilisé dans une attaque DoS car il est facile d'en écrire une étant donné qu'il s'agit de 7 octets ( ^ nul<^ ) et pourrait potentiellement être distribué et "configuré" assez facilement.

Voici un simple vbscript qui pourrait être utilisé pour écrire et lancer le fichier batch "tueur" (et faire tout cela silencieusement).

CreateObject("Scripting.FileSystemObject").CreateTextFile("killer.bat", True).Write("^ nul<^") & VbCr
CreateObject("WScript.Shell").Run "killer.bat", 0, False

Cela va créer un fichier batch nommé killer.bat avec un ^ nul<^\r et l'exécuter, cela pourrait être mis dans une .vbs et l'exécuter au démarrage, ou le mettre dans une macro Excel et l'exécuter.

echo|set /p="^ nul<^" > killer.bat

Cette ligne est l'équivalent de la ligne de commande pour créer le fichier batch "tueur" (un écho normal vers un fichier donnerait lieu à un message \r\n à la fin du fichier et donc le bogue ne sera pas présent).

Comme preuve du concept, j'ai créé ce vbscript (ainsi que d'autres tests de fichiers batch), et je les ai placés dans mon fichier Startup ainsi que le registre. Lorsque je me suis connecté, j'ai été accueilli par une invite de commande lors de l'utilisation des fichiers batch et rien lors de l'utilisation du vbscript, et en quelques secondes mon système s'est arrêté et était inutilisable car le script avait consommé toute ma RAM. Les scripts peuvent être arrêtés en tuant le script en cours d'exécution. cmd.exe mais comme ils travaillent très vite, vous n'aurez peut-être même pas le temps de lancer le gestionnaire des tâches avant qu'il ne consomme toute la RAM. Suppression par Safe Mode était le "remède" pour cela, mais pas la "solution".

Je peux aussi imaginer un scénario dans lequel un administrateur peu méfiant va exécuter une backup.bat script avec ce malheureux bug et de faire tomber leur serveur par inadvertance. Ou l'amusement que l'on pourrait avoir avec le at / schtasks.exe des commandes sur un système non sécurisé.

C'est vrai que je ne les vois pas. exploits quittant le domaine du DoS ou de la farce.

Je suis toujours en train d'examiner les différentes possibilités de tuyautage et de redirection des scripts "cassés" qui pourraient conduire à un RCE. En l'état actuel, le vecteur d'attaque le plus simple est une attaque DoS avec le fichier "quick". J'ai testé ce bogue sur Windows 98, 2000, XP, Vista, 7, 8 et les variantes du serveur (incluant les versions 32 et 64 bits). L'invite de commande de Windows 98 n'est pas affectée par ce bogue, mais toutes les versions supérieures le sont (y compris les versions 32 et 64 bits). command.com puisqu'il utilise cmd.exe pour analyser les fichiers batch). Par curiosité, je l'ai également testé sur ReactOS et Vin (aucun des deux n'avait ce problème).

Questions (modifié après plus de recherches)

Comme indiqué, je ne vois pas ce bogue comme étant "exploitable" plus qu'une "attaque" de déni de service (ou une farce à un collègue/ami), mais cela m'a fait réfléchir en général sur les débordements de cadre et les fuites de mémoire, plus spécifiquement s'ils sont exploitables (juste en général).

Mon expérience et ma compréhension du point de vue de l'ingénierie logicielle/du piratage me disent que les fuites de mémoire ou les débordements de trame pourraient être potentiellement exploités sur un ancien système d'exploitation (disons Windows 98/2000/XP ou les anciennes versions de *nix ? ) qui n'ont pas certaines protections en place (comme l'utilisation du bit NX ou ASLR) dans les bonnes conditions, mais je n'ai pas été en mesure de trouver des recherches dans ces domaines en dehors des vecteurs d'attaque "normaux" (débordements de tampon basés sur la pile) ou de la documentation générale sur ce que ces choses "sont" (c'est-à-dire les discussions du "livre blanc" sur ce qu'est un débordement de trame ou une fuite de mémoire et ce que sont NX/ASLR) et non sur "pourquoi" vous ne pouvez pas.

J'ai expérimenté l'injection d'un fil ou d'une autre méthode dans le processus d'exécution. cmd.exe afin d'effectuer des tests et d'analyser le dépassement de trame et la fuite de mémoire qui s'y produisent (ainsi que pour s'amuser de manière générale avec ce bogue, comme l'utilisation de CreateProcess et ensuite EmptyWorkingSet pour le plaisir en général) et je sais que je n'arriverai à rien avec ce bogue particulier, mais cela m'a fait réfléchir (ou plutôt trop réfléchir) : y a-t-il déjà eu un exploit de débordement de trame ou de fuite de mémoire dans la nature ou y a-t-il une documentation que je pourrais lire qui explique pourquoi (plus spécifiquement/techniquement) ce n'est pas faisable ?

Je comprends le "pourquoi" mais plus de spécificité, comme "le registre EIP est protégé parce que XYZ..." plutôt que simplement "non, ce n'est pas possible", serait utile ; je sais que chaque architecture est différente et je pourrais demander plus de détails que ce qui pourrait être obtenu dans une réponse, mais des liens ou des points de discussion que je pourrais référencer seraient également utiles puisque je ne semble pas trouver beaucoup de choses en référence à cela.

J'ai nagé dans l'assemblage, une nouvelle perspective aide toujours :)

Note : J'ai envoyé un email (le 25/04/2014) à Microsoft avec ce bug et ils ont répondu en disant qu'ils ont transmis cela à l'équipe de développement et qu'ils enquêtent, aucun correctif dans un bulletin de sécurité n'est prévu (je suis d'accord avec eux sur ce point car rien n'indique encore que c'est une faille sérieuse). Je modifierai cette page si d'autres mises à jour sont effectuées.

20voto

ssg Points 20321

ma question est de savoir pourquoi une fuite de mémoire ou plus spécifiquement un débordement de trame n'est pas 'exploitable'.

Vous devez exécuter un fichier batch ou une commande sur la machine pour l'exploiter. Si vous avez cela, vous pouvez déjà effectuer n'importe quoi avec un exécutable autonome plutôt que d'essayer d'exploiter cmd.

En fait cmd.exe contient déjà une vulnérabilité même sans votre bug. "Il permet l'exécution de code arbitraire". :)

Même lorsque vous exploitez cmd, votre "charge utile" s'exécute toujours au niveau de privilège de cmd.exe. Vous ne pouvez pas procéder à une élévation de privilèges en exploitant cmd.exe lui-même, car il s'agit d'un processus en mode utilisateur.

Si cmd.exe a été exécuté en tant qu'"Administrateur", il ne s'agit pas d'une escalade mais d'une simple décision de l'utilisateur. Il en va de même pour un utilisateur qui exécute votre exécutable en tant qu'administrateur. Il n'y a pas d'"escalade non désirée" ici.

La fuite de mémoire elle-même ne sert à rien non plus car vous pouvez, encore une fois, allouer autant de mémoire que vous le souhaitez. Et s'il était limité par la politique, cmd.exe ne pourrait pas non plus allouer plus de mémoire. Ce n'est pas différent que d'exécuter un while(1)malloc(1000000); (ce qui accaparerait aussi le CPU bien sûr :))

En bref :

  • Vous ne gagnez pas plus de privilèges que vous n'en avez avec votre exploit.
  • Vous ne pouvez pas provoquer l'exécution d'un code sans avoir accès à la machine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X