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.