use crate::ndr::{NdrDecoder, NdrEncoder};
use crate::transport::RpcTcp;
use crate::{ndr_transfer_syntax, Result, RpcError, Syntax};
pub const EPM_OPNUM_MAP: u16 = 3;
pub fn epm_syntax() -> Syntax {
Syntax::new("e1af8308-5d1f-11c9-91a4-08002b14a0fa", 3, 0)
}
const PROT_UUID: u8 = 0x0D; const PROT_RPC_CO: u8 = 0x0B; const PROT_TCP: u8 = 0x07;
const PROT_IP: u8 = 0x09;
fn floor(t: &mut Vec<u8>, lhs: &[u8], rhs: &[u8]) {
t.extend_from_slice(&(lhs.len() as u16).to_le_bytes());
t.extend_from_slice(lhs);
t.extend_from_slice(&(rhs.len() as u16).to_le_bytes());
t.extend_from_slice(rhs);
}
pub fn build_tower_with_port(target: Syntax, port: u16) -> Vec<u8> {
let ndr = ndr_transfer_syntax();
let mut t = Vec::new();
t.extend_from_slice(&5u16.to_le_bytes());
let mut lhs = vec![PROT_UUID];
lhs.extend_from_slice(&target.uuid);
lhs.extend_from_slice(&target.ver_major.to_le_bytes());
floor(&mut t, &lhs, &target.ver_minor.to_le_bytes());
let mut lhs = vec![PROT_UUID];
lhs.extend_from_slice(&ndr.uuid);
lhs.extend_from_slice(&ndr.ver_major.to_le_bytes());
floor(&mut t, &lhs, &ndr.ver_minor.to_le_bytes());
floor(&mut t, &[PROT_RPC_CO], &0u16.to_le_bytes());
floor(&mut t, &[PROT_TCP], &port.to_be_bytes());
floor(&mut t, &[PROT_IP], &0u32.to_be_bytes());
t
}
pub fn build_tower(target: Syntax) -> Vec<u8> {
build_tower_with_port(target, 0)
}
pub fn parse_tower_port(tower: &[u8]) -> Option<u16> {
if tower.len() < 2 {
return None;
}
let floors = u16::from_le_bytes([tower[0], tower[1]]);
let mut p = 2usize;
for _ in 0..floors {
let lhs_len = u16::from_le_bytes([*tower.get(p)?, *tower.get(p + 1)?]) as usize;
p += 2;
let lhs = tower.get(p..p + lhs_len)?;
p += lhs_len;
let rhs_len = u16::from_le_bytes([*tower.get(p)?, *tower.get(p + 1)?]) as usize;
p += 2;
let rhs = tower.get(p..p + rhs_len)?;
p += rhs_len;
if lhs.first() == Some(&PROT_TCP) && rhs.len() >= 2 {
return Some(u16::from_be_bytes([rhs[0], rhs[1]]));
}
}
None
}
pub fn encode_map(target: Syntax) -> Vec<u8> {
let tower = build_tower(target);
let mut e = NdrEncoder::new();
e.null_ptr(); e.referent(); e.u32(tower.len() as u32); e.u32(tower.len() as u32); e.bytes(&tower);
while e.len() % 4 != 0 {
e.u8(0); }
e.bytes(&[0u8; 20]); e.u32(1); e.into_bytes()
}
pub fn decode_map_response(stub: &[u8]) -> Result<u16> {
let mut d = NdrDecoder::new(stub);
let _entry_handle = d.read_bytes(20)?;
let num_towers = d.u32()?;
if num_towers == 0 {
return Err(RpcError::Protocol("EPM returned no towers".into()));
}
let _max = d.u32()?;
let _offset = d.u32()?;
let actual = d.u32()?;
for _ in 0..actual {
let _ref = d.u32()?; }
let _mc = d.u32()?;
let tower_len = d.u32()? as usize;
let tower = d.read_bytes(tower_len)?;
parse_tower_port(tower).ok_or(RpcError::Protocol("no TCP floor in tower".into()))
}
pub async fn resolve_port(host: &str, target: Syntax) -> Result<u16> {
let mut rpc = RpcTcp::connect(&format!("{host}:135")).await?;
rpc.bind(epm_syntax()).await?;
let resp = rpc.call(EPM_OPNUM_MAP, &encode_map(target)).await?;
decode_map_response(&resp)
}
#[cfg(test)]
mod tests {
use super::*;
fn samr() -> Syntax {
Syntax::new("12345778-1234-abcd-ef00-0123456789ac", 1, 0)
}
#[test]
fn tower_has_five_floors_and_zero_port() {
let t = build_tower(samr());
assert_eq!(u16::from_le_bytes([t[0], t[1]]), 5);
assert_eq!(parse_tower_port(&t), Some(0));
}
#[test]
fn port_roundtrips_through_tower() {
let t = build_tower_with_port(samr(), 49155);
assert_eq!(parse_tower_port(&t), Some(49155));
}
#[test]
fn fuzz_epm_parsers_never_panic() {
let seed = build_tower_with_port(samr(), 49155);
let mut s: u64 = 0xeec0_1234_5678_9abc;
let mut rng = || {
s ^= s >> 12;
s ^= s << 25;
s ^= s >> 27;
s.wrapping_mul(0x2545_F491_4F6C_DD1D)
};
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let mut fail = None;
for _ in 0..200_000 {
let mut buf = if rng() & 1 == 0 {
seed.clone()
} else {
let n = rng() as usize % 128;
(0..n).map(|_| rng() as u8).collect()
};
for _ in 0..(rng() as usize % 6) {
if !buf.is_empty() {
let i = rng() as usize % buf.len();
buf[i] = rng() as u8;
}
}
let b = buf.clone();
if std::panic::catch_unwind(|| {
let _ = parse_tower_port(&b);
let _ = decode_map_response(&b);
})
.is_err()
{
fail = Some(buf);
break;
}
}
std::panic::set_hook(prev);
if let Some(buf) = fail {
panic!(
"EPM parser panicked on {} bytes: {}",
buf.len(),
buf.iter().map(|x| format!("{x:02x}")).collect::<String>()
);
}
}
#[test]
fn decode_map_response_extracts_port() {
let tower = build_tower_with_port(samr(), 49664);
let mut s = Vec::new();
s.extend_from_slice(&[0u8; 20]); s.extend_from_slice(&1u32.to_le_bytes()); s.extend_from_slice(&1u32.to_le_bytes()); s.extend_from_slice(&0u32.to_le_bytes()); s.extend_from_slice(&1u32.to_le_bytes()); s.extend_from_slice(&0x0002_0004u32.to_le_bytes()); s.extend_from_slice(&(tower.len() as u32).to_le_bytes()); s.extend_from_slice(&(tower.len() as u32).to_le_bytes()); s.extend_from_slice(&tower);
s.extend_from_slice(&0u32.to_le_bytes());
assert_eq!(decode_map_response(&s).unwrap(), 49664);
}
}