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 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;
#[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()
}
}
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_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>,
}
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 })
}
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})")));
}
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})"
)));
}
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 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})"
)));
}
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 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;
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;
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 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));
}
}