use anyhow::{Context, Result};
use clap::Parser;
use crate::ui;
#[derive(Parser)]
pub(crate) struct CheckKrbSealArgs {
#[arg(long)]
pub host: String,
#[arg(long)]
pub spn_host: Option<String>,
#[arg(long)]
pub domain: String,
#[arg(long)]
pub realm: String,
#[arg(long)]
pub kdc: String,
#[arg(long)]
pub user: String,
#[arg(long, default_value = "")]
pub password: String,
#[arg(long)]
pub try_call: bool,
#[arg(long)]
pub use_ticket_key: bool,
}
pub(crate) async fn check_krb_seal(mut a: CheckKrbSealArgs) -> Result<()> {
let mut checklist = ui::StageChecklist::new([
"resolve password",
"asktgt (AS-REP)",
"TGS for cifs/<host> ×2",
"SMB session-setup (Kerberos)",
"tree-connect IPC$ + open lsarpc",
"DCE-RPC BIND sealed Kerberos",
"LsarOpenPolicy2 (opnum 44)",
]);
let result = run(&mut a, &mut checklist).await;
match &result {
Ok(()) => checklist.render("krb-seal stages"),
Err(e) => {
let brief = format!("{e:#}")
.lines()
.next()
.unwrap_or("failed")
.chars()
.take(80)
.collect::<String>();
checklist.mark_current_failed(brief);
checklist.render("krb-seal stages (failed)");
}
}
result
}
async fn run(a: &mut CheckKrbSealArgs, checklist: &mut ui::StageChecklist) -> Result<()> {
use adhammer_kerberos::rpc_sealer::AesCts96Sealer;
use adhammer_kerberos::{
build_ap_req_gss, build_ap_req_gss_aes256, get_service_ticket, get_tgt,
};
use dcerpc::transport::SmbPipe;
use dcerpc::Syntax;
a.password = crate::resolve_secret(&a.password, "ADHAMMER_PASSWORD")?;
checklist.record_ok("resolve password", "resolved");
let tgt = get_tgt(&a.user, &a.password, &a.realm, &a.kdc)
.await
.with_context(|| format!("get_tgt for {}@{}", a.user, a.realm))?;
checklist.record_ok("asktgt (AS-REP)", format!("TGT for {}@{}", a.user, a.realm));
let spn_host = a.spn_host.clone().unwrap_or_else(|| a.host.clone());
let spn = format!("cifs/{spn_host}");
let st_smb = get_service_ticket(&tgt, &spn, &a.kdc)
.await
.with_context(|| format!("TGS for {spn} (SMB leg)"))?;
let st_rpc = get_service_ticket(&tgt, &spn, &a.kdc)
.await
.with_context(|| format!("TGS for {spn} (RPC leg)"))?;
checklist.record_ok(
"TGS for cifs/<host> ×2",
format!("two AES256 service tickets for {spn}"),
);
let (spnego_smb, subkey_smb) =
build_ap_req_gss(&st_smb).context("build AP-REQ for SMB session-setup")?;
let mut smb = smb2_client::SmbClient::connect(&a.host)
.await
.with_context(|| format!("SMB TCP connect to {}:445", a.host))?;
smb.login_kerberos(&spnego_smb, &subkey_smb)
.await
.context("SMB2 SESSION_SETUP with Kerberos AP-REQ")?;
checklist.record_ok(
"SMB session-setup (Kerberos)",
"SESSION_SETUP OK with AES128 subkey",
);
smb.tree_connect(&format!("\\\\{}\\IPC$", a.host))
.await
.context("SMB2 TREE_CONNECT \\IPC$")?;
let pipe_id = smb
.open_pipe("lsarpc")
.await
.context("SMB2 OPEN \\PIPE\\lsarpc")?;
checklist.record_ok(
"tree-connect IPC$ + open lsarpc",
"pipe file handle acquired",
);
let (spnego_rpc, subkey_rpc) =
build_ap_req_gss_aes256(&st_rpc).context("build AES256 AP-REQ for RPC bind")?;
let base_key: [u8; 32] = if a.use_ticket_key {
st_rpc.session_key.as_slice().try_into().context(
"ticket session key is not 32 bytes (AES256) — TGS-REQ may have returned RC4/AES128",
)?
} else {
subkey_rpc
};
let sealer = Box::new(AesCts96Sealer::new_initiator(base_key, false));
let mut pipe = SmbPipe::new(&mut smb, pipe_id);
let lsarpc = Syntax {
uuid: [
0x78, 0x57, 0x34, 0x12, 0x34, 0x12, 0xcd, 0xab, 0xef, 0x00, 0x01, 0x23, 0x45, 0x67,
0x89, 0xab,
],
ver_major: 0,
ver_minor: 0,
};
pipe.bind_sealed_kerberos(lsarpc, &spnego_rpc, sealer)
.await
.context("DCE-RPC BIND with sealed Kerberos")?;
checklist.record_ok("DCE-RPC BIND sealed Kerberos", "BIND_ACK received");
if a.try_call {
let stub = dcerpc::lsat::encode_open_policy2();
match pipe.call_sealed_kerberos(44, &stub).await {
Ok(resp) => {
checklist.record_ok(
"LsarOpenPolicy2 (opnum 44)",
format!("{} byte response — decoding TBD", resp.len()),
);
println!("[+] sealed call round-trip: {} bytes returned", resp.len());
}
Err(e) => {
anyhow::bail!("call_sealed_kerberos faulted: {e}");
}
}
} else {
checklist.record_skipped(
"LsarOpenPolicy2 (opnum 44)",
"pass --try-call to send one sealed request",
);
}
Ok(())
}