c - cmd.exe parsing bug leads to other exploits? -
before continue, please note rather lengthy infosec notice windows command prompt have found bug might exploitable using simple batch files. bug prevalent in versions of windows 2000 , works on 64 , 32-bit machines, , being it's batch file parsing bug, requires no additional software install (cmd.exe
default part of windows) , can initiated user level of privilege (assuming can run cmd.exe
, parse batch files). included in summary assembly of bug happens (with breakdown of code flow show why). isn't rce level bug (not i've been able find yet), dos type , require user run (or have startup item), given simplicity of , ubiquity of windows systems, figured deserved second look. please note i'm not responsible if run of these bug enabling batch files , crash system (task manager , kill pid works on runaway script in case run them).
tldr: batch file line ^ nul<^
cause massive memory leak, while batch file line ^|^
causes command prompt crash due 'infinite recursion'. these behaviors can lead interesting batch 'hacks' on windows system (win2k+) , reason logic error in cmd.exe
batch file processing (see assembly , pseudo-c code below more).
background
while answering question on superuser (link) came across interesting anomaly how command interpreter parses batch files. if caret character (^
) last character of file, memory leak/cmd.exe crash can happen. caret has last character of file , cannot followed \n
(the line feed character), though \r
(the carriage return character) ok it's stripped before caret parsed; nothing can trail though cause parser proceed (since ^\r\t
become ^\t
\t
"ignored"). last note carriage return characters being ok bug still happen makes little more interesting because 'fakes' newline in text editors, , in notepad fooled thinking there's newline @ end ('technically' it's carriage return or 'old mac' new line).
after doing quick investigations found ^
@ end of file can lead memory leak or can crash command prompt (more command.com or cmd.exe program), found specific batch files (and sequences thereof) can lead interesting behavior. further investigations lead me other people noting similar issues; a stack overflow question user noted memory leak , message board topic @ ss64.com noted other interesting behaviors caret @ eof. stack overflow question helped confirm suspensions it's infinite loop type situation, didn't try dive deeper that. ss64.org topic discussed various ways make command prompt crash, failed explain kind of crash or why.
naturally lead me question happening , why (and exploited). answers mixed , fix quite simple (at least seems 'simple' assembly inspecting). found there few combinations of batch file trickery can yield memory leak, how fast or slow depends on put in batch file (not how many carets, sequences of pipes , interestingly line length [more later]) , regardless of crash or memory leak, parser code in tight loop on single thread, cpu usage increases (single core cpu or pushed single affinity, yields average of 98+% cpu usage).
the crashing
to crash command prompt simple enough, batch file no newlines containing following characters ^&^
(or ^|^
), cause command prompt batch being processed on crash error code of 0xc00000fd
, stack/frame overflow due infinite recursion. confirmed 'infinite loop' scenario, didn't quite explain memory leak (or why matter crashing due infinite recursion??). extent, started examine of simplest ways produce memory leak; turns out, 2 byte batch file needed consume 100% cpu , eat memory (albeit @ insanely slow rate, ~8k/5s on 2.2ghz i7).
eat memory
the batch file used test contained following hex bytes:
0x01 0x5e
0x5e
caret character (^
) , 0x01
ascii character code 'start of header'. first hex code half significant in bug, can't null (0x00
), \r
, |
, or &
these characters cause batch file exit via normal termination (i.e. 'invalid command detected'). used 0x01
demonstrate didn't have valid command (or printable character matter) induce bug, 2 byte batch file containing a^
suffice too.
doing other testing, found quickest way eat memory (and simplest) batch file following line: ^ nul<^
... having inadvertently run myself couple of times, please aware cripple 64-bit system rather quickly; took 20 seconds eat of remaining 14gb of ram (16gb total) on quad core (htt, 8 effective cores) i7, rendered machine useless while trying page (i had restart machine). running 'quick' version on 32-bit system ended command parser out of memory
error exhausts 32-bit 2gb limit. doesn't crash command prompt, instead seems there's check ensure memory can malloc
d, , when can't aborts batch processing.
chained bug fun
note though of these 'batch exploits' can 'chained' together, on 32-bit system (assuming had 4gb of ram no pae), 1 run following command preform dos on it: cmd eat_mem.bat | eat_mem.bat
(this launch 2 command parsers , exhaust 2gb each).
another interesting variation chain 'crash' batch files together; example, assuming there's file called crash.bat
, contained our 'crash exploit' above (^&^
), following: cmd crash.bat | crash.bat
, note interesting behavior. particular 1 causes output of cmd crash.bat
written pipe has crashed (due parser bug) , gives wrote non-existent pipe
errors when attempt ctrl+c
out of it; interestingly enough though still allows execute commands, typing notepad
, hitting enter launch notepad. have explored more possible pipe hijacking don't have fruitful yet (more on 'exploits' below).
8k buffer
as stack overflow question/answer had alluded to, more ^
had on line, faster eat memory (aside 'quick' version found). through more experiments, found 'line length' can have interesting effects on bug well. if last line of batch file has ^
@ end of , 8192 bytes long (including caret), bug fails...yet below , above multiples of 8192 cause bug. more bytes in string, faster memory consumed (up multiple of 8192). 8192 number interesting our memory leak 8k (or in larger chunks thereof).
it should noted file size irrelevant bug , because of can put in innocuous batch file. test put 'quick' version (^ nul<^
) @ end of batch file use compile lot of legacy code. compiler script under 21kb recursive calls , multiple functions (to allow me compile things) , putting 'buggy' line @ end of file allowed me run compile script normal , complete normal, when hit 'bug' spun cpu , ate memory.
if unassuming software engineer given 'simple' compiler script (with call :eof
randomly in there 'forcing' bug), wouldn't think twice if script kicked cpu, , if wasn't paying attention task manager not see how ram eaten , have restart (not fun).
dig deeper
from here decided higher level inspections see happening command prompt while leak happening. opened process explorer , procmon tools inspect happening in processes (on stack pe) , system (via procmon). procmon confirmed 8k (more 8191 bytes) leak call readfile
8191
byte buffer , pe confirmed calls kernel readfile
, though wasn't sure why batch file being read?
interestingly, bug allows batch files modified while running , re-parsed (up 8k of 'parsing', line feeds , all). should noted contents can modified , commands processed, i've noticed unless there's lot of other commands (more line feeds before final ^
), timing can play part in success of modified batch 'hack' though happens more not (i.e. works of time).
these batch exploits lead me wonder if same done programmatically; first try simple c program had system("crash.bat");
called batch file containing ^&^
in it. launched separate cmd.exe
process subsequently crashed returning control program (this expected). switched simple memory leak file (contained a^
) , ran program again, time cmd.exe
ran , started spin (as expected), when pressed ctrl+c
, cmd.exe
process orphaned , control returned program leaving me have end rogue cmd.exe
process through task manager. tried using lines file directly in program (i.e. calling effect of system("^ nul<^");
) got valid results (valid in case meaning command deemed 'invalid' command parser). tried variations of how hook process or take advantage of stack/frame overflow error, due cmd.exe
being parser itself, of more obvious exploits won't easy. extent, decided break open cmd.exe
process , inspect assembly verify leaking , better understand happening , how exploited (if @ all).
the code (cmd.exe)
i used visual studio 2010 , attached running cmd.exe
process, using 'simple' memory leak batch file (a^
) paused process step through assembly while parser in error state. after inspecting assembly , using process explorer verify module in during debug, able trace lot of code flow , found parser looked ^
character (hex 0x5e
). following assembly flow can see issue lie.
note lot of 'main' , 'parsing' function assembly has been removed brevities sake (relevant code posted full dump of i've found available upon request [1.2 mb asm dump comments]):
; 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 '<' 000000004a162502 0f 84 5b ff ff ff je 000000004a162463 000000004a162508 83 fa 5e cmp edx,5eh ; start '^' 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 ; starts large chunk of code more parsing (as calls ; criticalsection code) it's omitted because issues prevalent in ; rest of code pertaining 'caret_parser' not returning properly. 'memory leak' ; in section code (an 8k buffer read that's 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 0 if eof 000000004a1701a8 66 41 3b c4 cmp ax,r12w ; r12w 0x0a ('\n') here, eol check (fail in eof case) 000000004a1701ac 0f 85 82 23 ff ff jne 000000004a162534 ; jump '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
based on assembly, able ascertain cmd.exe
parsing code doing (psuedo-c code):
void main_parser_fn() { /* 'some_read_condition' based on lot of things interestingly 1 of them 8k buffer size; asm shows 8191 byte buffer reading/parsing, couldn't ascertain why having buffer divisible 8192 bytes in line buffer 'ok' more or less causes 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 here due eof error) } }
this (of course) rough approximation based on input/output , assembly, seems code next character after caret issue resides.
the bug explained
the bug when caret detected, next character read file (to 'escaped'). if caret last character of file, results in logic error, when call get_next_char
made, file pointer incremented one; in case puts passed eof
. since eof
ignored when command parser continues read next input, 'resets' file pointer due eof+1
error. in case, putting file pointer eof+1
causes pointer @ large negative number, , since file's can't go below 0, it's file pointer reset 0 , parsing continues beginning of file.
this explains memory leaks , why it's 8k (an 8k 'read buffer' being filled), , can explain recursion issue. when |
or &
detected in batch file, it's recursively parsed out, , since there's eof
bug recursion becomes infinite no return paths can had.
edit: comments have pointed out, , further research has shown, caret doesn't have @ end of file bug happen, i'm investigating (and breaking down more of asm) see if there other scenario's , why/how it's happening.
the fix
it seems simple fix check eof
on read of next character when parsing caret. check (and subsequent 'proper batch abort' functionality) fix memory leak issue infinite recursion.
exploits?
after inspecting process more , thinking possible exploits, don't see being anywhere serious ms14-019, given ease of use/implementation (and relative ease of fixing it), consider 'medium' level alert 'exploits' require user run batch file, , 'obvious' avenues trying exploit stack/frame overflow error or launch shell code through batch file prove more difficult on many other exploits yield more 'fruitful' outcome vs innocuous batch bug. though can see being used in dos attack it's easy write 1 given it's 7 bytes (^ nul<^
) , potentially distributed , 'setup' rather easy.
here's simple vbscript used write , launch 'killer' batch file (and silently)
createobject("scripting.filesystemobject").createtextfile("killer.bat", true).write("^ nul<^") & vbcr createobject("wscript.shell").run "killer.bat", 0, false
that create batch file named killer.bat
^ nul<^\r
in , run it, put in .vbs
file , run @ startup, or put in excel macro , run.
echo|set /p="^ nul<^" > killer.bat
that line command line equivalent create 'killer' batch file (doing normal echo file result in \r\n
@ end of file , bug won't present).
as proof of concept, created vbscript (as other batch file tests), , put them in startup
folder , registry well. when logged in, greeted command prompt when using batch files , nothing when using vbscript, , in few seconds system came halt , unusable because script had consumed ram. scripts can ended via killing running cmd.exe
process, because work fast, might not have enough time launch task manager before consumes of ram. removal through safe mode
'cure' this, not 'fix'.
i envision scenario unsuspecting admin going run backup.bat
script unfortunate bug in , inadvertently bringing down server. or fun might had at
/schtasks.exe
commands on unsecured system.
granted don't see these 'exploits' leaving realm of dos or prank.
i'm still inspecting various avenues piping , redirection of 'broken' scripts possibly lead rce, stands simplest attack vector dos attack 'quick' file. i've tested bug on windows 98, 2000, xp, vista, 7, 8, , server variations (to include 32 , 64 bit flavors). windows 98 command prompt not affected this, every version above (to include command.com
since uses cmd.exe
parse batch files). out of curiosity tested on reactos , wine (neither of had issue).
questions (edited after more research)
as stated, don't see bug being "exploitable" more denial of service "attack" (or prank on co-worker/friend), got me thinking in general frame overflow's , memory leaks, more if exploitable (just in general).
my experience , understanding software engineering/hacker perspective tells me memory leaks or frame overflow's potentially exploited on older os (say windows 98/2000/xp or older versions of *nix?) don't have protections in place (like use of nx bit or aslr) given right conditions, haven't been able find research these areas aside 'normal' attack vectors (stack based buffer overflows) or general documentation on these things 'are' (i.e. 'white paper' discussions on frame overflow or memory leak , nx/aslr are) , not on 'why' can't.
i've been experimenting injecting thread or other method running cmd.exe
process run tests on , analyze frame overflow , memory leak occurs within (as general fun can had bug, using createprocess
, emptyworkingset
general fun) , know won't 'get anywhere' particular bug, got me thinking (or on thinking rather): has there ever been frame overflow exploit or memory leak exploit in wild or there documentation might able read explains why (more specifically/technically) it's not feasible?
i understand 'why' more specificity, 'the eip register protected because xyz...' opposed 'no it's not possible', helpful; know each architecture different , might asking more details had in answer, links or discussion points reference helpful since can't seem find in reference this.
i've been swimming in assembly, fresh perspective helps :)
note: sent email (on 04/25/2014) microsoft bug , have responded saying have forward development team , investigating, no fix in security bulletin planned (agree them on nothing yet indicates it's serious flaw). edit should more updates come.
Comments
Post a Comment