use super::owner::{Listener, System};
use crate::error::AppError;
use std::collections::HashMap;
use windows_sys::Win32::{
Foundation::{CloseHandle, ERROR_INSUFFICIENT_BUFFER, HANDLE, INVALID_HANDLE_VALUE},
NetworkManagement::IpHelper::{
GetExtendedTcpTable, MIB_TCP6ROW_OWNER_PID, MIB_TCPROW_OWNER_PID,
TCP_TABLE_OWNER_PID_LISTENER,
},
Networking::WinSock::{AF_INET, AF_INET6},
Security::{GetLengthSid, GetTokenInformation, TOKEN_QUERY, TOKEN_USER, TokenUser},
Storage::Packaging::Appx::{GetPackageFamilyName, PACKAGE_FAMILY_NAME_MAX_LENGTH},
System::{
Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW,
TH32CS_SNAPPROCESS,
},
Registry::{
HKEY_CURRENT_USER, RRF_NOEXPAND, RRF_RT_REG_EXPAND_SZ, RRF_RT_REG_SZ, RegGetValueW,
},
Threading::{
GetCurrentProcess, OpenProcess, OpenProcessToken, PROCESS_QUERY_LIMITED_INFORMATION,
},
},
};
const TEAMS_FAMILY: &str = "MSTeams_8wekyb3d8bbwe";
pub struct WindowsSystem;
struct Handle(HANDLE);
impl Handle {
fn new(handle: HANDLE) -> Option<Self> {
(!handle.is_null() && handle != INVALID_HANDLE_VALUE).then_some(Self(handle))
}
}
impl Drop for Handle {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
impl System for WindowsSystem {
fn listeners(&self, port: u16) -> Result<Vec<Listener>, AppError> {
let mut listeners = vec![];
for row in tcp_table::<MIB_TCPROW_OWNER_PID>(AF_INET)? {
if u16::from_be(row.dwLocalPort as u16) == port {
listeners.push(Listener {
pid: row.dwOwningPid,
loopback: row.dwLocalAddr.to_ne_bytes() == [127, 0, 0, 1],
});
}
}
for row in tcp_table::<MIB_TCP6ROW_OWNER_PID>(AF_INET6)? {
if u16::from_be(row.dwLocalPort as u16) == port {
listeners.push(Listener {
pid: row.dwOwningPid,
loopback: row.ucLocalAddr == std::net::Ipv6Addr::LOCALHOST.octets(),
});
}
}
Ok(listeners)
}
fn processes(&self) -> Result<HashMap<u32, (u32, String)>, AppError> {
let failed = || AppError::Desktop("could not inspect running processes".into());
let snapshot = Handle::new(unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) })
.ok_or_else(failed)?;
let mut entry = PROCESSENTRY32W {
dwSize: size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
let mut processes = HashMap::new();
let mut more = unsafe { Process32FirstW(snapshot.0, &mut entry) } != 0;
while more {
let name = &entry.szExeFile;
let end = name.iter().position(|&c| c == 0).unwrap_or(name.len());
processes.insert(
entry.th32ProcessID,
(
entry.th32ParentProcessID,
String::from_utf16_lossy(&name[..end]),
),
);
more = unsafe { Process32NextW(snapshot.0, &mut entry) } != 0;
}
Ok(processes)
}
fn trusted_teams(&self, pid: u32) -> bool {
let Some(process) =
Handle::new(unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) })
else {
return false;
};
package_family(&process).as_deref() == Some(TEAMS_FAMILY)
&& user_sid(process.0).is_some_and(|sid| {
user_sid(unsafe { GetCurrentProcess() }) == Some(sid)
})
}
fn user_debugging_flags(&self) -> bool {
user_environment("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS").is_some_and(|value| {
value.split_whitespace().any(|token| {
["--remote-debugging-port", "--remote-debugging-address"]
.iter()
.any(|flag| token == *flag || token.starts_with(&format!("{flag}=")))
})
})
}
}
fn user_environment(name: &str) -> Option<String> {
let wide = |text: &str| text.encode_utf16().chain([0]).collect::<Vec<u16>>();
let (key, value) = (wide("Environment"), wide(name));
let flags = RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND;
let mut size = 0u32;
let status = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
key.as_ptr(),
value.as_ptr(),
flags,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut size,
)
};
if status != 0 {
return None;
}
let mut buffer = vec![0u16; (size as usize).div_ceil(2)];
let status = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
key.as_ptr(),
value.as_ptr(),
flags,
std::ptr::null_mut(),
buffer.as_mut_ptr().cast(),
&mut size,
)
};
if status != 0 {
return None;
}
buffer.truncate((size as usize / 2).min(buffer.len()));
let end = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
Some(String::from_utf16_lossy(&buffer[..end]))
}
fn tcp_table<Row: Copy>(family: u16) -> Result<Vec<Row>, AppError> {
let failed = || AppError::Desktop("could not inspect TCP listeners".into());
let mut size = 0u32;
let mut buffer: Vec<u64> = vec![];
for _ in 0..4 {
let status = unsafe {
GetExtendedTcpTable(
buffer.as_mut_ptr().cast(),
&mut size,
0,
family.into(),
TCP_TABLE_OWNER_PID_LISTENER,
0,
)
};
if status == 0 {
let base = buffer.as_ptr().cast::<u8>();
let count = unsafe { base.cast::<u32>().read() } as usize;
let offset = size_of::<u32>().next_multiple_of(align_of::<Row>());
if offset + count * size_of::<Row>() > size as usize {
return Err(failed());
}
let rows = unsafe { base.add(offset).cast::<Row>() };
return Ok((0..count)
.map(|i| unsafe { rows.add(i).read_unaligned() })
.collect());
}
if status != ERROR_INSUFFICIENT_BUFFER {
return Err(failed());
}
buffer = vec![0; (size as usize).div_ceil(8)];
}
Err(failed())
}
fn package_family(process: &Handle) -> Option<String> {
let mut length = PACKAGE_FAMILY_NAME_MAX_LENGTH + 1;
let mut name = vec![0u16; length as usize];
let status = unsafe { GetPackageFamilyName(process.0, &mut length, name.as_mut_ptr()) };
if status != 0 {
return None;
}
let end = name.iter().position(|&c| c == 0).unwrap_or(name.len());
Some(String::from_utf16_lossy(&name[..end]))
}
fn user_sid(process: HANDLE) -> Option<Vec<u8>> {
let mut token = std::ptr::null_mut();
if unsafe { OpenProcessToken(process, TOKEN_QUERY, &mut token) } == 0 {
return None;
}
let token = Handle::new(token)?;
let mut size = 0u32;
unsafe { GetTokenInformation(token.0, TokenUser, std::ptr::null_mut(), 0, &mut size) };
let mut buffer = vec![0u64; (size as usize).div_ceil(8)];
let ok = unsafe {
GetTokenInformation(
token.0,
TokenUser,
buffer.as_mut_ptr().cast(),
size,
&mut size,
)
};
if ok == 0 {
return None;
}
unsafe {
let user = &*buffer.as_ptr().cast::<TOKEN_USER>();
let sid = user.User.Sid;
let length = GetLengthSid(sid) as usize;
Some(std::slice::from_raw_parts(sid.cast::<u8>(), length).to_vec())
}
}