use crate::{pdu, Result, RpcError, Syntax};
use ntlmssp::{Ntlm, SealState};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
pub struct RpcTcp {
stream: TcpStream,
call_id: u32,
seal: Option<SealState>,
session_key: Option<[u8; 16]>,
pending_relay_bind: Option<u32>,
}
impl RpcTcp {
pub async fn connect(addr: &str) -> Result<Self> {
let stream = smb2_client::socks::dial(addr, 135).await?;
Ok(RpcTcp {
stream,
call_id: 1,
seal: None,
session_key: None,
pending_relay_bind: None,
})
}
pub fn session_key(&self) -> Option<[u8; 16]> {
self.session_key
}
async fn send(&mut self, buf: &[u8]) -> Result<()> {
self.stream.write_all(buf).await?;
Ok(())
}
async fn recv(&mut self) -> Result<Vec<u8>> {
let mut head = [0u8; 16];
self.stream.read_exact(&mut head).await?;
let frag = u16::from_le_bytes([head[8], head[9]]) as usize;
if frag < 16 {
return Err(RpcError::Protocol(format!("frag_length {frag} < 16")));
}
let mut rest = vec![0u8; frag - 16];
self.stream.read_exact(&mut rest).await?;
let mut pdu = head.to_vec();
pdu.append(&mut rest);
Ok(pdu)
}
pub async fn bind(&mut self, syntax: Syntax) -> Result<()> {
let bind = pdu::build_bind(self.call_id, syntax);
self.call_id += 1;
self.send(&bind).await?;
let resp = self.recv().await?;
pdu::expect_bind_ack(&resp)
}
pub async fn call(&mut self, opnum: u16, stub: &[u8]) -> Result<Vec<u8>> {
let req = pdu::build_request(self.call_id, 0, opnum, stub);
self.call_id += 1;
self.send(&req).await?;
let resp = self.recv().await?;
pdu::parse_response(&resp)
}
pub async fn bind_sealed(
&mut self,
syntax: Syntax,
domain: &str,
user: &str,
password: &str,
workstation: &str,
) -> Result<()> {
let ntlm = Ntlm::new_sealed();
let bind_call_id = self.call_id;
self.call_id += 1;
let bind = pdu::build_bind_auth(bind_call_id, syntax, ntlm.negotiate());
self.send(&bind).await?;
let ack = self.recv().await?;
pdu::expect_bind_ack(&ack)?;
let challenge = pdu::extract_auth_value(&ack)?;
let (type3, exported) = ntlm
.authenticate(&challenge, domain, user, password, workstation)
.map_err(|e| RpcError::Protocol(format!("ntlm authenticate: {e}")))?;
let auth3 = pdu::build_auth3(bind_call_id, &type3);
self.send(&auth3).await?; self.session_key = Some(exported);
self.seal = Some(SealState::new(&exported));
Ok(())
}
pub async fn bind_sealed_hash(
&mut self,
syntax: Syntax,
domain: &str,
user: &str,
nt_hash: &[u8; 16],
workstation: &str,
) -> Result<()> {
let ntlm = Ntlm::new_sealed();
let bind_call_id = self.call_id;
self.call_id += 1;
let bind = pdu::build_bind_auth(bind_call_id, syntax, ntlm.negotiate());
self.send(&bind).await?;
let ack = self.recv().await?;
pdu::expect_bind_ack(&ack)?;
let challenge = pdu::extract_auth_value(&ack)?;
let (type3, exported) = ntlm
.authenticate_hash(&challenge, domain, user, nt_hash, workstation)
.map_err(|e| RpcError::Protocol(format!("ntlm authenticate (hash): {e}")))?;
let auth3 = pdu::build_auth3(bind_call_id, &type3);
self.send(&auth3).await?;
self.session_key = Some(exported);
self.seal = Some(SealState::new(&exported));
Ok(())
}
pub async fn bind_relay_start(
&mut self,
syntax: Syntax,
victim_type1: &[u8],
) -> Result<Vec<u8>> {
let bind_call_id = self.call_id;
self.call_id += 1;
let bind = pdu::build_bind_auth_level(
bind_call_id,
syntax,
victim_type1,
pdu::RPC_C_AUTHN_LEVEL_PKT_CONNECT,
);
self.send(&bind).await?;
let ack = self.recv().await?;
pdu::expect_bind_ack(&ack)?;
let type2 = pdu::extract_auth_value(&ack)?;
self.pending_relay_bind = Some(bind_call_id);
Ok(type2)
}
pub async fn bind_relay_finish(&mut self, victim_type3: &[u8]) -> Result<()> {
let bind_call_id = self
.pending_relay_bind
.take()
.ok_or_else(|| RpcError::Protocol("bind_relay_finish without _start".into()))?;
let auth3 = pdu::build_auth3_level(
bind_call_id,
victim_type3,
pdu::RPC_C_AUTHN_LEVEL_PKT_CONNECT,
);
self.send(&auth3).await?; Ok(())
}
pub async fn call_sealed(&mut self, opnum: u16, stub: &[u8]) -> Result<Vec<u8>> {
const STUB_OFF: usize = 24; let pad_len = ((4 - (stub.len() % 4)) % 4) as u8;
let mut stub_padded = stub.to_vec();
stub_padded.extend(std::iter::repeat(0u8).take(pad_len as usize));
let mut req = pdu::build_request_sealed(
self.call_id,
0,
opnum,
&stub_padded,
pad_len,
&[0u8; 16],
stub.len() as u32,
);
self.call_id += 1;
let n = req.len();
let sign_over = req[..n - 16].to_vec();
let seal = self
.seal
.as_mut()
.ok_or_else(|| RpcError::Protocol("session not sealed".into()))?;
let (sealed, signature) = seal.seal_pdu(&sign_over, &stub_padded);
req[STUB_OFF..STUB_OFF + stub_padded.len()].copy_from_slice(&sealed);
req[n - 16..].copy_from_slice(&signature);
self.send(&req).await?;
const PFC_LAST_FRAG: u8 = 0x02;
let mut plain = Vec::new();
loop {
let resp = self.recv().await?;
let h = pdu::parse_header(&resp)?;
if h.ptype == pdu::ptype::FAULT {
let status = resp
.get(24..28)
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
.unwrap_or(0);
return Err(RpcError::Fault(status));
}
if h.ptype != pdu::ptype::RESPONSE {
return Err(RpcError::UnexpectedPdu(h.ptype));
}
let pfc = resp[3];
let auth_length = u16::from_le_bytes([resp[10], resp[11]]) as usize;
let frag = (h.frag_length as usize).min(resp.len());
let sec_trailer_start = frag - 8 - auth_length;
let resp_pad = resp[sec_trailer_start + 2] as usize;
let sig = resp[frag - auth_length..frag].to_vec();
let pdu_no_sig = &resp[..frag - auth_length];
let seal = self.seal.as_mut().unwrap();
let mut chunk = seal
.unseal_pdu(pdu_no_sig, STUB_OFF, sec_trailer_start - STUB_OFF, &sig)
.map_err(|e| RpcError::Protocol(format!("unseal response: {e}")))?;
chunk.truncate(chunk.len().saturating_sub(resp_pad));
plain.extend_from_slice(&chunk);
if pfc & PFC_LAST_FRAG != 0 {
break;
}
}
Ok(plain)
}
pub async fn call_sealed_object(
&mut self,
opnum: u16,
object: &[u8; 16],
stub: &[u8],
) -> Result<Vec<u8>> {
const STUB_OFF: usize = 40; let pad_len = ((4 - (stub.len() % 4)) % 4) as u8;
let mut stub_padded = stub.to_vec();
stub_padded.extend(std::iter::repeat(0u8).take(pad_len as usize));
let mut req = pdu::build_request_sealed_object(
self.call_id,
0,
opnum,
object,
&stub_padded,
pad_len,
&[0u8; 16],
stub.len() as u32,
);
self.call_id += 1;
let n = req.len();
let sign_over = req[..n - 16].to_vec();
let seal = self
.seal
.as_mut()
.ok_or_else(|| RpcError::Protocol("session not sealed".into()))?;
let (sealed, signature) = seal.seal_pdu(&sign_over, &stub_padded);
req[STUB_OFF..STUB_OFF + stub_padded.len()].copy_from_slice(&sealed);
req[n - 16..].copy_from_slice(&signature);
self.send(&req).await?;
const RESP_STUB_OFF: usize = 24;
const PFC_LAST_FRAG: u8 = 0x02;
let mut plain = Vec::new();
loop {
let resp = self.recv().await?;
let h = pdu::parse_header(&resp)?;
if h.ptype == pdu::ptype::FAULT {
let status = resp
.get(24..28)
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
.unwrap_or(0);
return Err(RpcError::Fault(status));
}
if h.ptype != pdu::ptype::RESPONSE {
return Err(RpcError::UnexpectedPdu(h.ptype));
}
let pfc = resp[3];
let auth_length = u16::from_le_bytes([resp[10], resp[11]]) as usize;
let frag = (h.frag_length as usize).min(resp.len());
let sec_trailer_start = frag - 8 - auth_length;
let resp_pad = resp[sec_trailer_start + 2] as usize;
let sig = resp[frag - auth_length..frag].to_vec();
let pdu_no_sig = &resp[..frag - auth_length];
let seal = self.seal.as_mut().unwrap();
let mut chunk = seal
.unseal_pdu(
pdu_no_sig,
RESP_STUB_OFF,
sec_trailer_start - RESP_STUB_OFF,
&sig,
)
.map_err(|e| RpcError::Protocol(format!("unseal response: {e}")))?;
chunk.truncate(chunk.len().saturating_sub(resp_pad));
plain.extend_from_slice(&chunk);
if pfc & PFC_LAST_FRAG != 0 {
break;
}
}
Ok(plain)
}
}
pub struct SmbPipe<'a> {
client: &'a mut smb2_client::SmbClient,
file_id: [u8; 16],
call_id: u32,
seal: Option<SealState>,
}
impl<'a> SmbPipe<'a> {
pub fn new(client: &'a mut smb2_client::SmbClient, file_id: [u8; 16]) -> Self {
SmbPipe {
client,
file_id,
call_id: 1,
seal: None,
}
}
async fn transact(&mut self, pdu_bytes: &[u8]) -> Result<Vec<u8>> {
self.client
.transact(&self.file_id, pdu_bytes)
.await
.map_err(|e| RpcError::Protocol(format!("smb transact: {e}")))
}
pub async fn bind(&mut self, syntax: Syntax) -> Result<()> {
let bind = pdu::build_bind(self.call_id, syntax);
self.call_id += 1;
let resp = self.transact(&bind).await?;
pdu::expect_bind_ack(&resp)
}
pub async fn call(&mut self, opnum: u16, stub: &[u8]) -> Result<Vec<u8>> {
let req = pdu::build_request(self.call_id, 0, opnum, stub);
self.call_id += 1;
let resp = self.transact(&req).await?;
pdu::parse_response(&resp)
}
pub async fn bind_sealed(
&mut self,
syntax: Syntax,
domain: &str,
user: &str,
password: &str,
workstation: &str,
) -> Result<()> {
let ntlm = Ntlm::new_sealed();
let bind_call_id = self.call_id;
self.call_id += 1;
let bind = pdu::build_bind_auth(bind_call_id, syntax, ntlm.negotiate());
let ack = self.transact(&bind).await?;
pdu::expect_bind_ack(&ack)?;
let challenge = pdu::extract_auth_value(&ack)?;
let (type3, exported) = ntlm
.authenticate(&challenge, domain, user, password, workstation)
.map_err(|e| RpcError::Protocol(format!("ntlm authenticate: {e}")))?;
let auth3 = pdu::build_auth3(bind_call_id, &type3);
self.client
.write_pipe(&self.file_id, &auth3)
.await
.map_err(|e| RpcError::Protocol(format!("auth3 write: {e}")))?;
self.seal = Some(SealState::new(&exported));
Ok(())
}
pub async fn bind_sealed_hash(
&mut self,
syntax: Syntax,
domain: &str,
user: &str,
nt_hash: &[u8; 16],
workstation: &str,
) -> Result<()> {
let ntlm = Ntlm::new_sealed();
let bind_call_id = self.call_id;
self.call_id += 1;
let bind = pdu::build_bind_auth(bind_call_id, syntax, ntlm.negotiate());
let ack = self.transact(&bind).await?;
pdu::expect_bind_ack(&ack)?;
let challenge = pdu::extract_auth_value(&ack)?;
let (type3, exported) = ntlm
.authenticate_hash(&challenge, domain, user, nt_hash, workstation)
.map_err(|e| RpcError::Protocol(format!("ntlm authenticate (hash): {e}")))?;
let auth3 = pdu::build_auth3(bind_call_id, &type3);
self.client
.write_pipe(&self.file_id, &auth3)
.await
.map_err(|e| RpcError::Protocol(format!("auth3 write: {e}")))?;
self.seal = Some(SealState::new(&exported));
Ok(())
}
pub async fn call_sealed(&mut self, opnum: u16, stub: &[u8]) -> Result<Vec<u8>> {
const STUB_OFF: usize = 24;
let pad_len = ((4 - (stub.len() % 4)) % 4) as u8;
let mut stub_padded = stub.to_vec();
stub_padded.extend(std::iter::repeat(0u8).take(pad_len as usize));
let mut req = pdu::build_request_sealed(
self.call_id,
0,
opnum,
&stub_padded,
pad_len,
&[0u8; 16],
stub.len() as u32,
);
self.call_id += 1;
let n = req.len();
let sign_over = req[..n - 16].to_vec();
let seal = self
.seal
.as_mut()
.ok_or_else(|| RpcError::Protocol("pipe not sealed".into()))?;
let (sealed, signature) = seal.seal_pdu(&sign_over, &stub_padded);
req[STUB_OFF..STUB_OFF + stub_padded.len()].copy_from_slice(&sealed);
req[n - 16..].copy_from_slice(&signature);
const PFC_LAST_FRAG: u8 = 0x02;
let mut raw = self.transact(&req).await?;
let mut plain = Vec::new();
let mut off = 0usize;
loop {
let mut hit_last = false;
while off + 16 <= raw.len() {
let h = pdu::parse_header(&raw[off..])?;
if h.ptype == pdu::ptype::FAULT {
let status = raw
.get(off + 24..off + 28)
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
.unwrap_or(0);
return Err(RpcError::Fault(status));
}
if h.ptype != pdu::ptype::RESPONSE {
return Err(RpcError::UnexpectedPdu(h.ptype));
}
let frag = h.frag_length as usize;
if frag < 24 || off + frag > raw.len() {
break; }
let pdu = &raw[off..off + frag];
let pfc = pdu[3];
let auth_length = u16::from_le_bytes([pdu[10], pdu[11]]) as usize;
let sec_trailer_start = frag - 8 - auth_length;
let resp_pad = pdu[sec_trailer_start + 2] as usize;
let sig = pdu[frag - auth_length..frag].to_vec();
let pdu_no_sig = &pdu[..frag - auth_length];
let seal = self.seal.as_mut().unwrap();
let mut chunk = seal
.unseal_pdu(pdu_no_sig, STUB_OFF, sec_trailer_start - STUB_OFF, &sig)
.map_err(|e| RpcError::Protocol(format!("unseal response: {e}")))?;
chunk.truncate(chunk.len().saturating_sub(resp_pad));
plain.extend_from_slice(&chunk);
off += frag;
if pfc & PFC_LAST_FRAG != 0 {
hit_last = true;
break;
}
}
if hit_last {
break;
}
let more = self
.client
.read_pipe(&self.file_id, 0x0001_0000)
.await
.map_err(|e| RpcError::Protocol(format!("pipe read: {e}")))?;
if more.is_empty() {
break; }
raw.extend_from_slice(&more);
}
Ok(plain)
}
}