Page 1 of 2

Destructive env var substring

Posted: 18 Feb 2013 04:36
by Queue

Code: Select all

@echo off
echo %%cmdcmdline%%=%cmdcmdline%
echo %%cmdcmdline:~0,6%%=%cmdcmdline:~0,6%
echo %%cmdcmdline%%=%cmdcmdline%
echo;
echo %%temp%%=%temp%
echo %%temp:~0,6%%=%temp:~0,6%
echo %%temp%%=%temp%
echo;
echo %%cd%%=%cd%
echo %%cd:~0,6%%=%cd:~0,6%
echo %%cd%%=%cd%
echo;
pause
cls

outputs:

Code: Select all

%cmdcmdline%=cmd /c ""C:\Documents and Settings\Administrator\Desktop\test19.bat" "
%cmdcmdline:~0,6%=cmd /c
%cmdcmdline%=cmd /c

%temp%=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp
%temp:~0,6%=C:\DOC
%temp%=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp

%cd%=C:\Documents and Settings\Administrator\Desktop
%cd:~0,6%=C:\Doc
%cd%=C:\Documents and Settings\Administrator\Desktop

Press any key to continue . . .

That third line, %cmdcmdline%=cmd /c, what madness is that?! This was on XP SP3. I was experimenting with cmdcmdline, considering ways to check it to determine the current execution state, when I noticed doing a substring on it permanently altered it; the clipped result propagated to child batch instances.

Obviously there are workarounds, but I don't suppose there's a technical explanation for why that's occurring, is there?

Edit: Not surprisingly, string replacements also permanently alter it.

Queue

Re: Destructive env var substring

Posted: 18 Feb 2013 05:49
by dbenham
Whoah - That is totally unexpected :shock:

Confirmed on Vista 64. And I get the same result on the command line as I do in a batch script. I also get the same result with delayed expansion.

I haven't a clue why that happens :?

That is a really interesting (and perhaps disturbing) find.


Dave Benham

Re: Destructive env var substring

Posted: 18 Feb 2013 06:59
by jeb
dbenham wrote:Whoah - That is totally unexpected :shock:

Yes, I never saw this (and I tested really much with cmdcmdline).

Confirmed on Win7 x64, too.
And the change is permanent, also after leaving the batch file, SETLOCAL/ENDLOCAL can't prevent the change.
The other tested pseudo variables seems not to be affected.
( Tested CD, __CD__, =exitcode, =exitcodeAscii )
=C: can't be tested, as the colon avoids changes

The implicit changing of the pseudo variable is done in the moment of the replacement, tested with an embedded <CR> character.

Code: Select all

setlocal EnableDelayedExpansion
echo ........1=!cmdcmdline!
echo ........2=!cmdcmdline:~0,1!
echo ........3=%cmdcmdline:*C=C-12345<###CR###>
678%
echo ........4=!cmdcmdline!
endlocal


The <###CR###> is a single CR character in the text file (placed with a good text editor).

Output wrote:........1=C:\Windows\system32\cmd.exe
........2=C
........3=C-12345678
678.....4=C-12345


Line 3 is expected, as the CR is removed before the echo can output the line.
But line4 shows, that the CR is in the pseudo variable now.

Very interesting effect :D, perhaps this can be used for storing data behind the endlocal barrier!
Or for more complex replacements ...

jeb

Re: Destructive env var substring

Posted: 18 Feb 2013 08:39
by dbenham
jeb wrote:Very interesting effect :D, perhaps this can be used for storing data behind the endlocal barrier!
Great idea :!:

This code will transport whatever text appears in the 1st parameter across the ENDLOCAL barrier (minus enclosing quotes), as long as the value does not contain !. The mechanism becomes dead once it is cleared (empty string) or begins with =. A value beginning with = could be resurrected as long as there is some other character available. But an empty value cannot be resurrected.

Code: Select all

@echo off
setlocal enableDelayedExpansion
for /f delims^=^ eol^= %%A in ("!cmdcmdline:~0,1!") do >nul echo "!cmdcmdline:*%%A=%~1!"
endlocal
setlocal enableDelayedExpansion
echo !cmdcmdline!

But I have one concern, what memory is being overwritten :?: Is there a danger if the value is longer than the original CMDCMDLINE length :?

jeb wrote:Or for more complex replacements ...

I'm intrigued. What are you thinking?


Dave Benham

Re: Destructive env var substring

Posted: 18 Feb 2013 09:08
by jeb
dbenham wrote: A value beginning with = could be resurrected as long as there is some other character available. But an empty value cannot be resurrected.

This can be solved with prefixing the data with a single character like a hash and remove it later.

dbenham wrote:But I have one concern, what memory is being overwritten :?: Is there a danger if the value is longer than the original CMDCMDLINE length :?

Good idea :idea: , that explains why some of my tests fails.

Code: Select all

C:\temp>echo %cmdcmdline:*C=X-123456789012345678901234567890%
X-123456789012345678901234567890md

C:\temp>cmd
                     ####  Here a message box pops up with "cmd.exe application error" ... (translated from german) #####
C:\temp>


dbenham wrote:jeb wrote:
Or for more complex replacements ...

I'm intrigued. What are you thinking?

Currently nothing concrete :) , but like the CR sample, it can be interessting to embedd characters, which is impossible in other situations.

jeb

Re: Destructive env var substring

Posted: 18 Feb 2013 09:13
by jeb
The buffer overflow can be avoided by setting the cmdcmdline to a longer string like.

Code: Select all

C:\temp>%comspec%\###############################################\..\..\cmd.exe
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. Alle Rechte vorbehalten.

C:\temp>echo %cmdcmdline%
C:\Windows\system32\cmd.exe\###############################################\..\..\cmd.exe


Btw.
The cmdcmdline even can survive an EXIT barrier :!:

jeb

Re: Destructive env var substring

Posted: 18 Feb 2013 11:55
by Liviu
Now, that's my kind of bug ;-) and thanks for sharing.

FWIW I found one (and only, so far) previous report of the same bug, including 'setlocal' survivability, at http://ss64.org/viewtopic.php?id=383. Also, fired up some old VMs and can confirm that the bug existed in both NT4 (sp6) and 2000 (sp4). This carbon-dates it back to last millennium. Remarkable.

Like jeb, and probably most users, I've toyed with %cmdcmdline% before. I've actually recommended a cmdcmdline substring at least once (http://groups.google.com/group/alt.msdos.batch.nt/msg/2c981b8eecc3c481), it just never crossed my mind to check the variable afterwards.

The idea of using it to return values across 'exit' boundaries is oh so tempting. Unfortunately, the threat of memory corruption with unpredictable consequences is quite real, as noted.

Liviu

Re: Destructive env var substring

Posted: 18 Feb 2013 12:49
by dbenham
Liviu wrote:The idea of using it to return values across 'exit' boundaries is oh so tempting. Unfortunately, the threat of memory corruption with unpredictable consequences is quite real, as noted.
Agreed :(


Given the observed behavior, I suppose this is to be expected:

Code: Select all

@echo off
setlocal
set "wmic=wmic process where "name='cmd.exe'" get commandLine, processId"
cmd /v:on /c %%wmic%% ^
& echo !cmdcmdline:~0,1! ^
& echo( ^
& %%wmic%%
-- OUTPUT --

Code: Select all

CommandLine                                                     ProcessId
"C:\Windows\System32\cmd.exe"                                   5332
cmd  /v:on /c %wmic% & echo !cmdcmdline:~0,1! & echo( & %wmic%  6364

c

CommandLine                    ProcessId
"C:\Windows\System32\cmd.exe"  5332
c                              6364


jeb wrote:Btw.
The cmdcmdline even can survive an EXIT barrier :!:
Surviving batch termination with EXIT /B, yes. But surviving shell termination with EXIT, I don't see how :?:


Dave Benham

Re: Destructive env var substring

Posted: 18 Feb 2013 14:40
by Liviu
dbenham wrote:Given the observed behavior, I suppose this is to be expected: [...] -- OUTPUT --

Code: Select all

CommandLine                    ProcessId
c                              6364

The same command line of "c" is reported by task manager (vista+) or sysinternals' process explorer. Wow.

It's a bit hard to explain why I find that unexpected without going into low level windows things, but... Basically, the above indicates that what gets overwritten (and possibly corrupted) is not just some working copy of the command line managed by cmd internally but, in fact, the master copy of the command line passed to the cmd.exe instance in its process environment block (RTL_USER_PROCESS_PARAMETERS::CommandLine in PEB::ProcessParameters for those curious). This ranks even higher now in my list of dumbest bugs.

Liviu

[ EDIT ] P.S. Just a couple of comments on practical ramifications...

This explains why %cmdcmdline% is the only variable affected, since it's the only one presumed to be constant during a given run. All other dynamic variables (%date%, %time%, "%=c:% etc) are subject to change. So, %cmdcmdline% must have some dedicated code to account for its unique status, and it looks like the error is in that code.

Given the way windows stores the master command line passed to a process, it'd be safe to overwrite it with any string shorter than or equal to the original. This may well confuse anyone looking at the command line in wmic/task-manager/process-explorer, but would not otherwise cause memory corruption, nor malfunctions (except maybe in batch code relying on cmdcmdline specifically). However, I would still advise against such trick, since it's technically a "buffer overflow vulnerability" which could be fixed in the next hotfix or service pack at any time.

Re: Destructive env var substring

Posted: 18 Feb 2013 16:15
by Queue
jeb wrote:Good idea :idea: , that explains why some of my tests fails.

Code: Select all

C:\temp>echo %cmdcmdline:*C=X-123456789012345678901234567890%
X-123456789012345678901234567890md

C:\temp>cmd
                     ####  Here a message box pops up with "cmd.exe application error" ... (translated from german) #####
C:\temp>


jeb

I couldn't reproduce this (on XP SP3). Looking at cmd's memory directly, there is a massive amount of null padding after the string that's getting clobbered. I'm going to see if I can make a string long enough to make it choke for me. I assume this differs per Windows version, based on how large your %path% env var is (it basically immediately precedes the clobbered string), and likely some other factors.

Edit - With a long enough string, it just makes cmd silently exit (once the string tries to write into memory that's off limits). Effectively, I just looped echo %cmdcmdline:cmd=xcmd% for a while. It had to be really long though, somewhere around 1256 characters long (it's a unicode string in memory, and I have roughly 2512 bytes of space before it hits memory addresses it's not allowed to write to).

Edit 2 - Ok, two strings are getting overwritten

Code: Select all

C:\WINDOWS\system32\cmd.exe
WinSta0\Default

No quotes on the first string, unlike the string that's being clobbered (that immediately precedes it), and regardless, overwriting either of these doesn't make anything break (again, for me, on XP SP3).

Edit 3 - It's also mildly worth noting that %cmdcmdline% often has a trailing space:

Code: Select all

echo [%cmdcmdline%]
["C:\WINDOWS\system32\cmd.exe" ]
when launched via shellexecute due to the standard exefile open command being "%1" %* (note the space between "%1" and %*).

Queue

Re: Destructive env var substring

Posted: 18 Feb 2013 17:47
by dbenham
Liviu wrote:
dbenham wrote:Given the observed behavior, I suppose this is to be expected: [...] -- OUTPUT --

Code: Select all

CommandLine                    ProcessId
c                              6364

The same command line of "c" is reported by task manager (vista+) or sysinternals' process explorer. Wow.

It's a bit hard to explain why I find that unexpected without going into low level windows things, but... Basically, the above indicates that what gets overwritten (and possibly corrupted) is not just some working copy of the command line managed by cmd internally but, in fact, the master copy of the command line passed to the cmd.exe instance in its process environment block (RTL_USER_PROCESS_PARAMETERS::CommandLine in PEB::ProcessParameters for those curious). This ranks even higher now in my list of dumbest bugs.
I couldn't think of any other place that CMD would store that information, which is why I checked WMIC in the first place.

Liviu wrote:Given the way windows stores the master command line passed to a process, it'd be safe to overwrite it with any string shorter than or equal to the original. This may well confuse anyone looking at the command line in wmic/task-manager/process-explorer, but would not otherwise cause memory corruption, nor malfunctions (except maybe in batch code relying on cmdcmdline specifically). However, I would still advise against such trick, since it's technically a "buffer overflow vulnerability" which could be fixed in the next hotfix or service pack at any time.
Microsoft makes no promises as to the veracity of the command line stored in the PEB. See Raymond Chen's The Old New Thing: How do I get the command line of another process? Some processes intentionally clear the info stored there, thus making the command line inaccessible to prying eyes. Which raises an interesting possible use for the "feature".


Dave Benham

Re: Destructive env var substring

Posted: 18 Feb 2013 18:38
by Liviu
dbenham wrote:
Liviu wrote:Basically, the above indicates that what gets overwritten (and possibly corrupted) is [...] in fact, the master copy of the command line passed to the cmd.exe instance in its process environment block
I couldn't think of any other place that CMD would store that information, which is why I checked WMIC in the first place.
And I was confident enough that cmd would make its working copy of it that I didn't think to check ;-)

dbenham wrote:
Liviu wrote:Given the way windows stores the master command line passed to a process, it'd be safe to overwrite it with any string shorter than or equal to the original. [...] I would still advise against such trick, since it's technically a "buffer overflow vulnerability" which could be fixed in the next hotfix or service pack at any time.
Microsoft makes no promises as to the veracity of the command line stored in the PEB.
They actually do. The API level docs for CreateProcess explain in some detail how the string is built and passed so that "the new process can use GetCommandLine to retrieve the entire command line" (http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx). That much is guaranteed to hold true when the process is started. However, the string that GetCommandLine returns when called from the newly started process is writeable - not read only - so the process itself can technically modify it (http://msdn.microsoft.com/en-us/library/windows/desktop/ms683156(v=vs.85).aspx - return type is a LPTSTR not a LPCTSTR). Sometimes that string is a copy, for example when the process calls the ANSI flavor of GetCommandLineA. Other times, such as calling GetCommandLineW, it appears to be an actual pointer to the original buffer. Which is exactly what happens here, albeit because of a cmd bug since I can't see anyone making string extraction or substitution a "destructive" operation on purpose.

dbenham wrote:Some processes intentionally clear the info stored there, thus making the command line inaccessible to prying eyes. Which raises an interesting possible use for the "feature".
Yes, but possibilities are limited by the original length of the %cmdcmdline% which could be as low as 3 characters for "cmd" alone.

To clarify my previous "advise against" stand, I was referring to using %cmdcmdline% for unrestrained string storage, without regard to its original length. Anything that goes over that original length has the potential to corrupt memory in unpredictable ways. I am aware that the allocated block is usually larger than the precise length - as Queue noted - but that's up to the heap manager in charge at the time, subject to change, and not something that can be relied upon.

Liviu

Re: Destructive env var substring

Posted: 19 Feb 2013 03:39
by Queue
Just some fun stuff before I hit the hay:

Code: Select all

@echo off

echo %cmdcmdline%

echo;
echo;|echo %%cmdcmdline%%

echo;
set cmdcmdline=%cmdcmdline%
echo %cmdcmdline:~1,5%
set cmdcmdline=
echo %cmdcmdline%

echo;
:: %cmdcmdline:.=,%
echo %cmdcmdline:"=%&echo %cmdcmdline:~1,5%&call(%cmdcmdline:* =!%&echo %cmdcmdline%

echo;
pause
cls

outputs:

Code: Select all

cmd /c ""C:\Documents and Settings\Administrator\Desktop\test20.bat" "

C:\WINDOWS\system32\cmd.exe /S /D /c" echo %cmdcmdline%"

md /c
cmd /c ""C:\Documents and Settings\Administrator\Desktop\test20.bat" "

cmd /c C:\Documents and Settings\Administrator\Desktop\test20,bat
md /c
!/c

Press any key to continue . . .

For the last 3 echo'd lines, note that most of it is all put on one line via &'s. The output isn't surprising, just potentially usable.

Queue

Re: Destructive env var substring

Posted: 19 Feb 2013 06:13
by dbenham
Queue wrote:For the last 3 echo'd lines, note that most of it is all put on one line via &'s. The output isn't surprising, just potentially usable.

Yes, normal expansion usually cannot see changes made by prior statements within the same block of parsed code. But since the expansion is modifying the source, it does see the changes in this case.

The same effect can be achieved with a single statement, even a REMark :)
The REM below makes two changes to CMDCMDLINE.

Code: Select all

@echo off
echo %cmdcmdline%
rem %cmdcmdline:~0,1% %cmdcmdline:"=hello world%
echo %cmdcmdline%
--OUTPUT--

Code: Select all

"C:\Windows\System32\cmd.exe"
hello world


Dave Benham

Re: Destructive env var substring

Posted: 19 Feb 2013 06:28
by dbenham
Liviu wrote:
dbenham wrote:Some processes intentionally clear the info stored there, thus making the command line inaccessible to prying eyes. Which raises an interesting possible use for the "feature".
Yes, but possibilities are limited by the original length of the %cmdcmdline% which could be as low as 3 characters for "cmd" alone.

I wasn't thinking about returning a value. I was considering clearing the value in case the command line contains sensitive information. Something like:

Code: Select all

@echo off
echo before = %cmdcmdline%

:loop
if "%cmdcmdline:~0,-1%" neq "" goto :loop

echo after = %cmdcmdline%

I'm hoping the memory is filled with zeros at this point.


Dave Benham