Is there an API call to prompt user for UAC elevation? - winapi

My app needs to write to a file in \ProgramData that could be protected. This only happens once after installation.
Is there an API function that would take ACL info and prompt the user to authorize the app to access the file? In other words the app would ask Windows to prompt the user for confirmation and authorization. This should happen interactively, and allow the app to resume execution after access to the file has been authorized. The app runs as Standard User, does not require Admin privilege.
The file gets opened with CreateFile().
Edit: There is a subtle difference between my query and the others said to be duplicates. I am asking for permission to access one specific object, a file. The others are asking to elevate the privileges of the whole process. Anyway, I am grateful for all responses which include a solution.

If you don't want to elevate your entire app, you have a few options:
spawn a separate elevated process just to access the file. Use ShellExecute/Ex() with the runas verb, or CreateProcessElevated(), to run a second copy of your app, or another helper app, with command-line parameters to tell it what to do. The main process can wait for the second process to exit, if needed.
create a COM object to access the file, and then use the COM Elevation Moniker to run the COM object in an elevated state.
prompt the user for credentials using CredUIPromptForCredentials() or CredUIPromptForWindowsCredentials() (see Asking the User for Credentials for more details), then logon to the specified account using LogonUser() to get a token, impersonate that token using ImpersonateLoggedOnUser(), access the file as needed, and then stop impersonating using RevertToSelf() and close the token with CloseHandle().

Thanks to #Remy for the ShellExecuteEx suggestion, here are the sordid details. Note the use of 'cmd' and the double-command, so the user only has to reply once. Also, [1] must wait for process completion otherwise you could find yourself creating the file before it was deleted, and [2] don't wait for the process if it failed.
// delete file with Admin privilege
// 'file_name' is path of file to be deleted
SHELLEXECUTEINFO shex;
char param[512];
char *cmd = "/C \"attrib -H \"%s\" && del /F /Q \"%s\"\""; // double command
_snprintf(param, sizeof(param), cmd, file_name, file_name);
ZeroMemory(&shex, sizeof(shex));
shex.cbSize = sizeof(shex);
shex.lpVerb = "runas"; // runas, open
shex.lpFile = "cmd"; // not 'del'
shex.lpParameters = param;
shex.nShow = SW_HIDE;
shex.fMask = SEE_MASK_NOCLOSEPROCESS;
BOOL retshx = ShellExecuteEx(&shex);
// wait otherwise could return before completed
if(retshx)
{ time_t st = clock();
DWORD exitCode;
do
{ if(!GetExitCodeProcess(shex.hProcess, &exitCode))
break;
if(clock() - st > CLOCKS_PER_SEC * 5) // max 5 seconds
break;
} while(exitCode != STATUS_WAIT_0); // STILL_ACTIVE
CloseHandle(shex.hProcess);
}

Processes can only be launched with an elevated token, they can't gain it after the fact. So you can either re-launch your app elevated with a command line argument telling it what to do (simple solution), or implement an out-of-proc COM server that you can create elevated and pass instructions to it (harder).
A third solution is to leverage the built-in UAC support of the IFileOperation interface, but this doesn't let you read/write, only copy. So you could make a copy of the file you need to modify, modify the copy and then use IFileOperation to copy the temporary over the original.

Related

Run a process as administrator from non admin user - local admin credentials

I have a Window 10 vb.net main process (tester) that needs to create a process to clean up certain registry area. The products we test have a single FTDI serial port adapter, but over time these instances build up and slow the registry to a crawl.
As the main process starts, the desire is to use an off the shelf utility, DeviceCleanupCmd.exe, to search and remove the driver instance build up. This requires administrator privileges for that sub process. I've been able to do this on XP with script, but the system is running with full administrator privileges. Since it's not on the network, there's very little risk.
Moving forward, we are trying to replace the XP system with a Windows 10 Enterprise box that is network connected. I wanted to incorporate the equivalent of the script into the vb.net process and I modified the vb.net application to use
Process() with StartInfo.Verb = "runas"
Local user credentials (not a domain user) the application provides to start the sub process.
Using this method, my sub process executes, but not with administrator level privileges. The Stackoverflow reference below explains that I can't use this method and have to use CreateProcessWithLogonW. I understand Ian Boyd's concerns for security in the post.
Run process as administrator from a non-admin application
I have followed the Microsoft implementation that follows and am still having issues.
https://learn.microsoft.com/en-us/troubleshoot/dotnet/visual-basic/start-process-as-another-user
Here's the essential part of my code. You will see that I truncated the MS example to use the W2K portion, as we won't be going back that far!
wUser = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(UserName + Chr(0)))
wDomain = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(DomainName + Chr(0)))
wPassword = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(Password + Chr(0)))
wCommandLine = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(CommandLine + Chr(0)))
wCurrentDir = System.Text.Encoding.Default.GetString(UnicodeStringToBytes(CurrentDirectory + Chr(0)))
Result = CreateProcessWithLogonW(wUser, wDomain, wPassword, CREATE_DEFAULT_ERROR_MODE, 0&, wCommandLine, CREATE_NEW_CONSOLE, 0&, wCurrentDir, si, pi)
If Result <> 0 Then
CloseHandle(pi.hThread)
CloseHandle(pi.hProcess)
W2KRunAsUser = 0
Else
W2KRunAsUser = Err.LastDllError
Status = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, CType(0&, IntPtr), W2KRunAsUser, 0, strMessage, Len(strMessage), 0)
MsgBox("CreateProcessWithLogonW() failed with error: " & W2KRunAsUser & " " & strMessage, vbExclamation)
End If
Please remember that my target administrator user is only resident on the PC I'm running the application on - not on the domain. I've verified that I can login with the credentials and run DeviceCleanupCmd.exe with success. In order to login locally, I must use a \ before the user. When I run the code, I've attempted the following, with the error message that Windows 10 supplies after the colon (:):
Providing "user", "password", and "domain" (I know it's not required): The directory name is invalid.
Providing "user", "password", and domain as "": The directory name is invalid.
Providing "\user", "password", and domain as "": The directory name is invalid.
Providing ".\user", "password", and domain as "": The stub received bad data.
Providing "user", "password", and domain as "computer name": The directory name is invalid.
Providing "non-admin user", "password", and "domain of user": DeviceCleanupCmd.exe runs but complains 'No admin privileges available'.
Because the application is complaining about an invalid directory name, I've purposely placed the DeviceCleanupCmd.exe in a C:\sub-directory that is not protected by Windows (Like Program Files). Attempt 6 above would seem to prove that there's not rights/access violations.
I have also tried CREATE_NEW_CONSOLE in place of CREATE_DEFAULT_ERROR_MODE and same results as above. I've proved the user is being decoded, as when I provide an incorrect password, I get "The user name or password is incorrect".
#Hurshey provided the insight to look at the Windows Task Scheduler and the following links support full configuring the task + incorporating the exported XML into NSIS - my target deployment method.
https://www.windowscentral.com/how-create-automated-task-using-task-scheduler-windows-10
https://nsis.sourceforge.io/Talk:Scheduled_Tasks

CreateFile returns Access_Denied in Win10

CreateFile() returns ACCESS_DENIED in Windows10 when i try in my project. If i create sample application using CreateFile() it works fine. Even tried to check admin privileges before calling CreateFile(), user is in admin mode. Same code works fine in Win 7.
Below is code sample:
WCHAR userPath[] = L"C:\\test.txt";
HANDLE hFile = NULL;
hFile = ::CreateFile(userPath,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
wprintf(L"Error HANDLE = 0x%x \n",hFile);
}
else
{
wprintf(L"Suceess HANDLE = 0x%x \n",hFile);
::CloseHandle(hFile);
}
The most obvious explanation is that your user simply does not have sufficient rights to create files at the root level of the system drive.
Since Windows 7, and possibly even Vista, the default security configuration of the system drive permits standard user to create folders at the root level, but not files. So, my hypothesis is that you are not running your process elevated as you claim, but are in fact running the process with standard user rights. In order for you to create a file at that location you will need to either:
Run the process with elevated rights, or
Modify the security settings of the volume.
I was running my application in browser. There was issue with IE settings. When i disabled protected mode from IE->Security->Enable protected Mode, CreateFile() worked. Another solution is adding the ip to trusted site in IE.

Running EXE application in GINA beofe login screen (Command line)

I have created a Credential Launcher for Windows 7 and was able to run Windows application after the Tile button click event, it was very easy.
I added a few registry settings and *pbAutoLogon = FALSE;.
However now i am now trying to do the same for Windows XP.
Which function I should target or how to achieve the same results ?
I see you tagged your question with "Gina", so I guess you know that Credential Providers do not exist on XP.
Your answer depends on when exactly you want to run that program, especially with regards to the secure attention sequence (SAS, or when a user press CTRL-ALT-Delete)
Before the SAS, use WlxDisplaySASNotice
After the SAS, use WlxLoggedOutSAS
Since you don't want to write a whole GINA yourself, you could use a custom Gina that wraps msgina.dll. Here is one I wrote, you can find the original I started from in the Platform SDK.
Using that approch, you get a chance to execute code just before or just after certain events, like running your program after a successful logon, something like :
int WINAPI WlxLoggedOutSAS(PVOID pWlxContext, DWORD dwSasType, PLUID pAuthenticationId, PSID pLogonSid, PDWORD pdwOptions, PHANDLE phToken, PWLX_MPR_NOTIFY_INFO pMprNotifyInfo, PVOID * pProfile)
{
int result;
result = pfWlxLoggedOutSAS(pWlxContext, dwSasType, pAuthenticationId, pLogonSid, pdwOptions, phToken, pMprNotifyInfo, pProfile);
if (result == WLX_SAS_ACTION_LOGON)
{
//We have a successful logon, let's run our code
run_my_custom_code();
}
return result;
}
There are some caveats, though :
The code cannot block. Winlogon will wait, but your users might not. Spanw a process and let it run.
Your program will be running with SYSTEM privileges, which is a security risk. Sandboxing your process could be hard. If you can't break out of it, don't assume nobody can...

How to elevate .net application permissions?

I have an application that would check for updates upon start and, if updates are found, it would copy some files over the network to the program files folder. Obviously such task can't be performed by Standard Users under normal scenarios.
I tried creating a service to do the update process but I had some security issues and I asked this question about it in superusers.
Now, considering the fact that most applications require elevated privileges to perform such task I think that might be the right approach. But how do I request elevation for the updater under all Windows version as of XP, included. I've found many topics about a manifest file, but since I need this to work with XP I can't create a solution specifically for UAC.
Privileges can only be elevated at startup for a process; a running process' privileges cannot be elevated. In order to elevate an existing application, a new instance of the application process must be created, with the verb “runas”:
private static string ElevatedExecute(NameValueCollection parameters)
{
string tempFile = Path.GetTempFileName();
File.WriteAllText(tempFile, ConstructQueryString(parameters));
try
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = true;
startInfo.WorkingDirectory = Environment.CurrentDirectory;
Uri uri = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase);
startInfo.FileName = uri.LocalPath;
startInfo.Arguments = "\"" + tempFile + "\"";
startInfo.Verb = "runas";
Process p = Process.Start(startInfo);
p.WaitForExit();
return File.ReadAllText(tempFile);
}
catch (Win32Exception exception)
{
return exception.Message;
}
finally
{
File.Delete(tempFile);
}
}
After the user confirms the execution of the program as administrator, another instance of the same application is executed without a UI; one can display a UI running without elevated privileges, and another one running in the background with elevated privileges. The first process waits until the second finishes its execution. For more information and a working example you can check out the MSDN archive.
To prevent all this dialog shenanigans in the middle of some lengthy process, you'll need to run your entire host process with elevated permissions by embedding the appropriate manifest in your application to require the 'highestAvailable' execution level: this will cause the UAC prompt to appear as soon as your app is started, and cause all child processes to run with elevated permissions without additional prompting.

Using Pgp.exe from a MVC application

I've been tasked with converting a legacy application to mvc. The app used pgp.exe to pgp sign user input and send it as an email. The application works locally and on a test server but won't run on a live server. I've had to jump though hoops such as running a specified user in the application pool so that we can set the keys in the users profile BUT it worked.
For some reason on the live server which is windows 2003 IIS 6 and identical to the testing server it fails. The problem is pgp.exe just wont seem to sign and create files the message I get from the console out put is. "Signature Error"?? When I put the command into a shell window logged in as the app pool user it runs no problem (after a fight with some permissions) but when running through the mvc application/IIS server it fails. The code used to call the process is below.
var startInfo = new ProcessStartInfo();
startInfo.FileName = _pgpexeLocation;
//startInfo.FileName = "pgp.exe";
startInfo.Arguments = string.Format("-sta \"{0}\" -u keyuser-z keypass +COMPATIBLE +FORCE", _tempFilePath);
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.LoadUserProfile = true;
using (Process exeProcess = Process.Start(startInfo))
{
// TODO: set limit to wait for and deal with exit
exeProcess.WaitForExit();
//var stringItem = exeProcess.StandardOutput.ReadToEnd();
//Logger.Info(stringItem);
}
I'm clutching at straws here hoping somebody has done something similar before and can help. I'm guessing it's key location or file location not being picked up somewhere but not sure what else to try?
Turns out that even though the app pool was using a specific user and I'd set the keys up in that users appdata folder when I checked the underlying process call it was actually trying to pick the keys up from the Default User profile. Not sure if this was an IIS config or something similar but moving the keys and pgp folder to this appdata instead worked?

Resources