深度分析:添加完整exchange hash components logging
添加详细logging: - V_C/V_S: 完整SSH string encoding bytes - I_C/I_S: prepend SSH_MSG_KEXINIT byte验证 - K_S: 完整host key blob bytes - Q_C/Q_S: 完整32 bytes ECDH keys - K: shared secret mpint encoding bytes 验证结果: ✅ 所有encoding格式正确(SSH string, mpint) ✅ KEXINIT prepend byte正确(uint32(len+1) + byte(20) + payload) ✅ 所有component lengths正确 但仍MAC失败,唯一可能: - OpenSSH client计算exchange hash方式不同 - 需要对比OpenSSH client连接OpenSSH server成功 vs MarkBaseSSH失败 下一步建议: 1. 手动启动OpenSSH server(解决port占用) 2. 使用Wireshark GUI完整对比packet 3. 或使用OpenSSH client源码验证exchange hash计算 Session progress: - OpenSSH源码深度对比:100% - KEXINIT encoding修复:100% - Exchange hash components验证:100% - MAC失败root cause:待查
This commit is contained in:
@@ -209,11 +209,15 @@ impl KexExchangeHandler {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
// RFC 4253 Section 7: V_C and V_S are version strings (without \r\n based on testing)
|
||||
hasher.update(&(client_version.len() as u32).to_be_bytes());
|
||||
let vc_ssh_string = &(client_version.len() as u32).to_be_bytes();
|
||||
hasher.update(vc_ssh_string);
|
||||
hasher.update(client_version.as_bytes());
|
||||
info!(" Exchange hash component V_C: len={} bytes=[{:?}] data=[{:?}]", 4+client_version.len(), vc_ssh_string, client_version.as_bytes());
|
||||
|
||||
hasher.update(&(server_version.len() as u32).to_be_bytes());
|
||||
let vs_ssh_string = &(server_version.len() as u32).to_be_bytes();
|
||||
hasher.update(vs_ssh_string);
|
||||
hasher.update(server_version.as_bytes());
|
||||
info!(" Exchange hash component V_S: len={} bytes=[{:?}] data=[{:?}]", 4+server_version.len(), vs_ssh_string, server_version.as_bytes());
|
||||
|
||||
// OpenSSH kexgex.c: "kexinit messages: fake header: len+SSH2_MSG_KEXINIT"
|
||||
// KEXINIT payload should NOT include SSH_MSG_KEXINIT type byte
|
||||
@@ -227,25 +231,35 @@ impl KexExchangeHandler {
|
||||
info!("I_S (server KEXINIT without type byte): {} bytes", server_kexinit_without_type.len());
|
||||
|
||||
// Exchange hash: uint32(len+1) + uint8(SSH_MSG_KEXINIT) + payload_without_type
|
||||
hasher.update(&((client_kexinit_without_type.len() + 1) as u32).to_be_bytes());
|
||||
let ic_len_bytes = &((client_kexinit_without_type.len() + 1) as u32).to_be_bytes();
|
||||
hasher.update(ic_len_bytes);
|
||||
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
|
||||
hasher.update(client_kexinit_without_type);
|
||||
info!(" Exchange hash component I_C: len={} bytes=[{:?}] type=[20] payload_len={} (first 8 bytes=[{:?}])", 4+1+client_kexinit_without_type.len(), ic_len_bytes, client_kexinit_without_type.len(), &client_kexinit_without_type[..std::cmp::min(8, client_kexinit_without_type.len())]);
|
||||
|
||||
hasher.update(&((server_kexinit_without_type.len() + 1) as u32).to_be_bytes());
|
||||
let is_len_bytes = &((server_kexinit_without_type.len() + 1) as u32).to_be_bytes();
|
||||
hasher.update(is_len_bytes);
|
||||
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
|
||||
hasher.update(server_kexinit_without_type);
|
||||
info!(" Exchange hash component I_S: len={} bytes=[{:?}] type=[20] payload_len={} (first 8 bytes=[{:?}])", 4+1+server_kexinit_without_type.len(), is_len_bytes, server_kexinit_without_type.len(), &server_kexinit_without_type[..std::cmp::min(8, server_kexinit_without_type.len())]);
|
||||
|
||||
hasher.update(&(host_key_blob.len() as u32).to_be_bytes());
|
||||
let ks_len_bytes = &(host_key_blob.len() as u32).to_be_bytes();
|
||||
hasher.update(ks_len_bytes);
|
||||
hasher.update(host_key_blob);
|
||||
info!(" Exchange hash component K_S: len={} bytes=[{:?}] blob_len={} (full=[{:?}])", 4+host_key_blob.len(), ks_len_bytes, host_key_blob.len(), host_key_blob);
|
||||
|
||||
hasher.update(&(client_public_key.len() as u32).to_be_bytes());
|
||||
let qc_len_bytes = &(client_public_key.len() as u32).to_be_bytes();
|
||||
hasher.update(qc_len_bytes);
|
||||
hasher.update(client_public_key);
|
||||
info!(" Exchange hash component Q_C: len={} bytes=[{:?}] key=[{:?}]", 4+client_public_key.len(), qc_len_bytes, client_public_key);
|
||||
|
||||
hasher.update(&(server_public_key.len() as u32).to_be_bytes());
|
||||
let qs_len_bytes = &(server_public_key.len() as u32).to_be_bytes();
|
||||
hasher.update(qs_len_bytes);
|
||||
hasher.update(server_public_key);
|
||||
info!(" Exchange hash component Q_S: len={} bytes=[{:?}] key=[{:?}]", 4+server_public_key.len(), qs_len_bytes, server_public_key);
|
||||
|
||||
info!("Exchange hash components:");
|
||||
info!(" shared_secret raw ({} bytes): {:?}", shared_secret.len(), &shared_secret[..std::cmp::min(8, shared_secret.len())]);
|
||||
info!(" shared_secret raw full (32 bytes): {:?}", shared_secret);
|
||||
|
||||
// RFC 8731 Section 3.1: X25519 output is little-endian
|
||||
// OpenSSH sshbuf_put_bignum2_bytes() uses bytes DIRECTLY (no reversal)
|
||||
@@ -260,7 +274,7 @@ impl KexExchangeHandler {
|
||||
}
|
||||
let trimmed_shared_secret = &shared_secret[start..];
|
||||
|
||||
info!(" shared_secret after removing leading zeros ({} bytes): {:?}", trimmed_shared_secret.len(), &trimmed_shared_secret[..std::cmp::min(8, trimmed_shared_secret.len())]);
|
||||
info!(" shared_secret after removing leading zeros ({} bytes): {:?}", trimmed_shared_secret.len(), trimmed_shared_secret);
|
||||
|
||||
let mpint_shared_secret_data = if trimmed_shared_secret.len() > 0 && trimmed_shared_secret[0] >= 0x80 {
|
||||
let mut mpint = vec![0u8];
|
||||
@@ -274,8 +288,10 @@ impl KexExchangeHandler {
|
||||
info!(" mpint_shared_secret_data ({} bytes): {:?}", mpint_shared_secret_data.len(), &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())]);
|
||||
|
||||
// mpint格式 = uint32(length) + mpint_data
|
||||
hasher.update(&(mpint_shared_secret_data.len() as u32).to_be_bytes());
|
||||
let mpint_len_bytes = &(mpint_shared_secret_data.len() as u32).to_be_bytes();
|
||||
hasher.update(mpint_len_bytes);
|
||||
hasher.update(&mpint_shared_secret_data);
|
||||
info!(" Exchange hash component K (shared secret mpint): len={} bytes=[{:?}] data_len={} (first 8 bytes=[{:?}])", 4+mpint_shared_secret_data.len(), mpint_len_bytes, mpint_shared_secret_data.len(), &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())]);
|
||||
|
||||
Ok(hasher.finalize().to_vec())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user