So I was trying to port a code from C# to Rust that can run a Windows command as another user. I got it working fine, but the problem is I want to redirect the output to current console instead of showing it on a new one.
This piece of C# code working as intended:
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
public class RunasCsl
{
private const Int32 StartfUseStdHandles = 0x00000100;
private const int BUFFER_SIZE_PIPE = 1048576;
private const uint CREATE_NO_WINDOW = 0x08000000;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x400;
private const uint DUPLICATE_SAME_ACCESS = 0x00000002;
private const UInt32 INFINITE = 4294967295;
private WindowStationDACL stationDaclObj;
private IntPtr hTokenPreviousImpersonatingThread;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
private struct ProcessInformation
{
public IntPtr process;
public IntPtr thread;
public int processId;
public int threadId;
}
[StructLayout(LayoutKind.Sequential)]
private struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcessWithLogonW(String userName, String domain, String password,
UInt32 logonFlags, String applicationName, String commandLine, uint creationFlags, UInt32 environment,
String currentDirectory, ref STARTUPINFO startupInfo, out ProcessInformation processInformation);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe,
ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize);
[DllImport("kernel32.dll")]
private static extern bool SetNamedPipeHandleState(IntPtr hNamedPipe, ref UInt32 lpMode,
IntPtr lpMaxCollectionCount, IntPtr lpCollectDataTimeout);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead,
out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle,
IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess,
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);
private bool CreateAnonymousPipeEveryoneAccess(ref IntPtr hReadPipe, ref IntPtr hWritePipe)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = IntPtr.Zero;
sa.bInheritHandle = true;
if (CreatePipe(out hReadPipe, out hWritePipe, ref sa, (uint)BUFFER_SIZE_PIPE))
return true;
return false;
}
private string ReadOutputFromPipe(IntPtr hReadPipe)
{
string output = "";
uint dwBytesRead = 0;
byte[] buffer = new byte[BUFFER_SIZE_PIPE];
if (!ReadFile(hReadPipe, buffer, BUFFER_SIZE_PIPE, out dwBytesRead, IntPtr.Zero))
{
output += "No output received from the process.\r\n";
}
output += Encoding.Default.GetString(buffer, 0, (int)dwBytesRead);
return output;
}
private void RunasSetupStdHandlesForProcess(ref STARTUPINFO startupInfo,
out IntPtr hOutputWrite, out IntPtr hErrorWrite, out IntPtr hOutputRead)
{
IntPtr hOutputReadTmpLocal = IntPtr.Zero;
IntPtr hOutputWriteLocal = IntPtr.Zero;
IntPtr hErrorWriteLocal = IntPtr.Zero;
IntPtr hOutputReadLocal = IntPtr.Zero;
IntPtr hCurrentProcess = Process.GetCurrentProcess().Handle;
if (!CreateAnonymousPipeEveryoneAccess(ref hOutputReadTmpLocal, ref hOutputWriteLocal))
throw new RunasCsException("CreatePipe", true);
if (!DuplicateHandle(hCurrentProcess, hOutputWriteLocal, hCurrentProcess, out hErrorWriteLocal, 0, true,
DUPLICATE_SAME_ACCESS))
throw new RunasCsException("DuplicateHandle stderr write pipe", true);
if (!DuplicateHandle(hCurrentProcess, hOutputReadTmpLocal, hCurrentProcess, out hOutputReadLocal, 0,
false,
DUPLICATE_SAME_ACCESS))
throw new RunasCsException("DuplicateHandle stdout read pipe", true);
CloseHandle(hOutputReadTmpLocal);
UInt32 PIPE_NOWAIT = 0x00000001;
if (!SetNamedPipeHandleState(hOutputReadLocal, ref PIPE_NOWAIT, IntPtr.Zero, IntPtr.Zero))
throw new RunasCsException("SetNamedPipeHandleState", true);
startupInfo.dwFlags = StartfUseStdHandles;
startupInfo.hStdOutput = hOutputWriteLocal;
startupInfo.hStdError = hErrorWriteLocal;
hOutputWrite = hOutputWriteLocal;
hErrorWrite = hErrorWriteLocal;
hOutputRead = hOutputReadLocal;
}
public string RunAs()
{
IntPtr hOutputWrite;
IntPtr hErrorWrite;
IntPtr hOutputRead;
string username = "test";
string password = "1";
string domainName = ".";
string commandLine = "C:\\WINDOWS\\system32\\cmd.exe /c whoami /all";
string currentDirectory = "C:\\Windows\\system32";
STARTUPINFO startupInfo = new STARTUPINFO();
ProcessInformation processInfo = new ProcessInformation();
uint logonFlags = 0;
RunasSetupStdHandlesForProcess(ref startupInfo, out hOutputWrite, out hErrorWrite,
out hOutputRead);
if (!CreateProcessWithLogonW(username, domainName, password, logonFlags, null, commandLine,
CREATE_UNICODE_ENVIRONMENT, (UInt32)0, currentDirectory, ref startupInfo, out processInfo))
throw new RunasCsException("CreateProcessWithLogonW error", true);
string output = "";
WaitForSingleObject(processInfo.process, INFINITE);
output += "\r\n" + ReadOutputFromPipe(hOutputRead);
CloseHandle(processInfo.process);
CloseHandle(processInfo.thread);
return output;
}
}
class MainClass
{
static void Main(string[] args)
{
RunasCsl invoker = new RunasCsl();
Console.Out.Write(invoker.RunAs());
Console.Out.Flush();
}
}
And here is what I got with Rust so far:
use std::mem::{size_of, MaybeUninit};
use std::process::{exit};
use core::ffi::c_void;
use std::ptr;
use std::str;
use std::ffi::CStr;
use std::os::raw::c_char;
use windows::core::{Error, PWSTR, PCWSTR};
use windows::imp::WaitForSingleObject;
use windows::w;
use windows::Win32::Security::SECURITY_ATTRIBUTES;
use windows::Win32::Foundation::{WAIT_OBJECT_0, HANDLE, BOOL, CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS};
use windows::Win32::System::Threading::{CreateProcessWithLogonW,GetCurrentProcess,
STARTUPINFOW, PROCESS_INFORMATION, CREATE_PROCESS_LOGON_FLAGS,
CREATE_UNICODE_ENVIRONMENT,CREATE_NO_WINDOW, STARTF_USESTDHANDLES,
INFINITE};
use windows::Win32::System::Pipes::{SetNamedPipeHandleState, CreatePipe, NAMED_PIPE_MODE, PIPE_NOWAIT};
use windows::Win32::Storage::FileSystem::ReadFile;
fn main() {
let lpusername: PCWSTR = w!("test").into();
let lpdomain: PCWSTR = w!("").into();
let lppassword: PCWSTR = w!("1").into();
let lpapplicationname: PCWSTR = PCWSTR::null();
let cmd: PCWSTR = w!("C:\\WINDOWS\\system32\\cmd.exe /c whoami /all").into();
let lpcommandline: PWSTR = PWSTR::from_raw(cmd.as_ptr() as *mut u16);
let dwcreationflags = CREATE_UNICODE_ENVIRONMENT;
let lpcurrentdirectory: PCWSTR = w!("C:\\Windows\\system32").into();
let handler_setup = runas_setup_std_handles_for_process().unwrap();
let mut si = handler_setup.0;
let output_read_handler = handler_setup.3;
let mut pi: PROCESS_INFORMATION = Default::default();
unsafe {
if !CreateProcessWithLogonW(lpusername, lpdomain, lppassword, CREATE_PROCESS_LOGON_FLAGS::default(),
lpapplicationname, lpcommandline, dwcreationflags,
None, lpcurrentdirectory, &mut si, &mut pi).as_bool() {
eprintln!("CreateProcessWithLogonW error");
}
};
let r = unsafe { WaitForSingleObject(pi.hProcess.0, INFINITE) };
if r != WAIT_OBJECT_0.0 {
eprintln!("WaitForSingleObject: {:?}", Error::from_win32());
exit(1);
}
read_output_from_pipe(output_read_handler)
}
fn read_output_from_pipe(h_read_pipe: HANDLE){
const BUFFER_SIZE_PIPE: u32 = 1048576_u32;
let context: *mut c_void =ptr::null_mut();
let mut bytes_read: u32 = 0;
let bytes_read_ptr: *mut u32 = &mut bytes_read;
unsafe {
if !ReadFile(h_read_pipe, Some(context), BUFFER_SIZE_PIPE, Some(bytes_read_ptr), None).as_bool() {
println!("No output received from the process.\r\n");
return;
}
}
let data = unsafe {
CStr::from_ptr(context as *const c_char).to_string_lossy().into_owned()
};
let b = data.as_bytes();
let s = match str::from_utf8(b) {
Ok(v) => v,
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
println!("{}", s);
}
fn create_anonymous_pipe_everyone_access() -> Option<(HANDLE, HANDLE)> {
const SA: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES{
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: ptr::null_mut(),
bInheritHandle: BOOL(1),
};
unsafe {
const BUFFER_SIZE_PIPE: u32 = 1048576;
let mut read_handle = MaybeUninit::uninit();
let mut write_handle = MaybeUninit::uninit();
if CreatePipe(read_handle.as_mut_ptr(), write_handle.as_mut_ptr(), Some(&SA), BUFFER_SIZE_PIPE).as_bool() {
Some((read_handle.assume_init(), write_handle.assume_init()))
} else {
None
}
}
}
fn runas_setup_std_handles_for_process() -> Option<(STARTUPINFOW, HANDLE, HANDLE, HANDLE)> {
let mut startup_info: STARTUPINFOW = Default::default();
let h_current_process = unsafe { GetCurrentProcess() };
let Some((output_read_tmp_local, output_write_local)) = create_anonymous_pipe_everyone_access() else {
println!("Error Create pipe");
return None;
};
let mut tmp_handle = MaybeUninit::uninit();
let error_write_local: HANDLE;
let output_read_local: HANDLE;
unsafe {
if !DuplicateHandle(h_current_process, output_write_local,
h_current_process, tmp_handle.as_mut_ptr(),
0, true, DUPLICATE_SAME_ACCESS).as_bool() {
eprintln!("Error DuplicateHandle stderr write pipe");
return None;
}
error_write_local = tmp_handle.clone().assume_init();
if !DuplicateHandle(h_current_process, output_read_tmp_local,
h_current_process, tmp_handle.as_mut_ptr(),
0, true, DUPLICATE_SAME_ACCESS).as_bool() {
eprintln!("Error DuplicateHandle stdout read pipe");
return None;
}
output_read_local = tmp_handle.assume_init();
CloseHandle(output_read_tmp_local);
const LPMODE: NAMED_PIPE_MODE = PIPE_NOWAIT;
if !SetNamedPipeHandleState(output_read_local, Some(&LPMODE), None, None ).as_bool(){
eprintln!("Error SetNamedPipeHandleState");
return None;
}
}
startup_info.dwFlags = STARTF_USESTDHANDLES;
startup_info.hStdOutput = output_write_local;
startup_info.hStdError = error_write_local;
return Some((startup_info, output_write_local, error_write_local, output_read_local))
}
Running the Rust code only gives me "No output received from the process". The comand did execute, a terminal did showup, but empty, so I guess the output was directed somewhere. I am not sure if I did something wrong at the read_output_from_pipe
, or anywhere else.
Sorry for the long code, because I am not sure where is the problem, so I just included everything.
Would be great if somebody could have a look.
Thank you.