use std::ffi::{CString, OsStr, OsString, c_void};
use std::io::Error as IoError;
use std::os::windows::ffi::OsStrExt;
use std::str::FromStr;
use std::time::Duration;
use std::{env, iter, mem, ptr, thread};
use bstr::ByteSlice;
use color_eyre::eyre::Result;
use lazy_static::lazy_static;
use libc::{FILE, c_char, c_int, c_long, c_uint, intptr_t};
use log::{debug, error, info, trace, warn};
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Security::{PSID, TOKEN_ELEVATION_TYPE, TOKEN_MANDATORY_LABEL};
use windows::Win32::System::Console::{AllocConsole, AttachConsole, FreeConsole};
use windows_registry::LOCAL_MACHINE;
const _O_TEXT: c_int = 0x4000;
#[allow(dead_code)]
const STDIN_FILENO: c_int = 0;
#[allow(dead_code)]
const STDOUT_FILENO: c_int = 1;
#[allow(dead_code)]
const STDERR_FILENO: c_int = 2;
unsafe extern "C" {
pub fn _open_osfhandle(osfhandle: intptr_t, flags: c_int) -> c_int;
pub fn _fdopen(fd: c_int, mode: *const c_char) -> *mut FILE;
pub fn _dup2(fd1: c_int, fd2: c_int) -> c_int;
pub fn __acrt_iob_func(_Ix: c_uint) -> *mut FILE;
}
#[allow(dead_code)]
pub fn stdinf() -> *mut FILE
{
unsafe { __acrt_iob_func(0) }
}
pub fn stdoutf() -> *mut FILE
{
unsafe { __acrt_iob_func(1) }
}
pub fn stderrf() -> *mut FILE
{
unsafe { __acrt_iob_func(2) }
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct UcrtStdioStreamData
{
_ptr: *mut FILE,
_base: *mut i8,
_cnt: c_int,
_flags: c_long,
_file: c_long,
_charbuf: c_int,
_bufsiz: c_int,
_tmpfname: *mut i8,
_lock: *mut c_void,
}
pub fn restore_cstdio(parent_pid: u32) -> Result<()>
{
unsafe {
FreeConsole()?;
if let Err(_e) = AttachConsole(parent_pid) {
AllocConsole()?;
}
}
let res = unsafe { libc::freopen(b"CONIN$\0".as_ptr() as *const i8, b"w\0".as_ptr() as *const i8, stdinf()) };
if res.is_null() {
Err::<(), _>(IoError::last_os_error()).expect("Failed to resynchronize stdin");
}
let res = unsafe { libc::freopen(b"CONOUT$\0".as_ptr() as *const i8, b"wt\0".as_ptr() as *const i8, stdoutf()) };
if res.is_null() {
Err::<(), _>(IoError::last_os_error()).expect("Failed to resynchronize stdout");
}
let out = stdoutf() as *mut UcrtStdioStreamData;
let err = stderrf() as *mut UcrtStdioStreamData;
unsafe {
*err = *out;
}
Ok(())
}
fn os_str_to_null_terminated_vec(s: &OsStr) -> Vec<u16>
{
s.encode_wide().chain(iter::once(0)).collect()
}
fn admin_install_drivers(devices: &mut [wdi::DeviceInfo])
{
for dev in devices.into_iter() {
let hwid_str = dev
.hardware_id
.as_ref()
.expect("BMP WDI DeviceInfo always have hardware_id set")
.to_str_lossy()
.to_string();
println!("Installing for {}", &hwid_str);
thread::sleep(Duration::from_secs(1));
println!("Preparing driver for installation...");
wdi::prepare_driver(dev, "usb_driver", "usb_device.inf", &mut Default::default()).unwrap();
println!("Driver prepared.");
println!("About to install driver. This may take multiple minutes and there will be NO PROGRESS REPORTING!");
println!("Installing driver...");
thread::sleep(Duration::from_secs(1));
wdi::install_driver(dev, "usb_driver", "usb_device.inf", &mut Default::default()).unwrap();
println!("Driver successfully installed for {}", &hwid_str);
}
}
lazy_static! {
pub static ref APP_MODE_WDI_INFO: wdi::DeviceInfo = wdi::DeviceInfo {
vid: 0x1d50,
pid: 0x6018,
is_composite: true,
mi: 4,
desc: CString::new("Black Magic DFU (Interface 4)").unwrap().into_bytes_with_nul().to_vec(),
driver: None,
device_id: None,
hardware_id: Some(CString::new(r"USB\VID_1D50&PID_6018&REV_0100&MI_04").unwrap().to_bytes_with_nul().to_vec()),
compatible_id: Some(CString::new(r"USB\Class_fe&SubClass_01&Prot_01").unwrap().to_bytes_with_nul().to_vec()),
upper_filter: None,
driver_version: 0,
};
pub static ref DFU_MODE_WDI_INFO: wdi::DeviceInfo = wdi::DeviceInfo {
vid: 0x1d50,
pid: 0x6017,
is_composite: false,
mi: 0,
desc: CString::new("Black Magic Probe DFU").unwrap().to_bytes_with_nul().to_vec(),
driver: None,
device_id: None,
hardware_id: Some(CString::new(r"USB\VID_1D50&PID_6017&REV_0100").unwrap().to_bytes_with_nul().to_vec()),
compatible_id: Some(CString::new(r"USB\Cass_FE&SubClass_01&Prot_02").unwrap().to_bytes_with_nul().to_vec()),
upper_filter: None,
driver_version: 0,
};
}
pub fn hwid_bound_to_driver(hardware_id: &str, enumerator: &str) -> Result<Vec<String>>
{
debug!(
"Checking what drivers device {} under enumerator {} is bound to",
hardware_id, enumerator
);
let mut driver_names: Vec<String> = Vec::new();
let hwid_lower = hardware_id.to_lowercase();
let driver_db_subkey_path = format!(r"SYSTEM\DriverDatabase\DeviceIds\{}", enumerator);
trace!(r"Opening HKLM:\{}", driver_db_subkey_path);
let driver_db_subkey_handle = LOCAL_MACHINE.open(&driver_db_subkey_path).map_err(|e| {
error!("Error opening USB driver database in registry: {}", &e);
e
})?;
let driver_db_subkeys = driver_db_subkey_handle.keys().map_err(|e| {
warn!("Error enumerating registry subkeys of {}: {}", driver_db_subkey_path, &e);
e
})?;
for dev_key_name in driver_db_subkeys {
let dev_key_lower = dev_key_name.to_lowercase();
if dev_key_lower == hwid_lower {
trace!(r"Opening HKLM:\{}\{}", driver_db_subkey_path, &dev_key_name);
let dev_key = driver_db_subkey_handle.open(&dev_key_name).map_err(|e| {
warn!(
"Error opening know-to-exist subkey {} when checking bound drivers: {}",
dev_key_name, &e
);
e
})?;
let driver_values = dev_key.values().map_err(|e| {
warn!(
"Error enumerating values of key {:?} when checking bound drivers: {}",
dev_key, &e
);
e
})?;
for (driver_name, _driver_value) in driver_values {
driver_names.push(driver_name);
}
}
}
Ok(driver_names)
}
enum PrivilegeLevel
{
NotPrivileged,
Elevated,
HighIntegrityAdmin,
}
struct ProcessToken
{
token: HANDLE,
}
impl ProcessToken
{
pub fn for_current_process() -> Result<Self>
{
use windows::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows::Win32::Security::{TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY};
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
let mut token = INVALID_HANDLE_VALUE;
unsafe {
OpenProcessToken(
GetCurrentProcess(),
TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
&mut token,
)?
};
Ok(Self {
token,
})
}
fn elevation_type(&self) -> Result<TOKEN_ELEVATION_TYPE>
{
use windows::Win32::Security::{GetTokenInformation, TokenElevationType};
let mut elevation_type = TOKEN_ELEVATION_TYPE::default();
let mut size = 0;
unsafe {
GetTokenInformation(
self.token,
TokenElevationType,
Some(&mut elevation_type as *mut TOKEN_ELEVATION_TYPE as *mut _),
std::mem::size_of_val(&elevation_type) as u32,
&mut size,
)?
};
Ok(elevation_type)
}
fn integrity_level(&self) -> Result<TOKEN_MANDATORY_LABEL>
{
use windows::Win32::Security::{GetTokenInformation, TokenIntegrityLevel};
let mut integrity_level = TOKEN_MANDATORY_LABEL::default();
let mut size = 0;
unsafe {
GetTokenInformation(
self.token,
TokenIntegrityLevel,
Some(&mut integrity_level as *mut TOKEN_MANDATORY_LABEL as *mut _),
std::mem::size_of_val(&integrity_level) as u32,
&mut size,
)?
};
Ok(integrity_level)
}
fn as_impersonation_token(&self) -> Result<Self>
{
use windows::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows::Win32::Security::{
DuplicateTokenEx, SecurityImpersonation, TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_SESSIONID,
TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY, TokenImpersonation,
};
let mut token = INVALID_HANDLE_VALUE;
unsafe {
DuplicateTokenEx(
self.token,
TOKEN_ADJUST_SESSIONID |
TOKEN_ADJUST_DEFAULT |
TOKEN_ASSIGN_PRIMARY |
TOKEN_IMPERSONATE |
TOKEN_DUPLICATE | TOKEN_QUERY,
None,
SecurityImpersonation,
TokenImpersonation,
&mut token,
)?
};
Ok(Self {
token,
})
}
fn check_membership(&self, sid: PSID) -> Result<bool>
{
use windows::Win32::Foundation::{FALSE, TRUE};
use windows::Win32::Security::CheckTokenMembership;
let mut is_member = FALSE;
unsafe { CheckTokenMembership(Some(self.token), sid, &mut is_member)? };
Ok(is_member == TRUE)
}
pub fn privilege_level(&self) -> Result<PrivilegeLevel>
{
use windows::Win32::Foundation::TRUE;
use windows::Win32::Security::{
CreateWellKnownSid, IsWellKnownSid, SID, TokenElevationTypeFull, WinBuiltinAdministratorsSid,
WinHighLabelSid,
};
let elevation_type = self.elevation_type()?;
if elevation_type == TokenElevationTypeFull {
return Ok(PrivilegeLevel::Elevated);
}
let integrity_level = self.integrity_level()?;
if unsafe { IsWellKnownSid(integrity_level.Label.Sid, WinHighLabelSid) } == TRUE {
return Ok(PrivilegeLevel::NotPrivileged);
}
let impersonation_token = self.as_impersonation_token()?;
let mut size = 0;
let mut administrators_sid = SID::default();
let administrators_psid = PSID(&mut administrators_sid as *mut SID as *mut _);
unsafe { CreateWellKnownSid(WinBuiltinAdministratorsSid, None, Some(administrators_psid), &mut size)? };
if impersonation_token.check_membership(administrators_psid)? {
Ok(PrivilegeLevel::HighIntegrityAdmin)
} else {
Ok(PrivilegeLevel::NotPrivileged)
}
}
}
impl Drop for ProcessToken
{
fn drop(&mut self)
{
use windows::Win32::Foundation::CloseHandle;
unsafe { CloseHandle(self.token).expect("token should have been valid to close") }
}
}
pub fn ensure_access(parent_pid: Option<u32>, explicitly_requested: bool, force: bool)
{
debug!("Checking Windows registry driver database to determine if WinUSB is bound to BMP device nodes");
let mut devices_needing_driver: Vec<wdi::DeviceInfo> = Vec::with_capacity(2);
if force {
info!("Force installing WinUSB driver for app mode and DFU mode BMP devices...");
devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
} else {
match hwid_bound_to_driver("VID_1D50&PID_6018&MI_04", "USB") {
Ok(driver_names) if driver_names.len() == 0 => {
devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for app mode BMP device...");
},
Err(_e) => {
devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for app mode BMP device...");
},
Ok(driver_names) => {
trace!("App mode BMP bound to drivers: {:?}", driver_names);
},
}
match hwid_bound_to_driver("VID_1D50&PID_6017", "USB") {
Ok(driver_names) if driver_names.len() == 0 => {
devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for DFU mode BMP device...");
},
Err(_e) => {
devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
info!("Scheduling WinUSB driver installation for DFU mode BMP device...");
},
Ok(driver_names) => {
trace!("DFU mode BMP bound to drivers: {:?}", driver_names);
},
}
}
if devices_needing_driver.len() == 0 {
if explicitly_requested {
println!("Drivers are already installed for BMP devices; nothing to do.");
}
return;
}
println!("The WinUSB driver needs to be installed for the Black Magic Probe device before continuing. Standby...");
thread::sleep(Duration::from_secs(1));
let token = ProcessToken::for_current_process().expect("Unable to determine the current process's privilege level");
let level = token
.privilege_level()
.expect("Unable to determine the current process's privilege level");
let need_to_elevate = matches!(level, PrivilegeLevel::NotPrivileged | PrivilegeLevel::HighIntegrityAdmin);
if !need_to_elevate {
if let Some(pid) = parent_pid {
match restore_cstdio(pid) {
Ok(_) => (),
Err(_e) => {
todo!("Create a log file!");
},
}
}
admin_install_drivers(&mut devices_needing_driver);
println!(
"Successfully installed drivers for {} USB interfaces.",
devices_needing_driver.len()
);
std::process::exit(0);
}
let mut args: Vec<OsString> = Vec::with_capacity(env::args_os().len() + 1);
args.extend(env::args_os().map(|s| s.to_owned()));
args.push(OsString::from_str("--windows-wdi-install-mode").unwrap());
use windows::Win32::Foundation::{HANDLE, HINSTANCE, HWND, WAIT_FAILED};
use windows::Win32::System::Registry::HKEY;
use windows::Win32::System::Threading::{GetExitCodeProcess, INFINITE, WaitForSingleObject};
use windows::Win32::UI::Shell::{SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW, SHELLEXECUTEINFOW_0, ShellExecuteExW};
use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
use windows_strings::PCWSTR;
let verb: Vec<u16> = OsStr::new("runas").encode_wide().chain(iter::once(0)).collect();
let mut args: Vec<OsString> = env::args_os().map(|s| s.to_owned()).collect();
let _ = args.remove(0);
args.push(OsStr::new(&format!("--windows-wdi-install-mode={}", std::process::id())).to_owned());
let file = os_str_to_null_terminated_vec(env::current_exe().unwrap().as_os_str());
let parameters: OsString = args.join(OsStr::new(" "));
let parameters = os_str_to_null_terminated_vec(¶meters);
let cwd = os_str_to_null_terminated_vec(
env::current_dir()
.expect("Unable to get current working directory")
.as_os_str(),
);
let mut info = SHELLEXECUTEINFOW {
cbSize: mem::size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: SEE_MASK_NOCLOSEPROCESS,
hwnd: HWND::default(),
lpVerb: PCWSTR::from_raw(verb.as_ptr()),
lpFile: PCWSTR::from_raw(file.as_ptr()),
lpParameters: PCWSTR::from_raw(parameters.as_ptr()),
lpDirectory: PCWSTR::from_raw(cwd.as_ptr()),
nShow: SW_HIDE.0,
hInstApp: HINSTANCE::default(),
lpIDList: ptr::null_mut(),
lpClass: PCWSTR::null(),
hkeyClass: HKEY::default(),
dwHotKey: 0,
Anonymous: SHELLEXECUTEINFOW_0 {
hMonitor: HANDLE::default(),
},
hProcess: HANDLE::default(),
};
unsafe { ShellExecuteExW(&mut info) }.expect("Error calling ShellExecuteExW()");
if unsafe { WaitForSingleObject(info.hProcess, INFINITE) } == WAIT_FAILED {
Err::<(), _>(IoError::last_os_error()).expect("Error calling WaitForSingleObject()");
}
std::thread::sleep(std::time::Duration::from_secs(5));
let mut exit_code = 0;
match unsafe { GetExitCodeProcess(info.hProcess, &mut exit_code) } {
Ok(()) => {
if exit_code != 0 {
error!(
"Elevated process exited with {}; driver installation probably failed",
exit_code
);
std::process::exit(exit_code as i32);
} else {
info!("Exiting parent process. Elevated process exited successfully.");
}
},
Err(error) => panic!("Error calling GetExitCodeProcess(): {}", error),
}
println!(
"Driver installation should be complete. You may need to unplug the device and plug it back in before things \
work."
);
}