TL;DR:
CreateProcess(?, ?, ?, ...) for:
Pass current process params (i.e. "batchfile" %*)
correctly connect stdin and stdout
creation flags?
I have the following problem:
I need to launch a given 3rd party executable with a custom environment and custom parameters. (Both semi-fixed)
I cannot use a batch file, because the (again, 3rd party) side invoking the module directly calls CreateProcess
I need to pass on any additional paramers passed
So, what I'd like to do is create a very simple executable launcher that would be the equivalent of a batch file like:
set PATH=...
set WHATEVER=...
...\3rd-pty-tool.exe -switch1 -switch2 %*
exit /B %ERRORLEVEL%
And I certainly don't want to mess with any bat2exe converter stuff - just too ugly when I have Visual Studio around anyway.
Running another executable via CreateProcess is trivial in principle:
STARTUPINFO info={sizeof(info)};
PROCESS_INFORMATION processInfo;
if (CreateProcess(?, ?, ?, ?, ?, ?, ?, ?, &info, &processInfo))
{
WaitForSingleObject(processInfo.hProcess, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
Setting up the environment for the child process via _putenv et al. is also pretty easy.
What is not trivial to me is however what to pass on to CreateProcess:
BOOL WINAPI CreateProcess(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
How to I get at the %* equivalent for the current Win32 process?
Pass only lpApplicationName, only lpCommandLine or both?
What to do about handle inheritance and creation flags?
How to I correctly forward / return stdin and stdout?
Not a dupe: CreateProcess to execute Windows command
Should be reasonably straightforward.
BOOL WINAPI CreateProcess(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
Let's take it in order.
lpApplicationName - if you have the full path to the executable you want to run, put it here. That ensures that you get the executable you were expecting, even if another executable with the same name is on the PATH.
lpCommandLine - the first element is the executable name. If you've specified lpApplicationName this doesn't need to be fully qualified, or even be the executable's actual name, but it does need to be present. This must be a writable buffer, it cannot be a constant string.
If your extra arguments can go at the end of the command line, this is easy:
wchar_t buffer[1024];
wcscpy_s(buffer, _countof(buffer), GetCommandLine());
wcscat_s(buffer, _countof(buffer), L" -switch1 -switch2");
Otherwise, you'll need to parse the command line to find the right place to insert the arguments, something like this:
while (*lpCmdLine == L' ') lpCmdLine++;
while (ch = *lpCmdLine)
{
if (ch == L'"') for (lpCmdLine++; ch = *lpCmdLine; lpCmdLine++) if (ch == L'"') break;
if (ch == L' ') break;
lpCmdLine++;
}
while (*lpCmdLine == L' ') lpCmdLine++;
lpProcessAttributes and lpThreadAttributes - NULL.
bInheritHandles - TRUE, since you want the child to inherit the standard handles.
dwCreationFlags - none needed in your scenario, so 0.
lpEnvironment - NULL to pass the current environment. In some situations you'd want to explicitly construct a new environment, or a modified copy of your environment, but since your process exists only to launch the child that shouldn't be necessary.
lpCurrentDirectory - NULL to inherit your current directory.
lpStartupInfo - call GetStartupInfo to fill this out with the same settings as the current process, or just leave it empty as in the code you posted.
lpProcessInformation - this is an output parameter, used as shown in your code. In your scenario, where one application is standing in for another, you might want to keep the process handle and use it to wait for the child process to exit before exiting yourself. (This isn't necessary if you know that your parent won't get confused if you exit before your child does.)
You don't need to do anything special about the standard handles, apart from ensuring that bInheritHandles is set. The default is for the child to keep the same standard handles as the parent.
Related
I'm analyzing a malware in IDA, where I observed the malware launching a MessageBox with negative value of hWnd (FFFFFFF7h).
Message box arguments (Source:MSDN)
int WINAPI MessageBox(
_In_opt_ HWND hWnd,
_In_opt_ LPCTSTR lpText,
_In_opt_ LPCTSTR lpCaption,
_In_ UINT uType
);
hWnd stands for owner window, I wasn't able to find what it means when hWnd argument is negative. Any help is very much appreciated.
MSDN -> "GetDlgCtrlID function" -> Remarks:
"... Although GetDlgCtrlID may return a value if hwndCtl is a handle to a top-level window, top-level windows cannot have identifiers and such a return value is never valid."
It seems it is wrong information - "never valid".
At least for Win2k...Win8 this return value is just kernel pointer to hmenu(bar).
And my question is (primarily to MS insiders): why MSDN so inaccurate here?
(Screenshot: http://files.rsdn.ru/42164/gwl(-1)_tagwnd.png)
Upd (tagWND):
Also demo: http://files.rsdn.ru/42164/gwl(-1)_tagwnd.zip
It is not inaccurate. You create a top-level window with CreateWindowEx(). Which looks like this:
HWND WINAPI CreateWindowEx(
_In_ DWORD dwExStyle,
_In_opt_ LPCTSTR lpClassName,
_In_opt_ LPCTSTR lpWindowName,
_In_ DWORD dwStyle,
_In_ int x,
_In_ int y,
_In_ int nWidth,
_In_ int nHeight,
_In_opt_ HWND hWndParent,
_In_opt_ HMENU hMenu,
_In_opt_ HINSTANCE hInstance,
_In_opt_ LPVOID lpParam
);
Note how you don't specify the ID anywhere. But the fine print is in the description for the hMenu argument:
A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The application determines the child-window identifier; it must be unique for all child windows with the same parent window.
So you can have a menu OR a child ID. Overloaded, pretty common in the winapi, a child control can't have a menu and a toplevel window can't have a child ID. If you forge ahead and ignore this and call GetDlgCtrlID() on a toplevel window anyway then you will get back the value of the hMenu argument you specified in the create call. Well, today, always follow the api or you might get a rude surprise some day, you'd of course use GetMenu() instead.
I am running on 64-bit Windows 7.
I want to get the FileID associated with some of the NTFS System Files. Some of them ("$Mft", "$MftMirr") I can open but others ("$LogFile" and "$Bitmap") fail with an "access denied" error or invalid parameter ("$Volume").
I have tried doing that as an administrator and running as a local service but they still fail.
I am using the following:
/* open the file for checking the File ID */
h = CreateFileW (
argv[i] , // _In_ LPCTSTR lpFileName,
FILE_READ_ATTRIBUTES , // _In_ DWORD dwDesiredAccess,
FILE_SHARE_DELETE |
FILE_SHARE_READ |
FILE_SHARE_WRITE , // _In_ DWORD dwShareMode,
NULL , // _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
OPEN_EXISTING , // _In_ DWORD dwCreationDisposition,
FILE_ATTRIBUTE_NORMAL| FILE_FLAG_BACKUP_SEMANTICS, // _In_ DWORD dwFlagsAndAttributes,
NULL // _In_opt_ HANDLE hTemplateFile
);
if (h == INVALID_HANDLE_VALUE) {
err = GetLastError();
wprintf(L"Can't open: '%s'. err:%u(0x%x)\n", argv[i], err, err);
continue;
}
I did set the following privileges as well (I know it is an overkill but I didn't know which one would work): SE_TCB_NAME, SE_DEBUG_NAME, SE_SECURITY_NAME, SE_BACKUP_NAME, SE_RESTORE_NAME, SE_MANAGE_VOLUME_NAME
You can use fget or ntfscopy that are mentioned here:
http://blog.opensecurityresearch.com/2011/10/how-to-acquire-locked-files-from.html
I have a test that creates a series of folders in a loop until it exceeds the MAX_PATH (260). This returns ERROR_PATH_NOT_FOUND(0x3). We have a build machine that runs this test but on the build machine it returns ERROR_FILENAME_EXCED_RANGE (0xce).
My machine is Windows 7 but the build machine is Vista. Could that be why they return different values? If not, does anyone know why this might happen?
EDIT: I am expecting to get an error, im testing a file system driver. I just do not understand why i am getting two different error codes from the same test on different machines.
Here is the code
homeDir << "C:\Users\me\TestFolder";
string childDir = "\\LongChildDirectoryName";
string dir = homeDir.str();
DWORD lastErr = ERROR_SUCCESS;
while(lastErr == ERROR_SUCCESS)
{
int len = dir.size();
if(len > (MAX_PATH - 12))
{
CuFail(tc, "Filepath greater than max allowed should be");
}
dir += childDir;
if(!CreateDirectory(dir.c_str(), NULL))
{
lastErr = GetLastError();
if (lastErr == ERROR_ALREADY_EXISTS)
lastErr = ERROR_SUCCESS;
}
}
CuAssert(tc, "Check error is ERROR_PATH_NOT_FOUND", lastErr == ERROR_PATH_NOT_FOUND);
The logic is flawed. If homeDir.str() returns a name that doesn't exist, the return value from CreateDirectory will be ERROR_PATH_NOT_FOUND. You can demonstrate the problem by simply doing this:
string childDir("\\LongChildDirectoryName");
string dir("foo");
The CreateDirectory call will then get the path foo\LongChildDirectoryName, and if foo doesn't exist, you get ERROR_PATH_NOT_FOUND. The fix is simply to add this before the while loop:
CreateDirectory(dir.c_str(), NULL);
You also need to move the length check after the strings have been concatenated, not before. Using the "\\?\" syntax Alex suggested would also be a good idea.
To use longer paths you need to use the "wide" version of CreateFile(), CreateFileW().
See this MSDN article on the topic:
HANDLE WINAPI CreateFile(
__in LPCTSTR lpFileName,
__in DWORD dwDesiredAccess,
__in DWORD dwShareMode,
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
__in DWORD dwCreationDisposition,
__in DWORD dwFlagsAndAttributes,
__in_opt HANDLE hTemplateFile
);
lpFileName [in]
The name of the file or device to be created or opened.
In the ANSI version of this function, the name is limited to MAX_PATH characters.
To extend this limit to 32,767 wide characters, call the Unicode version of the
function and prepend "\\?\" to the path. For more information, see Naming Files,
Paths, and Namespaces.
Can someone please tell me how this was done? Take a look at the "Command Line" for SmcGui.exe. You will notice that it's a Named Pipe string... The full string is:
\\.\pipe\SygateSecurityAgentR41T67564 \\.\pipe\SygateSecurityAgentW18467T67564
From the MSDN docs on [CreateProcess][1]
BOOL WINAPI CreateProcess(
__in_opt LPCTSTR lpApplicationName,
__inout_opt LPTSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCTSTR lpCurrentDirectory,
__in LPSTARTUPINFO lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
lpApplicationName [in, optional]
The name of the module to be
executed. This module can be a Windows-based application. It can be
some other type of module (for example, MS-DOS or OS/2) if the
appropriate subsystem is available on the local computer.
The string
can specify the full path and file name of the module to execute or it
can specify a partial name. In the case of a partial name, the
function uses the current drive and current directory to complete the
specification. The function will not use the search path. This
parameter must include the file name extension; no default extension
is assumed.
The lpApplicationName parameter can be NULL. In that case,
the module name must be the first white space–delimited token in the
lpCommandLine string.