Hello,
I am trying to start process from my app, but since I am using app as an Admin, I need to launch that exe as standard user.
Similar function that I had while working with C# was this one, which was found online
public static int RunAsDesktopUser(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName));
// To start process as shell user you will need to carry out these steps:
// 1. Enable the SeIncreaseQuotaPrivilege in your current token
// 2. Get an HWND representing the desktop shell (GetShellWindow)
// 3. Get the Process ID(PID) of the process associated with that window(GetWindowThreadProcessId)
// 4. Open that process(OpenProcess)
// 5. Get the access token from that process (OpenProcessToken)
// 6. Make a primary token with that token(DuplicateTokenEx)
// 7. Start the new process with that primary token(CreateProcessWithTokenW)
var hProcessToken = IntPtr.Zero;
// Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.)
try
{
var process = GetCurrentProcess();
if (!OpenProcessToken(process, 0x0020, ref hProcessToken))
return 0;
var tkp = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new LUID_AND_ATTRIBUTES[1]
};
if (!LookupPrivilegeValue(null, "SeIncreaseQuotaPrivilege", ref tkp.Privileges[0].Luid))
return 0;
tkp.Privileges[0].Attributes = 0x00000002;
if (!AdjustTokenPrivileges(hProcessToken, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero))
return 0;
}
finally
{
CloseHandle(hProcessToken);
}
// Get an HWND representing the desktop shell.
// CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been
// replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and
// restarted elevated.
var hwnd = GetShellWindow();
if (hwnd == IntPtr.Zero)
return 0 ;
var hShellProcess = IntPtr.Zero;
var hShellProcessToken = IntPtr.Zero;
var hPrimaryToken = IntPtr.Zero;
try
{
// Get the PID of the desktop shell process.
uint dwPID;
if (GetWindowThreadProcessId(hwnd, out dwPID) == 0)
return 0;
// Open the desktop shell process in order to query it (get the token)
hShellProcess = OpenProcess(ProcessAccessFlags.QueryInformation, false, dwPID);
if (hShellProcess == IntPtr.Zero)
return 0;
// Get the process token of the desktop shell.
if (!OpenProcessToken(hShellProcess, 0x0002, ref hShellProcessToken))
return 0;
var dwTokenRights = 395U;
// Duplicate the shell's process token to get a primary token.
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hPrimaryToken))
return 0;
// Start the target process with the new token.
var si = new STARTUPINFO();
var pi = new PROCESS_INFORMATION();
if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, "", 0, IntPtr.Zero, Path.GetDirectoryName(fileName), ref si, out pi))
return 0;
return pi.dwProcessId;
}
finally
{
CloseHandle(hShellProcessToken);
CloseHandle(hPrimaryToken);
CloseHandle(hShellProcess);
}
}
I tried fiddleing around with winapi, even tried using github copilot and chatgpt but I just cannot find a way to run this process.
Here is my current code
extern crate winapi;
use winapi::shared::minwindef::{FALSE, DWORD, UINT};
use winapi::shared::ntdef::HANDLE;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::winuser::{GetShellWindow, GetWindowThreadProcessId};
use winapi::um::processthreadsapi::{OpenProcess, OpenProcessToken, GetCurrentProcess, PROCESS_INFORMATION, STARTUPINFOW};
use winapi::um::winbase::{CreateProcessWithTokenW, LookupPrivilegeValueA, CREATE_NEW_CONSOLE, STARTF_USESHOWWINDOW};
use winapi::um::securitybaseapi::{DuplicateTokenEx, AdjustTokenPrivileges};
use winapi::um::winnt::{SecurityImpersonation, TokenPrimary, LUID, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, TOKEN_ALL_ACCESS, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_PRIVILEGES, TOKEN_QUERY};
use winapi::um::handleapi::CloseHandle;
use std::mem;
use std::ptr::null_mut;
use std::ffi::CString;
use std::os::raw::c_void;
use std::default::Default;
pub unsafe fn run_as_desktop_user(executable_path: &str) -> Result<(), Box<dyn std::error::Error>> {
// Step 1: Enable the SeIncreaseQuotaPrivilege in your current token
let mut h_process_token: HANDLE = null_mut();
if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &mut h_process_token) == 0 {
return Err("Failed to open process token".into());
}
let mut tkp = TOKEN_PRIVILEGES {
PrivilegeCount: 1,
Privileges: [LUID_AND_ATTRIBUTES {
Luid: LUID {
LowPart: 0,
HighPart: 0,
},
Attributes: SE_PRIVILEGE_ENABLED,
}; 1],
};
let se_name = CString::new("SeIncreaseQuotaPrivilege").unwrap();
if LookupPrivilegeValueA(null_mut(), se_name.as_ptr(), &mut tkp.Privileges[0].Luid) == 0 {
CloseHandle(h_process_token);
return Err("Failed to lookup privilege value".into());
}
if AdjustTokenPrivileges(h_process_token, FALSE, &mut tkp, std::mem::size_of::<TOKEN_PRIVILEGES>() as DWORD, null_mut(), null_mut()) == 0 {
CloseHandle(h_process_token);
return Err("Failed to adjust token privileges".into());
}
// Step 2: Get an HWND representing the desktop shell
let hwnd_shell = GetShellWindow();
if hwnd_shell.is_null() {
CloseHandle(h_process_token);
return Err("Failed to get shell window handle".into());
}
// Step 3: Get the Process ID of the process associated with that window
let mut dw_pid: DWORD = 0;
GetWindowThreadProcessId(hwnd_shell, &mut dw_pid);
if dw_pid == 0 {
CloseHandle(h_process_token);
return Err("Failed to get process ID".into());
}
// Step 4: Open that process
let h_shell_process = OpenProcess(TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, FALSE, dw_pid);
if h_shell_process.is_null() {
CloseHandle(h_process_token);
return Err("Failed to open shell process".into());
}
println!("Process 2 {:#?}", h_shell_process);
// Step 5: Get the access token from that process
let mut h_shell_process_token: HANDLE = null_mut();
if OpenProcessToken(h_shell_process, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &mut h_shell_process_token) == 0 {
let error = GetLastError();
println!("OpenProcessToken failed with error code: {}", error);
CloseHandle(h_process_token);
CloseHandle(h_shell_process);
return Err(format!("Failed to open process token from shell. Error: {}", error).into());
}
println!("Process 3");
// Step 6: Duplicate the shell's process token
let mut h_new_token: HANDLE = null_mut();
if DuplicateTokenEx(h_shell_process_token, TOKEN_ALL_ACCESS, null_mut(), SecurityImpersonation, TokenPrimary, &mut h_new_token) == 0 {
CloseHandle(h_process_token);
CloseHandle(h_shell_process);
CloseHandle(h_shell_process_token);
return Err("Failed to duplicate token".into());
}
println!("Process 4");
// Step 7: Start the new process with that primary token
let app_name = CString::new(executable_path).unwrap();
let mut si = STARTUPINFOW {
cb: mem::size_of::<STARTUPINFOW>() as UINT,
lpReserved: null_mut(),
lpDesktop: null_mut(),
lpTitle: null_mut(),
dwFlags: 0, // Set specific flags as needed, for example, STARTF_USESHOWWINDOW if required
cbReserved2: 0,
lpReserved2: null_mut(),
hStdInput: null_mut(),
hStdOutput: null_mut(),
hStdError: null_mut(),
dwX: 0,
dwY: 0,
dwXSize: 0,
dwYSize: 0,
dwXCountChars: 0,
dwYCountChars: 0,
dwFillAttribute: 0,
wShowWindow: 0, // If STARTF_USESHOWWINDOW is used, set this to the appropriate show window constant like SW_SHOW
};
let mut pi = PROCESS_INFORMATION {
hProcess: null_mut(),
hThread: null_mut(),
dwProcessId: 0,
dwThreadId: 0,
};
println!("Process 5");
if CreateProcessWithTokenW(h_new_token, 0, null_mut(), app_name.as_ptr() as *mut _, CREATE_NEW_CONSOLE, null_mut(), null_mut(), &mut si, &mut pi) == 0 {
CloseHandle(h_process_token);
CloseHandle(h_shell_process);
CloseHandle(h_shell_process_token);
CloseHandle(h_new_token);
return Err("Failed to create process with duplicated token".into());
}
println!("Process started successfully, PID: {}", pi.dwProcessId);
// Close all the handles now that we're done with them
CloseHandle(h_process_token);
CloseHandle(h_shell_process);
CloseHandle(h_shell_process_token);
CloseHandle(h_new_token);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
Ok(())
}
Function fails at OpenProcessToken with error code of 5.
Any help would be more than welcome.
Thanks!