use crate::ndr::{NdrDecoder, NdrEncoder};
use crate::transport::SmbPipe;
use crate::{Result, RpcError, Syntax};
use smb2_client::SmbClient;
pub fn winreg_syntax() -> Syntax {
Syntax::new("338cd001-2244-31f1-aaaa-900038001003", 1, 0)
}
pub mod opnum {
pub const OPEN_LOCAL_MACHINE: u16 = 2; pub const OPEN_USERS: u16 = 4; pub const BASE_REG_CLOSE_KEY: u16 = 5;
pub const BASE_REG_ENUM_KEY: u16 = 9;
pub const BASE_REG_OPEN_KEY: u16 = 15;
pub const BASE_REG_QUERY_INFO_KEY: u16 = 16;
pub const BASE_REG_QUERY_VALUE: u16 = 17;
}
const KEY_READ: u32 = 0x0002_0019;
const QUERY_BUF: u32 = 0x0002_0000; const MAX_SUBKEYS: u32 = 100_000;
#[derive(Clone, Copy, Debug, Default)]
pub struct Hkey(pub [u8; 20]);
impl Hkey {
fn decode(d: &mut NdrDecoder) -> Result<Self> {
let attrs = d.u32()?;
let uuid = d.uuid()?;
let mut h = [0u8; 20];
h[..4].copy_from_slice(&attrs.to_le_bytes());
h[4..].copy_from_slice(&uuid);
Ok(Hkey(h))
}
fn encode(&self, e: &mut NdrEncoder) {
e.bytes(&self.0);
}
fn is_null(&self) -> bool {
self.0 == [0u8; 20]
}
}
#[derive(Clone, Debug)]
pub struct RegValue {
pub ty: u32, pub data: Vec<u8>,
}
impl RegValue {
pub fn as_dword(&self) -> Option<u32> {
self.data
.get(0..4)
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
}
pub fn as_string(&self) -> String {
let units: Vec<u16> = self
.data
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect();
String::from_utf16_lossy(&units)
.replace('\0', "\n")
.trim()
.to_string()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RegistrySession {
pub sid: String,
}
fn encode_ustr(e: &mut NdrEncoder, s: &str) {
let mut units: Vec<u16> = s.encode_utf16().collect();
units.push(0); let n = units.len() as u32;
let bytes = (n * 2) as u16;
e.u16(bytes); e.u16(bytes); e.referent(); e.u32(n); e.u32(0); e.u32(n); for u in units {
e.u16(u);
}
e.align(4);
}
fn encode_open_local_machine() -> Vec<u8> {
let mut e = NdrEncoder::new();
e.null_ptr(); e.u32(KEY_READ); e.into_bytes()
}
fn encode_open_users() -> Vec<u8> {
encode_open_local_machine() }
fn encode_open_key(hkey: &Hkey, subkey: &str) -> Vec<u8> {
encode_open_key_opts(hkey, subkey, 0)
}
fn encode_open_key_opts(hkey: &Hkey, subkey: &str, dw_options: u32) -> Vec<u8> {
let mut e = NdrEncoder::new();
hkey.encode(&mut e);
encode_ustr(&mut e, subkey);
e.u32(dw_options); e.u32(KEY_READ); e.into_bytes()
}
fn encode_query_value(hkey: &Hkey, value: &str) -> Vec<u8> {
let mut e = NdrEncoder::new();
hkey.encode(&mut e);
encode_ustr(&mut e, value);
e.referent();
e.u32(0);
e.referent();
e.u32(QUERY_BUF); e.u32(0); e.u32(0); e.referent();
e.u32(QUERY_BUF);
e.referent();
e.u32(0);
e.into_bytes()
}
fn encode_close(hkey: &Hkey) -> Vec<u8> {
let mut e = NdrEncoder::new();
hkey.encode(&mut e);
e.into_bytes()
}
fn decode_query_value(stub: &[u8]) -> Result<RegValue> {
let mut d = NdrDecoder::new(stub);
let mut ty = 0u32;
if d.u32()? != 0 {
ty = d.u32()?;
}
let mut data = Vec::new();
if d.u32()? != 0 {
let _max = d.u32()?;
let _off = d.u32()?;
let actual = d.u32()? as usize;
data = d.read_bytes(actual)?.to_vec();
d.align(4);
}
Ok(RegValue { ty, data })
}
pub struct RegistryClient<'a> {
pipe: SmbPipe<'a>,
deferred: std::collections::VecDeque<Hkey>,
}
const MAX_DEFERRED: usize = 96;
impl<'a> RegistryClient<'a> {
pub async fn connect(
client: &'a mut SmbClient,
domain: &str,
user: &str,
password: &str,
host: &str,
) -> Result<RegistryClient<'a>> {
let file_id = client
.open_pipe("winreg")
.await
.map_err(|e| RpcError::Protocol(format!("open \\winreg: {e}")))?;
let mut pipe = SmbPipe::new(client, file_id);
pipe.bind_sealed(winreg_syntax(), domain, user, password, host)
.await?;
Ok(RegistryClient {
pipe,
deferred: std::collections::VecDeque::new(),
})
}
async fn after_open(&mut self, opened: &Hkey) -> Result<()> {
self.deferred.push_back(*opened);
if self.deferred.len() > MAX_DEFERRED {
if let Some(old) = self.deferred.pop_front() {
let _ = self
.pipe
.call_sealed(opnum::BASE_REG_CLOSE_KEY, &encode_close(&old))
.await;
}
}
Ok(())
}
#[cfg(test)]
pub(crate) fn deferred_len(&self) -> usize {
self.deferred.len()
}
#[doc(hidden)]
pub fn __deferred_len_debug(&self) -> usize {
self.deferred.len()
}
async fn open_hklm(&mut self) -> Result<Hkey> {
let resp = self
.pipe
.call_sealed(opnum::OPEN_LOCAL_MACHINE, &encode_open_local_machine())
.await?;
let mut d = NdrDecoder::new(&resp);
let h = Hkey::decode(&mut d)?;
let ret = d.u32().unwrap_or(u32::MAX);
if ret != 0 || h.is_null() {
return Err(RpcError::Protocol(format!(
"OpenLocalMachine failed ({ret})"
)));
}
self.after_open(&h).await?;
Ok(h)
}
async fn open_hku(&mut self) -> Result<Hkey> {
let resp = self
.pipe
.call_sealed(opnum::OPEN_USERS, &encode_open_users())
.await?;
let mut d = NdrDecoder::new(&resp);
let h = Hkey::decode(&mut d)?;
let ret = d.u32().unwrap_or(u32::MAX);
if ret != 0 || h.is_null() {
return Err(RpcError::Protocol(format!("OpenUsers failed ({ret})")));
}
self.after_open(&h).await?;
Ok(h)
}
async fn open_key(&mut self, parent: &Hkey, subkey: &str) -> Result<Hkey> {
let resp = self
.pipe
.call_sealed(opnum::BASE_REG_OPEN_KEY, &encode_open_key(parent, subkey))
.await?;
let mut d = NdrDecoder::new(&resp);
let h = Hkey::decode(&mut d)?;
let ret = d.u32().unwrap_or(u32::MAX);
if ret != 0 || h.is_null() {
return Err(RpcError::Protocol(format!(
"BaseRegOpenKey('{subkey}') failed ({ret})"
)));
}
self.after_open(&h).await?;
Ok(h)
}
async fn query_value(&mut self, key: &Hkey, value: &str) -> Result<RegValue> {
let resp = self
.pipe
.call_sealed(opnum::BASE_REG_QUERY_VALUE, &encode_query_value(key, value))
.await?;
decode_query_value(&resp)
}
async fn close(&mut self, key: &Hkey) {
let _ = self
.pipe
.call_sealed(opnum::BASE_REG_CLOSE_KEY, &encode_close(key))
.await;
}
pub async fn read_value(&mut self, subkey: &str, value: &str) -> Result<RegValue> {
let hklm = self.open_hklm().await?;
let key = self.open_key(&hklm, subkey).await;
let key = match key {
Ok(k) => k,
Err(e) => {
self.close(&hklm).await;
return Err(e);
}
};
let v = self.query_value(&key, value).await;
self.close(&key).await;
self.close(&hklm).await;
v
}
pub async fn hklm(&mut self) -> Result<Hkey> {
self.open_hklm().await
}
pub async fn hku(&mut self) -> Result<Hkey> {
self.open_hku().await
}
pub async fn logged_on_sids(&mut self) -> Result<Vec<RegistrySession>> {
let hku = self.open_hku().await?;
let mut out = Vec::new();
for idx in 0..MAX_SUBKEYS {
match self.enum_key(&hku, idx).await? {
None => break,
Some(name) if name.starts_with("S-1-5-21") && !name.ends_with("_Classes") => {
out.push(RegistrySession { sid: name });
}
_ => {}
}
}
self.close_handle_no_wait(&hku).await;
Ok(out)
}
pub async fn open(&mut self, parent: &Hkey, subkey: &str) -> Result<Hkey> {
self.open_key(parent, subkey).await
}
pub async fn open_backup(&mut self, parent: &Hkey, subkey: &str) -> Result<Hkey> {
let resp = self
.pipe
.call_sealed(
opnum::BASE_REG_OPEN_KEY,
&encode_open_key_opts(parent, subkey, 4),
)
.await?;
let mut d = NdrDecoder::new(&resp);
let h = Hkey::decode(&mut d)?;
let ret = d.u32().unwrap_or(u32::MAX);
if ret != 0 || h.is_null() {
return Err(RpcError::Protocol(format!(
"BaseRegOpenKey('{subkey}', BACKUP_RESTORE) failed ({ret})"
)));
}
self.after_open(&h).await?;
Ok(h)
}
pub async fn query(&mut self, key: &Hkey, value: &str) -> Result<RegValue> {
self.query_value(key, value).await
}
pub async fn close_handle(&mut self, key: &Hkey) {
self.close(key).await;
}
pub async fn close_handle_no_wait(&mut self, key: &Hkey) {
if let Some(pos) = self.deferred.iter().position(|h| h.0 == key.0) {
self.deferred.remove(pos);
}
}
pub async fn query_info_class(&mut self, key: &Hkey) -> Result<String> {
let resp = self
.pipe
.call_sealed(opnum::BASE_REG_QUERY_INFO_KEY, &encode_query_info_key(key))
.await?;
decode_query_info_class(&resp)
}
pub async fn enum_key(&mut self, key: &Hkey, dw_index: u32) -> Result<Option<String>> {
let resp = self
.pipe
.call_sealed(opnum::BASE_REG_ENUM_KEY, &encode_enum_key(key, dw_index))
.await?;
decode_enum_key(&resp)
}
}
fn encode_query_info_key(key: &Hkey) -> Vec<u8> {
let mut e = NdrEncoder::new();
key.encode(&mut e);
e.u16(0); e.u16(1024); e.referent(); e.u32(512); e.u32(0); e.u32(0); e.into_bytes()
}
fn decode_query_info_class(stub: &[u8]) -> Result<String> {
let mut d = NdrDecoder::new(stub);
let length = d.u16()?;
let _maximum_length = d.u16()?;
let referent = d.u32()?;
if referent == 0 || length == 0 {
return Ok(String::new());
}
let _max = d.u32()?;
let _off = d.u32()?;
let actual = d.u32()? as usize;
if actual
.checked_mul(2)
.map_or(true, |need| need > d.remaining())
{
return Err(RpcError::Protocol(format!(
"BaseRegQueryInfoKey: lpClassOut actual={actual} exceeds remaining stub"
)));
}
let mut units = Vec::with_capacity(actual);
for _ in 0..actual {
units.push(d.u16()?);
}
while units.last() == Some(&0) {
units.pop();
}
Ok(String::from_utf16_lossy(&units))
}
fn encode_enum_key(key: &Hkey, dw_index: u32) -> Vec<u8> {
const CAP_WCHARS: u32 = 512;
let mut e = NdrEncoder::new();
key.encode(&mut e);
e.u32(dw_index);
e.u16(0);
e.u16((CAP_WCHARS * 2) as u16);
e.referent();
e.u32(CAP_WCHARS); e.u32(0); e.u32(0); e.referent(); const SPACES: u32 = 64;
e.u16((SPACES * 2) as u16); e.u16((SPACES * 2 + 2) as u16); e.referent(); e.u32(SPACES + 1); e.u32(0);
e.u32(SPACES); for _ in 0..SPACES {
e.u16(0x20); }
e.null_ptr();
e.into_bytes()
}
fn decode_enum_key(stub: &[u8]) -> Result<Option<String>> {
if stub.len() < 4 {
return Ok(None);
}
let ret = u32::from_le_bytes(stub[stub.len() - 4..].try_into().unwrap());
if ret == 0x0000_0103 || ret == 0x0000_00EA {
return Ok(None);
}
if ret != 0 {
return Err(RpcError::Protocol(format!(
"BaseRegEnumKey failed (win32 {ret})"
)));
}
let mut d = NdrDecoder::new(stub);
let length = d.u16()?;
let _max_len = d.u16()?;
let referent = d.u32()?;
if referent == 0 || length == 0 {
return Ok(Some(String::new()));
}
let _max = d.u32()?;
let _off = d.u32()?;
let actual = d.u32()? as usize;
if actual
.checked_mul(2)
.map_or(true, |need| need > d.remaining())
{
return Err(RpcError::Protocol(format!(
"BaseRegEnumKey: lpNameOut actual={actual} exceeds remaining stub"
)));
}
let mut units = Vec::with_capacity(actual);
for _ in 0..actual {
units.push(d.u16()?);
}
while units.last() == Some(&0) {
units.pop();
}
Ok(Some(String::from_utf16_lossy(&units)))
}
#[cfg(test)]
mod tests {
use super::*;
fn le16(b: &[u8], o: usize) -> u16 {
u16::from_le_bytes([b[o], b[o + 1]])
}
fn le32(b: &[u8], o: usize) -> u32 {
u32::from_le_bytes(b[o..o + 4].try_into().unwrap())
}
#[test]
fn ustr_counts_include_nul() {
let mut e = NdrEncoder::new();
encode_ustr(&mut e, "AB");
let b = e.into_bytes();
assert_eq!(le16(&b, 0), 6);
assert_eq!(le16(&b, 2), 6);
assert_ne!(le32(&b, 4), 0); assert_eq!(le32(&b, 8), 3, "max_count = 3 (A B NUL)");
assert_eq!(le32(&b, 12), 0, "offset");
assert_eq!(le32(&b, 16), 3, "actual_count");
assert_eq!(le16(&b, 20), b'A' as u16);
}
#[test]
fn open_local_machine_stub() {
let b = encode_open_local_machine();
assert_eq!(le32(&b, 0), 0, "ServerName NULL");
assert_eq!(le32(&b, 4), KEY_READ);
}
#[test]
fn open_users_stub_matches_open_local_machine() {
assert_eq!(encode_open_users(), encode_open_local_machine());
}
#[test]
fn registry_session_sid_filter() {
let keep = |s: &str| s.starts_with("S-1-5-21") && !s.ends_with("_Classes");
assert!(keep("S-1-5-21-111-222-333-1103"));
assert!(!keep("S-1-5-21-111-222-333-1103_Classes")); assert!(!keep(".DEFAULT")); assert!(!keep("S-1-5-18")); }
#[test]
fn query_value_roundtrip_decodes_dword() {
let mut e = NdrEncoder::new();
e.referent();
e.u32(4); e.referent();
e.u32(4); e.u32(0); e.u32(4); e.bytes(&0x0004_0000u32.to_le_bytes());
e.align(4);
e.referent();
e.u32(4); e.referent();
e.u32(4); e.u32(0); let v = decode_query_value(&e.into_bytes()).unwrap();
assert_eq!(v.ty, 4);
assert_eq!(v.as_dword(), Some(0x0004_0000));
}
fn hostile_ustr_reply(hostile_actual: u32) -> Vec<u8> {
let mut r = Vec::new();
r.extend_from_slice(&2u16.to_le_bytes()); r.extend_from_slice(&2u16.to_le_bytes()); r.extend_from_slice(&0x2_0000u32.to_le_bytes()); r.extend_from_slice(&hostile_actual.to_le_bytes()); r.extend_from_slice(&0u32.to_le_bytes()); r.extend_from_slice(&hostile_actual.to_le_bytes()); r
}
#[test]
fn query_info_class_actual_is_bounded_against_stub() {
let stub = hostile_ustr_reply(0xFFFF_FFFF);
let err = decode_query_info_class(&stub).unwrap_err();
assert!(
matches!(err, RpcError::Protocol(ref s) if s.contains("actual=")),
"expected Protocol(actual …), got {err:?}"
);
}
#[test]
fn enum_key_actual_is_bounded_against_stub() {
let mut stub = hostile_ustr_reply(0xFFFF_FFFF);
stub.extend_from_slice(&0u32.to_le_bytes());
let err = decode_enum_key(&stub).unwrap_err();
assert!(
matches!(err, RpcError::Protocol(ref s) if s.contains("actual=")),
"expected Protocol(actual …), got {err:?}"
);
}
}