If I start an Electron program that has an argument with a colon, the program immediately exits.
Process process = new Process();
process.StartInfo.FileName = "C:\\Program Files (x86)\\SomeElectronApp.exe";
process.StartInfo.Arguments = "ab:c d";
process.Start();
process.WaitForExit();
Console.WriteLine(1); // break point here
However, if I swap the arguments such that the one with the colon always comes last, then the program starts successfully.
This is mentioned here:
[This] is a security mitigation against an age old windows flaw
Does anyone have insight into what this flaw is?
Update:
Found the source code here:
The function CheckCommandLineArguments has the logic:
else if (IsUrlArg(argv[i])) {
block_args = true;
}
Where IsUrlArg does:
// colon indicates that the argument starts with a URI scheme
if (c == ':') {
// it could also be a Windows filesystem path
if (p == arg + 1)
break;
return true;
}
The "flaw" is most likely unquoted filenames containing whitespace.
Consider
> "C:\Program Files (x86)\SomeElectronApp.exe" "C:\Program Files (x86)\SomeElectronFile.dat"
vs
> "C:\Program Files (x86)\SomeElectronApp.exe" C:\Program Files (x86)\SomeElectronFile.dat
or
> C:\Program Files (x86)\SomeElectronApp.exe C:\Program Files (x86)\SomeElectronFile.dat
If by mistake you have set up a file association as
path\to\exe %1
instead of
path\to\exe "%1"
then every program using the ShellExecute function will pass you an unquoted filename, even if it contains whitespace.
The thing that makes it a "windows" flaw is that on Windows, a single command-line string is passed to the spawned program, which is responsible for breaking it up into the argv array. In contrast most other OSes pass an argument array, so the launching program has to go to some effort to break filenames into multiple words.
Found the answer from looking at the commit.
It was done to fix a remote code execution vulnerability (CVE-2018-1000006). Source:
Affected versions of electron may be susceptible to a remote code execution flaw when certain conditions are met:
The electron application is running on Windows.
The electron application registers as the default handler for a protocol, such as nodeapp://.
This vulnerability is caused by a failure to sanitize additional arguments to chromium in the command line handler for Electron.
blog post here
Related
I want to have my C# (Xamarin) program run an EXE or batch (BAT) file. The user will be running my program, and will click on one of several buttons, some of which open Web pages and others of which run external programs. These files will be on the same computer as the one running the main program and don't need greater permissions. The overall program will be in Windows, UWP.
I already have code to pull info from the database saying "the button the user clicked references a program and it's (eg) C:\Tools\MyTool.exe". (Real path more like (C:\Users\Me\source\repos\ProductNameV2\ProductName\ProductName.UWP\Assets\EXE\whatever.exe".) I used a "demo.bat" file containing nothing but echo and pause statements, or references to a built-in Windows program like Notepad or Calc that an ordinary command prompt can recognize without an explicit path (ie. that's part of the recognized system Path). Yes, the real path to the dummy file does exist; I checked. I've also explicitly added files demo.bat and dummy.txt to my C# project.
Here's roughly what I've tried so far to actually run a batch file, or an EXE, or just to try opening a text file. Nothing works.
1)
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\demo.bat"); // Returns false.
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\dummy.txt"); // Returns true.
await Launcher.OpenAsync(#"file:///C:\Tools\demo.bat") // Seems to do nothing; silently fails.
await Launcher.OpenAsync(#"file:///C:\Tools\dummy.txt") // Same.
2)
Process batchProcess = new Process();
batchProcess.StartInfo.FileName = #"file:///C:\Tools\demo.bat"; // Same result with notepad.exe
batchProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
batchProcess.Start();
batchProcess.WaitForExit();
// Result: "Access is denied" error during Start().
3)
var otherProcessInfo = new ProcessStartInfo(#"file:///C:\Tools\demo.bat")
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
// Result: "The system cannot find the file specified" despite it being the same path as in previous examples.
// Also tried literally using the path C:\Tools\demo.bat, without adding that to the C# project.
// One thing that slightly works is to use:
var otherProcessInfo = new ProcessStartInfo("cmd.exe", "/c echo Hello world!");
// This version opens a window and instantly closes it again. With "/c pause" instead, it opens, saying "press any key to continue".
// Chaining multiple commands with newline or semicolon characters doesn't work as a form of batch file.
So: the only tiny success I've had here is to run cmd.exe, to run a one-line command. I suppose that depending on what the batch file must do, there's some possibility of receiving a string, breaking it into lines, then running cmd.exe using method 3 to call them one at a time. Which is ugly at best.
Is there some better way to do this -- to run a batch file or an EXE from within my program?
EDIT: Yes, I did in fact look at documentation before asking. Why did I use URIs? Because of multiple errors telling me that the simple path strings ("C:\this\that") I was using were in an "Invalid URI format". Using Process.Start("notepad.exe") silently fails, doing nothing. Using a method involving System.Diagnostics.Process (found at How to run external program via a C# program? and yes I saw that before) fails with an error of "Access denied" when using my batch file reference, or silently failing (no window opens) using plain old notepad.exe. I avoided setting Process options that say hide the window.
So to rephrase: Is there a way to make my program run some EXE somewhere on the computer, or to run a batch file that has more than one command in it? What is that way?
Using the data you collected, I was able to run a batch file by doing the following:
var strPathToExeOrBat = System.IO.Path.Combine("C:\\Tools", "demo.bat");
var otherProcessInfo = new ProcessStartInfo("cmd.exe", $"/c call \"{strPathToExeOrBat\"");
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
I also think it would be helpful to review the capabilities of the cmd.exe application.
I found this post to be helpful:
https://stackoverflow.com/questions/515309/what-does-cmd-c-mean#:~:text=%2FC%20Carries%20out%20the%20command%20specified%20by%20the%20string%20and,switches%20by%20typing%20cmd%20%2F%3F%20.
In particular the /k option will leave the window open, if you don't want it to close after running a script.
Thank you very much for your question! It really helped me find the answer to this! (at least for my situation of a .NET MAUI windows app, but MAUI is built off of Xamarin.Forms, so you shouldn't have a problem doing the same thing)
EDIT: Updated to use file path from question and string interpolation with System.IO.Path.Combine for slightly greater cross platform capability
My question may seem as a straight forward one, because it was asked many times before.Anyway I think my case is completely different and I can't find any intuition about it. I have an exe file compiled from a code written in assembly language and I want to run this exe using another code and capture its output. I did this using C# and here is the code for it:
static string runCommand(string command, string args)
{
if (File.Exists(command))
{
Process p = new Process();
p.StartInfo.FileName = command;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.Arguments = args;
p.Start();
p.WaitForExit();
string output = p.StandardOutput.ReadToEnd();
return output;
}
return "";
}
The code for the target exe file is pretty simple (I'm using Irvine library):
INCLUDE Irvine32.inc
.data
.code
main proc
call dumpregs
exit
main ENDP
end main
Where command is the exe file path, args is the arguments passed to the exe file (be noted that no arguments required by this exe)
The output is always equal to "" !. When I run the exe using command prompt, the console output is definitely not empty.Here is what I get:
I also tried to capture the console output using python but the returned string is awlays empty.
I have done this several times but the exe file was written in C# instead of assembly language, but I think it shouldn't be any difference exist.
EDIT
Solutions attempted so far and suggested by hatchet:
Capturing console output from a .NET application (C#)
How to spawn a process and capture its STDOUT in .NET? [duplicate]
Process.start: how to get the output?
None of them worked for me
If you need any further information let me know in the comments
Your C# code is correct. The standard way of capturing the output of a console process is to redirect the standard output by setting the process object's StartInfo.RedirectStandardOutput property to true, as described here. This is exactly what you have done.
The problem, as correctly diagnosed by rkhb, is that you're using the Irvine32 library in the auxiliary process, and its implementation of dumpregs calls the Win32 WriteConsole function, which cannot be redirected. If you attempt to redirect the standard output (e.g., to a pipe or file), then WriteConsole will fail with ERROR_INVALID_HANDLE, as documented on MSDN:
ReadConsole and WriteConsole can only be used with console handles; ReadFile and WriteFile can be used with other handles (such as files or pipes). ReadConsole and WriteConsole fail if used with a standard handle that has been redirected and is no longer a console handle.
Like rkhb's comment, this except from the documentation also hints at the solution. To support redirection, you need to change the implementation of dumpregs to call WriteFile instead of WriteConsole. This is a more general function that can write to any type of handle, including the standard console output (a la WriteConsole) or any other type of object that you might redirect the output to. Once you've made this change, you will, of course, need to rebuild the Irvine32 library.
The only significant limitation of WriteFile is that it doesn't support Unicode output like WriteConsole does, but that isn't a problem in your case. The output of dumpregs is all ANSI.
This is a silly and tricky issue that I am facing.
The below code works well (it launches Calculator):
ProcessStartInfo psStartInfo = new ProcessStartInfo();
psStartInfo.FileName = #"c:\windows\system32\calc.exe";
Process ps = Process.Start(psStartInfo);
However the below one for SoundRecorder does not work. It gives me "The system cannot find the file specified" error.
ProcessStartInfo psStartInfo = new ProcessStartInfo();
psStartInfo.FileName = #"c:\windows\system32\soundrecorder.exe";
Process ps = Process.Start(psStartInfo);
I am able to launch Sound Recorder by using Start -> Run -> "c:\windows\system32\soundrecorder.exe" command.
Any idea whats going wrong?
I am using C# in Visual Studio 2015 and using Windows 7 OS.
UPDATE 1: I tried a File.Exists check and it shows me MessageBox from the below code:
if (File.Exists(#"c:\windows\system32\soundrecorder.exe"))
{
ProcessStartInfo psStartInfo = new ProcessStartInfo();
psStartInfo.FileName = #"c:\windows\system32\soundrecorder.exe";
Process ps = Process.Start(psStartInfo);
}
else
{
MessageBox.Show("File not found");
}
Most likely your app is 32-bit, and in 64-bit Windows references to C:\Windows\System32 get transparently redirected to C:\Windows\SysWOW64 for 32-bit apps. calc.exe happens to exist in both places, while soundrecorder.exe exists in the true System32 only.
When you launch from Start / Run the parent process is the 64-bit explorer.exe so no redirection is done, and the 64-bit C:\Windows\System32\soundrecorder.exe is found and started.
From File System Redirector:
In most cases, whenever a 32-bit application attempts to access %windir%\System32, the access is redirected to %windir%\SysWOW64.
[ EDIT ] From the same page:
32-bit applications can access the native system directory by substituting %windir%\Sysnative for %windir%\System32.
So the following would work to start soundrecorder.exe from the (real) C:\Windows\System32.
psStartInfo.FileName = #"C:\Windows\Sysnative\soundrecorder.exe";
Old thread but providing one more possible case
In my case i was using arguments inside Process.Start
System.Diagnostics.Process.Start("C:\\MyAppFolder\\MyApp.exe -silent");
I changed it to
ProcessStartInfo info = new ProcessStartInfo("C:\\MyAppFolder\\MyApp.exe");
info.Arguments = "-silent";
Process.Start(info)
Then it worked.
One more case, similar to Ranadheer Reddy's answer, but different enough to trip me up for awhile.
I was making a simple mistake. I had this:
ProcessStartInfo info = new ProcessStartInfo("C:\\MyAppFolder\\MyApp.exe ");
info.Arguments = "-silent";
Process.Start(info);
See that space after the end of the path to the app? Yeah. It doesn't like that. It will not find your file if you include that.
The solution was to remove the extraneous space. Then it worked.
This is an easy enough mistake to make if you're converting an app from starting processes by launching "cmd.exe /c MyApp.exe -silent" to running "MyApp.exe" directly instead, which is what I was doing. I hope recording my misfortune here will help future developers.
Summary:
A c# app is supposed to call gpg.exe to encrypt a file before sending it out. In testing we found a computer configuration where gpg consistently fails.
The goal I have is to create a wrapper batch file that calls GPG but prints the command line args to an output file, and have our app call this.
The C# code for calling the command:
public static int StartProcess(string command, string arguments, int waitForProcess)
{
ProcessStartInfo startInfo = null;
if (string.IsNullOrEmpty(arguments))
startInfo = new ProcessStartInfo(command);
else
startInfo = new ProcessStartInfo(command, arguments);
startInfo.CreateNoWindow = true;
startInfo.ErrorDialog = false;
startInfo.UseShellExecute = false;
Process p = new Process();
p.StartInfo = startInfo;
p.Start();
Note that I have no control over what filename the system is expecting as this is production code: we are expecting command to be C:\Program Files\gnupg\gpg.
I renamed the real gpg.exe to gpg2.exe, and created a batch file (ignore the terrible batch code unless relevant please. It's supposed to be a quick diagnostic rather than a robust solution):
#echo off
ECHO %* > "C:\Program Files\GNU\GnuPG\gpgoutput.txt"
"C:\Program Files\GNU\GnuPG\gpg2" %1 %2 %3 %4 %5 %6 %7 %8 %9 >> gpgoutput.txt
if ERRORLEVEL 1 echo 1 >> gpgoutput.txt
if ERRORLEVEL 0 echo 0 >> gpgoutput.txt
if ERRORLEVEL 2 echo 2 >> gpgoutput.txt
exit /b 0
Turns out p.Start() will throw an error and isn't able to find the batch file. So, I downloaded bat_to_exe_converter to convert the bat file to exe format. I Saved the file as GPG.exe
Process flow should now be C# app calls 'gpg.exe' > calls gpg2.exe and logs the variables/exit code.
This code runs fine from a command prompt but when called from the c# app, the exit code is 9211 and I can see that the gpgoutput.txt file is not created, implying that it doesn't actually run 'gpg.exe'
Why doesn't the above code get executed correctly from my C# application? My only thought is that this must be related to either permissions or the directory somehow.
EDIT: I have tried manually setting command to C:\Program Files\gnupg\gpg.bat and the code works fine. The issue seems to be caused by
a) I get an error if it is only looking for C:\Program Files\gnupg\gpg and the file is saved as gpg.bat "the system cannot find the file specified"
b) If I convert the batch to gpg.exe, the system CAN find the file, but gives exit code ~9200. Is this the fault of the exe converter?
First, I would not use the batch-file-converted-to-exe. All that's going to do is make things harder.
Second, you should be able to run a batch file using Process. With ProcessStartInfo.UseShellExecute set to true, I would expect it to "just work". But even barring that, you should be able to execute the command-line interpreter, as "cmd.exe /c ..." where the "..." is the full command to execute the batch file. If you are still having problems there, you might ask that as a different question.
Finally, as far as your wrapper batch file goes, the most obvious problem is that you are explicitly setting the exit code to 0 with exit /b 0. If you want the exit code from the gpg2.exe process to be returned, you need to save it immediately after running gpg2.exe (e.g. set EXITCODE=%errorlevel%), and then use that value when you exit the batch file (e.g. exit /b %EXITCODE%).
If you return the actual exit code for the process from your batch file, then the C# program should be able to read the code just fine.
(Saving the value immediately ensures that other statements later in the batch file, which could also affect the errorlevel value, won't interfere with you returning the correct exit code.)
I’m trying to launch an application (Operating System, My Application and the application I want to launch are all 32 bits), from .NET 3.51.
The code that launches the Process is used for other applications, but there’s one that is giving us a headache. If we “double click” on the application’s icon, it works as expected, meaning that it works fine as an application in the computer. Double clicking the .exe directly, also works.
The operating system is Windows 7 32Bits (Home and/or Professional).
Our .NET application is compiled with x86 to avoid problems.
The code that launches “Processes” is located inside a DLL (also 32 bits) made by us, basically it’s a simple DLL that holds some “Common Code” across the board, common methods, functions and stuff we use throughout our code. One of those methods look like this:
public static bool FireUpProcess( Process process, string path, bool enableRaisingEvents,
ProcessWindowStyle windowStyle, string arguments )
{
if ( process != null )
{
try
{
process.StartInfo.FileName = #path;
if ( arguments != null )
{
if ( arguments != String.Empty )
{
process.StartInfo.Arguments = arguments;
}
}
process.StartInfo.WindowStyle = windowStyle;
process.EnableRaisingEvents = enableRaisingEvents;
process.Start();
}
catch
{
try
{
process.Kill();
}
catch ( InvalidOperationException )
{
} // The process is not even created
return false;
}
}
else
{
return false;
}
return true;
}
I don’t know who wrote this method, but it has been working for roughly six years with different applications, therefore I assume it’s “ok”. However, we have a customer with a piece of software that won’t launch when passed through that argument.
The arguments are:
process is a System.Diagnostics.Process created with a simple "new Process();”
path is a full path to the .exe “c:/path/to/my.exe”.
enableRaisingEvents is false
windowStyle is Maximized (but have tried others).
It gives a crappy MessageBox… which I have happily immortalized. It’s in spanish but the translation ought to be easy:
It says:
Application Error
An unexpected exception has occurred for the program (0x0eedfade) at …
Googling that 0x0eedfade gives strange results that look scary, but the truth is, if I go to the .exe that I’m trying to launch and double click it, it works perfectly.
For The Record: If I try to launch other things (I.e.: Notepad.exe, Adobe Acrobat Reader) it works, but Firefox doesn’t open and doesn’t show an error.
This “some work, some doesn’t” behavior leads me to believe that there might be a problem with a Windows 7 security mechanism or similar that I don’t know.
What am I missing or doing wrong?
UPDATE: Ok; I’ve gotten a copy of the software. It’s a messy software but it works. Now that I can debug, I see that the program gives an error when launched with my FireUpProcess method.
As suggested I added the WorkingDirectory code, but here’s the code:
public static bool FireUpProcess(Process process, string path, bool enableRaisingEvents, ProcessWindowStyle windowStyle)
{
if (process != null)
{
try
{
if ( !String.IsNullOrEmpty(#path) )
{
process.StartInfo.FileName = #path;
process.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(#path);
process.StartInfo.WindowStyle = windowStyle;
// Suscribe to the exit notification
process.EnableRaisingEvents = enableRaisingEvents;
// Disable to prevent multiple launchs
Framework.Check.LogWarning("LAUNCHING EXTERNAL DEVICE WITH PATH: " + path);
process.Start(); // HERE The program reports the following:
That means, “The program could not be started because ddip.dll is missing… try reinstalling bla bla”.
The thing is, if I execute the same #path from the command line, the program opens perfectly:
That opens the program. And the same happens if I click on the “shortcut” that it’s located in the “programs” menu. There aren’t any parameters in that shortcut, it’s a simple call to the executable file.
So the question is now: What is the difference between my code and the other methods?
There has got to be something different that causes my process not to start.
Any ideas?
UPDATE AND SOLUTION
I made it work by using one of the below provided answers. Turns out that none directly pointed me to the solution, but they all gave me good ideas here and there.
I added an app manifest to our application (should have had it since the age of vista, don’t know why it wasn’t there in the 1st place). The app manifest I added by using VStudio 2008 add file -> app manifest.
In it, I made sure we have this:
<requestedExecutionLevel level=“asInvoker” uiAccess=“false” />
We don’t need admin or anything like that, but apparently Vista/7 need to know it.
After that was added, the process is correctly launched.
note: UseShellExecute is true by default (as suggested by some), you have to explicitly turn it to false if that’s what you want.
You are not setting the process.StartInfo.WorkingDirectory property. There's plenty of poorly written software out there that assumes the working directory will be the directory in which the EXE is stored. At least add this line:
process.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(#path);
The exception is however rather strange. I'd definitely recommend you tell the customer to update their anti-malware tools.
If the exe has a manifest, you should set UseShellExecute to true on the process object before you call Start. It's not a bad idea in any case.
As Kate Gregory pointed out, if you want to "emulate" the user double clicking on the icon, you have to set UseShellExecute to true. Setting this flags make the code use a totally different path, using the underlying windows ShellExecute function.
Now, I will add to this, that if you're running on a UAC-equipped Windows (Vista, 7, 2008, ...) you maybe should also try to use the runas verb as explained here and here.
With .NET, that would be:
if (System.Environment.OSVersion.Version.Major >= 6) // UAC's around...
{
processStartInfo.Verb = "runas";
}
I've had similar problems in the past. I resolved it by executing the cmd app as follows:
public static bool FireUpProcess(Process process, string path, bool enableRaisingEvents, ProcessWindowStyle windowStyle)
{
//if path contains " ", surround it with quotes.
//add /c and the path as parameters to the cmd process.
//Any other parameters can be added after the path.
ProcessStartInfo psi = new ProcessStartInfo("cmd", "/c" + path ));
psi.WorkingDirectory = System.IO.Path.GetDirectoryName(#path);
psi.WindowStyle = windowStyle;
// Suscribe to the exit notification
process.EnableRaisingEvents = enableRaisingEvents;
// Disable to prevent multiple launchs
Framework.Check.LogWarning("LAUNCHING EXTERNAL DEVICE WITH PATH: " + path);
process.Start(); ...}
If it is possible I would try to use Process Monitor from Sysinternals. When you start it up you can deselect Registry and Network Activity on the toolbar (the 5 icons on the right side). Then you only see Process and Disk activity. Since it looks like a file not found problem you should use the Filter dialog (6. icon from the left) select Process Name from the Drop down list (Architecture is the default) and enter your failing executable name. This will greatly limit the captured output so you can see what is going on. Then start the exectuable and check in the Result Column for NAME NOT FOUND result. This are the locations where a file was searched but not found. If you know the offending dll name you can search for it with Ctrl+F as usual to dig it out. Then you can compare the different search paths from your working application and when it was started from your application.
Could it be that the environment variable PATH has a different value inside your process? It could be that adding . (the current directory) helps to fix the dll search path. Or is the application started from a different user account? It could also be the new feature that when an application is installing things into Programm Files but has no rights (only administrator can do this) Windows will redirect the writes into the user profile. This is a secure and transparent way to get more secure. But this could cause e.g. during first application startup some e.g. config file to be deployed into the Administrators Profile when he is running the application not with consent from the UAC dialog. Then other users might also start the application but fail because the additional config file is located in the Administrators profile and not in Program Files as expected for everyone.
I believe Hans Passant is on the right track. In addition to what he said, check to ensure that ddip.dll and the exe are in the same directory. This is not always the case as there are other ways to bind assemblies outside the bin. Namely, the GAC and AssemblyResolve event. Considering your situation I see no reason the GAC is involved. Check the exe's code that is launched for any hooks into the AssemblyResolve event. If it's hooked into you may need to update the implementation to allow another process to launch it.
Because you are getting an exception regarding a missing DLL, I have little confidence in the answers regarding path delimiter issues. Nonetheless, you have the application code, so verify that it references ddip.dll. This will give you a good deal of confidence that you are in fact referencing the correct .exe and therefore it's not just a path delimiter problem with the command prompt (E.G. misinterpreted spaces).